diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt b/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt index 66ee7ab8..2424c19e 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt @@ -5,9 +5,11 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import com.yogeshpaliyal.deepr.ui.screens.home.ViewType import com.yogeshpaliyal.deepr.viewmodel.SortType import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -31,6 +33,7 @@ class AppPreferenceDataStore( private val DEFAULT_PAGE_FAVOURITES = booleanPreferencesKey("default_page_favourites") private val IS_THUMBNAIL_ENABLE = booleanPreferencesKey("is_thumbnail_enable") private val SERVER_PORT = stringPreferencesKey("server_port") + private val VIEW_TYPE = intPreferencesKey("view_type") } val getSortingOrder: Flow<@SortType String> = @@ -38,6 +41,11 @@ class AppPreferenceDataStore( preferences[SORTING_ORDER] ?: SortType.SORT_CREATED_BY_DESC } + val viewType: Flow<@ViewType Int> = + context.appDataStore.data.map { preferences -> + preferences[VIEW_TYPE] ?: ViewType.LIST + } + val getUseLinkBasedIcons: Flow = context.appDataStore.data.map { preferences -> preferences[USE_LINK_BASED_ICONS] ?: true // Default to link-based icons @@ -111,6 +119,12 @@ class AppPreferenceDataStore( } } + suspend fun setViewType(viewType: @ViewType Int) { + context.appDataStore.edit { prefs -> + prefs[VIEW_TYPE] = viewType + } + } + suspend fun setSyncFilePath(path: String) { context.appDataStore.edit { prefs -> prefs[SYNC_FILE_PATH] = path diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/ColorsUtils.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/ColorsUtils.kt new file mode 100644 index 00000000..23e71fef --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/ColorsUtils.kt @@ -0,0 +1,13 @@ +package com.yogeshpaliyal.deepr.ui + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +@Composable +fun getDeeprItemBackgroundColor(isFavourite: Long): Color = + if (isFavourite == 1L) MaterialTheme.colorScheme.surfaceContainerHighest else MaterialTheme.colorScheme.surfaceContainer + +@Composable +fun getDeeprItemTextColor(isFavourite: Long): Color = + if (isFavourite == 1L) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItem.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItem.kt index 20a14327..9cee74dc 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItem.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItem.kt @@ -8,19 +8,16 @@ import androidx.compose.foundation.ExperimentalFoundationApi 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.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio -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.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarBorder @@ -30,38 +27,30 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SwipeToDismissBox -import androidx.compose.material3.SwipeToDismissBoxDefaults -import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.Text -import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.yogeshpaliyal.deepr.GetLinksAndTags import com.yogeshpaliyal.deepr.R import com.yogeshpaliyal.deepr.Tags +import com.yogeshpaliyal.deepr.ui.getDeeprItemBackgroundColor +import com.yogeshpaliyal.deepr.ui.getDeeprItemTextColor import compose.icons.TablerIcons import compose.icons.tablericons.DotsVertical -import compose.icons.tablericons.Edit -import compose.icons.tablericons.Trash -import kotlinx.coroutines.launch import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Locale @@ -74,6 +63,10 @@ sealed class MenuItem( item: GetLinksAndTags, ) : MenuItem(item) + class Copy( + item: GetLinksAndTags, + ) : MenuItem(item) + class Shortcut( item: GetLinksAndTags, ) : MenuItem(item) @@ -103,23 +96,6 @@ sealed class MenuItem( ) : MenuItem(item) } -@Composable -@Preview -private fun DeeprItemPreview() { - DeeprItem( - account = - createDeeprObject( - name = "Yogesh Paliyal", - link = "https://yogeshpaliyal.com", - thumbnail = "https://yogeshpaliyal.com/og.png", - ), - {}, - {}, - listOf(), - isThumbnailEnable = true, - ) -} - @OptIn(ExperimentalFoundationApi::class) @Composable fun DeeprItem( @@ -138,94 +114,11 @@ fun DeeprItem( val linkCopied = stringResource(R.string.link_copied) - val dismissState = - rememberSwipeToDismissBoxState( - initialValue = SwipeToDismissBoxValue.Settled, - positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold, - ) - - val scope = rememberCoroutineScope() - - SwipeToDismissBox( - modifier = - modifier - .fillMaxSize() - .clip(RoundedCornerShape(8.dp)), - state = dismissState, - onDismiss = { - scope.launch { - dismissState.reset() - } - when (it) { - SwipeToDismissBoxValue.EndToStart -> { - onItemClick(MenuItem.Delete(account)) - false - } - - SwipeToDismissBoxValue.StartToEnd -> { - onItemClick(MenuItem.Edit(account)) - false - } - - else -> { - false - } - } - }, - backgroundContent = { - when (dismissState.dismissDirection) { - SwipeToDismissBoxValue.StartToEnd -> { - Box( - modifier = - Modifier - .background( - Color.Gray.copy(alpha = 0.5f), - ).fillMaxSize() - .clip( - RoundedCornerShape(8.dp), - ), - contentAlignment = Alignment.CenterStart, - ) { - Icon( - imageVector = TablerIcons.Edit, - contentDescription = stringResource(R.string.edit), - tint = Color.White, - modifier = Modifier.padding(16.dp), - ) - } - } - - SwipeToDismissBoxValue.EndToStart -> { - Box( - modifier = - Modifier - .background( - Color.Red.copy(alpha = 0.5f), - ).fillMaxSize() - .clip( - RoundedCornerShape(8.dp), - ), - contentAlignment = Alignment.CenterEnd, - ) { - Icon( - imageVector = TablerIcons.Trash, - contentDescription = stringResource(R.string.delete), - tint = Color.White, - modifier = Modifier.padding(16.dp), - ) - } - } - - else -> { - Color.White - } - } - }, - ) { + DeeprItemSwipable(account, onItemClick, modifier) { Card( colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + containerColor = getDeeprItemBackgroundColor(account.isFavourite), ), modifier = Modifier @@ -278,6 +171,7 @@ fun DeeprItem( maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.labelLarge, + color = getDeeprItemTextColor(account.isFavourite), ) } Text( @@ -285,13 +179,14 @@ fun DeeprItem( maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodySmall, + color = getDeeprItemTextColor(account.isFavourite), ) Spacer(modifier = Modifier.height(4.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Text( text = formatDateTime(account.createdAt), style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = getDeeprItemTextColor(account.isFavourite), ) } } @@ -313,12 +208,7 @@ fun DeeprItem( } else { stringResource(R.string.add_to_favourites) }, - tint = - if (account.isFavourite == 1L) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurface - }, + tint = getDeeprItemTextColor(account.isFavourite), modifier = Modifier.size(28.dp), ) } @@ -340,7 +230,7 @@ fun DeeprItem( Text( text = stringResource(R.string.opened_count, account.openedCount), style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = getDeeprItemTextColor(account.isFavourite), ) } } diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemCompact.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemCompact.kt new file mode 100644 index 00000000..5d8daa5d --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemCompact.kt @@ -0,0 +1,142 @@ +package com.yogeshpaliyal.deepr.ui.screens.home + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.yogeshpaliyal.deepr.GetLinksAndTags +import com.yogeshpaliyal.deepr.R +import com.yogeshpaliyal.deepr.ui.getDeeprItemBackgroundColor +import com.yogeshpaliyal.deepr.ui.getDeeprItemTextColor +import compose.icons.TablerIcons +import compose.icons.tablericons.DotsVertical + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun DeeprItemCompact( + account: GetLinksAndTags, + onItemClick: (MenuItem) -> Unit, + isThumbnailEnable: Boolean, + modifier: Modifier = Modifier, +) { + DeeprItemSwipable(account, onItemClick, modifier) { + Card( + colors = + CardDefaults.cardColors( + containerColor = getDeeprItemBackgroundColor(account.isFavourite), + ), + modifier = + Modifier + .fillMaxWidth() + .combinedClickable( + onClick = { onItemClick(MenuItem.Click(account)) }, + onLongClick = { + onItemClick(MenuItem.Copy(account)) + }, + ), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row( + modifier = Modifier.weight(1f), + verticalAlignment = Alignment.CenterVertically, + ) { + if (account.thumbnail.isNotEmpty() && isThumbnailEnable) { + AsyncImage( + model = account.thumbnail, + contentDescription = account.name, + modifier = + Modifier + .size(48.dp) + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant), + placeholder = null, + error = null, + contentScale = ContentScale.Crop, + ) + Spacer(modifier = Modifier.width(12.dp)) + } + + Column(modifier = Modifier.weight(1f)) { + if (account.name.isNotEmpty()) { + Text( + text = account.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleSmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + } + Text( + text = account.link, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + Row(modifier = Modifier.fillMaxWidth()) { + Text( + text = stringResource(R.string.opened_count, account.openedCount), + style = MaterialTheme.typography.labelSmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + Spacer(modifier = Modifier.weight(1f)) + account.tagsIds?.split(",")?.size?.let { tagsCount -> + if (tagsCount > 0) { + Text( + text = stringResource(R.string.number_tags, tagsCount), + style = MaterialTheme.typography.labelSmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + } + } + } + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton(onClick = { + onItemClick(MenuItem.MoreOptionsBottomSheet(account)) + }) { + Icon( + imageVector = TablerIcons.DotsVertical, + contentDescription = stringResource(R.string.more_options), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemGrid.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemGrid.kt new file mode 100644 index 00000000..e9bbc91f --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemGrid.kt @@ -0,0 +1,142 @@ +package com.yogeshpaliyal.deepr.ui.screens.home + +import androidx.compose.foundation.ExperimentalFoundationApi +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.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.yogeshpaliyal.deepr.GetLinksAndTags +import com.yogeshpaliyal.deepr.R +import com.yogeshpaliyal.deepr.ui.getDeeprItemBackgroundColor +import com.yogeshpaliyal.deepr.ui.getDeeprItemTextColor +import compose.icons.TablerIcons +import compose.icons.tablericons.DotsVertical + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun DeeprItemGrid( + account: GetLinksAndTags, + onItemClick: (MenuItem) -> Unit, + modifier: Modifier = Modifier, + isThumbnailEnable: Boolean = true, +) { + DeeprItemSwipable(account, onItemClick, modifier) { + Card( + colors = + CardDefaults.cardColors( + containerColor = getDeeprItemBackgroundColor(account.isFavourite), + ), + modifier = + Modifier + .fillMaxWidth() + .combinedClickable( + onClick = { onItemClick(MenuItem.Click(account)) }, + onLongClick = { + onItemClick(MenuItem.Copy(account)) + }, + ), + ) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + Box { + if (account.thumbnail.isNotEmpty() && isThumbnailEnable) { + AsyncImage( + model = account.thumbnail, + contentDescription = account.name, + modifier = + Modifier + .fillMaxWidth() + .aspectRatio(1f) + .background(MaterialTheme.colorScheme.surfaceVariant), + placeholder = null, + error = null, + contentScale = ContentScale.Crop, + ) + } + } + + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(12.dp), + ) { + if (account.name.isNotEmpty()) { + Text( + text = account.name, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium, + color = getDeeprItemTextColor(account.isFavourite), + ) + Spacer(modifier = Modifier.height(4.dp)) + } + + Text( + text = account.link, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.opened_count, account.openedCount), + style = MaterialTheme.typography.bodySmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + account.tagsIds?.split(",")?.size?.let { tagsCount -> + if (tagsCount > 0) { + Text( + text = stringResource(R.string.number_tags, tagsCount), + style = MaterialTheme.typography.labelSmall, + color = getDeeprItemTextColor(account.isFavourite), + ) + } + } + } + IconButton(onClick = { + onItemClick(MenuItem.MoreOptionsBottomSheet(account)) + }) { + Icon( + imageVector = TablerIcons.DotsVertical, + contentDescription = stringResource(R.string.more_options), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemSwipable.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemSwipable.kt new file mode 100644 index 00000000..f70be059 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItemSwipable.kt @@ -0,0 +1,121 @@ +package com.yogeshpaliyal.deepr.ui.screens.home + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxDefaults +import androidx.compose.material3.SwipeToDismissBoxValue +import androidx.compose.material3.rememberSwipeToDismissBoxState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.yogeshpaliyal.deepr.GetLinksAndTags +import com.yogeshpaliyal.deepr.R +import compose.icons.TablerIcons +import compose.icons.tablericons.Edit +import compose.icons.tablericons.Trash +import kotlinx.coroutines.launch + +@Composable +fun DeeprItemSwipable( + account: GetLinksAndTags, + onItemClick: (MenuItem) -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + val dismissState = + rememberSwipeToDismissBoxState( + initialValue = SwipeToDismissBoxValue.Settled, + positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold, + ) + + val scope = rememberCoroutineScope() + + SwipeToDismissBox( + modifier = + modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)), + state = dismissState, + onDismiss = { + scope.launch { + dismissState.reset() + } + when (it) { + SwipeToDismissBoxValue.EndToStart -> { + onItemClick(MenuItem.Delete(account)) + false + } + + SwipeToDismissBoxValue.StartToEnd -> { + onItemClick(MenuItem.Edit(account)) + false + } + + else -> { + false + } + } + }, + backgroundContent = { + when (dismissState.dismissDirection) { + SwipeToDismissBoxValue.StartToEnd -> { + Box( + modifier = + Modifier + .background( + Color.Gray.copy(alpha = 0.5f), + ).fillMaxSize() + .clip( + RoundedCornerShape(8.dp), + ), + contentAlignment = Alignment.CenterStart, + ) { + Icon( + imageVector = TablerIcons.Edit, + contentDescription = stringResource(R.string.edit), + tint = Color.White, + modifier = Modifier.padding(16.dp), + ) + } + } + + SwipeToDismissBoxValue.EndToStart -> { + Box( + modifier = + Modifier + .background( + Color.Red.copy(alpha = 0.5f), + ).fillMaxSize() + .clip( + RoundedCornerShape(8.dp), + ), + contentAlignment = Alignment.CenterEnd, + ) { + Icon( + imageVector = TablerIcons.Trash, + contentDescription = stringResource(R.string.delete), + tint = Color.White, + modifier = Modifier.padding(16.dp), + ) + } + } + + else -> { + Color.White + } + } + }, + ) { + content() + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/Home.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/Home.kt index 814e8c0f..60b93c41 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/Home.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/Home.kt @@ -1,5 +1,8 @@ package com.yogeshpaliyal.deepr.ui.screens.home +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.animation.AnimatedVisibility @@ -12,7 +15,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize @@ -22,13 +27,19 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Star +import androidx.compose.material.icons.rounded.StarBorder import androidx.compose.material3.AppBarWithSearch import androidx.compose.material3.ContainedLoadingIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilterChip import androidx.compose.material3.FloatingToolbarDefaults import androidx.compose.material3.FloatingToolbarExitDirection import androidx.compose.material3.HorizontalFloatingToolbar @@ -72,6 +83,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp @@ -83,7 +95,9 @@ import com.yogeshpaliyal.deepr.GetLinksAndTags import com.yogeshpaliyal.deepr.R import com.yogeshpaliyal.deepr.SharedLink import com.yogeshpaliyal.deepr.Tags +import com.yogeshpaliyal.deepr.analytics.AnalyticsEvents import com.yogeshpaliyal.deepr.analytics.AnalyticsManager +import com.yogeshpaliyal.deepr.analytics.AnalyticsParams import com.yogeshpaliyal.deepr.ui.components.ClearInputIconButton import com.yogeshpaliyal.deepr.ui.components.CreateShortcutDialog import com.yogeshpaliyal.deepr.ui.components.DeleteConfirmationDialog @@ -91,6 +105,15 @@ import com.yogeshpaliyal.deepr.ui.components.QrCodeDialog import com.yogeshpaliyal.deepr.ui.components.ServerStatusBar import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServer import com.yogeshpaliyal.deepr.ui.screens.Settings +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.Click +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.Copy +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.Delete +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.Edit +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.FavouriteClick +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.MoreOptionsBottomSheet +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.ResetCounter +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.Shortcut +import com.yogeshpaliyal.deepr.ui.screens.home.MenuItem.ShowQrCode import com.yogeshpaliyal.deepr.util.QRScanner import com.yogeshpaliyal.deepr.util.isValidDeeplink import com.yogeshpaliyal.deepr.util.normalizeLink @@ -137,6 +160,7 @@ fun HomeScreen( resetSharedText: () -> Unit, ) { var isTagsSelectionActive by remember { mutableStateOf(false) } + val currentViewType by viewModel.viewType.collectAsStateWithLifecycle() var selectedLink by remember { mutableStateOf(null) } val selectedTag by viewModel.selectedTagFilter.collectAsStateWithLifecycle() @@ -235,17 +259,32 @@ fun HomeScreen( }, trailingIcon = { if (searchBarState.currentValue == SearchBarValue.Collapsed) { - TooltipBox( - positionProvider = - TooltipDefaults.rememberTooltipPositionProvider( - TooltipAnchorPosition.Below, - ), - tooltip = { PlainTooltip { Text(stringResource(R.string.sorting)) } }, - state = rememberTooltipState(), - ) { - FilterMenu(onSortOrderChange = { - viewModel.setSortOrder(it) - }) + Row { + TooltipBox( + positionProvider = + TooltipDefaults.rememberTooltipPositionProvider( + TooltipAnchorPosition.Below, + ), + tooltip = { PlainTooltip { Text(stringResource(R.string.sorting)) } }, + state = rememberTooltipState(), + ) { + FilterMenu(onSortOrderChange = { + viewModel.setSortOrder(it) + }) + } + + TooltipBox( + positionProvider = + TooltipDefaults.rememberTooltipPositionProvider( + TooltipAnchorPosition.Below, + ), + tooltip = { PlainTooltip { Text("View Type") } }, + state = rememberTooltipState(), + ) { + ViewTypeMenu(currentViewType, { + viewModel.setViewType(it) + }) + } } } else { if (textFieldState.text.isNotEmpty()) { @@ -283,7 +322,7 @@ fun HomeScreen( ServerStatusBar( onServerStatusClick = { // Navigate to LocalNetworkServer screen when status bar is clicked - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.NAVIGATE_LOCAL_SERVER) + analyticsManager.logEvent(AnalyticsEvents.NAVIGATE_LOCAL_SERVER) if (backStack.lastOrNull() !is LocalNetworkServer) { backStack.add(LocalNetworkServer) } @@ -334,7 +373,7 @@ fun HomeScreen( colors = FloatingToolbarDefaults.standardFloatingToolbarColors(), content = { IconButton(onClick = { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.SCAN_QR_CODE) + analyticsManager.logEvent(AnalyticsEvents.SCAN_QR_CODE) qrScanner.launch(ScanOptions()) }) { Icon( @@ -352,7 +391,7 @@ fun HomeScreen( } IconButton(onClick = { // Settings action - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.NAVIGATE_SETTINGS) + analyticsManager.logEvent(AnalyticsEvents.NAVIGATE_SETTINGS) backStack.add(Settings) }) { Icon( @@ -384,6 +423,7 @@ fun HomeScreen( hazeState = hazeState, contentPaddingValues = contentPadding, selectedTag = selectedTag, + currentViewType = currentViewType, searchQuery = textFieldState.text.toString(), favouriteFilter = favouriteFilter, editDeepr = { @@ -441,6 +481,7 @@ fun Content( hazeState: HazeState, selectedTag: List, contentPaddingValues: PaddingValues, + currentViewType: @ViewType Int, searchQuery: String, favouriteFilter: Int, modifier: Modifier = Modifier, @@ -494,49 +535,60 @@ fun Content( val onItemClick: (MenuItem) -> Unit = { showMoreSelectedItem = null when (it) { - is MenuItem.Click -> { + is Click -> { viewModel.incrementOpenedCount(it.item.id) openDeeplink(context, it.item.link) analyticsManager.logEvent( - com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.OPEN_LINK, - mapOf(com.yogeshpaliyal.deepr.analytics.AnalyticsParams.LINK_ID to it.item.id), + AnalyticsEvents.OPEN_LINK, + mapOf(AnalyticsParams.LINK_ID to it.item.id), ) } - is MenuItem.Delete -> { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.ITEM_MENU_DELETE) + is Delete -> { + analyticsManager.logEvent(AnalyticsEvents.ITEM_MENU_DELETE) showDeleteConfirmDialog = it.item } is MenuItem.Edit -> { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.ITEM_MENU_EDIT) + analyticsManager.logEvent(AnalyticsEvents.ITEM_MENU_EDIT) editDeepr(it.item) } - is MenuItem.FavouriteClick -> { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.ITEM_MENU_FAVOURITE) + is FavouriteClick -> { + analyticsManager.logEvent(AnalyticsEvents.ITEM_MENU_FAVOURITE) viewModel.toggleFavourite(it.item.id) } - is MenuItem.ResetCounter -> { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.ITEM_MENU_RESET_COUNTER) + is ResetCounter -> { + analyticsManager.logEvent(AnalyticsEvents.ITEM_MENU_RESET_COUNTER) viewModel.resetOpenedCount(it.item.id) Toast.makeText(context, "Opened count reset", Toast.LENGTH_SHORT).show() } - is MenuItem.Shortcut -> { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.ITEM_MENU_SHORTCUT) + is Shortcut -> { + analyticsManager.logEvent(AnalyticsEvents.ITEM_MENU_SHORTCUT) showShortcutDialog = it.item } - is MenuItem.ShowQrCode -> { - analyticsManager.logEvent(com.yogeshpaliyal.deepr.analytics.AnalyticsEvents.ITEM_MENU_QR_CODE) + is ShowQrCode -> { + analyticsManager.logEvent(AnalyticsEvents.ITEM_MENU_QR_CODE) showQrCodeDialog = it.item } - is MenuItem.MoreOptionsBottomSheet -> { + is MoreOptionsBottomSheet -> { showMoreSelectedItem = it.item } + + is Copy -> { + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = + ClipData.newPlainText(context.getString(R.string.link_copied), it.item.link) + clipboard.setPrimaryClip(clip) + Toast + .makeText(context, context.getString(R.string.link_copied), Toast.LENGTH_SHORT) + .show() + } } } @@ -550,22 +602,25 @@ fun Content( contentPaddingValues = contentPaddingValues, accounts = accounts!!, selectedTag = selectedTag, - onItemClick = onItemClick, onTagClick = { - // Toggle the tag in the filter by tag name viewModel.setSelectedTagByName(it) }, isThumbnailEnable = isThumbnailEnable, searchQuery = searchQuery, favouriteFilter = favouriteFilter, + viewType = currentViewType, + onItemClick = onItemClick, ) } - showMoreSelectedItem?.let { account -> ModalBottomSheet(sheetState = showMoreBottomSheet, onDismissRequest = { showMoreSelectedItem = null }) { val isThumbnailEnable by viewModel.isThumbnailEnable.collectAsStateWithLifecycle() + var tagsExpanded by remember { mutableStateOf(false) } + val selectedTags = + remember(account.tagsNames) { account.tagsNames?.split(",")?.toMutableList() } + LazyColumn { item { ListItem( @@ -585,7 +640,7 @@ fun Content( .padding(4.dp) .fillMaxWidth() .clickable { - onItemClick(MenuItem.Click(account)) + onItemClick(Click(account)) }, colors = ListItemDefaults.colors(containerColor = Color.Transparent), ) @@ -608,6 +663,24 @@ fun Content( } } + item { + MenuListItem( + text = + if (account.isFavourite == 1L) { + stringResource(R.string.remove_from_favourites) + } else { + stringResource( + R.string.add_to_favourites, + ) + }, + icon = if (account.isFavourite == 1L) Icons.Rounded.Star else Icons.Rounded.StarBorder, + selectable = true, + onClick = { + onItemClick(FavouriteClick(account)) + }, + ) + } + if (account.notes.isNotEmpty()) { item { MenuListItem( @@ -620,7 +693,7 @@ fun Content( item { ShortcutMenuItem(account, { - onItemClick(MenuItem.Shortcut(it)) + onItemClick(Shortcut(it)) }) } @@ -629,7 +702,7 @@ fun Content( text = stringResource(R.string.show_qr_code), icon = TablerIcons.Qrcode, onClick = { - onItemClick(MenuItem.ShowQrCode(account)) + onItemClick(ShowQrCode(account)) }, ) } @@ -639,7 +712,7 @@ fun Content( text = stringResource(R.string.reset_opened_count), icon = TablerIcons.Refresh, onClick = { - onItemClick(MenuItem.ResetCounter(account)) + onItemClick(ResetCounter(account)) }, ) } @@ -649,7 +722,7 @@ fun Content( text = stringResource(R.string.edit), icon = TablerIcons.Edit, onClick = { - onItemClick(MenuItem.Edit(account)) + onItemClick(Edit(account)) }, ) } @@ -658,7 +731,7 @@ fun Content( text = stringResource(R.string.delete), icon = TablerIcons.Trash, onClick = { - onItemClick(MenuItem.Delete(account)) + onItemClick(Delete(account)) }, colors = ListItemDefaults.colors( @@ -680,7 +753,7 @@ fun Content( ), textStyle = MaterialTheme.typography.bodySmall, onClick = { - onItemClick(MenuItem.Edit(account)) + onItemClick(Edit(account)) }, icon = null, colors = @@ -690,6 +763,53 @@ fun Content( ) } } + + item { + Column(modifier = Modifier.padding(horizontal = 12.dp)) { + // Determine max tags to show based on expanded state + val maxTagsToShow = if (tagsExpanded) selectedTags?.size ?: 0 else 9 + val visibleTags = selectedTags?.take(maxTagsToShow) ?: emptyList() + val hiddenTagsCount = (selectedTags?.size ?: 0) - visibleTags.size + + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + visibleTags.forEach { tag -> + val isSelected = selectedTag.any { it.name == tag.trim() } + FilterChip( + modifier = Modifier.padding(0.dp), + elevation = null, + selected = isSelected, + onClick = { + viewModel.setSelectedTagByName(tag) + showMoreSelectedItem = null + }, + label = { Text(tag.trim()) }, + ) + } + } + + // Show "Load More" or "Show Less" button if there are more than 9 tags + if ((selectedTags?.size ?: 0) > 9) { + androidx.compose.material3.TextButton( + onClick = { tagsExpanded = !tagsExpanded }, + modifier = Modifier.padding(start = 4.dp), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp), + ) { + Text( + text = + if (tagsExpanded) { + stringResource(R.string.show_less_tags) + } else { + stringResource(R.string.load_more_tags, hiddenTagsCount) + }, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold), + ) + } + } + } + } } } } @@ -749,6 +869,7 @@ fun DeeprList( searchQuery: String, favouriteFilter: Int, modifier: Modifier = Modifier, + viewType: @ViewType Int = ViewType.LIST, ) { // Determine which empty state to show val isSearchActive = searchQuery.isNotBlank() @@ -781,12 +902,14 @@ fun DeeprList( R.string.no_search_results, R.string.no_search_results_description, ) + isTagFilterActive -> Triple( TablerIcons.Tag, R.string.no_links_with_tags, R.string.no_links_with_tags_description, ) + isFavouriteFilterActive -> Triple( TablerIcons.Link, @@ -829,30 +952,80 @@ fun DeeprList( Spacer(modifier = Modifier.weight(1f)) // Push content up } } + AnimatedVisibility( visible = accounts.isNotEmpty(), enter = scaleIn() + expandVertically(expandFrom = Alignment.CenterVertically), exit = scaleOut() + shrinkVertically(shrinkTowards = Alignment.CenterVertically), ) { - LazyColumn( - modifier = modifier, - contentPadding = contentPaddingValues, - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - items( - count = accounts.size, - key = { index -> accounts[index].id }, - ) { index -> - val account = accounts[index] - - DeeprItem( - modifier = Modifier.animateItem(), - account = account, - selectedTag = selectedTag, - onItemClick = onItemClick, - onTagClick = onTagClick, - isThumbnailEnable = isThumbnailEnable, - ) + when (viewType) { + ViewType.LIST -> { + LazyColumn( + modifier = modifier, + contentPadding = contentPaddingValues, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + items( + count = accounts.size, + key = { index -> accounts[index].id }, + ) { index -> + val account = accounts[index] + + DeeprItem( + modifier = Modifier.animateItem(), + account = account, + selectedTag = selectedTag, + onItemClick = onItemClick, + onTagClick = onTagClick, + isThumbnailEnable = isThumbnailEnable, + ) + } + } + } + + ViewType.GRID -> { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(minSize = 160.dp), + modifier = modifier, + contentPadding = contentPaddingValues, + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalItemSpacing = 8.dp, + ) { + items( + count = accounts.size, + key = { index -> accounts[index].id }, + ) { index -> + val account = accounts[index] + + DeeprItemGrid( + modifier = Modifier.animateItem(), + account = account, + onItemClick = onItemClick, + ) + } + } + } + + ViewType.COMPACT -> { + LazyColumn( + modifier = modifier, + contentPadding = contentPaddingValues, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + items( + count = accounts.size, + key = { index -> accounts[index].id }, + ) { index -> + val account = accounts[index] + + DeeprItemCompact( + modifier = Modifier.animateItem(), + account = account, + onItemClick = onItemClick, + isThumbnailEnable = isThumbnailEnable, + ) + } + } } } } diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/ViewType.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/ViewType.kt new file mode 100644 index 00000000..29a309e9 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/ViewType.kt @@ -0,0 +1,16 @@ +package com.yogeshpaliyal.deepr.ui.screens.home + +import androidx.annotation.IntDef + +@Retention(AnnotationRetention.SOURCE) +@Target( + AnnotationTarget.TYPE, +) +@IntDef(value = [ViewType.LIST, ViewType.GRID, ViewType.COMPACT]) +annotation class ViewType { + companion object { + const val LIST = 0 + const val GRID = 1 + const val COMPACT = 2 + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/ViewTypeMenu.kt b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/ViewTypeMenu.kt new file mode 100644 index 00000000..f5d7dc5d --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/ViewTypeMenu.kt @@ -0,0 +1,113 @@ +package com.yogeshpaliyal.deepr.ui.screens.home + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.Text +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 compose.icons.TablerIcons +import compose.icons.tablericons.LayoutGrid +import compose.icons.tablericons.LayoutList +import compose.icons.tablericons.LayoutRows + +@Composable +fun ViewTypeMenu( + currentViewType: @ViewType Int, + setViewType: (@ViewType Int) -> Unit, + modifier: Modifier = Modifier, +) { + var expanded by remember { mutableStateOf(false) } + Box(modifier) { + IconButton(onClick = { expanded = true }) { + Icon( + when (currentViewType) { + ViewType.LIST -> TablerIcons.LayoutList + ViewType.GRID -> TablerIcons.LayoutGrid + ViewType.COMPACT -> TablerIcons.LayoutRows + else -> TablerIcons.LayoutList + }, + contentDescription = "View Type", + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + DropdownMenuItem( + text = { Text("List view") }, + onClick = { + setViewType(ViewType.LIST) + expanded = false + }, + colors = + MenuDefaults.itemColors( + textColor = + if (currentViewType == ViewType.LIST) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ), + leadingIcon = { + Icon( + TablerIcons.LayoutList, + contentDescription = null, + ) + }, + ) + DropdownMenuItem( + text = { Text("Grid view") }, + onClick = { + setViewType(ViewType.GRID) + expanded = false + }, + colors = + MenuDefaults.itemColors( + textColor = + if (currentViewType == ViewType.GRID) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ), + leadingIcon = { + Icon( + TablerIcons.LayoutGrid, + contentDescription = null, + ) + }, + ) + DropdownMenuItem( + text = { Text("Compat view") }, + onClick = { + setViewType(ViewType.COMPACT) + expanded = false + }, + colors = + MenuDefaults.itemColors( + textColor = + if (currentViewType == ViewType.COMPACT) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ), + leadingIcon = { + Icon( + TablerIcons.LayoutRows, + contentDescription = null, + ) + }, + ) + } + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt b/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt index 710a564d..2818b1c4 100644 --- a/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt @@ -19,6 +19,7 @@ import com.yogeshpaliyal.deepr.data.LinkInfo import com.yogeshpaliyal.deepr.data.NetworkRepository import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore import com.yogeshpaliyal.deepr.sync.SyncRepository +import com.yogeshpaliyal.deepr.ui.screens.home.ViewType import com.yogeshpaliyal.deepr.util.RequestResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -581,6 +582,10 @@ class AccountViewModel( preferenceDataStore.getAutoBackupLocation .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") + val viewType = + preferenceDataStore.viewType + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ViewType.LIST) + val lastBackupTime = preferenceDataStore.getLastBackupTime .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0L) @@ -591,6 +596,12 @@ class AccountViewModel( } } + fun setViewType(viewType: @ViewType Int) { + viewModelScope.launch(Dispatchers.IO) { + preferenceDataStore.setViewType(viewType) + } + } + fun setAutoBackupLocation(location: String) { viewModelScope.launch(Dispatchers.IO) { preferenceDataStore.setAutoBackupLocation(location) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8b4db7e8..1e27473c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,6 +55,7 @@ Suggestions: Filter +%d more + Tags: %d Show less Add shortcut Edit shortcut