From 3115c8c72449008d3162f3de1f9004c8478da6ac Mon Sep 17 00:00:00 2001 From: Tom Vincent Date: Mon, 8 Dec 2025 17:11:57 +0000 Subject: [PATCH 1/3] Add list view --- .../wiiznokes/gitnote/data/AppPreferences.kt | 2 + .../github/wiiznokes/gitnote/ui/model/Grid.kt | 7 +- .../gitnote/ui/screen/app/grid/GridScreen.kt | 373 +++++++++++------- .../gitnote/ui/screen/app/grid/ListView.kt | 182 +++++++++ .../gitnote/ui/screen/app/grid/TopGrid.kt | 70 +++- .../gitnote/ui/viewmodel/GridViewModel.kt | 13 +- app/src/main/res/values/strings.xml | 3 + 7 files changed, 491 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt index 51374d41..63a84d3f 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt @@ -9,6 +9,7 @@ import io.github.wiiznokes.gitnote.ui.model.Cred import io.github.wiiznokes.gitnote.ui.model.CredType import io.github.wiiznokes.gitnote.ui.model.GitAuthor import io.github.wiiznokes.gitnote.ui.model.NoteMinWidth +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.model.SortOrder import io.github.wiiznokes.gitnote.ui.model.StorageConfiguration import io.github.wiiznokes.gitnote.ui.theme.Theme @@ -140,6 +141,7 @@ class AppPreferences( val noteMinWidth = enumPreference("noteMinWidth", NoteMinWidth.Default) val showFullNoteHeight = booleanPreference("showFullNoteHeight", false) + val noteViewType = enumPreference("noteViewType", NoteViewType.Grid) val rememberLastOpenedFolder = booleanPreference("rememberLastOpenedFolder", false) val lastOpenedFolder = stringPreference("lastOpenedFolder", "") diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt index 328bbc82..8f35ef35 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt @@ -34,9 +34,14 @@ enum class NoteMinWidth(val size: Int) { override fun toString(): String = this.size.toString() } +enum class NoteViewType { + Grid, + List, +} + data class GridNote( @Embedded val note: Note, val isUnique: Boolean, val selected: Boolean = false, -) \ No newline at end of file +) diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt index 55d85f0c..63e322d7 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContent @@ -21,6 +22,7 @@ import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -64,15 +66,17 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.paging.compose.collectAsLazyPagingItems -import dev.jeziellago.compose.markdowntext.MarkdownText import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.data.room.Note import io.github.wiiznokes.gitnote.ui.component.CustomDropDown import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.model.EditType import io.github.wiiznokes.gitnote.ui.model.FileExtension +import io.github.wiiznokes.gitnote.ui.model.GridNote +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.screen.app.DrawerScreen import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel +import java.util.Date private const val TAG = "GridScreen" @@ -112,6 +116,8 @@ fun GridScreen( } } + val noteViewType by vm.prefs.noteViewType.getAsState() + val searchFocusRequester = remember { FocusRequester() } val fabExpanded = remember { @@ -148,7 +154,8 @@ fun GridScreen( onEditClick = onEditClick, selectedNotes = selectedNotes, nestedScrollConnection = nestedScrollConnection, - padding = padding + padding = padding, + noteViewType = noteViewType, ) TopBar( @@ -181,21 +188,25 @@ private fun GridView( onEditClick: (Note, EditType) -> Unit, selectedNotes: List, padding: PaddingValues, + noteViewType: NoteViewType, ) { val gridNotes = vm.gridNotes.collectAsLazyPagingItems() val gridState = rememberLazyStaggeredGridState() + val listState = rememberLazyListState() val query = vm.query.collectAsState() - var lastQuery: String = rememberSaveable { query.value } + val lastQuery = rememberSaveable { mutableStateOf(query.value) } - if (lastQuery != query.value) { - @Suppress("UNUSED_VALUE") - lastQuery = query.value - LaunchedEffect(null) { - gridState.animateScrollToItem(index = 0) + LaunchedEffect(query.value, noteViewType) { + if (lastQuery.value != query.value) { + lastQuery.value = query.value + when (noteViewType) { + NoteViewType.Grid -> gridState.animateScrollToItem(index = 0) + NoteViewType.List -> listState.animateScrollToItem(index = 0) + } } } @@ -206,160 +217,81 @@ private fun GridView( vm.refresh() }) + val noteMinWidth = vm.prefs.noteMinWidth.getAsState() + val showFullPathOfNotes = vm.prefs.showFullPathOfNotes.getAsState() + val showFullNoteHeight = vm.prefs.showFullNoteHeight.getAsState() + + val topSpacerHeight = topBarHeight + 40.dp + 15.dp + Box { // todo: scroll even when there is nothing to scroll // todo: add scroll bar - val noteMinWidth = vm.prefs.noteMinWidth.getAsState() - val showFullPathOfNotes = vm.prefs.showFullPathOfNotes.getAsState() - val showFullNoteHeight = vm.prefs.showFullNoteHeight.getAsState() - - LazyVerticalStaggeredGrid( - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - .nestedScroll(nestedScrollConnection), contentPadding = PaddingValues( - horizontal = 3.dp - ), columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), state = gridState - - ) { + val commonModifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + .nestedScroll(nestedScrollConnection) + + when (noteViewType) { + NoteViewType.Grid -> { + LazyVerticalStaggeredGrid( + modifier = commonModifier, + contentPadding = PaddingValues( + horizontal = 3.dp + ), + columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), + state = gridState - item( - span = StaggeredGridItemSpan.FullLine - ) { - Spacer(modifier = Modifier.height(topBarHeight + 40.dp + 15.dp)) - } + ) { + item( + span = StaggeredGridItemSpan.FullLine + ) { + Spacer(modifier = Modifier.height(topSpacerHeight)) + } - items( - count = gridNotes.itemCount, - key = { index -> - val note = gridNotes[index]!! - note.note.id - } - ) { index -> - val gridNote = gridNotes[index]!! - val dropDownExpanded = remember { - mutableStateOf(false) - } + items( + count = gridNotes.itemCount, + key = { index -> + val note = gridNotes[index]!! + note.note.id + } + ) { index -> + val gridNote = gridNotes[index]!! + + NoteCard( + gridNote = gridNote, + vm = vm, + onEditClick = onEditClick, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes.value, + showFullNoteHeight = showFullNoteHeight.value, + modifier = Modifier + .padding(3.dp) + ) + } - val clickPosition = remember { - mutableStateOf(Offset.Zero) - } - Card( - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface - ), - border = if (dropDownExpanded.value) { - BorderStroke( - width = 2.dp, color = MaterialTheme.colorScheme.primary - ) - } else if (gridNote.selected) { - BorderStroke( - width = 2.dp, color = MaterialTheme.colorScheme.onSurface - ) - } else { - BorderStroke( - width = 1.dp, - color = MaterialTheme.colorScheme.surfaceColorAtElevation(1000.dp) - ) - }, - modifier = Modifier - .sizeIn( - maxHeight = if (showFullNoteHeight.value) Dp.Unspecified else 500.dp - ) - .padding(3.dp) - .combinedClickable(onLongClick = { - dropDownExpanded.value = true - }, onClick = { - if (selectedNotes.isEmpty()) { - onEditClick( - gridNote.note, EditType.Update - ) - } else { - vm.selectNote( - gridNote.note, add = !gridNote.selected - ) - } - }) - .pointerInteropFilter { - clickPosition.value = Offset(it.x, it.y) - false - }, - ) { - Box { - - // need this box for clickPosition - Box { - CustomDropDown( - expanded = dropDownExpanded, - shape = MaterialTheme.shapes.medium, - options = listOf( - CustomDropDownModel( - text = stringResource(R.string.delete_this_note), - onClick = { - vm.deleteNote(gridNote.note) - }), - if (selectedNotes.isEmpty()) CustomDropDownModel( - text = stringResource( - R.string.select_multiple_notes - ), onClick = { - vm.selectNote(gridNote.note, true) - }) else null, - ), - clickPosition = clickPosition - ) - } - Column( - modifier = Modifier.padding(10.dp), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.Start, - ) { - Text( - text = if (showFullPathOfNotes.value || !gridNote.isUnique) gridNote.note.relativePath else - gridNote.note.nameWithoutExtension(), - modifier = Modifier.padding(bottom = 6.dp), - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - ), - color = MaterialTheme.colorScheme.tertiary - ) - - if (gridNote.note.fileExtension() is FileExtension.Md) { -// MarkdownText( -// markdown = gridNote.note.content, -// disableLinkMovementMethod = true, -// isTextSelectable = false, -// onLinkClicked = { } -// ) - Text( - text = gridNote.note.content, - modifier = Modifier, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) - } else { - Text( - text = gridNote.note.content, - modifier = Modifier, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) - } - } + item( + span = StaggeredGridItemSpan.FullLine + ) { + Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) } } } - - item( - span = StaggeredGridItemSpan.FullLine - ) { - Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) + NoteViewType.List -> { + NoteListView( + gridNotes = gridNotes, + listState = listState, + topSpacerHeight = topSpacerHeight, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes.value, + onEditClick = onEditClick, + vm = vm, + ) } } @@ -378,6 +310,151 @@ private fun GridView( } +@Composable +private fun NoteCard( + gridNote: GridNote, + vm: GridViewModel, + onEditClick: (Note, EditType) -> Unit, + selectedNotes: List, + showFullPathOfNotes: Boolean, + showFullNoteHeight: Boolean, + modifier: Modifier = Modifier, +) { + val dropDownExpanded = remember { + mutableStateOf(false) + } + + val clickPosition = remember { + mutableStateOf(Offset.Zero) + } + + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface + ), + border = if (dropDownExpanded.value) { + BorderStroke( + width = 2.dp, color = MaterialTheme.colorScheme.primary + ) + } else if (gridNote.selected) { + BorderStroke( + width = 2.dp, color = MaterialTheme.colorScheme.onSurface + ) + } else { + BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.surfaceColorAtElevation(1000.dp) + ) + }, + modifier = modifier + .sizeIn( + maxHeight = if (showFullNoteHeight) Dp.Unspecified else 500.dp + ) + .combinedClickable(onLongClick = { + dropDownExpanded.value = true + }, onClick = { + if (selectedNotes.isEmpty()) { + onEditClick( + gridNote.note, EditType.Update + ) + } else { + vm.selectNote( + gridNote.note, add = !gridNote.selected + ) + } + }) + .pointerInteropFilter { + clickPosition.value = Offset(it.x, it.y) + false + }, + ) { + NoteCardContent( + gridNote = gridNote, + vm = vm, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes, + clickPosition = clickPosition, + dropDownExpanded = dropDownExpanded + ) + } +} + +@Composable +private fun NoteCardContent( + gridNote: GridNote, + vm: GridViewModel, + selectedNotes: List, + showFullPathOfNotes: Boolean, + clickPosition: MutableState, + dropDownExpanded: MutableState, +) { + Box { + + // need this box for clickPosition + Box { + CustomDropDown( + expanded = dropDownExpanded, + shape = MaterialTheme.shapes.medium, + options = listOf( + CustomDropDownModel( + text = stringResource(R.string.delete_this_note), + onClick = { + vm.deleteNote(gridNote.note) + }), + if (selectedNotes.isEmpty()) CustomDropDownModel( + text = stringResource( + R.string.select_multiple_notes + ), onClick = { + vm.selectNote(gridNote.note, true) + }) else null, + ), + clickPosition = clickPosition + ) + } + Column( + modifier = Modifier.padding(10.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + val title = if (showFullPathOfNotes || !gridNote.isUnique) { + gridNote.note.relativePath + } else { + gridNote.note.nameWithoutExtension() + } + Text( + text = title, + modifier = Modifier.padding(bottom = 6.dp), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold, + ), + color = MaterialTheme.colorScheme.tertiary + ) + + if (gridNote.note.fileExtension() is FileExtension.Md) { +// MarkdownText( +// markdown = gridNote.note.content, +// disableLinkMovementMethod = true, +// isTextSelectable = false, +// onLinkClicked = { } +// ) + Text( + text = gridNote.note.content, + modifier = Modifier, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + } else { + Text( + text = gridNote.note.content, + modifier = Modifier, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } +} // https://stackoverflow.com/questions/73079388/android-jetpack-compose-keyboard-not-close // https://medium.com/@debdut.saha.1/top-app-bar-animation-using-nestedscrollconnection-like-facebook-jetpack-compose-b446c109ee52 @@ -430,4 +507,4 @@ private fun rememberNestedScrollConnection( } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt new file mode 100644 index 00000000..39b16a5e --- /dev/null +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt @@ -0,0 +1,182 @@ +package io.github.wiiznokes.gitnote.ui.screen.app.grid + +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Description +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.material3.Text +import io.github.wiiznokes.gitnote.R +import io.github.wiiznokes.gitnote.data.room.Note +import io.github.wiiznokes.gitnote.ui.component.CustomDropDown +import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel +import io.github.wiiznokes.gitnote.ui.model.EditType +import io.github.wiiznokes.gitnote.ui.model.GridNote +import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel +import java.text.DateFormat +import java.util.Date +import androidx.paging.compose.LazyPagingItems + +@Composable +internal fun NoteListView( + gridNotes: LazyPagingItems, + listState: LazyListState, + topSpacerHeight: Dp, + selectedNotes: List, + showFullPathOfNotes: Boolean, + onEditClick: (Note, EditType) -> Unit, + vm: GridViewModel, +) { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + state = listState + ) { + item { + Spacer(modifier = Modifier.height(topSpacerHeight)) + } + + items( + count = gridNotes.itemCount, + key = { index -> + val note = gridNotes[index]!! + note.note.id + } + ) { index -> + val gridNote = gridNotes[index]!! + NoteListRow( + gridNote = gridNote, + vm = vm, + onEditClick = onEditClick, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes, + ) + } + + item { + Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) + } + } +} + +@Composable +private fun NoteListRow( + gridNote: GridNote, + vm: GridViewModel, + onEditClick: (Note, EditType) -> Unit, + selectedNotes: List, + showFullPathOfNotes: Boolean, +) { + val dropDownExpanded = remember { mutableStateOf(false) } + val clickPosition = remember { mutableStateOf(androidx.compose.ui.geometry.Offset.Zero) } + + val formattedDate = remember(gridNote.note.lastModifiedTimeMillis) { + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT) + .format(Date(gridNote.note.lastModifiedTimeMillis)) + } + + val title = gridNote.note.relativePath + + val rowBackground = + if (gridNote.selected) MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp) + else MaterialTheme.colorScheme.surface + + Column( + modifier = Modifier + .fillMaxWidth() + .background(rowBackground) + .combinedClickable( + onLongClick = { dropDownExpanded.value = true }, + onClick = { + if (selectedNotes.isEmpty()) { + onEditClick(gridNote.note, EditType.Update) + } else { + vm.selectNote(gridNote.note, add = !gridNote.selected) + } + } + ) + .pointerInteropFilter { + clickPosition.value = androidx.compose.ui.geometry.Offset(it.x, it.y) + false + } + ) { + Box { + CustomDropDown( + expanded = dropDownExpanded, + shape = MaterialTheme.shapes.medium, + options = listOf( + CustomDropDownModel( + text = stringResource(R.string.delete_this_note), + onClick = { vm.deleteNote(gridNote.note) }), + if (selectedNotes.isEmpty()) CustomDropDownModel( + text = stringResource(R.string.select_multiple_notes), + onClick = { vm.selectNote(gridNote.note, true) }) else null, + ), + clickPosition = clickPosition + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + imageVector = Icons.Rounded.Description, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface + ) + + Column( + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold, + ), + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = formattedDate, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.surfaceColorAtElevation(80.dp) + ) + } +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt index fe95cf1c..30a07781 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt @@ -29,6 +29,8 @@ import androidx.compose.material.icons.filled.CloudUpload import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Menu import androidx.compose.material.icons.rounded.MoreVert +import androidx.compose.material.icons.rounded.ViewList +import androidx.compose.material.icons.rounded.ViewModule import androidx.compose.material3.DrawerState import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -66,6 +68,7 @@ import io.github.wiiznokes.gitnote.manager.SyncState import io.github.wiiznokes.gitnote.ui.component.CustomDropDown import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.component.SimpleIcon +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -141,6 +144,7 @@ private fun SearchBar( } val query = vm.query.collectAsState() + val noteViewType = vm.prefs.noteViewType.getAsState() if (query.value.isNotEmpty()) { BackHandler { clearQuery() @@ -202,6 +206,28 @@ private fun SearchBar( vm.consumeOkSyncState() }) + IconButton( + onClick = { + vm.toggleViewType() + } + ) { + SimpleIcon( + imageVector = if (noteViewType.value == NoteViewType.Grid) { + Icons.Rounded.ViewList + } else { + Icons.Rounded.ViewModule + }, + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + if (noteViewType.value == NoteViewType.Grid) { + R.string.switch_to_list_view + } else { + R.string.switch_to_grid_view + } + ) + ) + } + Box { val expanded = remember { mutableStateOf(false) } IconButton( @@ -248,15 +274,41 @@ private fun SearchBar( } } else { { - IconButton( - onClick = { - clearQuery() - } + Row( + verticalAlignment = Alignment.CenterVertically ) { - SimpleIcon( - imageVector = Icons.Rounded.Close, - tint = MaterialTheme.colorScheme.onSurface - ) + IconButton( + onClick = { + vm.toggleViewType() + } + ) { + SimpleIcon( + imageVector = if (noteViewType.value == NoteViewType.Grid) { + Icons.Rounded.ViewList + } else { + Icons.Rounded.ViewModule + }, + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + if (noteViewType.value == NoteViewType.Grid) { + R.string.switch_to_list_view + } else { + R.string.switch_to_grid_view + } + ) + ) + } + + IconButton( + onClick = { + clearQuery() + } + ) { + SimpleIcon( + imageVector = Icons.Rounded.Close, + tint = MaterialTheme.colorScheme.onSurface + ) + } } } } @@ -410,4 +462,4 @@ private fun SyncStateIcon( modifier = modifier, ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt index a2205fc1..71a47545 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt @@ -17,6 +17,7 @@ import io.github.wiiznokes.gitnote.data.room.RepoDatabase import io.github.wiiznokes.gitnote.helper.NameValidation import io.github.wiiznokes.gitnote.manager.StorageManager import io.github.wiiznokes.gitnote.ui.model.FileExtension +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -157,6 +158,17 @@ class GridViewModel : ViewModel() { _selectedNotes.emit(emptyList()) } + fun toggleViewType() { + viewModelScope.launch { + val next = if (prefs.noteViewType.get() == NoteViewType.Grid) { + NoteViewType.List + } else { + NoteViewType.Grid + } + prefs.noteViewType.update(next) + } + } + fun deleteSelectedNotes() { CoroutineScope(Dispatchers.IO).launch { val currentSelectedNotes = selectedNotes.value @@ -254,4 +266,3 @@ class GridViewModel : ViewModel() { } } } - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57ab7abd..88a605f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,6 +69,9 @@ Folder already exist No modification Grid + List + Switch to list view + Switch to grid view About Choose method Create local repository From 325258557ae9da9cf978be9a68f18e9655b5b6ec Mon Sep 17 00:00:00 2001 From: Tom Vincent Date: Tue, 9 Dec 2025 18:37:42 +0000 Subject: [PATCH 2/3] PR feedback --- .../gitnote/ui/component/CustomDropDown.kt | 13 ++- .../gitnote/ui/screen/app/grid/GridScreen.kt | 77 +++++----------- .../gitnote/ui/screen/app/grid/ListView.kt | 46 +++++++--- .../gitnote/ui/screen/app/grid/TopGrid.kt | 91 ++++++------------- 4 files changed, 91 insertions(+), 136 deletions(-) diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt index 7ac898c5..5b88f176 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt @@ -16,10 +16,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.core.util.TypedValueCompat.pxToDp +import androidx.compose.ui.platform.LocalDensity private val TAG = "CustomDropDown" @@ -41,10 +40,10 @@ fun CustomDropDown( mutableStateOf(Offset.Zero) } ) { - val m = LocalResources.current.displayMetrics - val x = pxToDp(clickPosition.value.x, m).dp - val y = pxToDp(clickPosition.value.y, m).dp - val offset = DpOffset(x, y) + val density = LocalDensity.current + val offset = with(density) { + DpOffset(clickPosition.value.x.toDp(), clickPosition.value.y.toDp()) + } MaterialTheme( shapes = MaterialTheme.shapes.copy(extraSmall = shape) @@ -75,4 +74,4 @@ fun CustomDropDown( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt index 63e322d7..930eabb8 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt @@ -68,8 +68,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.paging.compose.collectAsLazyPagingItems import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.data.room.Note -import io.github.wiiznokes.gitnote.ui.component.CustomDropDown -import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.model.EditType import io.github.wiiznokes.gitnote.ui.model.FileExtension import io.github.wiiznokes.gitnote.ui.model.GridNote @@ -191,25 +189,8 @@ private fun GridView( noteViewType: NoteViewType, ) { val gridNotes = vm.gridNotes.collectAsLazyPagingItems() - - val gridState = rememberLazyStaggeredGridState() - val listState = rememberLazyListState() - - val query = vm.query.collectAsState() - val lastQuery = rememberSaveable { mutableStateOf(query.value) } - - LaunchedEffect(query.value, noteViewType) { - if (lastQuery.value != query.value) { - lastQuery.value = query.value - when (noteViewType) { - NoteViewType.Grid -> gridState.animateScrollToItem(index = 0) - NoteViewType.List -> listState.animateScrollToItem(index = 0) - } - } - } - val isRefreshing by vm.isRefreshing.collectAsStateWithLifecycle() val pullRefreshState = rememberPullRefreshState(isRefreshing, { @@ -235,29 +216,25 @@ private fun GridView( when (noteViewType) { NoteViewType.Grid -> { + val gridState = rememberLazyStaggeredGridState() + + LaunchedEffect(query.value) { + gridState.animateScrollToItem(index = 0) + } + LazyVerticalStaggeredGrid( modifier = commonModifier, - contentPadding = PaddingValues( - horizontal = 3.dp - ), + contentPadding = PaddingValues(horizontal = 3.dp), columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), state = gridState - ) { - - item( - span = StaggeredGridItemSpan.FullLine - ) { + item(span = StaggeredGridItemSpan.FullLine) { Spacer(modifier = Modifier.height(topSpacerHeight)) } - items( count = gridNotes.itemCount, - key = { index -> - val note = gridNotes[index]!! - note.note.id - } + key = { index -> gridNotes[index]!!.note.id } ) { index -> val gridNote = gridNotes[index]!! @@ -268,24 +245,27 @@ private fun GridView( selectedNotes = selectedNotes, showFullPathOfNotes = showFullPathOfNotes.value, showFullNoteHeight = showFullNoteHeight.value, - modifier = Modifier - .padding(3.dp) + modifier = Modifier.padding(3.dp) ) } - - item( - span = StaggeredGridItemSpan.FullLine - ) { + item(span = StaggeredGridItemSpan.FullLine) { Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) } } } NoteViewType.List -> { + val listState = rememberLazyListState() + + LaunchedEffect(query.value) { + listState.animateScrollToItem(index = 0) + } + NoteListView( gridNotes = gridNotes, listState = listState, + modifier = commonModifier, topSpacerHeight = topSpacerHeight, selectedNotes = selectedNotes, showFullPathOfNotes = showFullPathOfNotes.value, @@ -392,22 +372,11 @@ private fun NoteCardContent( // need this box for clickPosition Box { - CustomDropDown( - expanded = dropDownExpanded, - shape = MaterialTheme.shapes.medium, - options = listOf( - CustomDropDownModel( - text = stringResource(R.string.delete_this_note), - onClick = { - vm.deleteNote(gridNote.note) - }), - if (selectedNotes.isEmpty()) CustomDropDownModel( - text = stringResource( - R.string.select_multiple_notes - ), onClick = { - vm.selectNote(gridNote.note, true) - }) else null, - ), + NoteActionsDropdown( + vm = vm, + gridNote = gridNote, + selectedNotes = selectedNotes, + dropDownExpanded = dropDownExpanded, clickPosition = clickPosition ) } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt index 39b16a5e..30e0d61c 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt @@ -40,11 +40,14 @@ import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel import java.text.DateFormat import java.util.Date import androidx.paging.compose.LazyPagingItems +import androidx.compose.runtime.MutableState +import androidx.compose.ui.geometry.Offset @Composable internal fun NoteListView( gridNotes: LazyPagingItems, listState: LazyListState, + modifier: Modifier = Modifier, topSpacerHeight: Dp, selectedNotes: List, showFullPathOfNotes: Boolean, @@ -52,7 +55,7 @@ internal fun NoteListView( vm: GridViewModel, ) { LazyColumn( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), state = listState ) { item { @@ -119,22 +122,16 @@ private fun NoteListRow( } ) .pointerInteropFilter { - clickPosition.value = androidx.compose.ui.geometry.Offset(it.x, it.y) + clickPosition.value = Offset(it.x, it.y) false } ) { Box { - CustomDropDown( - expanded = dropDownExpanded, - shape = MaterialTheme.shapes.medium, - options = listOf( - CustomDropDownModel( - text = stringResource(R.string.delete_this_note), - onClick = { vm.deleteNote(gridNote.note) }), - if (selectedNotes.isEmpty()) CustomDropDownModel( - text = stringResource(R.string.select_multiple_notes), - onClick = { vm.selectNote(gridNote.note, true) }) else null, - ), + NoteActionsDropdown( + vm = vm, + gridNote = gridNote, + selectedNotes = selectedNotes, + dropDownExpanded = dropDownExpanded, clickPosition = clickPosition ) @@ -180,3 +177,26 @@ private fun NoteListRow( ) } } + +@Composable +internal fun NoteActionsDropdown( + vm: GridViewModel, + gridNote: GridNote, + selectedNotes: List, + dropDownExpanded: MutableState, + clickPosition: MutableState, +) { + CustomDropDown( + expanded = dropDownExpanded, + shape = MaterialTheme.shapes.medium, + options = listOf( + CustomDropDownModel( + text = stringResource(R.string.delete_this_note), + onClick = { vm.deleteNote(gridNote.note) }), + if (selectedNotes.isEmpty()) CustomDropDownModel( + text = stringResource(R.string.select_multiple_notes), + onClick = { vm.selectNote(gridNote.note, true) }) else null, + ), + clickPosition = clickPosition + ) +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt index 30a07781..dd16187b 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt @@ -194,46 +194,43 @@ private fun SearchBar( ) } }, - trailingIcon = if (queryTextField.text.isEmpty()) { - { - Row( - verticalAlignment = Alignment.CenterVertically - ) { + trailingIcon = { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (queryTextField.text.isEmpty()) { val syncState = vm.syncState.collectAsState() - - SyncStateIcon(syncState.value, { + SyncStateIcon(syncState.value) { vm.consumeOkSyncState() - }) + } + } - IconButton( - onClick = { - vm.toggleViewType() - } - ) { - SimpleIcon( - imageVector = if (noteViewType.value == NoteViewType.Grid) { - Icons.Rounded.ViewList + IconButton( + onClick = { vm.toggleViewType() } + ) { + SimpleIcon( + imageVector = if (noteViewType.value == NoteViewType.Grid) { + Icons.Rounded.ViewList + } else { + Icons.Rounded.ViewModule + }, + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + if (noteViewType.value == NoteViewType.Grid) { + R.string.switch_to_list_view } else { - Icons.Rounded.ViewModule - }, - tint = MaterialTheme.colorScheme.onSurface, - contentDescription = stringResource( - if (noteViewType.value == NoteViewType.Grid) { - R.string.switch_to_list_view - } else { - R.string.switch_to_grid_view - } - ) + R.string.switch_to_grid_view + } ) - } + ) + } + if (queryTextField.text.isEmpty()) { Box { val expanded = remember { mutableStateOf(false) } IconButton( - onClick = { - expanded.value = true - } + onClick = { expanded.value = true } ) { SimpleIcon( imageVector = Icons.Rounded.MoreVert, @@ -270,39 +267,9 @@ private fun SearchBar( ) ) } - } - } - } else { - { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - IconButton( - onClick = { - vm.toggleViewType() - } - ) { - SimpleIcon( - imageVector = if (noteViewType.value == NoteViewType.Grid) { - Icons.Rounded.ViewList - } else { - Icons.Rounded.ViewModule - }, - tint = MaterialTheme.colorScheme.onSurface, - contentDescription = stringResource( - if (noteViewType.value == NoteViewType.Grid) { - R.string.switch_to_list_view - } else { - R.string.switch_to_grid_view - } - ) - ) - } - + } else { IconButton( - onClick = { - clearQuery() - } + onClick = { clearQuery() } ) { SimpleIcon( imageVector = Icons.Rounded.Close, From 6e9edc2cc792326cd605422e79f77b4ebfab5f27 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:59:21 +0100 Subject: [PATCH 3/3] minor modif --- .../wiiznokes/gitnote/data/AppPreferences.kt | 1 - .../github/wiiznokes/gitnote/data/room/Dao.kt | 1 - .../gitnote/data/room/RepoDatabase.kt | 2 +- .../gitnote/ui/component/CustomDropDown.kt | 2 +- .../github/wiiznokes/gitnote/ui/model/Init.kt | 3 +- .../gitnote/ui/screen/app/edit/EditScreen.kt | 2 - .../gitnote/ui/screen/app/grid/GridScreen.kt | 223 ++++++++++-------- .../gitnote/ui/screen/app/grid/ListView.kt | 43 +--- .../gitnote/ui/screen/app/grid/TopGrid.kt | 26 +- .../gitnote/ui/screen/setup/SetupNav.kt | 5 +- .../setup/remote/AuthorizeGitNoteScreen.kt | 2 - .../remote/SelectGenerateNewSshKeysScreen.kt | 3 - 12 files changed, 156 insertions(+), 157 deletions(-) diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt index 63a84d3f..5747647c 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt @@ -13,7 +13,6 @@ import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.model.SortOrder import io.github.wiiznokes.gitnote.ui.model.StorageConfiguration import io.github.wiiznokes.gitnote.ui.theme.Theme -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.runBlocking import kotlin.io.path.pathString diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt index ad56a32b..74fd2c6c 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt @@ -1,7 +1,6 @@ package io.github.wiiznokes.gitnote.data.room import android.util.Log -import android.webkit.MimeTypeMap import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt index 280397eb..aabe62cf 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt @@ -27,7 +27,7 @@ abstract class RepoDatabase : RoomDatabase() { abstract val repoDatabaseDao: RepoDatabaseDao companion object { - fun generateUid() = Random.Default.nextInt() + fun generateUid() = Random.nextInt() fun buildDatabase(context: Context): RepoDatabase { val onMigration = object : Callback() { diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt index 5b88f176..12ae7db0 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt @@ -16,9 +16,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.platform.LocalDensity private val TAG = "CustomDropDown" diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt index 14dd422d..140a0563 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt @@ -39,7 +39,8 @@ enum class CredType { @Parcelize sealed class StorageConfiguration : Parcelable { data object App : StorageConfiguration() - class Device(var path: String, val useUrlForRootFolder: Boolean = false) : StorageConfiguration() + class Device(var path: String, val useUrlForRootFolder: Boolean = false) : + StorageConfiguration() fun repoPath(): String { return when (this) { diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt index 3c650954..8e3f5570 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt @@ -1,6 +1,5 @@ package io.github.wiiznokes.gitnote.ui.screen.app.edit -import android.webkit.MimeTypeMap import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -49,7 +48,6 @@ import io.github.wiiznokes.gitnote.manager.extensionType import io.github.wiiznokes.gitnote.ui.component.SimpleIcon import io.github.wiiznokes.gitnote.ui.destination.EditParams import io.github.wiiznokes.gitnote.ui.model.EditType -import io.github.wiiznokes.gitnote.ui.model.FileExtension import io.github.wiiznokes.gitnote.ui.viewmodel.edit.MarkDownVM import io.github.wiiznokes.gitnote.ui.viewmodel.edit.TextVM import io.github.wiiznokes.gitnote.ui.viewmodel.edit.newEditViewModel diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt index 930eabb8..407ffb43 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt @@ -13,16 +13,16 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContent import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -46,7 +46,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -65,16 +64,18 @@ import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.data.room.Note +import io.github.wiiznokes.gitnote.ui.component.CustomDropDown +import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.model.EditType import io.github.wiiznokes.gitnote.ui.model.FileExtension import io.github.wiiznokes.gitnote.ui.model.GridNote import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.screen.app.DrawerScreen import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel -import java.util.Date private const val TAG = "GridScreen" @@ -82,6 +83,8 @@ private const val TAG = "GridScreen" private const val maxOffset = -500f internal val topBarHeight = 80.dp +internal val topSpacerHeight = topBarHeight + 40.dp + 15.dp + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun GridScreen( @@ -198,11 +201,7 @@ private fun GridView( vm.refresh() }) - val noteMinWidth = vm.prefs.noteMinWidth.getAsState() val showFullPathOfNotes = vm.prefs.showFullPathOfNotes.getAsState() - val showFullNoteHeight = vm.prefs.showFullNoteHeight.getAsState() - - val topSpacerHeight = topBarHeight + 40.dp + 15.dp Box { @@ -222,37 +221,15 @@ private fun GridView( gridState.animateScrollToItem(index = 0) } - LazyVerticalStaggeredGrid( + GridNotesView( + gridNotes = gridNotes, + gridState = gridState, modifier = commonModifier, - contentPadding = PaddingValues(horizontal = 3.dp), - columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), - state = gridState - ) { - item(span = StaggeredGridItemSpan.FullLine) { - Spacer(modifier = Modifier.height(topSpacerHeight)) - } - - items( - count = gridNotes.itemCount, - key = { index -> gridNotes[index]!!.note.id } - ) { index -> - val gridNote = gridNotes[index]!! - - NoteCard( - gridNote = gridNote, - vm = vm, - onEditClick = onEditClick, - selectedNotes = selectedNotes, - showFullPathOfNotes = showFullPathOfNotes.value, - showFullNoteHeight = showFullNoteHeight.value, - modifier = Modifier.padding(3.dp) - ) - } - - item(span = StaggeredGridItemSpan.FullLine) { - Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) - } - } + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes.value, + onEditClick = onEditClick, + vm = vm, + ) } NoteViewType.List -> { @@ -266,7 +243,6 @@ private fun GridView( gridNotes = gridNotes, listState = listState, modifier = commonModifier, - topSpacerHeight = topSpacerHeight, selectedNotes = selectedNotes, showFullPathOfNotes = showFullPathOfNotes.value, onEditClick = onEditClick, @@ -290,6 +266,55 @@ private fun GridView( } + +@Composable +private fun GridNotesView( + gridNotes: LazyPagingItems, + gridState: LazyStaggeredGridState, + modifier: Modifier = Modifier, + selectedNotes: List, + showFullPathOfNotes: Boolean, + onEditClick: (Note, EditType) -> Unit, + vm: GridViewModel, +) { + + + val noteMinWidth = vm.prefs.noteMinWidth.getAsState() + val showFullNoteHeight = vm.prefs.showFullNoteHeight.getAsState() + + LazyVerticalStaggeredGrid( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 3.dp), + columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), + state = gridState + ) { + item(span = StaggeredGridItemSpan.FullLine) { + Spacer(modifier = Modifier.height(topSpacerHeight)) + } + + items( + count = gridNotes.itemCount, + key = { index -> gridNotes[index]!!.note.id } + ) { index -> + val gridNote = gridNotes[index]!! + + NoteCard( + gridNote = gridNote, + vm = vm, + onEditClick = onEditClick, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes, + showFullNoteHeight = showFullNoteHeight.value, + modifier = Modifier.padding(3.dp) + ) + } + + item(span = StaggeredGridItemSpan.FullLine) { + Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) + } + } +} + @Composable private fun NoteCard( gridNote: GridNote, @@ -348,30 +373,8 @@ private fun NoteCard( false }, ) { - NoteCardContent( - gridNote = gridNote, - vm = vm, - selectedNotes = selectedNotes, - showFullPathOfNotes = showFullPathOfNotes, - clickPosition = clickPosition, - dropDownExpanded = dropDownExpanded - ) - } -} - -@Composable -private fun NoteCardContent( - gridNote: GridNote, - vm: GridViewModel, - selectedNotes: List, - showFullPathOfNotes: Boolean, - clickPosition: MutableState, - dropDownExpanded: MutableState, -) { - Box { - - // need this box for clickPosition Box { + NoteActionsDropdown( vm = vm, gridNote = gridNote, @@ -379,52 +382,80 @@ private fun NoteCardContent( dropDownExpanded = dropDownExpanded, clickPosition = clickPosition ) - } - Column( - modifier = Modifier.padding(10.dp), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.Start, - ) { - val title = if (showFullPathOfNotes || !gridNote.isUnique) { - gridNote.note.relativePath - } else { - gridNote.note.nameWithoutExtension() - } - Text( - text = title, - modifier = Modifier.padding(bottom = 6.dp), - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - ), - color = MaterialTheme.colorScheme.tertiary - ) - if (gridNote.note.fileExtension() is FileExtension.Md) { + Column( + modifier = Modifier.padding(10.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + val title = if (showFullPathOfNotes || !gridNote.isUnique) { + gridNote.note.relativePath + } else { + gridNote.note.nameWithoutExtension() + } + Text( + text = title, + modifier = Modifier.padding(bottom = 6.dp), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold, + ), + color = MaterialTheme.colorScheme.tertiary + ) + + if (gridNote.note.fileExtension() is FileExtension.Md) { // MarkdownText( // markdown = gridNote.note.content, // disableLinkMovementMethod = true, // isTextSelectable = false, // onLinkClicked = { } // ) - Text( - text = gridNote.note.content, - modifier = Modifier, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) - } else { - Text( - text = gridNote.note.content, - modifier = Modifier, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) + Text( + text = gridNote.note.content, + modifier = Modifier, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + } else { + Text( + text = gridNote.note.content, + modifier = Modifier, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + } } } } } +@Composable +internal fun NoteActionsDropdown( + vm: GridViewModel, + gridNote: GridNote, + selectedNotes: List, + dropDownExpanded: MutableState, + clickPosition: MutableState, +) { + + // need this box for clickPosition + Box { + CustomDropDown( + expanded = dropDownExpanded, + shape = MaterialTheme.shapes.medium, + options = listOf( + CustomDropDownModel( + text = stringResource(R.string.delete_this_note), + onClick = { vm.deleteNote(gridNote.note) }), + if (selectedNotes.isEmpty()) CustomDropDownModel( + text = stringResource(R.string.select_multiple_notes), + onClick = { vm.selectNote(gridNote.note, true) }) else null, + ), + clickPosition = clickPosition + ) + } +} + // https://stackoverflow.com/questions/73079388/android-jetpack-compose-keyboard-not-close // https://medium.com/@debdut.saha.1/top-app-bar-animation-using-nestedscrollconnection-like-facebook-jetpack-compose-b446c109ee52 // todo: fix scroll is blocked when the full size of the grid is the screen, diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt index 30e0d61c..442c33ec 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt @@ -17,45 +17,39 @@ import androidx.compose.material.icons.rounded.Description import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.pointerInteropFilter -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.material3.Text -import io.github.wiiznokes.gitnote.R +import androidx.paging.compose.LazyPagingItems import io.github.wiiznokes.gitnote.data.room.Note -import io.github.wiiznokes.gitnote.ui.component.CustomDropDown -import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.model.EditType import io.github.wiiznokes.gitnote.ui.model.GridNote import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel import java.text.DateFormat import java.util.Date -import androidx.paging.compose.LazyPagingItems -import androidx.compose.runtime.MutableState -import androidx.compose.ui.geometry.Offset @Composable internal fun NoteListView( gridNotes: LazyPagingItems, listState: LazyListState, modifier: Modifier = Modifier, - topSpacerHeight: Dp, selectedNotes: List, showFullPathOfNotes: Boolean, onEditClick: (Note, EditType) -> Unit, vm: GridViewModel, ) { + LazyColumn( - modifier = modifier.fillMaxWidth(), + modifier = modifier, state = listState ) { item { @@ -94,7 +88,7 @@ private fun NoteListRow( showFullPathOfNotes: Boolean, ) { val dropDownExpanded = remember { mutableStateOf(false) } - val clickPosition = remember { mutableStateOf(androidx.compose.ui.geometry.Offset.Zero) } + val clickPosition = remember { mutableStateOf(Offset.Zero) } val formattedDate = remember(gridNote.note.lastModifiedTimeMillis) { DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT) @@ -176,27 +170,4 @@ private fun NoteListRow( color = MaterialTheme.colorScheme.surfaceColorAtElevation(80.dp) ) } -} - -@Composable -internal fun NoteActionsDropdown( - vm: GridViewModel, - gridNote: GridNote, - selectedNotes: List, - dropDownExpanded: MutableState, - clickPosition: MutableState, -) { - CustomDropDown( - expanded = dropDownExpanded, - shape = MaterialTheme.shapes.medium, - options = listOf( - CustomDropDownModel( - text = stringResource(R.string.delete_this_note), - onClick = { vm.deleteNote(gridNote.note) }), - if (selectedNotes.isEmpty()) CustomDropDownModel( - text = stringResource(R.string.select_multiple_notes), - onClick = { vm.selectNote(gridNote.note, true) }) else null, - ), - clickPosition = clickPosition - ) -} +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt index dd16187b..06995679 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt @@ -1,6 +1,5 @@ package io.github.wiiznokes.gitnote.ui.screen.app.grid -import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility @@ -23,13 +22,13 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ViewList import androidx.compose.material.icons.filled.CloudDone import androidx.compose.material.icons.filled.CloudDownload import androidx.compose.material.icons.filled.CloudUpload import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Menu import androidx.compose.material.icons.rounded.MoreVert -import androidx.compose.material.icons.rounded.ViewList import androidx.compose.material.icons.rounded.ViewModule import androidx.compose.material3.DrawerState import androidx.compose.material3.Icon @@ -126,8 +125,7 @@ private fun SearchBar( ) { - var queryTextField by remember { - Log.d(TAG, "") + val queryTextField = remember { mutableStateOf( TextFieldValue( text = vm.query.value, @@ -138,7 +136,7 @@ private fun SearchBar( val focusManager = LocalFocusManager.current fun clearQuery() { - queryTextField = TextFieldValue("") + queryTextField.value = TextFieldValue("") vm.clearQuery() focusManager.clearFocus() } @@ -160,9 +158,9 @@ private fun SearchBar( .padding(top = 15.dp) .offset { IntOffset(x = 0, y = offset.roundToInt()) } .focusRequester(searchFocusRequester), - value = queryTextField, + value = queryTextField.value, onValueChange = { - queryTextField = it + queryTextField.value = it vm.search(it.text) }, colors = TextFieldDefaults.colors( @@ -199,7 +197,9 @@ private fun SearchBar( verticalAlignment = Alignment.CenterVertically ) { - if (queryTextField.text.isEmpty()) { + val isEmpty = queryTextField.value.text.isEmpty() + + if (isEmpty) { val syncState = vm.syncState.collectAsState() SyncStateIcon(syncState.value) { vm.consumeOkSyncState() @@ -211,7 +211,7 @@ private fun SearchBar( ) { SimpleIcon( imageVector = if (noteViewType.value == NoteViewType.Grid) { - Icons.Rounded.ViewList + Icons.AutoMirrored.Rounded.ViewList } else { Icons.Rounded.ViewModule }, @@ -226,7 +226,7 @@ private fun SearchBar( ) } - if (queryTextField.text.isEmpty()) { + if (isEmpty) { Box { val expanded = remember { mutableStateOf(false) } IconButton( @@ -240,7 +240,7 @@ private fun SearchBar( val readOnlyMode = vm.prefs.isReadOnlyModeActive.getAsState().value - @Suppress("KotlinConstantConditions") + @Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants") CustomDropDown( expanded = expanded, options = listOf( @@ -267,7 +267,9 @@ private fun SearchBar( ) ) } - } else { + } + + if (!isEmpty) { IconButton( onClick = { clearQuery() } ) { diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt index 0bd5e954..96c6b21a 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt @@ -96,7 +96,10 @@ fun SetupNav( ) }, onFinish = { path, useUrlForRootFolder -> - val storageConfig = StorageConfiguration.Device(path, useUrlForRootFolder = useUrlForRootFolder) + val storageConfig = StorageConfiguration.Device( + path, + useUrlForRootFolder = useUrlForRootFolder + ) when (setupDestination.newRepoMethod) { NewRepoMethod.Create -> vm.createLocalRepo( diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt index 6066d606..4a9ee095 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt @@ -3,8 +3,6 @@ package io.github.wiiznokes.gitnote.ui.screen.setup.remote import android.content.Intent import android.util.Log import androidx.compose.foundation.layout.Arrangement -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt index 440473f5..5c75105a 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt @@ -1,8 +1,6 @@ package io.github.wiiznokes.gitnote.ui.screen.setup.remote import androidx.compose.foundation.layout.Arrangement -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.res.stringResource @@ -12,7 +10,6 @@ import io.github.wiiznokes.gitnote.ui.component.AppPage import io.github.wiiznokes.gitnote.ui.component.SetupButton import io.github.wiiznokes.gitnote.ui.component.SetupLine import io.github.wiiznokes.gitnote.ui.component.SetupPage -import io.github.wiiznokes.gitnote.ui.component.SimpleButton @Composable