Skip to content

Commit ec66367

Browse files
Add favourites feature with filtering and toggle functionality (#171)
* Initial plan * Add favourites feature: database, UI, and filtering Co-authored-by: yogeshpaliyal <9381846+yogeshpaliyal@users.noreply.github.com> * Add database migration for isFavourite column Co-authored-by: yogeshpaliyal <9381846+yogeshpaliyal@users.noreply.github.com> * feat: add favourites functionality with UI updates and menu item handling * feat: add favourites tab and update UI for favourites functionality * feat: update dropdown menu item colors for improved error visibility --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yogeshpaliyal <9381846+yogeshpaliyal@users.noreply.github.com> Co-authored-by: Yogesh Choudhary Paliyal <yogeshpaliyal.foss@gmail.com>
1 parent fec0419 commit ec66367

File tree

8 files changed

+292
-152
lines changed

8 files changed

+292
-152
lines changed

app/src/main/java/com/yogeshpaliyal/deepr/server/LocalServerRepositoryImpl.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ class LocalServerRepositoryImpl(
104104
"",
105105
"",
106106
null,
107+
-1L,
108+
-1L,
107109
"DESC",
108110
"createdAt",
109111
"DESC",
@@ -148,7 +150,7 @@ class LocalServerRepositoryImpl(
148150
// Count how many links use this tag
149151
val linkCount =
150152
deeprQueries
151-
.getLinksAndTags("", "", "", tag.id, "DESC", "createdAt", "DESC", "createdAt")
153+
.getLinksAndTags("", "", "", tag.id, -1L, -1L, "DESC", "createdAt", "DESC", "createdAt")
152154
.executeAsList()
153155
.size
154156
TagResponse(

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/DeeprItem.kt

Lines changed: 196 additions & 97 deletions
Large diffs are not rendered by default.

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/Home.kt

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.yogeshpaliyal.deepr.ui.screens.home
22

3-
import android.content.ClipData
4-
import android.content.ClipboardManager
5-
import android.content.Context
3+
import android.R.attr.label
64
import android.widget.Toast
75
import androidx.activity.compose.rememberLauncherForActivityResult
86
import androidx.compose.animation.AnimatedVisibility
@@ -38,6 +36,9 @@ import androidx.compose.material3.PlainTooltip
3836
import androidx.compose.material3.Scaffold
3937
import androidx.compose.material3.SearchBarDefaults
4038
import androidx.compose.material3.SearchBarValue
39+
import androidx.compose.material3.SegmentedButton
40+
import androidx.compose.material3.SegmentedButtonDefaults
41+
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
4142
import androidx.compose.material3.Text
4243
import androidx.compose.material3.TooltipAnchorPosition
4344
import androidx.compose.material3.TooltipBox
@@ -148,7 +149,8 @@ fun HomeScreen(
148149
if (!sharedText?.url.isNullOrBlank() && selectedLink == null) {
149150
val normalizedLink = normalizeLink(sharedText.url)
150151
if (isValidDeeplink(normalizedLink)) {
151-
selectedLink = createDeeprObject(link = normalizedLink, name = sharedText.title ?: "")
152+
selectedLink =
153+
createDeeprObject(link = normalizedLink, name = sharedText.title ?: "")
152154
} else {
153155
Toast
154156
.makeText(context, "Invalid deeplink from shared content", Toast.LENGTH_SHORT)
@@ -255,6 +257,31 @@ fun HomeScreen(
255257
}
256258
},
257259
)
260+
261+
val favouriteFilter by viewModel.favouriteFilter.collectAsStateWithLifecycle()
262+
// Favourite filter tabs
263+
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
264+
SegmentedButton(
265+
shape =
266+
SegmentedButtonDefaults.itemShape(
267+
index = 0,
268+
count = 2,
269+
),
270+
onClick = { viewModel.setFavouriteFilter(-1) },
271+
selected = favouriteFilter == -1,
272+
label = { Text(stringResource(R.string.all)) },
273+
)
274+
SegmentedButton(
275+
shape =
276+
SegmentedButtonDefaults.itemShape(
277+
index = 1,
278+
count = 2,
279+
),
280+
onClick = { viewModel.setFavouriteFilter(1) },
281+
selected = favouriteFilter == 1,
282+
label = { Text(stringResource(R.string.favourites)) },
283+
)
284+
}
258285
}
259286
},
260287
bottomBar = {
@@ -429,26 +456,23 @@ fun Content(
429456
accounts = accounts!!,
430457
selectedTag = selectedTag,
431458
onItemClick = {
432-
viewModel.incrementOpenedCount(it.id)
433-
openDeeplink(context, it.link)
434-
},
435-
onRemoveClick = {
436-
viewModel.deleteAccount(it.id)
437-
Toast.makeText(context, "Deleted", Toast.LENGTH_SHORT).show()
438-
},
439-
onShortcutClick = {
440-
showShortcutDialog = it
441-
},
442-
onEditClick = editDeepr,
443-
onItemLongClick = {
444-
val clipboard =
445-
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
446-
val clip = ClipData.newPlainText("Link copied", it.link)
447-
clipboard.setPrimaryClip(clip)
448-
Toast.makeText(context, "Link copied", Toast.LENGTH_SHORT).show()
449-
},
450-
onQrCodeCLick = {
451-
showQrCodeDialog = it
459+
when (it) {
460+
is MenuItem.Click -> {
461+
viewModel.incrementOpenedCount(it.item.id)
462+
openDeeplink(context, it.item.link)
463+
}
464+
is MenuItem.Delete -> showDeleteConfirmDialog = it.item
465+
is MenuItem.Edit -> editDeepr(it.item)
466+
is MenuItem.FavouriteClick -> viewModel.toggleFavourite(it.item.id)
467+
is MenuItem.ResetCounter -> {
468+
viewModel.resetOpenedCount(it.item.id)
469+
Toast.makeText(context, "Opened count reset", Toast.LENGTH_SHORT).show()
470+
}
471+
is MenuItem.Shortcut -> {
472+
showShortcutDialog = it.item
473+
}
474+
is MenuItem.ShowQrCode -> showQrCodeDialog = it.item
475+
}
452476
},
453477
onTagClick = {
454478
if (viewModel.selectedTagFilter.value?.name == it) {
@@ -457,13 +481,6 @@ fun Content(
457481
viewModel.setSelectedTagByName(it)
458482
}
459483
},
460-
onResetOpenedCountClick = {
461-
viewModel.resetOpenedCount(it.id)
462-
Toast.makeText(context, "Opened count reset", Toast.LENGTH_SHORT).show()
463-
},
464-
onDeleteClick = {
465-
showDeleteConfirmDialog = it
466-
},
467484
)
468485
}
469486
}
@@ -473,15 +490,8 @@ fun DeeprList(
473490
accounts: List<GetLinksAndTags>,
474491
selectedTag: Tags?,
475492
contentPaddingValues: PaddingValues,
476-
onItemClick: (GetLinksAndTags) -> Unit,
477-
onRemoveClick: (GetLinksAndTags) -> Unit,
478-
onShortcutClick: (GetLinksAndTags) -> Unit,
479-
onEditClick: (GetLinksAndTags) -> Unit,
480-
onItemLongClick: (GetLinksAndTags) -> Unit,
481-
onQrCodeCLick: (GetLinksAndTags) -> Unit,
493+
onItemClick: (MenuItem) -> Unit,
482494
onTagClick: (String) -> Unit,
483-
onResetOpenedCountClick: (GetLinksAndTags) -> Unit,
484-
onDeleteClick: (GetLinksAndTags) -> Unit,
485495
modifier: Modifier = Modifier,
486496
) {
487497
AnimatedVisibility(
@@ -544,14 +554,7 @@ fun DeeprList(
544554
account = account,
545555
selectedTag = selectedTag,
546556
onItemClick = onItemClick,
547-
onRemoveClick = onRemoveClick,
548-
onShortcutClick = onShortcutClick,
549-
onEditClick = onEditClick,
550-
onItemLongClick = onItemLongClick,
551-
onQrCodeClick = onQrCodeCLick,
552557
onTagClick = onTagClick,
553-
onResetOpenedCountClick = onResetOpenedCountClick,
554-
onDeleteClick = onDeleteClick,
555558
)
556559
}
557560
}

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/home/SaveCompleteDialog.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ fun createDeeprObject(
2121
tagsNames = "",
2222
tagsIds = "",
2323
lastOpenedAt = "",
24+
isFavourite = 0,
2425
)

app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,20 @@ class AccountViewModel(
119119
private val _selectedTagFilter = MutableStateFlow<Tags?>(null)
120120
val selectedTagFilter: StateFlow<Tags?> = _selectedTagFilter
121121

122+
// State for favourite filter (-1 = All, 0 = Not Favourite, 1 = Favourite)
123+
private val _favouriteFilter = MutableStateFlow<Int>(-1)
124+
val favouriteFilter: StateFlow<Int> = _favouriteFilter
125+
122126
// Set tag filter
123127
fun setTagFilter(tag: Tags?) {
124128
_selectedTagFilter.value = tag
125129
}
126130

131+
// Set favourite filter
132+
fun setFavouriteFilter(filter: Int) {
133+
_favouriteFilter.value = filter
134+
}
135+
127136
// Remove tag from link
128137
fun removeTagFromLink(
129138
linkId: Long,
@@ -185,18 +194,23 @@ class AccountViewModel(
185194

186195
@OptIn(ExperimentalCoroutinesApi::class)
187196
val accounts: StateFlow<List<GetLinksAndTags>?> =
188-
combine(searchQuery, sortOrder, selectedTagFilter) { query, sorting, tag ->
189-
Triple(query, sorting, tag)
197+
combine(searchQuery, sortOrder, selectedTagFilter, favouriteFilter) { query, sorting, tag, favourite ->
198+
listOf(query, sorting, tag, favourite)
190199
}.flatMapLatest { combined ->
191-
val sorting = combined.second.split("_")
200+
val query = combined[0] as String
201+
val sorting = (combined[1] as String).split("_")
202+
val tag = combined[2] as Tags?
203+
val favourite = combined[3] as Int
192204
val sortField = sorting.getOrNull(0) ?: "createdAt"
193205
val sortType = sorting.getOrNull(1) ?: "DESC"
194206
deeprQueries
195207
.getLinksAndTags(
196-
combined.first,
197-
combined.first,
198-
combined.third?.id?.toString() ?: "",
199-
combined.third?.id,
208+
query,
209+
query,
210+
tag?.id?.toString() ?: "",
211+
tag?.id,
212+
favourite.toLong(),
213+
favourite.toLong(),
200214
sortType,
201215
sortField,
202216
sortType,
@@ -293,6 +307,12 @@ class AccountViewModel(
293307
}
294308
}
295309

310+
fun toggleFavourite(id: Long) {
311+
viewModelScope.launch(Dispatchers.IO) {
312+
deeprQueries.toggleFavourite(id)
313+
}
314+
}
315+
296316
fun updateDeeplink(
297317
id: Long,
298318
newLink: String,

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
<string name="qr_scanner">QR Scanner</string>
1717
<string name="tags">Tags</string>
1818
<string name="all">All</string>
19+
<string name="favourites">Favourites</string>
20+
<string name="add_to_favourites">Add to favourites</string>
21+
<string name="remove_from_favourites">Remove from favourites</string>
1922
<string name="add_link">Add Link</string>
2023
<string name="fetch_name_from_link">Fetch name from link</string>
2124
<string name="add_tag">Add Tag</string>

app/src/main/sqldelight/com/yogeshpaliyal/deepr/Deepr.sq

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ id INTEGER PRIMARY KEY NOT NULL,
33
link TEXT NOT NULL,
44
name TEXT NOT NULL DEFAULT '',
55
createdAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
6-
openedCount INTEGER NOT NULL DEFAULT 0
6+
openedCount INTEGER NOT NULL DEFAULT 0,
7+
isFavourite INTEGER NOT NULL DEFAULT 0
78
);
89

910
CREATE TABLE Tags (
@@ -39,6 +40,7 @@ SELECT
3940
Deepr.name,
4041
Deepr.createdAt,
4142
Deepr.openedCount,
43+
Deepr.isFavourite,
4244
DOL_Max.lastOpenedAt,
4345
GROUP_CONCAT(Tags.name, ', ') AS tagsNames,
4446
GROUP_CONCAT(Tags.id, ', ') AS tagsIds
@@ -61,6 +63,9 @@ WHERE
6163
AND (
6264
? = '' OR Tags.id = ?
6365
)
66+
AND (
67+
? = -1 OR Deepr.isFavourite = ?
68+
)
6469
GROUP BY
6570
Deepr.id
6671
ORDER BY
@@ -191,3 +196,9 @@ INSERT INTO DeeprOpenLog (deeplinkId) VALUES (?);
191196
getLastOpenedTime:
192197
SELECT openedAt FROM DeeprOpenLog WHERE deeplinkId = ? ORDER BY openedAt DESC LIMIT 1;
193198

199+
toggleFavourite:
200+
UPDATE Deepr SET isFavourite = CASE WHEN isFavourite = 0 THEN 1 ELSE 0 END WHERE id = ?;
201+
202+
setFavourite:
203+
UPDATE Deepr SET isFavourite = ? WHERE id = ?;
204+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE Deepr ADD COLUMN isFavourite INTEGER NOT NULL DEFAULT 0;

0 commit comments

Comments
 (0)