diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index e43de9d..6d0ee1c 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 57723f3..f65d128 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -17,8 +17,8 @@ android {
applicationId = "com.yangdai.opennote"
minSdk = 29
targetSdk = 34
- versionCode = 127
- versionName = "1.2.7"
+ versionCode = 128
+ versionName = "1.2.8"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -69,7 +69,6 @@ android {
dependencies {
// Kotlin
implementation(libs.kotlinx.serialization)
- implementation(libs.kotlinx.collections.immutable)
// CommonMark, for markdown rendering and parsing
implementation(libs.commonmark.ext.task.list.items)
diff --git a/app/release/app-release.apk b/app/release/app-release.apk
index 10a0d50..df99636 100644
Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6203741..234404b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,8 +10,6 @@
android:name="android.hardware.camera"
android:required="false" />
-
-
diff --git a/app/src/main/java/com/yangdai/opennote/ManageSpaceActivity.kt b/app/src/main/java/com/yangdai/opennote/ManageSpaceActivity.kt
index 971f1ff..95935ca 100644
--- a/app/src/main/java/com/yangdai/opennote/ManageSpaceActivity.kt
+++ b/app/src/main/java/com/yangdai/opennote/ManageSpaceActivity.kt
@@ -6,12 +6,14 @@ import android.content.Intent
import android.os.Bundle
import androidx.core.app.TaskStackBuilder
import androidx.core.net.toUri
+import com.yangdai.opennote.presentation.navigation.Screen
import com.yangdai.opennote.presentation.util.Constants.LINK
class ManageSpaceActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val intent = Intent(this, MainActivity::class.java).setData("$LINK/settings".toUri())
+ val intent =
+ Intent(this, MainActivity::class.java).setData("$LINK/${Screen.Settings.route}".toUri())
val pendingIntent = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(intent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
diff --git a/app/src/main/java/com/yangdai/opennote/data/di/AppModule.kt b/app/src/main/java/com/yangdai/opennote/data/di/AppModule.kt
index f30e59b..7b0b7ea 100644
--- a/app/src/main/java/com/yangdai/opennote/data/di/AppModule.kt
+++ b/app/src/main/java/com/yangdai/opennote/data/di/AppModule.kt
@@ -14,7 +14,7 @@ import com.yangdai.opennote.domain.usecase.DeleteNote
import com.yangdai.opennote.domain.usecase.DeleteNotesByFolderId
import com.yangdai.opennote.domain.usecase.GetFolders
import com.yangdai.opennote.domain.usecase.GetNotes
-import com.yangdai.opennote.domain.usecase.Operations
+import com.yangdai.opennote.domain.usecase.UseCases
import com.yangdai.opennote.domain.usecase.SearchNotes
import com.yangdai.opennote.domain.usecase.UpdateFolder
import com.yangdai.opennote.domain.usecase.UpdateNote
@@ -34,9 +34,8 @@ object AppModule {
@Singleton
@Provides
- fun provideDataStoreRepository(
- @ApplicationContext context: Context
- ): DataStoreRepository = DataStoreRepositoryImpl(context)
+ fun provideDataStoreRepository(@ApplicationContext context: Context): DataStoreRepository =
+ DataStoreRepositoryImpl(context)
@Provides
@Singleton
@@ -62,8 +61,8 @@ object AppModule {
fun provideNoteUseCases(
noteRepository: NoteRepository,
folderRepository: FolderRepository
- ): Operations {
- return Operations(
+ ): UseCases {
+ return UseCases(
getNotes = GetNotes(noteRepository),
getNoteById = GetNoteById(noteRepository),
deleteNote = DeleteNote(noteRepository),
diff --git a/app/src/main/java/com/yangdai/opennote/domain/usecase/GetNotes.kt b/app/src/main/java/com/yangdai/opennote/domain/usecase/GetNotes.kt
index a54ec30..a324863 100644
--- a/app/src/main/java/com/yangdai/opennote/domain/usecase/GetNotes.kt
+++ b/app/src/main/java/com/yangdai/opennote/domain/usecase/GetNotes.kt
@@ -32,7 +32,7 @@ class GetNotes(
is OrderType.Ascending -> {
when (noteOrder) {
is NoteOrder.Title -> notes.sortedBy { it.title.lowercase() }
- is NoteOrder.Date -> notes.sortedBy { it.timestamp }
+ is NoteOrder.Date -> notes.reversed()
}
}
diff --git a/app/src/main/java/com/yangdai/opennote/domain/usecase/Operations.kt b/app/src/main/java/com/yangdai/opennote/domain/usecase/UseCases.kt
similarity index 94%
rename from app/src/main/java/com/yangdai/opennote/domain/usecase/Operations.kt
rename to app/src/main/java/com/yangdai/opennote/domain/usecase/UseCases.kt
index e916167..597c19e 100644
--- a/app/src/main/java/com/yangdai/opennote/domain/usecase/Operations.kt
+++ b/app/src/main/java/com/yangdai/opennote/domain/usecase/UseCases.kt
@@ -1,6 +1,6 @@
package com.yangdai.opennote.domain.usecase
-data class Operations(
+data class UseCases(
val getNotes: GetNotes,
val getNoteById: GetNoteById,
val deleteNote: DeleteNote,
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/AdaptiveNavigationScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/AdaptiveNavigationScreen.kt
new file mode 100644
index 0000000..280d47c
--- /dev/null
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/AdaptiveNavigationScreen.kt
@@ -0,0 +1,70 @@
+package com.yangdai.opennote.presentation.component
+
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material3.DrawerState
+import androidx.compose.material3.ModalDrawerSheet
+import androidx.compose.material3.ModalNavigationDrawer
+import androidx.compose.material3.PermanentDrawerSheet
+import androidx.compose.material3.PermanentNavigationDrawer
+import androidx.compose.runtime.Composable
+
+/**
+ * A composable function that adapts the navigation drawer based on the screen size.
+ *
+ * @param isLargeScreen A boolean indicating whether the screen is large or not.
+ * @param drawerState The state of the drawer.
+ * @param gesturesEnabled A boolean indicating whether gestures are enabled or not.
+ * @param drawerContent The content of the drawer.
+ * @param content The main content.
+ */
+@Composable
+fun AdaptiveNavigationScreen(
+ isLargeScreen: Boolean,
+ drawerState: DrawerState,
+ gesturesEnabled: Boolean,
+ drawerContent: @Composable (ColumnScope.() -> Unit),
+ content: @Composable () -> Unit
+) = if (isLargeScreen) {
+ PermanentNavigationScreen(
+ drawerContent = drawerContent,
+ content = content
+ )
+} else {
+ ModalNavigationScreen(
+ drawerContent = drawerContent,
+ drawerState = drawerState,
+ gesturesEnabled = gesturesEnabled,
+ content = content
+ )
+}
+
+@Composable
+fun ModalNavigationScreen(
+ drawerContent: @Composable (ColumnScope.() -> Unit),
+ drawerState: DrawerState,
+ gesturesEnabled: Boolean,
+ content: @Composable () -> Unit
+) = ModalNavigationDrawer(
+ drawerContent = {
+ ModalDrawerSheet(
+ drawerState = drawerState,
+ content = drawerContent
+ )
+ },
+ drawerState = drawerState,
+ gesturesEnabled = gesturesEnabled,
+ content = content
+)
+
+@Composable
+fun PermanentNavigationScreen(
+ drawerContent: @Composable (ColumnScope.() -> Unit),
+ content: @Composable () -> Unit
+) = PermanentNavigationDrawer(
+ drawerContent = {
+ PermanentDrawerSheet(
+ content = drawerContent
+ )
+ },
+ content = content
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/AdaptiveTopSearchbar.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/AdaptiveTopSearchbar.kt
new file mode 100644
index 0000000..0f32697
--- /dev/null
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/AdaptiveTopSearchbar.kt
@@ -0,0 +1,309 @@
+package com.yangdai.opennote.presentation.component
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.ContextualFlowRow
+import androidx.compose.foundation.layout.ContextualFlowRowOverflow
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Clear
+import androidx.compose.material.icons.outlined.DeleteForever
+import androidx.compose.material.icons.outlined.GridView
+import androidx.compose.material.icons.outlined.History
+import androidx.compose.material.icons.outlined.KeyboardArrowDown
+import androidx.compose.material.icons.outlined.KeyboardArrowUp
+import androidx.compose.material.icons.outlined.Menu
+import androidx.compose.material.icons.outlined.Search
+import androidx.compose.material.icons.outlined.SortByAlpha
+import androidx.compose.material.icons.outlined.ViewAgenda
+import androidx.compose.material3.DockedSearchBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
+import androidx.compose.material3.SearchBar
+import androidx.compose.material3.SearchBarDefaults
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+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.graphics.Color
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.yangdai.opennote.MainActivity
+import com.yangdai.opennote.R
+import com.yangdai.opennote.presentation.event.ListEvent
+import com.yangdai.opennote.presentation.util.Constants
+import com.yangdai.opennote.presentation.util.Constants.DEFAULT_MAX_LINES
+import com.yangdai.opennote.presentation.viewmodel.SharedViewModel
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
+@Composable
+fun AdaptiveTopSearchbar(
+ viewModel: SharedViewModel = hiltViewModel(LocalContext.current as MainActivity),
+ isLargeScreen: Boolean,
+ enabled: Boolean,
+ onSearchBarActivationChange: (Boolean) -> Unit,
+ onDrawerStateChange: () -> Unit
+) {
+
+ val historySet by viewModel.historyStateFlow.collectAsStateWithLifecycle()
+ val settingsState by viewModel.settingsStateFlow.collectAsStateWithLifecycle()
+
+ var inputText by rememberSaveable {
+ mutableStateOf("")
+ }
+ var expanded by rememberSaveable {
+ mutableStateOf(false)
+ }
+ var maxLines by rememberSaveable {
+ mutableIntStateOf(DEFAULT_MAX_LINES)
+ }
+
+ LaunchedEffect(expanded) {
+ onSearchBarActivationChange(expanded)
+ }
+
+ val configuration = LocalConfiguration.current
+ val orientation = remember(configuration) { configuration.orientation }
+
+ fun search(text: String) {
+ if (text.isNotEmpty()) {
+ val newSet = historySet.toMutableSet()
+ newSet.add(text)
+ viewModel.putPreferenceValue(Constants.Preferences.SEARCH_HISTORY, newSet.toSet())
+ viewModel.onListEvent(ListEvent.Search(text))
+ } else {
+ viewModel.onListEvent(
+ ListEvent.Sort(
+ viewModel.dataStateFlow.value.noteOrder,
+ false,
+ null,
+ false
+ )
+ )
+ }
+ expanded = false
+ }
+
+ AdaptiveSearchBar(
+ isDocked = orientation != Configuration.ORIENTATION_PORTRAIT || isLargeScreen,
+ expanded = expanded,
+ onExpandedChange = { expanded = it },
+ inputField = {
+ SearchBarDefaults.InputField(
+ query = inputText,
+ onQueryChange = { inputText = it },
+ onSearch = { search(it) },
+ enabled = enabled,
+ expanded = expanded,
+ onExpandedChange = { expanded = it },
+ placeholder = { Text(text = stringResource(R.string.search)) },
+ leadingIcon = {
+ if (!isLargeScreen) {
+ AnimatedContent(targetState = expanded, label = "leading") {
+ if (it) {
+ IconButton(onClick = { search(inputText) }) {
+ Icon(
+ imageVector = Icons.Outlined.Search,
+ contentDescription = "Search"
+ )
+ }
+ } else {
+ IconButton(
+ enabled = enabled,
+ onClick = onDrawerStateChange
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Menu,
+ contentDescription = "Open Menu"
+ )
+ }
+ }
+ }
+ } else {
+ Icon(
+ imageVector = Icons.Outlined.Search,
+ contentDescription = "Search"
+ )
+ }
+ },
+ trailingIcon = {
+ AnimatedContent(targetState = expanded, label = "trailing") {
+ if (it) {
+ IconButton(onClick = {
+ if (inputText.isNotEmpty()) {
+ inputText = ""
+ } else {
+ search("")
+ }
+ }) {
+ Icon(
+ imageVector = Icons.Outlined.Clear,
+ contentDescription = "Clear"
+ )
+ }
+ } else {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(onClick = { viewModel.onListEvent(ListEvent.ChangeViewMode) }) {
+ Icon(
+ imageVector = if (!settingsState.isListView) Icons.Outlined.ViewAgenda else Icons.Outlined.GridView,
+ contentDescription = "View Mode"
+ )
+ }
+ IconButton(onClick = { viewModel.onListEvent(ListEvent.ToggleOrderSection) }) {
+ Icon(
+ imageVector = Icons.Outlined.SortByAlpha,
+ contentDescription = "Sort"
+ )
+ }
+ }
+ }
+ }
+ },
+ )
+ }
+ ) {
+
+ if (historySet.isEmpty()) return@AdaptiveSearchBar
+
+ ListItem(
+ colors = ListItemDefaults.colors(containerColor = Color.Transparent),
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Outlined.History,
+ contentDescription = "History"
+ )
+ },
+ headlineContent = { Text(text = stringResource(R.string.search_history)) },
+ trailingContent = {
+ Icon(
+ modifier = Modifier.clickable {
+ viewModel.putPreferenceValue(
+ Constants.Preferences.SEARCH_HISTORY,
+ setOf()
+ )
+ },
+ imageVector = Icons.Outlined.DeleteForever,
+ contentDescription = "Clear History"
+ )
+ }
+ )
+
+ ContextualFlowRow(
+ itemCount = historySet.size,
+ modifier = Modifier.padding(horizontal = 12.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ maxLines = maxLines,
+ overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
+ minRowsToShowCollapse = DEFAULT_MAX_LINES + 1,
+ expandIndicator = {
+ IconButton(onClick = { maxLines = Int.MAX_VALUE }) {
+ Icon(
+ imageVector = Icons.Outlined.KeyboardArrowDown,
+ contentDescription = "Expand"
+ )
+ }
+ },
+ collapseIndicator = {
+ IconButton(onClick = { maxLines = DEFAULT_MAX_LINES }) {
+ Icon(
+ imageVector = Icons.Outlined.KeyboardArrowUp,
+ contentDescription = "Collapse"
+ )
+ }
+ }
+ )
+ ) { index ->
+ SuggestionChip(
+ modifier = Modifier.defaultMinSize(48.dp),
+ onClick = { inputText = historySet.elementAt(index) },
+ label = {
+ Text(
+ text = historySet.elementAt(index),
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ })
+ }
+ }
+}
+
+/**
+ * A composable function that adapts the search bar based on the screen size and orientation.
+ *
+ * @param isDocked A boolean indicating whether the search bar is docked or not.
+ * @param expanded A boolean indicating whether the search bar is expanded or not.
+ * @param onExpandedChange A callback that is called when the search bar is expanded or collapsed.
+ * @param inputField The input field of the search bar.
+ * @param content The content of the search bar.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AdaptiveSearchBar(
+ isDocked: Boolean,
+ expanded: Boolean,
+ onExpandedChange: (Boolean) -> Unit,
+ inputField: @Composable () -> Unit,
+ content: @Composable (ColumnScope.() -> Unit)
+) = if (!isDocked) {
+
+ // Animate search bar padding when active state changes
+ val searchBarPadding by animateDpAsState(
+ targetValue = if (expanded) 0.dp else 16.dp,
+ label = "searchBarPadding"
+ )
+
+ SearchBar(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = searchBarPadding),
+ inputField = inputField,
+ expanded = expanded,
+ onExpandedChange = onExpandedChange,
+ content = content
+ )
+} else {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .statusBarsPadding()
+ .padding(top = 8.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ DockedSearchBar(
+ inputField = inputField,
+ expanded = expanded,
+ onExpandedChange = onExpandedChange,
+ content = content
+ )
+ }
+}
\ No newline at end of file
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 c8f59a9..330d1e5 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
@@ -19,6 +19,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -29,20 +30,22 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
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.Folders
-import com.yangdai.opennote.presentation.navigation.Settings
-import kotlinx.collections.immutable.ImmutableList
+import com.yangdai.opennote.presentation.navigation.Screen
+import com.yangdai.opennote.presentation.navigation.Screen.*
@Composable
fun DrawerContent(
- folderList: ImmutableList,
+ folderList: List,
selectedDrawerIndex: Int,
- navigateTo: (Any) -> Unit,
+ navigateTo: (Screen) -> Unit,
onClick: (Int, FolderEntity) -> Unit
) {
@@ -122,3 +125,41 @@ fun DrawerContent(
}
}
}
+
+@Composable
+fun DrawerItem(
+ icon: ImageVector,
+ iconTint: Color = MaterialTheme.colorScheme.onSurface,
+ label: String,
+ badge: String = "",
+ isSelected: Boolean,
+ onClick: () -> Unit
+) = NavigationDrawerItem(
+ modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
+ icon = {
+ Icon(
+ modifier = Modifier.padding(horizontal = 12.dp),
+ imageVector = icon,
+ tint = iconTint,
+ contentDescription = "Leading Icon"
+ )
+ },
+ label = {
+ Text(
+ text = label,
+ color = MaterialTheme.colorScheme.onSurface,
+ fontWeight = FontWeight.Bold,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ },
+ badge = {
+ Text(
+ text = badge,
+ style = MaterialTheme.typography.labelMedium
+ )
+ },
+ shape = MaterialTheme.shapes.large,
+ selected = isSelected,
+ onClick = onClick
+)
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerItem.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerItem.kt
deleted file mode 100644
index 71cf842..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/DrawerItem.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.NavigationDrawerItem
-import androidx.compose.material3.NavigationDrawerItemDefaults
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun DrawerItem(
- icon: ImageVector,
- iconTint: Color = MaterialTheme.colorScheme.onSurface,
- label: String,
- badge: String = "",
- isSelected: Boolean,
- onClick: () -> Unit
-) = NavigationDrawerItem(
- modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
- icon = {
- Icon(
- modifier = Modifier.padding(horizontal = 12.dp),
- imageVector = icon,
- tint = iconTint,
- contentDescription = "Leading Icon"
- )
- },
- label = {
- Text(
- text = label,
- color = MaterialTheme.colorScheme.onSurface,
- fontWeight = FontWeight.Bold,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- },
- badge = {
- Text(
- text = badge,
- style = MaterialTheme.typography.labelMedium
- )
- },
- shape = MaterialTheme.shapes.large,
- selected = isSelected,
- onClick = onClick
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/ExportDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/ExportDialog.kt
index 063a336..ab83d45 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/ExportDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/ExportDialog.kt
@@ -20,31 +20,28 @@ enum class ExportType {
fun ExportDialog(
onDismissRequest: () -> Unit,
onConfirm: (ExportType) -> Unit
-) {
-
- AlertDialog(
- title = {
- Text(text = stringResource(R.string.export_as))
- },
- text = {
- Column(modifier = Modifier.fillMaxWidth()) {
- TextOptionButton(text = "TXT") {
- onConfirm(ExportType.TXT)
- }
+) = AlertDialog(
+ title = {
+ Text(text = stringResource(R.string.export_as))
+ },
+ text = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ TextOptionButton(text = "TXT") {
+ onConfirm(ExportType.TXT)
+ }
- TextOptionButton(text = "MARKDOWN") {
- onConfirm(ExportType.MARKDOWN)
- }
+ TextOptionButton(text = "MARKDOWN") {
+ onConfirm(ExportType.MARKDOWN)
+ }
- TextOptionButton(text = "HTML") {
- onConfirm(ExportType.HTML)
- }
+ TextOptionButton(text = "HTML") {
+ onConfirm(ExportType.HTML)
}
- },
- onDismissRequest = onDismissRequest,
- confirmButton = {}
- )
-}
+ }
+ },
+ onDismissRequest = onDismissRequest,
+ confirmButton = {}
+)
@Composable
@Preview
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/FilterRadioButton.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/FilterRadioButton.kt
deleted file mode 100644
index 2849588..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/FilterRadioButton.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.RadioButton
-import androidx.compose.material3.RadioButtonDefaults
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun FilterRadioButton(
- text: String,
- selected: Boolean,
- onSelect: () -> Unit,
- modifier: Modifier = Modifier
-) {
- Row(
- modifier = modifier,
- verticalAlignment = Alignment.CenterVertically
- ) {
- RadioButton(
- selected = selected,
- onClick = onSelect,
- colors = RadioButtonDefaults.colors(
- selectedColor = MaterialTheme.colorScheme.primary,
- unselectedColor = MaterialTheme.colorScheme.onBackground
- )
- )
- Spacer(modifier = Modifier.width(8.dp))
- Text(text = text, style = MaterialTheme.typography.bodyMedium)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/FolderItem.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/FolderItem.kt
index c8a42d6..2d10541 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/FolderItem.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/FolderItem.kt
@@ -34,11 +34,11 @@ fun LazyGridItemScope.FolderItem(
onDelete: () -> Unit
) {
- var modify by remember {
+ var showModifyDialog by remember {
mutableStateOf(false)
}
- var delete by remember {
+ var showWarningDialog by remember {
mutableStateOf(false)
}
@@ -68,7 +68,7 @@ fun LazyGridItemScope.FolderItem(
Row {
IconButton(onClick = {
- modify = !modify
+ showModifyDialog = !showModifyDialog
}) {
Icon(
imageVector = Icons.Outlined.DriveFileRenameOutline,
@@ -77,7 +77,7 @@ fun LazyGridItemScope.FolderItem(
}
IconButton(onClick = {
- delete = !delete
+ showWarningDialog = !showWarningDialog
}) {
Icon(
imageVector = Icons.Outlined.Delete,
@@ -87,18 +87,20 @@ fun LazyGridItemScope.FolderItem(
}
}
- WarningDialog(
- showDialog = delete,
- message = stringResource(R.string.deleting_a_folder_will_also_delete_all_the_notes_it_contains_and_they_cannot_be_restored_do_you_want_to_continue),
- onDismissRequest = { delete = false }) {
- onDelete()
+ if (showWarningDialog) {
+ WarningDialog(
+ message = stringResource(R.string.deleting_a_folder_will_also_delete_all_the_notes_it_contains_and_they_cannot_be_restored_do_you_want_to_continue),
+ onDismissRequest = { showWarningDialog = false },
+ onConfirm = onDelete
+ )
}
- ModifyFolderDialog(
- showDialog = modify,
- folder = folder,
- onDismissRequest = { modify = false }) {
- onModify(it)
+ if (showModifyDialog) {
+ ModifyFolderDialog(
+ folder = folder,
+ onDismissRequest = { showModifyDialog = false }) {
+ onModify(it)
+ }
}
}
}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/FolderListDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/FolderListDialog.kt
index bb264d7..e8248e9 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/FolderListDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/FolderListDialog.kt
@@ -30,15 +30,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yangdai.opennote.R
import com.yangdai.opennote.data.local.entity.FolderEntity
-import kotlinx.collections.immutable.ImmutableList
-import kotlinx.collections.immutable.toImmutableList
@Composable
fun FolderListDialog(
hint: String = "",
oFolderId: Long?,
- folders: ImmutableList,
+ folders: List,
onDismissRequest: () -> Unit,
onSelect: (Long?) -> Unit
) {
@@ -143,7 +141,7 @@ fun FolderListDialogPreview() {
FolderEntity(1, "Folder 1", null),
FolderEntity(2, "Folder 2", null),
FolderEntity(3, "Folder 3", null)
- ).toImmutableList(),
+ ),
onDismissRequest = {},
onSelect = {}
)
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 86daf32..624dfdc 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
@@ -70,9 +70,7 @@ fun LinkDialog(
)
}
},
- onDismissRequest = {
- onDismissRequest()
- },
+ onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
onClick = {
@@ -93,7 +91,7 @@ fun LinkDialog(
}
},
dismissButton = {
- TextButton(onClick = { onDismissRequest() }) {
+ TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
}
}
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
deleted file mode 100644
index 52f6424..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/MainContent.kt
+++ /dev/null
@@ -1,474 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.slideInHorizontally
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutHorizontally
-import androidx.compose.animation.slideOutVertically
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.layout.calculateStartPadding
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
-import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
-import androidx.compose.foundation.lazy.staggeredgrid.items
-import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
-import androidx.compose.material.icons.outlined.Add
-import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.GridView
-import androidx.compose.material.icons.outlined.Menu
-import androidx.compose.material.icons.outlined.MoreVert
-import androidx.compose.material.icons.outlined.RestartAlt
-import androidx.compose.material.icons.outlined.SortByAlpha
-import androidx.compose.material.icons.outlined.Upload
-import androidx.compose.material.icons.outlined.ViewAgenda
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.BottomAppBar
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FloatingActionButton
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.VerticalDivider
-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.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-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.state.DataActionState
-import com.yangdai.opennote.presentation.state.DataState
-import kotlinx.collections.immutable.ImmutableList
-import kotlinx.collections.immutable.toImmutableList
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MainContent(
- isListViewMode: Boolean,
- dataActionState: DataActionState,
- isFloatingButtonVisible: Boolean,
- selectedFolder: FolderEntity,
- selectedDrawerIndex: Int,
- allNotesSelected: Boolean,
- selectedNotes: ImmutableList,
- isMultiSelectionModeEnabled: Boolean,
- isLargeScreen: Boolean,
- dataState: DataState,
- folderList: ImmutableList,
- navigateToNote: (Long) -> Unit,
- initializeNoteSelection: () -> Unit,
- onSearchBarActivationChange: (Boolean) -> Unit,
- onAllNotesSelectionChange: (Boolean) -> Unit,
- onMultiSelectionModeChange: (Boolean) -> Unit,
- onNoteClick: (NoteEntity) -> Unit,
- onListEvent: (ListEvent) -> Unit,
- onDrawerStateChange: () -> Unit,
- onExportClick: (ExportType) -> Unit,
- onExportCancelled: () -> Unit
-) {
-
- val staggeredGridState = rememberLazyStaggeredGridState()
- val lazyListState = rememberLazyListState()
-
- var folderName by rememberSaveable {
- mutableStateOf("")
- }
-
- // Bottom sheet visibility, reset when configuration changes, no need to use rememberSaveable
- var isFolderDialogVisible by remember {
- mutableStateOf(false)
- }
-
- // Whether to show the export dialog
- var isExportDialogVisible by remember {
- mutableStateOf(false)
- }
-
- LaunchedEffect(selectedFolder) {
- if (selectedFolder.id != null) {
- folderName = selectedFolder.name
- }
- }
-
- Scaffold(
- modifier = Modifier.fillMaxSize(),
- topBar = {
- AnimatedContent(targetState = selectedDrawerIndex == 0, label = "") {
- if (it) {
- TopSearchbar(
- enabled = !isMultiSelectionModeEnabled,
- isLargeScreen = isLargeScreen,
- onSearchBarActivationChange = onSearchBarActivationChange,
- onDrawerStateChange = onDrawerStateChange
- )
- } else {
-
- var showMenu by remember {
- mutableStateOf(false)
- }
-
- TopAppBar(
- title = {
- Text(
- text = if (selectedDrawerIndex == 1) stringResource(id = R.string.trash)
- else folderName,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- },
- navigationIcon = {
- if (!isLargeScreen) {
- IconButton(
- enabled = !isMultiSelectionModeEnabled,
- onClick = onDrawerStateChange
- ) {
- Icon(
- imageVector = Icons.Outlined.Menu,
- contentDescription = "Open Menu"
- )
- }
- }
- },
- actions = {
- IconButton(onClick = { onListEvent(ListEvent.ChangeViewMode) }) {
- Icon(
- imageVector = if (!isListViewMode) Icons.Outlined.ViewAgenda else Icons.Outlined.GridView,
- contentDescription = "View Mode"
- )
- }
- IconButton(onClick = { onListEvent(ListEvent.ToggleOrderSection) }) {
- Icon(
- imageVector = Icons.Outlined.SortByAlpha,
- contentDescription = "Sort"
- )
- }
- if (selectedDrawerIndex == 1) {
- IconButton(onClick = { showMenu = !showMenu }) {
- Icon(
- imageVector = Icons.Outlined.MoreVert,
- contentDescription = "More"
- )
- }
-
- DropdownMenu(
- expanded = showMenu,
- onDismissRequest = { showMenu = false }) {
- DropdownMenuItem(
- leadingIcon = {
- Icon(
- imageVector = Icons.Outlined.RestartAlt,
- contentDescription = "Restore"
- )
- },
- text = { Text(text = stringResource(id = R.string.restore_all)) },
- onClick = {
- onListEvent(ListEvent.RestoreNotes(dataState.notes.toImmutableList()))
- })
-
- DropdownMenuItem(
- leadingIcon = {
- Icon(
- imageVector = Icons.Outlined.Delete,
- contentDescription = "Delete"
- )
- },
- text = { Text(text = stringResource(id = R.string.delete_all)) },
- onClick = {
- onListEvent(
- ListEvent.DeleteNotes(
- dataState.notes.toImmutableList(),
- false
- )
- )
- })
- }
- }
- }
- )
- }
- }
- },
- bottomBar = {
- AnimatedVisibility(
- visible = isMultiSelectionModeEnabled,
- enter = slideInVertically { fullHeight -> fullHeight },
- exit = slideOutVertically { fullHeight -> fullHeight }
- ) {
- BottomAppBar {
- Row(
- modifier = Modifier
- .fillMaxSize()
- .padding(horizontal = 16.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
-
- Row(
- modifier = Modifier.fillMaxHeight(),
- verticalAlignment = Alignment.CenterVertically
- ) {
-
- Checkbox(
- checked = allNotesSelected,
- onCheckedChange = onAllNotesSelectionChange
- )
-
- Text(text = stringResource(R.string.checked))
-
- Text(text = selectedNotes.size.toString())
- }
-
- Row(
- modifier = Modifier.fillMaxHeight(),
- verticalAlignment = Alignment.CenterVertically
- ) {
-
- if (selectedDrawerIndex == 1) {
- TextButton(onClick = {
- onListEvent(ListEvent.RestoreNotes(selectedNotes))
- initializeNoteSelection()
- }) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Icon(
- imageVector = Icons.Outlined.RestartAlt,
- contentDescription = "Restore"
- )
- Text(text = stringResource(id = R.string.restore))
- }
- }
- } else {
- TextButton(onClick = {
- isExportDialogVisible = true
- }) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Icon(
- imageVector = Icons.Outlined.Upload,
- contentDescription = "Export"
- )
- Text(text = stringResource(id = R.string.export))
- }
- }
-
- TextButton(onClick = { isFolderDialogVisible = true }) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Icon(
- imageVector = Icons.AutoMirrored.Outlined.DriveFileMove,
- contentDescription = "Move"
- )
- Text(text = stringResource(id = R.string.move))
- }
- }
- }
-
- TextButton(onClick = {
- onListEvent(
- ListEvent.DeleteNotes(
- selectedNotes,
- selectedDrawerIndex != 1
- )
- )
- initializeNoteSelection()
- }) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Icon(
- imageVector = Icons.Outlined.Delete,
- contentDescription = "Delete"
- )
- Text(text = stringResource(id = R.string.delete))
- }
- }
- }
- }
- }
- }
- },
- floatingActionButton = {
- AnimatedVisibility(
- visible = isFloatingButtonVisible && !staggeredGridState.isScrollInProgress && !lazyListState.isScrollInProgress,
- enter = slideInHorizontally { fullWidth -> fullWidth * 3 / 2 },
- exit = slideOutHorizontally { fullWidth -> fullWidth * 3 / 2 }) {
- FloatingActionButton(
- onClick = {
- onListEvent(ListEvent.AddNote)
- navigateToNote(-1)
- }
- ) {
- Icon(imageVector = Icons.Outlined.Add, contentDescription = "Add")
- }
- }
-
- }) { innerPadding ->
-
- // Add layoutDirection, displayCutout, startPadding, and endPadding.
- val layoutDirection = LocalLayoutDirection.current
- val displayCutout = WindowInsets.displayCutout.asPaddingValues()
- val startPadding = displayCutout.calculateStartPadding(layoutDirection)
- val endPadding = displayCutout.calculateEndPadding(layoutDirection)
-
- Box(
- modifier = Modifier
- .fillMaxSize()
- .statusBarsPadding()
- .padding(top = 72.dp, start = startPadding, end = endPadding)
- ) {
- if (!isListViewMode) {
- LazyVerticalStaggeredGrid(
- modifier = Modifier
- .fillMaxSize(),
- state = staggeredGridState,
- // The staggered grid layout is adaptive, with a minimum column width of 160dp(mdpi)
- columns = StaggeredGridCells.Adaptive(160.dp),
- verticalItemSpacing = 8.dp,
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- // for better edgeToEdge experience
- contentPadding = PaddingValues(
- start = 16.dp,
- end = 16.dp,
- bottom = innerPadding.calculateBottomPadding()
- ),
- content = {
- items(
- dataState.notes,
- key = { item: NoteEntity -> item.id!! }) { note ->
- GridNoteCard(
- modifier = Modifier
- .fillMaxWidth()
- .animateItem(), // Add animation to the item
- note = note,
- isEnabled = isMultiSelectionModeEnabled,
- isSelected = selectedNotes.contains(note),
- onEnableChange = onMultiSelectionModeChange,
- onNoteClick = onNoteClick
- )
- }
- }
- )
- } else {
-
- if (dataState.notes.isEmpty()) {
- return@Box
- }
-
- VerticalDivider(
- Modifier
- .align(Alignment.TopStart)
- .fillMaxHeight()
- .padding(start = 15.dp),
- thickness = 2.dp
- )
- LazyColumn(
- modifier = Modifier
- .align(Alignment.Center)
- .fillMaxSize(),
- state = lazyListState,
- contentPadding = PaddingValues(
- start = 12.dp,
- end = 16.dp,
- bottom = innerPadding.calculateBottomPadding()
- )
- ) {
- items(
- dataState.notes,
- key = { item: NoteEntity -> item.id!! }) { note ->
- ColumnNoteCard(
- modifier = Modifier
- .fillMaxWidth()
- .animateItem(), // Add animation to the item
- note = note,
- isEnabled = isMultiSelectionModeEnabled,
- isSelected = selectedNotes.contains(note),
- onEnableChange = onMultiSelectionModeChange,
- onNoteClick = onNoteClick
- )
- }
- }
- }
- }
-
- if (dataState.isOrderSectionVisible) {
- AlertDialog(
- title = { Text(text = stringResource(R.string.sort_by)) },
- text = {
- OrderSection(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 12.dp),
- noteOrder = dataState.noteOrder,
- onOrderChange = {
- onListEvent(
- ListEvent.Sort(
- noteOrder = it,
- trash = selectedDrawerIndex == 1,
- filterFolder = selectedDrawerIndex != 0 && selectedDrawerIndex != 1,
- folderId = selectedFolder.id
- )
- )
- }
- )
- },
- onDismissRequest = { onListEvent(ListEvent.ToggleOrderSection) },
- confirmButton = {
- TextButton(onClick = { onListEvent(ListEvent.ToggleOrderSection) }) {
- Text(stringResource(id = android.R.string.ok))
- }
- })
- }
-
- if (isExportDialogVisible) {
- ExportDialog(onDismissRequest = { isExportDialogVisible = false }) {
- onExportClick(it)
- isExportDialogVisible = false
- }
- }
-
- if (isFolderDialogVisible) {
- FolderListDialog(
- hint = stringResource(R.string.destination_folder),
- oFolderId = selectedFolder.id,
- folders = folderList,
- onDismissRequest = { isFolderDialogVisible = false }
- ) {
- onListEvent(ListEvent.MoveNotes(selectedNotes, it))
- initializeNoteSelection()
- }
- }
-
- ProgressDialog(isLoading = dataActionState.loading, progress = dataActionState.progress) {
- onExportCancelled()
- }
- }
-}
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 1a10375..7f2fa69 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
@@ -7,88 +7,58 @@ import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
-import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.yangdai.opennote.presentation.theme.linkColor
+import com.yangdai.opennote.presentation.util.rememberCustomTabsIntent
@SuppressLint("SetJavaScriptEnabled")
@Composable
-fun MarkdownText(html: String) {
+fun MarkdownText(
+ html: String,
+ colorScheme: ColorScheme = MaterialTheme.colorScheme
+) {
- val textColor = MaterialTheme.colorScheme.onSurface.toArgb()
- val codeBackgroundColor = MaterialTheme.colorScheme.surfaceVariant.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 hexTextColor = remember(colorScheme) {
+ String.format("#%06X", 0xFFFFFF and colorScheme.onSurface.toArgb())
}
- val hexCodeBackgroundColor = remember {
- String.format("#%06X", 0xFFFFFF and codeBackgroundColor)
+ val hexCodeBackgroundColor = remember(colorScheme) {
+ String.format("#%06X", 0xFFFFFF and colorScheme.surfaceVariant.toArgb())
}
- val hexPreBackgroundColor = remember {
- String.format("#%06X", 0xFFFFFF and preBackgroundColor)
+ val hexPreBackgroundColor = remember(colorScheme) {
+ String.format("#%06X", 0xFFFFFF and colorScheme.surfaceColorAtElevation(1.dp).toArgb())
}
- val hexQuoteBackgroundColor = remember {
- String.format("#%06X", 0xFFFFFF and quoteBackgroundColor)
+ val hexQuoteBackgroundColor = remember(colorScheme) {
+ String.format("#%06X", 0xFFFFFF and colorScheme.secondaryContainer.toArgb())
}
- val hexLinkColor = remember {
+ val hexLinkColor = remember(colorScheme) {
String.format("#%06X", 0xFFFFFF and linkColor.toArgb())
}
- val hexBorderColor = remember {
- String.format("#%06X", 0xFFFFFF and borderColor)
- }
-
- val customTabsIntent = remember {
- CustomTabsIntent.Builder()
- .setShowTitle(true)
- .build()
+ val hexBorderColor = remember(colorScheme) {
+ String.format("#%06X", 0xFFFFFF and colorScheme.outline.toArgb())
}
- AndroidView(
- modifier = Modifier.fillMaxSize(),
- factory = {
- WebView(it).apply {
- layoutParams = ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- )
- webViewClient = object : WebViewClient() {
- override fun shouldOverrideUrlLoading(
- view: WebView?,
- request: WebResourceRequest
- ): Boolean {
- val url = request.url.toString()
- if (url.startsWith("http://") || url.startsWith("https://")) {
- customTabsIntent.launchUrl(it, Uri.parse(url))
- }
- return true
- }
- }
- settings.javaScriptEnabled = true
- settings.loadsImagesAutomatically = true
- settings.defaultTextEncodingName = "utf-8"
- isVerticalScrollBarEnabled = false
- isHorizontalScrollBarEnabled = false
- settings.setSupportZoom(true)
- settings.builtInZoomControls = true
- settings.displayZoomControls = false
- settings.useWideViewPort = false
- settings.loadWithOverviewMode = false
- setBackgroundColor(Color.TRANSPARENT)
- }
- },
- update = {
- val data = """
+ val data by remember(
+ html,
+ hexTextColor,
+ hexCodeBackgroundColor,
+ hexPreBackgroundColor,
+ hexQuoteBackgroundColor,
+ hexLinkColor,
+ hexBorderColor
+ ) {
+ mutableStateOf(
+ """
@@ -126,6 +96,45 @@ fun MarkdownText(html: String) {
""".trimIndent()
+ )
+ }
+
+ val customTabsIntent = rememberCustomTabsIntent()
+
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = {
+ WebView(it).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ webViewClient = object : WebViewClient() {
+ override fun shouldOverrideUrlLoading(
+ view: WebView?,
+ request: WebResourceRequest
+ ): Boolean {
+ val url = request.url.toString()
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ customTabsIntent.launchUrl(it, Uri.parse(url))
+ }
+ return true
+ }
+ }
+ settings.javaScriptEnabled = true
+ settings.loadsImagesAutomatically = true
+ settings.defaultTextEncodingName = "UTF-8"
+ isVerticalScrollBarEnabled = false
+ isHorizontalScrollBarEnabled = false
+ settings.setSupportZoom(true)
+ settings.builtInZoomControls = true
+ settings.displayZoomControls = false
+ settings.useWideViewPort = false
+ settings.loadWithOverviewMode = false
+ setBackgroundColor(Color.TRANSPARENT)
+ }
+ },
+ update = {
it.loadDataWithBaseURL(
null,
data,
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/MaskBox.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/MaskBox.kt
index a0ac359..1a542cf 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/MaskBox.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/MaskBox.kt
@@ -42,7 +42,7 @@ typealias MaskAnimActive = (MaskAnimModel, Float, Float) -> Unit
@SuppressLint("Recycle")
@Composable
fun MaskBox(
- animTime: Long = 650,
+ animTime: Long = 650L,
maskComplete: (MaskAnimModel) -> Unit,
animFinish: () -> Unit,
content: @Composable (MaskAnimActive) -> Unit,
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
deleted file mode 100644
index 86b6686..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/ModalNavigationScreen.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.DrawerState
-import androidx.compose.material3.ModalDrawerSheet
-import androidx.compose.material3.ModalNavigationDrawer
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.yangdai.opennote.data.local.entity.FolderEntity
-import kotlinx.collections.immutable.ImmutableList
-
-@Composable
-fun ModalNavigationScreen(
- drawerState: DrawerState,
- gesturesEnabled: Boolean,
- folderList: ImmutableList,
- selectedDrawerIndex: Int,
- content: @Composable () -> Unit,
- navigateTo: (Any) -> Unit,
- selectDrawer: (Int, FolderEntity)-> Unit
-) = ModalNavigationDrawer(
- modifier = Modifier.fillMaxSize(),
- drawerState = drawerState,
- gesturesEnabled = gesturesEnabled,
- drawerContent = {
- ModalDrawerSheet(drawerState = drawerState) {
- DrawerContent(
- folderList = folderList,
- selectedDrawerIndex = selectedDrawerIndex,
- navigateTo = { navigateTo(it) }
- ) { position, folder ->
- selectDrawer(position, folder)
- }
- }
- }
-) {
- content()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/ModifyFolderDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/ModifyFolderDialog.kt
index 2036ab0..c477786 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/ModifyFolderDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/ModifyFolderDialog.kt
@@ -38,7 +38,6 @@ import com.yangdai.opennote.data.local.entity.FolderEntity
@Preview
fun ModifyFolderDialogPreview() {
ModifyFolderDialog(
- showDialog = true,
folder = FolderEntity(),
onDismissRequest = {},
onModify = {}
@@ -47,14 +46,11 @@ fun ModifyFolderDialogPreview() {
@Composable
fun ModifyFolderDialog(
- showDialog: Boolean,
folder: FolderEntity,
onDismissRequest: () -> Unit,
onModify: (FolderEntity) -> Unit
) {
- if (!showDialog) return
-
var text by remember { mutableStateOf(folder.name) }
var color by remember { mutableStateOf(folder.color) }
@@ -95,9 +91,7 @@ fun ModifyFolderDialog(
}
}
},
- onDismissRequest = {
- onDismissRequest()
- },
+ onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
onClick = {
@@ -120,7 +114,7 @@ fun ModifyFolderDialog(
}
},
dismissButton = {
- TextButton(onClick = { onDismissRequest() }) {
+ TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
}
}
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 d4138fa..0f23c70 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
@@ -61,32 +61,30 @@ fun IconButtonWithTooltip(
contentDescription: String,
shortCutDescription: String? = null,
onClick: () -> Unit
+) = TooltipBox(
+ positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ tooltip = {
+ if (shortCutDescription != null) {
+ PlainTooltip(
+ content = { Text(shortCutDescription) }
+ )
+ }
+ },
+ state = rememberTooltipState(),
+ focusable = false,
+ enableUserInput = true
) {
- TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
- tooltip = {
- if (shortCutDescription != null) {
- PlainTooltip(
- content = { Text(shortCutDescription) }
- )
- }
- },
- state = rememberTooltipState(),
- focusable = false,
- enableUserInput = true
- ) {
- IconButton(onClick = onClick, enabled = enabled) {
- if (imageVector != null) {
- Icon(
- imageVector = imageVector,
- contentDescription = contentDescription
- )
- } else {
- Icon(
- painter = painterResource(id = painter!!),
- contentDescription = contentDescription
- )
- }
+ IconButton(onClick = onClick, enabled = enabled) {
+ if (imageVector != null) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = contentDescription
+ )
+ } else {
+ Icon(
+ painter = painterResource(id = painter!!),
+ contentDescription = contentDescription
+ )
}
}
}
@@ -298,10 +296,9 @@ fun NoteEditorRow(
IconButtonWithTooltip(
imageVector = Icons.Outlined.TableChart,
contentDescription = "Table",
- shortCutDescription = "Ctrl + T"
- ) {
- onTableButtonClick()
- }
+ shortCutDescription = "Ctrl + T",
+ onClick = onTableButtonClick
+ )
IconButtonWithTooltip(
imageVector = Icons.Outlined.AddChart,
@@ -315,26 +312,23 @@ fun NoteEditorRow(
IconButtonWithTooltip(
imageVector = Icons.Outlined.CheckBox,
contentDescription = "Task",
- shortCutDescription = "Ctrl + Shift + T"
- ) {
- onTaskButtonClick()
- }
+ shortCutDescription = "Ctrl + Shift + T",
+ onClick = onTaskButtonClick
+ )
IconButtonWithTooltip(
imageVector = Icons.Outlined.Link,
contentDescription = "Link",
- shortCutDescription = "Ctrl + K"
- ) {
- onLinkButtonClick()
- }
+ shortCutDescription = "Ctrl + K",
+ onClick = onLinkButtonClick
+ )
IconButtonWithTooltip(
imageVector = Icons.Outlined.DocumentScanner,
contentDescription = "OCR",
- shortCutDescription = "Ctrl + S"
- ) {
- onScanButtonClick()
- }
+ shortCutDescription = "Ctrl + S",
+ onClick = onScanButtonClick
+ )
}
}
}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/OrderSection.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/OrderSection.kt
deleted file mode 100644
index c0ee3dc..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/OrderSection.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.runtime.Composable
-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.domain.usecase.NoteOrder
-import com.yangdai.opennote.domain.usecase.OrderType
-
-@Composable
-fun OrderSection(
- modifier: Modifier = Modifier,
- noteOrder: NoteOrder = NoteOrder.Date(OrderType.Descending),
- onOrderChange: (NoteOrder) -> Unit
-) {
- Column(
- modifier = modifier
- ) {
- Row(
- modifier = Modifier.fillMaxWidth()
- ) {
- FilterRadioButton(
- text = stringResource(R.string.title),
- selected = noteOrder is NoteOrder.Title,
- onSelect = { onOrderChange(NoteOrder.Title(noteOrder.orderType)) }
- )
- Spacer(modifier = Modifier.width(8.dp))
- FilterRadioButton(
- text = stringResource(R.string.date),
- selected = noteOrder is NoteOrder.Date,
- onSelect = { onOrderChange(NoteOrder.Date(noteOrder.orderType)) }
- )
- }
- Spacer(modifier = Modifier.height(16.dp))
- Row(
- modifier = Modifier.fillMaxWidth()
- ) {
- FilterRadioButton(
- text = stringResource(R.string.ascending),
- selected = noteOrder.orderType is OrderType.Ascending,
- onSelect = {
- onOrderChange(noteOrder.copy(OrderType.Ascending))
- }
- )
- Spacer(modifier = Modifier.width(8.dp))
- FilterRadioButton(
- text = stringResource(R.string.descending),
- selected = noteOrder.orderType is OrderType.Descending,
- onSelect = {
- onOrderChange(noteOrder.copy(OrderType.Descending))
- }
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/OrderSectionDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/OrderSectionDialog.kt
new file mode 100644
index 0000000..6b1c603
--- /dev/null
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/OrderSectionDialog.kt
@@ -0,0 +1,120 @@
+package com.yangdai.opennote.presentation.component
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.SegmentedButton
+import androidx.compose.material3.SegmentedButtonDefaults
+import androidx.compose.material3.SingleChoiceSegmentedButtonRow
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.yangdai.opennote.R
+import com.yangdai.opennote.domain.usecase.NoteOrder
+import com.yangdai.opennote.domain.usecase.OrderType
+
+@Composable
+fun OrderSectionDialog(
+ noteOrder: NoteOrder = NoteOrder.Date(OrderType.Descending),
+ onOrderChange: (NoteOrder) -> Unit,
+ onDismiss: () -> Unit
+) {
+
+ var newOrder by remember { mutableStateOf(noteOrder) }
+
+ val typeOptions = listOf(
+ stringResource(R.string.title),
+ stringResource(R.string.date)
+ )
+
+ val orderOptions = listOf(
+ stringResource(R.string.ascending),
+ stringResource(R.string.descending)
+ )
+
+ AlertDialog(
+ onDismissRequest = onDismiss,
+ title = { Text(text = stringResource(R.string.sort_by)) },
+ text = {
+ Column {
+ SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
+ SegmentedButton(
+ shape = SegmentedButtonDefaults.itemShape(
+ index = 0,
+ count = typeOptions.size
+ ),
+ onClick = { newOrder = NoteOrder.Title(noteOrder.orderType) },
+ selected = newOrder is NoteOrder.Title
+ ) {
+ Text(typeOptions[0])
+ }
+ SegmentedButton(
+ shape = SegmentedButtonDefaults.itemShape(
+ index = 1,
+ count = typeOptions.size
+ ),
+ onClick = { newOrder = NoteOrder.Date(noteOrder.orderType) },
+ selected = newOrder is NoteOrder.Date
+ ) {
+ Text(typeOptions[1])
+ }
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
+ SegmentedButton(
+ shape = SegmentedButtonDefaults.itemShape(
+ index = 0,
+ count = typeOptions.size
+ ),
+ onClick = { newOrder = newOrder.copy(OrderType.Ascending) },
+ selected = newOrder.orderType is OrderType.Ascending
+ ) {
+ Text(orderOptions[0])
+ }
+ SegmentedButton(
+ shape = SegmentedButtonDefaults.itemShape(
+ index = 1,
+ count = typeOptions.size
+ ),
+ onClick = { newOrder = newOrder.copy(OrderType.Descending) },
+ selected = newOrder.orderType is OrderType.Descending
+ ) {
+ Text(orderOptions[1])
+ }
+ }
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = onDismiss) {
+ Text(stringResource(id = android.R.string.cancel))
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = {
+ onDismiss()
+ onOrderChange(newOrder)
+ }) {
+ Text(stringResource(id = android.R.string.ok))
+ }
+ }
+ )
+}
+
+@Preview
+@Composable
+fun OrderSectionDialogPreview() {
+ OrderSectionDialog(
+ noteOrder = NoteOrder.Date(OrderType.Descending),
+ onOrderChange = {},
+ onDismiss = {})
+}
\ No newline at end of file
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
deleted file mode 100644
index b051c92..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/PermanentNavigationScreen.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.PermanentDrawerSheet
-import androidx.compose.material3.PermanentNavigationDrawer
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.yangdai.opennote.data.local.entity.FolderEntity
-import kotlinx.collections.immutable.ImmutableList
-
-@Composable
-fun PermanentNavigationScreen(
- folderList: ImmutableList,
- selectedDrawerIndex: Int,
- content: @Composable () -> Unit,
- navigateTo: (Any) -> Unit,
- selectDrawer: (Int, FolderEntity) -> Unit
-) = PermanentNavigationDrawer(
- modifier = Modifier.fillMaxSize(),
- drawerContent = {
- PermanentDrawerSheet {
- DrawerContent(
- folderList = folderList,
- selectedDrawerIndex = selectedDrawerIndex,
- navigateTo = { navigateTo(it) }
- ) { position, folder ->
- selectDrawer(position, folder)
- }
- }
- }
-) {
- content()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/RatingDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/RatingDialog.kt
index f9d88c1..39b4c95 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/RatingDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/RatingDialog.kt
@@ -22,13 +22,10 @@ import com.yangdai.opennote.R
@Composable
fun RatingDialog(
- showDialog: Boolean,
onDismissRequest: () -> Unit,
onRatingChanged: (Int) -> Unit
) {
- if (!showDialog) return
-
var rating by remember { mutableIntStateOf(0) }
AlertDialog(
@@ -63,5 +60,5 @@ fun RatingDialog(
@Preview
@Composable
fun RatingDialogPreview() {
- RatingDialog(showDialog = true, onDismissRequest = {}, onRatingChanged = {})
+ RatingDialog(onDismissRequest = {}, onRatingChanged = {})
}
\ No newline at end of file
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/RichText.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/RichText.kt
index e1ec427..3b70bc1 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/RichText.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/RichText.kt
@@ -2,7 +2,6 @@ package com.yangdai.opennote.presentation.component
import android.net.Uri
import android.widget.Toast
-import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
@@ -11,6 +10,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -21,62 +22,27 @@ import androidx.compose.ui.text.style.LineBreak
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withLink
import com.yangdai.opennote.presentation.theme.linkColor
+import com.yangdai.opennote.presentation.util.rememberCustomTabsIntent
@Composable
fun RichText(str: String) {
val context = LocalContext.current
- val customTabsIntent = remember {
- CustomTabsIntent.Builder()
- .setShowTitle(true)
- .build()
- }
+ val customTabsIntent = rememberCustomTabsIntent()
val pattern = remember {
"\\[(.*?)]\\((.*?)\\)".toRegex()
}
- val text = str.replace("- [ ]", "◎").replace("- [x]", "◉")
-
- val matches = pattern.findAll(text)
-
- val annotatedString = buildAnnotatedString {
-
- var lastIndex = 0
-
- matches.forEach { matchResult ->
- val range = matchResult.range
- val title = matchResult.groupValues[1]
- val link = matchResult.groupValues[2]
-
- // Append plain text
- append(text.substring(lastIndex, range.first))
-
- val url = LinkAnnotation.Url(link) {
- // Handle click event
- val url = (it as LinkAnnotation.Url).url
-
- try {
- if (url.startsWith("http://") || url.startsWith("https://")) {
- customTabsIntent.launchUrl(context, Uri.parse(url))
- }
- } catch (e: Exception) {
- e.printStackTrace()
- // Show error message to the user
- Toast.makeText(context, "Failed to open link: $url", Toast.LENGTH_SHORT).show()
- }
- }
-
- withLink(url) {
- append(title)
- }
-
- lastIndex = range.last + 1
- }
+ val text by remember(str) {
+ mutableStateOf(str.replace("- [ ]", "◎").replace("- [x]", "◉"))
+ }
- // Append the rest of the text
- append(text.substring(lastIndex, text.length))
+ val matches by remember(pattern, text) {
+ mutableStateOf(
+ pattern.findAll(text)
+ )
}
SelectionContainer {
@@ -84,7 +50,44 @@ fun RichText(str: String) {
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
- text = annotatedString,
+ text = buildAnnotatedString {
+
+ var lastIndex = 0
+
+ matches.forEach { matchResult ->
+ val range = matchResult.range
+ val title = matchResult.groupValues[1]
+ val link = matchResult.groupValues[2]
+
+ // Append plain text
+ append(text.substring(lastIndex, range.first))
+
+ val url = LinkAnnotation.Url(link) {
+ // Handle click event
+ val url = (it as LinkAnnotation.Url).url
+
+ try {
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ customTabsIntent.launchUrl(context, Uri.parse(url))
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ // Show error message to the user
+ Toast.makeText(context, "Failed to open link: $url", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+
+ withLink(url) {
+ append(title)
+ }
+
+ lastIndex = range.last + 1
+ }
+
+ // Append the rest of the text
+ append(text.substring(lastIndex, text.length))
+ },
style = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSurface,
lineBreak = LineBreak.Paragraph
@@ -94,4 +97,4 @@ fun RichText(str: String) {
)
)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/SelectableColorPlatte.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/SelectableColorPlatte.kt
index ebbfa63..2a54556 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/SelectableColorPlatte.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/SelectableColorPlatte.kt
@@ -30,51 +30,49 @@ fun SelectableColorPlatte(
selected: Boolean,
colorScheme: ColorScheme,
onClick: () -> Unit
+) = Surface(
+ modifier = modifier,
+ shape = MaterialTheme.shapes.large,
+ color = MaterialTheme.colorScheme.surfaceContainer
) {
Surface(
- modifier = modifier,
- shape = MaterialTheme.shapes.large,
- color = MaterialTheme.colorScheme.surfaceContainer
+ modifier = Modifier
+ .clickable { onClick() }
+ .padding(12.dp)
+ .size(48.dp),
+ shape = CircleShape,
+ color = colorScheme.primary,
) {
- Surface(
- modifier = Modifier
- .clickable { onClick() }
- .padding(12.dp)
- .size(48.dp),
- shape = CircleShape,
- color = colorScheme.primary,
- ) {
- Box {
- Surface(
+ Box {
+ Surface(
+ modifier = Modifier
+ .size(48.dp)
+ .offset((-24).dp, 24.dp),
+ color = colorScheme.tertiary,
+ ) {}
+ Surface(
+ modifier = Modifier
+ .size(48.dp)
+ .offset(24.dp, 24.dp),
+ color = colorScheme.secondaryContainer,
+ ) {}
+ AnimatedVisibility(
+ visible = selected,
+ modifier = Modifier
+ .align(Alignment.Center)
+ .clip(CircleShape)
+ .background(colorScheme.tertiaryContainer),
+ enter = fadeIn() + expandIn(expandFrom = Alignment.Center),
+ exit = shrinkOut(shrinkTowards = Alignment.Center) + fadeOut()
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Check,
+ contentDescription = "Checked",
modifier = Modifier
- .size(48.dp)
- .offset((-24).dp, 24.dp),
- color = colorScheme.tertiary,
- ) {}
- Surface(
- modifier = Modifier
- .size(48.dp)
- .offset(24.dp, 24.dp),
- color = colorScheme.secondaryContainer,
- ) {}
- AnimatedVisibility(
- visible = selected,
- modifier = Modifier
- .align(Alignment.Center)
- .clip(CircleShape)
- .background(colorScheme.tertiaryContainer),
- enter = fadeIn() + expandIn(expandFrom = Alignment.Center),
- exit = shrinkOut(shrinkTowards = Alignment.Center) + fadeOut()
- ) {
- Icon(
- imageVector = Icons.Outlined.Check,
- contentDescription = "Checked",
- modifier = Modifier
- .padding(8.dp)
- .size(16.dp),
- tint = MaterialTheme.colorScheme.surface
- )
- }
+ .padding(8.dp)
+ .size(16.dp),
+ tint = MaterialTheme.colorScheme.surface
+ )
}
}
}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/SettingsDetailPane.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/SettingsDetailPane.kt
index eb60aef..c46c943 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/SettingsDetailPane.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/SettingsDetailPane.kt
@@ -8,7 +8,6 @@ import android.os.Build
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
@@ -103,8 +102,8 @@ import com.yangdai.opennote.presentation.theme.DarkOrangeColors
import com.yangdai.opennote.presentation.theme.DarkRedColors
import com.yangdai.opennote.presentation.theme.DarkPurpleColors
import com.yangdai.opennote.presentation.util.Constants
+import com.yangdai.opennote.presentation.util.rememberCustomTabsIntent
import com.yangdai.opennote.presentation.viewmodel.SharedViewModel
-import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -120,11 +119,7 @@ fun SettingsDetailPane(
val folderEntities by sharedViewModel.foldersStateFlow.collectAsStateWithLifecycle()
val actionState by sharedViewModel.dataActionStateFlow.collectAsStateWithLifecycle()
- val customTabsIntent = remember {
- CustomTabsIntent.Builder()
- .setShowTitle(true)
- .build()
- }
+ val customTabsIntent = rememberCustomTabsIntent()
val modeOptions = listOf(
stringResource(R.string.system_default),
@@ -146,7 +141,7 @@ fun SettingsDetailPane(
var showFolderDialog by rememberSaveable { mutableStateOf(false) }
var showRatingDialog by rememberSaveable { mutableStateOf(false) }
- var folderId: Long? = null
+ var folderId: Long? by rememberSaveable { mutableStateOf(null) }
val importLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenMultipleDocuments()
) { uriList ->
@@ -672,59 +667,65 @@ fun SettingsDetailPane(
}
}
- RatingDialog(
- showDialog = showRatingDialog,
- onDismissRequest = { showRatingDialog = false }) { stars ->
- if (stars > 3) {
- customTabsIntent.launchUrl(
- context,
- Uri.parse("https://play.google.com/store/apps/details?id=com.yangdai.opennote")
- )
- } else {
- if (stars == 0) return@RatingDialog
- // 获取当前应用的版本号
- val packageInfo =
- context.packageManager.getPackageInfo(context.packageName, 0)
- val appVersion = packageInfo.versionName
- val deviceModel = Build.MODEL
- val systemVersion = Build.VERSION.SDK_INT
-
- val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
- data = Uri.parse("mailto:")
- putExtra(Intent.EXTRA_EMAIL, arrayOf("dy15800837435@gmail.com"))
- putExtra(Intent.EXTRA_SUBJECT, "Feedback - Open Note")
- putExtra(
- Intent.EXTRA_TEXT,
- "Version: $appVersion\nDevice: $deviceModel\nSystem: $systemVersion\n"
+ if (showRatingDialog) {
+ RatingDialog(
+ onDismissRequest = { showRatingDialog = false }
+ ) { stars ->
+ if (stars > 3) {
+ customTabsIntent.launchUrl(
+ context,
+ Uri.parse("https://play.google.com/store/apps/details?id=com.yangdai.opennote")
)
- }
- context.startActivity(
- Intent.createChooser(
- emailIntent,
- "Feedback (E-mail)"
+ } else {
+ if (stars == 0) return@RatingDialog
+ // 获取当前应用的版本号
+ val packageInfo =
+ context.packageManager.getPackageInfo(context.packageName, 0)
+ val appVersion = packageInfo.versionName
+ val deviceModel = Build.MODEL
+ val systemVersion = Build.VERSION.SDK_INT
+
+ val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
+ data = Uri.parse("mailto:")
+ putExtra(Intent.EXTRA_EMAIL, arrayOf("dy15800837435@gmail.com"))
+ putExtra(Intent.EXTRA_SUBJECT, "Feedback - Open Note")
+ putExtra(
+ Intent.EXTRA_TEXT,
+ "Version: $appVersion\nDevice: $deviceModel\nSystem: $systemVersion\n"
+ )
+ }
+ context.startActivity(
+ Intent.createChooser(
+ emailIntent,
+ "Feedback (E-mail)"
+ )
)
- )
+ }
}
}
- WarningDialog(
- showDialog = showWarningDialog,
- message = stringResource(R.string.reset_database_warning),
- onDismissRequest = { showWarningDialog = false }) {
- sharedViewModel.onDatabaseEvent(DatabaseEvent.Reset)
+
+ if (showWarningDialog) {
+ WarningDialog(
+ message = stringResource(R.string.reset_database_warning),
+ onDismissRequest = { showWarningDialog = false }
+ ) {
+ sharedViewModel.onDatabaseEvent(DatabaseEvent.Reset)
+ }
}
+
ProgressDialog(
isLoading = actionState.loading,
progress = actionState.progress,
infinite = actionState.infinite,
- errorMessage = actionState.error
- ) {
- sharedViewModel.cancelDataAction()
- }
+ errorMessage = actionState.error,
+ onDismissRequest = sharedViewModel::cancelDataAction
+ )
+
if (showFolderDialog) {
FolderListDialog(
hint = stringResource(R.string.destination_folder),
oFolderId = folderId,
- folders = folderEntities.toImmutableList(),
+ folders = folderEntities,
onDismissRequest = { showFolderDialog = false }
) { id ->
folderId = id
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/ShareDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/ShareDialog.kt
index 68b7d7d..0857864 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/ShareDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/ShareDialog.kt
@@ -19,26 +19,24 @@ enum class ShareType {
fun ShareDialog(
onDismissRequest: () -> Unit,
onConfirm: (ShareType) -> Unit
-) {
- AlertDialog(
- onDismissRequest = onDismissRequest,
- title = {
- Text(text = stringResource(R.string.share_note_as))
- },
- text = {
- Column(modifier = Modifier.fillMaxWidth()) {
- TextOptionButton(text = stringResource(R.string.file)) {
- onConfirm(ShareType.FILE)
- }
+) = AlertDialog(
+ onDismissRequest = onDismissRequest,
+ title = {
+ Text(text = stringResource(R.string.share_note_as))
+ },
+ text = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ TextOptionButton(text = stringResource(R.string.file)) {
+ onConfirm(ShareType.FILE)
+ }
- TextOptionButton(text = stringResource(R.string.text)) {
- onConfirm(ShareType.TEXT)
- }
+ TextOptionButton(text = stringResource(R.string.text)) {
+ onConfirm(ShareType.TEXT)
}
- },
- confirmButton = {}
- )
-}
+ }
+ },
+ confirmButton = {}
+)
@Composable
@Preview
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
index 6ba1b40..0dbaccb 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/TableDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/TableDialog.kt
@@ -71,9 +71,7 @@ fun TableDialog(
)
}
},
- onDismissRequest = {
- onDismissRequest()
- },
+ onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
onClick = {
@@ -95,7 +93,7 @@ fun TableDialog(
}
},
dismissButton = {
- TextButton(onClick = { onDismissRequest() }) {
+ TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
}
}
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 9e8a02a..a2f6e4d 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
@@ -24,6 +24,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yangdai.opennote.R
@@ -103,9 +104,7 @@ fun TaskDialog(
}
}
},
- onDismissRequest = {
- onDismissRequest()
- },
+ onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
onClick = {
@@ -117,9 +116,15 @@ fun TaskDialog(
}
},
dismissButton = {
- TextButton(onClick = { onDismissRequest() }) {
+ TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
}
}
)
}
+
+@Preview
+@Composable
+fun TaskDialogPreview() {
+ TaskDialog(onDismissRequest = {}, onConfirm = {})
+}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/TopSearchbar.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/TopSearchbar.kt
deleted file mode 100644
index 5e4b7a2..0000000
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/TopSearchbar.kt
+++ /dev/null
@@ -1,277 +0,0 @@
-package com.yangdai.opennote.presentation.component
-
-import android.content.res.Configuration
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Clear
-import androidx.compose.material.icons.outlined.DeleteForever
-import androidx.compose.material.icons.outlined.GridView
-import androidx.compose.material.icons.outlined.History
-import androidx.compose.material.icons.outlined.Menu
-import androidx.compose.material.icons.outlined.Search
-import androidx.compose.material.icons.outlined.SortByAlpha
-import androidx.compose.material.icons.outlined.ViewAgenda
-import androidx.compose.material3.DockedSearchBar
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.ListItemDefaults
-import androidx.compose.material3.SearchBar
-import androidx.compose.material3.SearchBarDefaults
-import androidx.compose.material3.SuggestionChip
-import androidx.compose.material3.Text
-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.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.yangdai.opennote.MainActivity
-import com.yangdai.opennote.R
-import com.yangdai.opennote.presentation.event.ListEvent
-import com.yangdai.opennote.presentation.util.Constants
-import com.yangdai.opennote.presentation.viewmodel.SharedViewModel
-
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
-@Composable
-fun TopSearchbar(
- viewModel: SharedViewModel = hiltViewModel(LocalContext.current as MainActivity),
- isLargeScreen: Boolean,
- enabled: Boolean,
- onSearchBarActivationChange: (Boolean) -> Unit,
- onDrawerStateChange: () -> Unit
-) {
-
- val historySet by viewModel.historyStateFlow.collectAsStateWithLifecycle()
- val settingsState by viewModel.settingsStateFlow.collectAsStateWithLifecycle()
-
- var inputText by rememberSaveable {
- mutableStateOf("")
- }
- var expanded by rememberSaveable {
- mutableStateOf(false)
- }
-
- LaunchedEffect(expanded) {
- onSearchBarActivationChange(expanded)
- }
-
- val configuration = LocalConfiguration.current
- val orientation = remember(configuration) { configuration.orientation }
-
- fun search(text: String) {
- if (text.isNotEmpty()) {
- val newSet = historySet.toMutableSet()
- newSet.add(text)
- viewModel.putPreferenceValue(Constants.Preferences.SEARCH_HISTORY, newSet.toSet())
- viewModel.onListEvent(ListEvent.Search(text))
- } else {
- viewModel.onListEvent(
- ListEvent.Sort(
- viewModel.dataStateFlow.value.noteOrder,
- false,
- null,
- false
- )
- )
- }
- expanded = false
- }
-
- @Composable
- fun LeadingIcon() {
- if (!isLargeScreen) {
- AnimatedContent(targetState = expanded, label = "leading") {
- if (it) {
- IconButton(onClick = { search(inputText) }) {
- Icon(
- imageVector = Icons.Outlined.Search,
- contentDescription = "Search"
- )
- }
- } else {
- IconButton(
- enabled = enabled,
- onClick = onDrawerStateChange
- ) {
- Icon(
- imageVector = Icons.Outlined.Menu,
- contentDescription = "Open Menu"
- )
- }
- }
- }
- } else {
- Icon(
- imageVector = Icons.Outlined.Search,
- contentDescription = "Search"
- )
- }
- }
-
- @Composable
- fun TrailingIcon() {
- AnimatedContent(targetState = expanded, label = "trailing") {
- if (it) {
- IconButton(onClick = {
- if (inputText.isNotEmpty()) {
- inputText = ""
- } else {
- search("")
- }
- }) {
- Icon(
- imageVector = Icons.Outlined.Clear,
- contentDescription = "Clear"
- )
- }
- } else {
- Row(
- verticalAlignment = Alignment.CenterVertically
- ) {
- IconButton(onClick = { viewModel.onListEvent(ListEvent.ChangeViewMode) }) {
- Icon(
- imageVector = if (!settingsState.isListView) Icons.Outlined.ViewAgenda else Icons.Outlined.GridView,
- contentDescription = "View Mode"
- )
- }
- IconButton(onClick = { viewModel.onListEvent(ListEvent.ToggleOrderSection) }) {
- Icon(
- imageVector = Icons.Outlined.SortByAlpha,
- contentDescription = "Sort"
- )
- }
- }
- }
- }
- }
-
- @Composable
- fun History() {
-
- ListItem(
- colors = ListItemDefaults.colors(containerColor = Color.Transparent),
- leadingContent = {
- Icon(
- imageVector = Icons.Outlined.History,
- contentDescription = "History"
- )
- },
- headlineContent = { Text(text = stringResource(R.string.search_history)) },
- trailingContent = {
- // To align the icon with search bar, use icon.clickable{} instead of IconButton onClick()
- Icon(
- modifier = Modifier.clickable {
- viewModel.putPreferenceValue(Constants.Preferences.SEARCH_HISTORY, setOf())
- },
- imageVector = Icons.Outlined.DeleteForever,
- contentDescription = "Clear History"
- )
- }
- )
-
- FlowRow(
- Modifier.padding(horizontal = 12.dp),
- horizontalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- historySet.forEach {
- SuggestionChip(
- modifier = Modifier.defaultMinSize(48.dp),
- onClick = { inputText = it },
- label = {
- Text(
- text = it,
- textAlign = TextAlign.Center,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- })
- }
- }
- }
-
- // Search bar layout, switch between docked and expanded search bar based on window size and orientation
- if (orientation == Configuration.ORIENTATION_PORTRAIT && !isLargeScreen) {
-
- // Animate search bar padding when active state changes
- val searchBarPadding by animateDpAsState(
- targetValue = if (expanded) 0.dp else 16.dp,
- label = "searchBarPadding"
- )
-
- SearchBar(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = searchBarPadding),
- inputField = {
- SearchBarDefaults.InputField(
- query = inputText,
- onQueryChange = { inputText = it },
- onSearch = { search(it) },
- enabled = enabled,
- expanded = expanded,
- onExpandedChange = { expanded = it },
- placeholder = { Text(text = stringResource(R.string.search)) },
- leadingIcon = { LeadingIcon() },
- trailingIcon = { TrailingIcon() },
- )
- },
- expanded = expanded,
- onExpandedChange = { expanded = it }
- ) {
- History()
- }
- } else {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .statusBarsPadding()
- .padding(top = 8.dp),
- contentAlignment = Alignment.Center
- ) {
- DockedSearchBar(
- inputField = {
- SearchBarDefaults.InputField(
- query = inputText,
- onQueryChange = { inputText = it },
- onSearch = { search(it) },
- enabled = enabled,
- expanded = expanded,
- onExpandedChange = { expanded = it },
- placeholder = { Text(text = stringResource(R.string.search)) },
- leadingIcon = { LeadingIcon() },
- trailingIcon = { TrailingIcon() },
- )
- },
- expanded = expanded,
- onExpandedChange = { expanded = it }
- ) {
- History()
- }
- }
- }
-}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/component/WarningDialog.kt b/app/src/main/java/com/yangdai/opennote/presentation/component/WarningDialog.kt
index 3ef83e2..7613346 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/component/WarningDialog.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/component/WarningDialog.kt
@@ -11,38 +11,30 @@ import com.yangdai.opennote.R
@Composable
fun WarningDialog(
- showDialog: Boolean,
message: String,
onDismissRequest: () -> Unit,
onConfirm: () -> Unit
-) {
-
- if (!showDialog) return
-
- AlertDialog(
- title = { Text(text = stringResource(id = R.string.warning)) },
- text = {
- Text(text = message)
- },
- onDismissRequest = onDismissRequest,
- confirmButton = {
- TextButton(
- onClick = {
- onConfirm()
- onDismissRequest()
- },
- colors = ButtonDefaults.textButtonColors().copy(
- containerColor = MaterialTheme.colorScheme.errorContainer,
- contentColor = MaterialTheme.colorScheme.error
- )
- ) {
- Text(text = stringResource(id = android.R.string.ok))
- }
- },
- dismissButton = {
- TextButton(onClick = onDismissRequest) {
- Text(text = stringResource(id = android.R.string.cancel))
- }
+) = AlertDialog(
+ title = { Text(text = stringResource(id = R.string.warning)) },
+ text = { Text(text = message) },
+ onDismissRequest = onDismissRequest,
+ confirmButton = {
+ TextButton(
+ onClick = {
+ onConfirm()
+ onDismissRequest()
+ },
+ colors = ButtonDefaults.textButtonColors().copy(
+ containerColor = MaterialTheme.colorScheme.errorContainer,
+ contentColor = MaterialTheme.colorScheme.error
+ )
+ ) {
+ Text(text = stringResource(id = android.R.string.ok))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = onDismissRequest) {
+ Text(text = stringResource(id = android.R.string.cancel))
}
- )
-}
+ }
+)
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/event/ListEvent.kt b/app/src/main/java/com/yangdai/opennote/presentation/event/ListEvent.kt
index 26af0c7..327f0cd 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/event/ListEvent.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/event/ListEvent.kt
@@ -3,7 +3,6 @@ package com.yangdai.opennote.presentation.event
import com.yangdai.opennote.data.local.entity.NoteEntity
import com.yangdai.opennote.domain.usecase.NoteOrder
import com.yangdai.opennote.domain.usecase.OrderType
-import kotlinx.collections.immutable.ImmutableList
sealed interface ListEvent {
@@ -17,13 +16,9 @@ sealed interface ListEvent {
val trash: Boolean = false
) : ListEvent
- data class DeleteNotes(val noteEntities: ImmutableList, val recycle: Boolean) :
- ListEvent
-
- data class MoveNotes(val noteEntities: ImmutableList, val folderId: Long?) :
- ListEvent
-
- data class RestoreNotes(val noteEntities: ImmutableList) : ListEvent
+ data class DeleteNotes(val noteEntities: Collection, val recycle: Boolean) : ListEvent
+ data class MoveNotes(val noteEntities: Collection, val folderId: Long?) : ListEvent
+ data class RestoreNotes(val noteEntities: Collection) : ListEvent
data object ToggleOrderSection : ListEvent
data object ChangeViewMode : ListEvent
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 3af3285..8127ff8 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
@@ -33,6 +33,7 @@ import com.yangdai.opennote.presentation.screen.SettingsScreen
import com.yangdai.opennote.presentation.util.Constants.NAV_ANIMATION_TIME
import com.yangdai.opennote.presentation.util.Constants.LINK
import com.yangdai.opennote.presentation.util.parseSharedContent
+import com.yangdai.opennote.presentation.navigation.Screen.*
private const val ProgressThreshold = 0.35f
private const val INITIAL_OFFSET_FACTOR = 0.10f
@@ -108,14 +109,13 @@ fun AnimatedNavHost(
composable {
MainScreen(
isLargeScreen = isLargeScreen,
- navigateToNote = { navController.navigate("$Note/$it") }
- ) { route ->
- navController.navigate(route)
- }
+ navigateToNote = { navController.navigate(Note.passId(it)) },
+ navigateToScreen = { navController.navigate(it) }
+ )
}
composable(
- route = "$Note/{id}",
+ route = Note.route,
deepLinks = listOf(
navDeepLink {
action = Intent.ACTION_SEND
@@ -126,7 +126,7 @@ fun AnimatedNavHost(
mimeType = "text/*"
},
navDeepLink {
- uriPattern = "$LINK/note/{id}"
+ uriPattern = "$LINK/${Note.route}"
}
),
arguments = listOf(
@@ -147,8 +147,9 @@ fun AnimatedNavHost(
isLargeScreen = isLargeScreen,
sharedText = sharedText,
scannedText = scannedText,
- navigateUp = { navController.navigateBackWithHapticFeedback(hapticFeedback) }
- ) { navController.navigate(CameraX) }
+ navigateUp = { navController.navigateBackWithHapticFeedback(hapticFeedback) },
+ onScanTextClick = { navController.navigate(CameraX) }
+ )
}
composable {
@@ -169,7 +170,7 @@ fun AnimatedNavHost(
composable(
deepLinks = listOf(
navDeepLink {
- uriPattern = "$LINK/settings"
+ uriPattern = "$LINK/${Settings.route}"
}
)
) {
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 85a3bf2..c3ef367 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
@@ -3,15 +3,22 @@ package com.yangdai.opennote.presentation.navigation
import kotlinx.serialization.Serializable
@Serializable
-object Home
+sealed class Screen(val route: String) {
+ @Serializable
+ data object Home : Screen("home")
-const val Note = "note"
+ data object Note : Screen("note/{id}") {
+ fun passId(id: Long): String {
+ return this.route.replace("{id}", id.toString())
+ }
+ }
-@Serializable
-object Settings
+ @Serializable
+ data object Settings : Screen("settings")
-@Serializable
-object Folders
+ @Serializable
+ data object Folders : Screen("folders")
-@Serializable
-object CameraX
\ No newline at end of file
+ @Serializable
+ data object CameraX : Screen("cameraX")
+}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/screen/BaseScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/screen/BaseScreen.kt
index 06fc924..7dd0c60 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/screen/BaseScreen.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/screen/BaseScreen.kt
@@ -129,10 +129,11 @@ fun BaseScreen(
LoginOverlayScreen(
onAuthenticated = {
authenticated = true
+ },
+ onAuthenticationNotEnrolled = {
+ sharedViewModel.putPreferenceValue(Constants.Preferences.NEED_PASSWORD, false)
}
- ) {
- sharedViewModel.putPreferenceValue(Constants.Preferences.NEED_PASSWORD, false)
- }
+ )
}
}
}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/screen/FolderScreen.kt b/app/src/main/java/com/yangdai/opennote/presentation/screen/FolderScreen.kt
index 5190869..3e8ac85 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/screen/FolderScreen.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/screen/FolderScreen.kt
@@ -92,20 +92,22 @@ fun FolderScreen(
sharedViewModel.onFolderEvent(
FolderEvent.UpdateFolder(folderEntity)
)
- }) {
+ }
+ ) {
sharedViewModel.onFolderEvent(FolderEvent.DeleteFolder(it))
}
}
}
- ModifyFolderDialog(
- showDialog = showAddFolderDialog,
- folder = FolderEntity(),
- onDismissRequest = { showAddFolderDialog = false }
- ) {
- sharedViewModel.onFolderEvent(
- FolderEvent.AddFolder(it)
- )
+ if (showAddFolderDialog) {
+ ModifyFolderDialog(
+ folder = FolderEntity(),
+ onDismissRequest = { showAddFolderDialog = false }
+ ) {
+ sharedViewModel.onFolderEvent(
+ FolderEvent.AddFolder(it)
+ )
+ }
}
}
}
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 084ea59..603d40e 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
@@ -1,13 +1,64 @@
package com.yangdai.opennote.presentation.screen
import androidx.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
+import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
+import androidx.compose.foundation.lazy.staggeredgrid.items
+import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.DriveFileMove
+import androidx.compose.material.icons.outlined.Add
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.GridView
+import androidx.compose.material.icons.outlined.Menu
+import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material.icons.outlined.RestartAlt
+import androidx.compose.material.icons.outlined.SortByAlpha
+import androidx.compose.material.icons.outlined.Upload
+import androidx.compose.material.icons.outlined.ViewAgenda
+import androidx.compose.material3.BottomAppBar
+import androidx.compose.material3.Checkbox
import androidx.compose.material3.DrawerValue
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.VerticalDivider
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -16,27 +67,41 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
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.platform.LocalContext
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.yangdai.opennote.MainActivity
+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.component.AdaptiveNavigationScreen
+import com.yangdai.opennote.presentation.component.ColumnNoteCard
+import com.yangdai.opennote.presentation.component.DrawerContent
+import com.yangdai.opennote.presentation.component.ExportDialog
+import com.yangdai.opennote.presentation.component.FolderListDialog
+import com.yangdai.opennote.presentation.component.GridNoteCard
import com.yangdai.opennote.presentation.event.ListEvent
-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.component.ProgressDialog
+import com.yangdai.opennote.presentation.component.AdaptiveTopSearchbar
+import com.yangdai.opennote.presentation.component.OrderSectionDialog
import com.yangdai.opennote.presentation.event.DatabaseEvent
+import com.yangdai.opennote.presentation.navigation.Screen
import com.yangdai.opennote.presentation.viewmodel.SharedViewModel
-import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(
sharedViewModel: SharedViewModel = hiltViewModel(LocalContext.current as MainActivity),
isLargeScreen: Boolean,
navigateToNote: (Long) -> Unit,
- navigateTo: (Any) -> Unit
+ navigateToScreen: (Screen) -> Unit
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
@@ -46,6 +111,10 @@ fun MainScreen(
val folderList by sharedViewModel.foldersStateFlow.collectAsStateWithLifecycle()
val dataActionState by sharedViewModel.dataActionStateFlow.collectAsStateWithLifecycle()
+ val staggeredGridState = rememberLazyStaggeredGridState()
+ val lazyListState = rememberLazyListState()
+ val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
+
// Search bar state, reset when configuration changes, no need to use rememberSaveable
var isSearchBarActivated by remember { mutableStateOf(false) }
@@ -71,6 +140,7 @@ fun MainScreen(
val isFloatingButtonVisible by remember {
derivedStateOf {
selectedDrawerIndex == 0 && !isSearchBarActivated && !isMultiSelectionModeEnabled
+ && !staggeredGridState.isScrollInProgress && !lazyListState.isScrollInProgress
}
}
@@ -81,27 +151,6 @@ fun MainScreen(
allNotesSelected = false
}
- // Operation caused by selecting the drawer item
- fun selectDrawer(position: Int, folderEntity: FolderEntity) {
- if (selectedDrawerIndex != position) {
- initializeNoteSelection()
- selectedDrawerIndex = position
- selectedFolder = folderEntity
- when (position) {
- 0 -> sharedViewModel.onListEvent(ListEvent.Sort(trash = false))
-
- 1 -> sharedViewModel.onListEvent(ListEvent.Sort(trash = true))
-
- else -> sharedViewModel.onListEvent(
- ListEvent.Sort(
- filterFolder = true,
- folderId = folderEntity.id
- )
- )
- }
- }
- }
-
// select all and deselect all, triggered by the checkbox in the bottom bar
LaunchedEffect(allNotesSelected) {
selectedNotes = if (allNotesSelected) selectedNotes.plus(dataState.notes)
@@ -115,63 +164,411 @@ fun MainScreen(
}
}
- // Navigation drawer state, confirmStateChange is used to prevent drawer from closing when search bar is active
- val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
-
- fun closeDrawer() {
- coroutineScope.launch {
- drawerState.apply {
- close()
- }
- }
+ // Bottom sheet visibility, reset when configuration changes, no need to use rememberSaveable
+ var isFolderDialogVisible by remember {
+ mutableStateOf(false)
}
- BackHandler(!isLargeScreen && drawerState.isOpen) {
- closeDrawer()
+ // Whether to show the export dialog
+ var isExportDialogVisible by remember {
+ mutableStateOf(false)
}
- val movableContent = remember {
- movableContentOf {
- MainContent(
- isListViewMode = settingsState.isListView,
- dataActionState = dataActionState,
+ AdaptiveNavigationScreen(
+ isLargeScreen = isLargeScreen,
+ drawerState = drawerState,
+ gesturesEnabled = !isMultiSelectionModeEnabled && !isSearchBarActivated,
+ drawerContent = {
+ DrawerContent(
+ folderList = folderList,
selectedDrawerIndex = selectedDrawerIndex,
- selectedFolder = selectedFolder,
- selectedNotes = selectedNotes.toImmutableList(),
- allNotesSelected = allNotesSelected,
- isMultiSelectionModeEnabled = isMultiSelectionModeEnabled,
- isLargeScreen = isLargeScreen,
- dataState = dataState,
- folderList = folderList.toImmutableList(),
- isFloatingButtonVisible = isFloatingButtonVisible,
- navigateToNote = navigateToNote,
- initializeNoteSelection = { initializeNoteSelection() },
- onSearchBarActivationChange = { isSearchBarActivated = it },
- onAllNotesSelectionChange = { allNotesSelected = it },
- onMultiSelectionModeChange = { isMultiSelectionModeEnabled = it },
- onNoteClick = {
- if (isMultiSelectionModeEnabled) {
- selectedNotes =
- if (selectedNotes.contains(it)) selectedNotes.minus(it)
- else selectedNotes.plus(it)
- } else {
- if (selectedDrawerIndex != 1) {
- sharedViewModel.onListEvent(ListEvent.OpenNote(it))
- navigateToNote(it.id!!)
- } else {
- Unit
- }
+ navigateTo = { navigateToScreen(it) }
+ ) { position, folderEntity ->
+ if (selectedDrawerIndex != position) {
+ initializeNoteSelection()
+ selectedDrawerIndex = position
+ selectedFolder = folderEntity
+ when (position) {
+ 0 -> sharedViewModel.onListEvent(ListEvent.Sort(trash = false))
+
+ 1 -> sharedViewModel.onListEvent(ListEvent.Sort(trash = true))
+
+ else -> sharedViewModel.onListEvent(
+ ListEvent.Sort(
+ filterFolder = true,
+ folderId = folderEntity.id
+ )
+ )
}
- },
- onListEvent = { sharedViewModel.onListEvent(it) },
- onDrawerStateChange = {
+ }
+ if (!isLargeScreen)
coroutineScope.launch {
drawerState.apply {
- if (isClosed) open() else close()
+ close()
+ }
+ }
+ }
+ },
+ ) {
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ AnimatedContent(targetState = selectedDrawerIndex == 0, label = "") {
+ if (it) {
+ AdaptiveTopSearchbar(
+ enabled = !isMultiSelectionModeEnabled,
+ isLargeScreen = isLargeScreen,
+ onSearchBarActivationChange = { activated ->
+ isSearchBarActivated = activated
+ },
+ onDrawerStateChange = {
+ coroutineScope.launch {
+ drawerState.apply {
+ if (isClosed) open() else close()
+ }
+ }
+ }
+ )
+ } else {
+
+ var showMenu by remember {
+ mutableStateOf(false)
+ }
+
+ TopAppBar(
+ title = {
+ Text(
+ text = if (selectedDrawerIndex == 1) stringResource(id = R.string.trash)
+ else selectedFolder.name,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ },
+ navigationIcon = {
+ if (!isLargeScreen) {
+ IconButton(
+ enabled = !isMultiSelectionModeEnabled,
+ onClick = {
+ coroutineScope.launch {
+ drawerState.apply {
+ if (isClosed) open() else close()
+ }
+ }
+ }
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Menu,
+ contentDescription = "Open Menu"
+ )
+ }
+ }
+ },
+ actions = {
+ IconButton(onClick = { sharedViewModel.onListEvent(ListEvent.ChangeViewMode) }) {
+ Icon(
+ imageVector = if (!settingsState.isListView) Icons.Outlined.ViewAgenda else Icons.Outlined.GridView,
+ contentDescription = "View Mode"
+ )
+ }
+ IconButton(onClick = { sharedViewModel.onListEvent(ListEvent.ToggleOrderSection) }) {
+ Icon(
+ imageVector = Icons.Outlined.SortByAlpha,
+ contentDescription = "Sort"
+ )
+ }
+ if (selectedDrawerIndex == 1) {
+ IconButton(onClick = { showMenu = !showMenu }) {
+ Icon(
+ imageVector = Icons.Outlined.MoreVert,
+ contentDescription = "More"
+ )
+ }
+
+ DropdownMenu(
+ expanded = showMenu,
+ onDismissRequest = { showMenu = false }) {
+ DropdownMenuItem(
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Outlined.RestartAlt,
+ contentDescription = "Restore"
+ )
+ },
+ text = { Text(text = stringResource(id = R.string.restore_all)) },
+ onClick = {
+ sharedViewModel.onListEvent(
+ ListEvent.RestoreNotes(dataState.notes)
+ )
+ })
+
+ DropdownMenuItem(
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Outlined.Delete,
+ contentDescription = "Delete"
+ )
+ },
+ text = { Text(text = stringResource(id = R.string.delete_all)) },
+ onClick = {
+ sharedViewModel.onListEvent(
+ ListEvent.DeleteNotes(dataState.notes, false)
+ )
+ })
+ }
+ }
+ }
+ )
+ }
+ }
+ },
+ bottomBar = {
+ AnimatedVisibility(
+ visible = isMultiSelectionModeEnabled,
+ enter = slideInVertically { fullHeight -> fullHeight },
+ exit = slideOutVertically { fullHeight -> fullHeight }
+ ) {
+ BottomAppBar {
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ Row(
+ modifier = Modifier.fillMaxHeight(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ Checkbox(
+ checked = allNotesSelected,
+ onCheckedChange = { allNotesSelected = it }
+ )
+
+ Text(text = stringResource(R.string.checked))
+
+ Text(text = selectedNotes.size.toString())
+ }
+
+ Row(
+ modifier = Modifier.fillMaxHeight(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ if (selectedDrawerIndex == 1) {
+ TextButton(onClick = {
+ sharedViewModel.onListEvent(
+ ListEvent.RestoreNotes(selectedNotes)
+ )
+ initializeNoteSelection()
+ }) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ imageVector = Icons.Outlined.RestartAlt,
+ contentDescription = "Restore"
+ )
+ Text(text = stringResource(id = R.string.restore))
+ }
+ }
+ } else {
+ TextButton(onClick = {
+ isExportDialogVisible = true
+ }) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ imageVector = Icons.Outlined.Upload,
+ contentDescription = "Export"
+ )
+ Text(text = stringResource(id = R.string.export))
+ }
+ }
+
+ TextButton(onClick = { isFolderDialogVisible = true }) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Outlined.DriveFileMove,
+ contentDescription = "Move"
+ )
+ Text(text = stringResource(id = R.string.move))
+ }
+ }
+ }
+
+ TextButton(onClick = {
+ sharedViewModel.onListEvent(
+ ListEvent.DeleteNotes(
+ selectedNotes,
+ selectedDrawerIndex != 1
+ )
+ )
+ initializeNoteSelection()
+ }) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ imageVector = Icons.Outlined.Delete,
+ contentDescription = "Delete"
+ )
+ Text(text = stringResource(id = R.string.delete))
+ }
+ }
+ }
}
}
- },
- onExportClick = {
+ }
+ },
+ floatingActionButton = {
+ AnimatedVisibility(
+ visible = isFloatingButtonVisible,
+ enter = slideInHorizontally { fullWidth -> fullWidth * 3 / 2 },
+ exit = slideOutHorizontally { fullWidth -> fullWidth * 3 / 2 }) {
+ FloatingActionButton(
+ onClick = {
+ sharedViewModel.onListEvent(ListEvent.AddNote)
+ navigateToNote(-1)
+ }
+ ) {
+ Icon(imageVector = Icons.Outlined.Add, contentDescription = "Add")
+ }
+ }
+
+ }) { innerPadding ->
+
+ // Add layoutDirection, displayCutout, startPadding, and endPadding.
+ val layoutDirection = LocalLayoutDirection.current
+ val displayCutout = WindowInsets.displayCutout.asPaddingValues()
+ val startPadding = displayCutout.calculateStartPadding(layoutDirection)
+ val endPadding = displayCutout.calculateEndPadding(layoutDirection)
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .statusBarsPadding()
+ .padding(top = 72.dp, start = startPadding, end = endPadding)
+ ) {
+ if (!settingsState.isListView) {
+ LazyVerticalStaggeredGrid(
+ modifier = Modifier
+ .fillMaxSize(),
+ state = staggeredGridState,
+ // The staggered grid layout is adaptive, with a minimum column width of 160dp(mdpi)
+ columns = StaggeredGridCells.Adaptive(160.dp),
+ verticalItemSpacing = 8.dp,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ // for better edgeToEdge experience
+ contentPadding = PaddingValues(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = innerPadding.calculateBottomPadding()
+ ),
+ content = {
+ items(
+ dataState.notes,
+ key = { item: NoteEntity -> item.id!! }) { note ->
+ GridNoteCard(
+ modifier = Modifier
+ .fillMaxWidth()
+ .animateItem(), // Add animation to the item
+ note = note,
+ isEnabled = isMultiSelectionModeEnabled,
+ isSelected = selectedNotes.contains(note),
+ onEnableChange = { isMultiSelectionModeEnabled = it },
+ onNoteClick = {
+ if (isMultiSelectionModeEnabled) {
+ selectedNotes =
+ if (selectedNotes.contains(it)) selectedNotes.minus(
+ it
+ )
+ else selectedNotes.plus(it)
+ } else {
+ if (selectedDrawerIndex != 1) {
+ sharedViewModel.onListEvent(ListEvent.OpenNote(it))
+ navigateToNote(it.id!!)
+ } else {
+ Unit
+ }
+ }
+ }
+ )
+ }
+ }
+ )
+ } else {
+
+ if (dataState.notes.isEmpty()) {
+ return@Box
+ }
+
+ VerticalDivider(
+ Modifier
+ .align(Alignment.TopStart)
+ .fillMaxHeight()
+ .padding(start = 15.dp),
+ thickness = 2.dp
+ )
+ LazyColumn(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .fillMaxSize(),
+ state = lazyListState,
+ contentPadding = PaddingValues(
+ start = 12.dp,
+ end = 16.dp,
+ bottom = innerPadding.calculateBottomPadding()
+ )
+ ) {
+ items(
+ dataState.notes,
+ key = { item: NoteEntity -> item.id!! }) { note ->
+ ColumnNoteCard(
+ modifier = Modifier
+ .fillMaxWidth()
+ .animateItem(), // Add animation to the item
+ note = note,
+ isEnabled = isMultiSelectionModeEnabled,
+ isSelected = selectedNotes.contains(note),
+ onEnableChange = { isMultiSelectionModeEnabled = it },
+ onNoteClick = {
+ if (isMultiSelectionModeEnabled) {
+ selectedNotes =
+ if (selectedNotes.contains(it)) selectedNotes.minus(
+ it
+ )
+ else selectedNotes.plus(it)
+ } else {
+ if (selectedDrawerIndex != 1) {
+ sharedViewModel.onListEvent(ListEvent.OpenNote(it))
+ navigateToNote(it.id!!)
+ } else {
+ Unit
+ }
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+
+ if (dataState.isOrderSectionVisible) {
+ OrderSectionDialog(
+ noteOrder = dataState.noteOrder,
+ onOrderChange = {
+ sharedViewModel.onListEvent(
+ ListEvent.Sort(
+ noteOrder = it,
+ trash = selectedDrawerIndex == 1,
+ filterFolder = selectedDrawerIndex != 0 && selectedDrawerIndex != 1,
+ folderId = selectedFolder.id
+ )
+ )
+ },
+ onDismiss = { sharedViewModel.onListEvent(ListEvent.ToggleOrderSection) }
+ )
+ }
+
+ if (isExportDialogVisible) {
+ ExportDialog(onDismissRequest = { isExportDialogVisible = false }) {
sharedViewModel.onDatabaseEvent(
DatabaseEvent.Export(
context.contentResolver,
@@ -179,34 +576,27 @@ fun MainScreen(
it
)
)
- },
- onExportCancelled = {
- sharedViewModel.cancelDataAction()
+ isExportDialogVisible = false
}
- )
- }
- }
+ }
- if (!isLargeScreen) {
- ModalNavigationScreen(
- drawerState = drawerState,
- gesturesEnabled = !isMultiSelectionModeEnabled && !isSearchBarActivated,
- folderList = folderList.toImmutableList(),
- selectedDrawerIndex = selectedDrawerIndex,
- content = movableContent,
- navigateTo = navigateTo
- ) { position, folder ->
- selectDrawer(position, folder)
- closeDrawer()
- }
- } else {
- PermanentNavigationScreen(
- folderList = folderList.toImmutableList(),
- selectedDrawerIndex = selectedDrawerIndex,
- content = movableContent,
- navigateTo = navigateTo
- ) { position, folder ->
- selectDrawer(position, folder)
+ if (isFolderDialogVisible) {
+ FolderListDialog(
+ hint = stringResource(R.string.destination_folder),
+ oFolderId = selectedFolder.id,
+ folders = folderList,
+ onDismissRequest = { isFolderDialogVisible = false }
+ ) {
+ sharedViewModel.onListEvent(ListEvent.MoveNotes(selectedNotes, it))
+ initializeNoteSelection()
+ }
+ }
+
+ ProgressDialog(
+ isLoading = dataActionState.loading,
+ progress = dataActionState.progress,
+ onDismissRequest = sharedViewModel::cancelDataAction
+ )
}
}
}
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 d965e0a..ab4293e 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
@@ -99,7 +99,6 @@ import com.yangdai.opennote.presentation.event.UiEvent
import com.yangdai.opennote.presentation.util.Constants
import com.yangdai.opennote.presentation.util.timestampToFormatLocalDateTime
import com.yangdai.opennote.presentation.viewmodel.SharedViewModel
-import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import java.io.File
@@ -650,7 +649,7 @@ fun NoteScreen(
FolderListDialog(
hint = stringResource(R.string.destination_folder),
oFolderId = noteState.folderId,
- folders = folderList.toImmutableList(),
+ folders = folderList,
onDismissRequest = { showFolderDialog = false }
) {
sharedViewModel.onNoteEvent(NoteEvent.FolderChanged(it))
@@ -659,8 +658,7 @@ fun NoteScreen(
ProgressDialog(
isLoading = actionState.loading,
- progress = actionState.progress
- ) {
- sharedViewModel.cancelDataAction()
- }
+ progress = actionState.progress,
+ onDismissRequest = sharedViewModel::cancelDataAction
+ )
}
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/state/DataActionState.kt b/app/src/main/java/com/yangdai/opennote/presentation/state/DataActionState.kt
index bd2ffcd..9b0f356 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/state/DataActionState.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/state/DataActionState.kt
@@ -1,5 +1,8 @@
package com.yangdai.opennote.presentation.state
+import androidx.compose.runtime.Stable
+
+@Stable
data class DataActionState(
val loading: Boolean = false,
val progress: Float = 0f,
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/state/SettingsState.kt b/app/src/main/java/com/yangdai/opennote/presentation/state/SettingsState.kt
index e58102b..030a98f 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/state/SettingsState.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/state/SettingsState.kt
@@ -1,5 +1,8 @@
package com.yangdai.opennote.presentation.state
+import androidx.compose.runtime.Stable
+
+@Stable
data class SettingsState(
val theme: AppTheme = AppTheme.UNDEFINED,
val color: AppColor = AppColor.DYNAMIC,
@@ -8,11 +11,7 @@ data class SettingsState(
val shouldFollowSystem: Boolean = false,
val isSwitchActive: Boolean = false,
val isListView: Boolean = false
-) {
- companion object {
- val DEFAULT = SettingsState()
- }
-}
+)
enum class AppTheme(private val value: Int) {
UNDEFINED(-1),
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 72e8854..1fc2b27 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
@@ -1,7 +1,7 @@
package com.yangdai.opennote.presentation.util
object Constants {
-
+ const val DEFAULT_MAX_LINES = 2
const val NAV_ANIMATION_TIME = 300
const val MIME_TYPE_TEXT = "text/"
const val LINK = "https://www.yangdai-opennote.com"
diff --git a/app/src/main/java/com/yangdai/opennote/presentation/util/Utils.kt b/app/src/main/java/com/yangdai/opennote/presentation/util/Utils.kt
index dd6227b..a0ebc43 100644
--- a/app/src/main/java/com/yangdai/opennote/presentation/util/Utils.kt
+++ b/app/src/main/java/com/yangdai/opennote/presentation/util/Utils.kt
@@ -3,6 +3,9 @@ package com.yangdai.opennote.presentation.util
import android.content.Context
import android.content.Intent
import android.icu.text.DateFormat
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import java.util.Date
fun Intent.isTextMimeType() = type?.startsWith(Constants.MIME_TYPE_TEXT) == true
@@ -43,3 +46,12 @@ fun Long.timestampToFormatLocalDateTime(): String {
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
.format(Date(this))
}
+
+@Composable
+fun rememberCustomTabsIntent(): CustomTabsIntent {
+ return remember {
+ CustomTabsIntent.Builder()
+ .setShowTitle(true)
+ .build()
+ }
+}
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 44447f7..d9c4940 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
@@ -22,7 +22,7 @@ import com.yangdai.opennote.data.local.entity.NoteEntity
import com.yangdai.opennote.presentation.state.SettingsState
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.UseCases
import com.yangdai.opennote.domain.usecase.OrderType
import com.yangdai.opennote.presentation.component.ExportType
import com.yangdai.opennote.presentation.component.TaskItem
@@ -92,7 +92,7 @@ import javax.inject.Inject
class SharedViewModel @Inject constructor(
private val database: Database,
private val dataStoreRepository: DataStoreRepository,
- private val operations: Operations
+ private val useCases: UseCases
) : ViewModel() {
// 起始页加载状态,初始值为 true
@@ -107,7 +107,7 @@ class SharedViewModel @Inject constructor(
private val _noteState = MutableStateFlow(NoteState())
val noteStateFlow = _noteState.asStateFlow()
- val foldersStateFlow = operations.getFolders()
+ val foldersStateFlow = useCases.getFolders()
.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
@@ -186,7 +186,7 @@ class SharedViewModel @Inject constructor(
}.flowOn(Dispatchers.IO).stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
- initialValue = SettingsState.DEFAULT
+ initialValue = SettingsState()
)
fun putPreferenceValue(key: String, value: T) {
@@ -230,7 +230,7 @@ class SharedViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) {
if (event.recycle) {
event.noteEntities.forEach {
- operations.updateNote(
+ useCases.updateNote(
NoteEntity(
id = it.id,
title = it.title,
@@ -244,7 +244,7 @@ class SharedViewModel @Inject constructor(
}
} else {
event.noteEntities.forEach {
- operations.deleteNote(it)
+ useCases.deleteNote(it)
}
}
}
@@ -253,7 +253,7 @@ class SharedViewModel @Inject constructor(
is ListEvent.RestoreNotes -> {
viewModelScope.launch(Dispatchers.IO) {
event.noteEntities.forEach {
- operations.updateNote(
+ useCases.updateNote(
NoteEntity(
id = it.id,
title = it.title,
@@ -282,7 +282,7 @@ class SharedViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) {
val folderId = event.folderId
event.noteEntities.forEach {
- operations.updateNote(
+ useCases.updateNote(
NoteEntity(
id = it.id,
title = it.title,
@@ -319,20 +319,20 @@ class SharedViewModel @Inject constructor(
when (event) {
is FolderEvent.AddFolder -> {
viewModelScope.launch(Dispatchers.IO) {
- operations.addFolder(event.folder)
+ useCases.addFolder(event.folder)
}
}
is FolderEvent.DeleteFolder -> {
viewModelScope.launch(Dispatchers.IO) {
- operations.deleteNotesByFolderId(event.folder.id)
- operations.deleteFolder(event.folder)
+ useCases.deleteNotesByFolderId(event.folder.id)
+ useCases.deleteFolder(event.folder)
}
}
is FolderEvent.UpdateFolder -> {
viewModelScope.launch(Dispatchers.IO) {
- operations.updateFolder(event.folder)
+ useCases.updateFolder(event.folder)
}
}
}
@@ -345,7 +345,7 @@ class SharedViewModel @Inject constructor(
folderId: Long? = null,
) {
queryNotesJob?.cancel()
- queryNotesJob = operations.getNotes(noteOrder, trash, filterFolder, folderId)
+ queryNotesJob = useCases.getNotes(noteOrder, trash, filterFolder, folderId)
.flowOn(Dispatchers.IO)
.onEach { notes ->
_dataState.update {
@@ -364,7 +364,7 @@ class SharedViewModel @Inject constructor(
private fun searchNotes(keyWord: String) {
queryNotesJob?.cancel()
- queryNotesJob = operations.searchNotes(keyWord)
+ queryNotesJob = useCases.searchNotes(keyWord)
.flowOn(Dispatchers.IO)
.onEach { notes ->
_dataState.update {
@@ -409,7 +409,7 @@ class SharedViewModel @Inject constructor(
isMarkdown = noteState.isMarkdown,
timestamp = System.currentTimeMillis()
)
- operations.addNote(note)
+ useCases.addNote(note)
_event.emit(UiEvent.NavigateBack)
}
}
@@ -418,7 +418,7 @@ class SharedViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) {
val note = noteStateFlow.value
note.id?.let {
- operations.updateNote(
+ useCases.updateNote(
NoteEntity(
id = it,
title = titleState.text.toString(),
@@ -472,7 +472,7 @@ class SharedViewModel @Inject constructor(
// 判断id是否与oNote的id相同,不同则从数据库获取笔记,并更新oNote。
viewModelScope.launch(Dispatchers.IO) {
if (event.id != (_oNote.id ?: -1L))
- _oNote = operations.getNoteById(event.id)
+ _oNote = useCases.getNoteById(event.id)
?: NoteEntity(timestamp = System.currentTimeMillis())
_noteState.update { noteState ->
noteState.copy(
@@ -500,7 +500,7 @@ class SharedViewModel @Inject constructor(
)
if (note.id != null)
if (note.title != _oNote.title || note.content != _oNote.content || note.isMarkdown != _oNote.isMarkdown || note.folderId != _oNote.folderId)
- operations.updateNote(note)
+ useCases.updateNote(note)
}
}
}
@@ -568,7 +568,7 @@ class SharedViewModel @Inject constructor(
|| (fileName?.endsWith(".html") == true)),
timestamp = System.currentTimeMillis()
)
- operations.addNote(note)
+ useCases.addNote(note)
}
}
}
@@ -651,7 +651,7 @@ class SharedViewModel @Inject constructor(
it.copy(progress = 0.2f)
}
dataActionJob = viewModelScope.launch(Dispatchers.IO) {
- val notes = operations.getNotes().first()
+ val notes = useCases.getNotes().first()
val json = Json.encodeToString(notes)
_dataActionState.update {
it.copy(progress = 0.5f)
@@ -711,7 +711,7 @@ class SharedViewModel @Inject constructor(
it.copy(progress = 0.6f)
}
notes.forEachIndexed { _, noteEntity ->
- operations.addNote(noteEntity)
+ useCases.addNote(noteEntity)
}
}.onFailure { throwable ->
_dataActionState.update {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f528756..402d607 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,10 +1,9 @@
[versions]
-agp = "8.4.0"
+agp = "8.4.1"
glance = "1.1.0-rc01"
-kotlin = "2.0.0-RC3"
-ksp = "2.0.0-RC3-1.0.20"
+kotlin = "2.0.0"
+ksp = "2.0.0-1.0.21"
kotlinxSerialization = "1.6.3"
-kotlinxCollectionsImmutable = "0.3.7"
composeBom = "2024.05.00"
composeFoundation = "1.7.0-beta01"
@@ -82,7 +81,6 @@ androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref =
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
-kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
text-recognition = { group = "com.google.mlkit", name = "text-recognition", version.ref = "textRecognition" }
text-recognition-chinese = { group = "com.google.mlkit", name = "text-recognition-chinese", version.ref = "textRecognition" }