Skip to content

Commit 8e9427a

Browse files
authored
feat: add download button in songsheet in SongListScreen.kt (ssalggnikool#312)
* chore: update dependencies * feat: custom bitrate settings for transcoding * feat: use custom bitrate in player * feat: add download button in songsheet in SongListScreen.kt * fix: right addToQueue and playNext in SongListScreenItem
1 parent bbdd4e8 commit 8e9427a

5 files changed

Lines changed: 116 additions & 22 deletions

File tree

composeApp/src/commonMain/kotlin/paige/navic/di/ViewModelModule.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ val viewModelModule = module {
5252
viewModel { params ->
5353
SongListViewModel(
5454
artistId = params.getOrNull(),
55-
repository = get()
55+
repository = get(),
56+
downloadManager = get()
5657
)
5758
}
5859
viewModelOf(::ArtistListViewModel)

composeApp/src/commonMain/kotlin/paige/navic/ui/screens/song/SongListScreen.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ fun SongListScreen(
6262
val selectedReversed by viewModel.selectedReversed.collectAsStateWithLifecycle()
6363
val starred by viewModel.starred.collectAsStateWithLifecycle()
6464
val selectedSongRating by viewModel.selectedSongRating.collectAsStateWithLifecycle()
65+
val allDownloads by viewModel.allDownloads.collectAsStateWithLifecycle()
6566

6667
var shareId by remember { mutableStateOf<String?>(null) }
6768
var shareExpiry by remember { mutableStateOf<Duration?>(null) }
@@ -147,7 +148,11 @@ fun SongListScreen(
147148
player.addToQueueSingle(song)
148149
player.playAt(0)
149150
},
150-
onSetRating = { viewModel.rateSelectedSong(it) }
151+
onSetRating = { viewModel.rateSelectedSong(it) },
152+
onDownload = { viewModel.downloadSong(it) },
153+
allDownloads = allDownloads,
154+
onCancelDownload = { viewModel.cancelDownload(it.id) },
155+
onDeleteDownload = { viewModel.deleteDownload(it.id) }
151156
)
152157
}
153158
}

composeApp/src/commonMain/kotlin/paige/navic/ui/screens/song/components/Content.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import kotlinx.collections.immutable.ImmutableList
77
import navic.composeapp.generated.resources.Res
88
import navic.composeapp.generated.resources.info_no_songs
99
import org.jetbrains.compose.resources.stringResource
10+
import paige.navic.data.database.entities.DownloadEntity
1011
import paige.navic.domain.models.DomainSong
1112
import paige.navic.icons.Icons
1213
import paige.navic.icons.outlined.Note
@@ -18,32 +19,41 @@ fun LazyListScope.songListScreenContent(
1819
selectedSong: DomainSong?,
1920
selectedSongIsStarred: Boolean,
2021
selectedSongRating: Int,
22+
allDownloads: List<DownloadEntity>,
2123
onUpdateSelection: (DomainSong) -> Unit,
2224
onClearSelection: () -> Unit,
2325
onSetShareId: (String) -> Unit,
2426
onSetStarred: (Boolean) -> Unit,
2527
onPlayNext: (DomainSong) -> Unit,
2628
onAddToQueue: (DomainSong) -> Unit,
2729
onPlaySong: (DomainSong) -> Unit,
28-
onSetRating: (Int) -> Unit
30+
onSetRating: (Int) -> Unit,
31+
onDownload: (DomainSong) -> Unit,
32+
onCancelDownload: (DomainSong) -> Unit,
33+
onDeleteDownload: (DomainSong) -> Unit
2934
) {
3035
val data = state.data.orEmpty()
3136
if (data.isNotEmpty()) {
3237
items(data) { song ->
38+
val download = allDownloads.find { it.songId == song.id }
3339
SongListScreenItem(
3440
modifier = Modifier.animateItem(),
3541
song = song,
3642
selected = song == selectedSong,
37-
starred = selectedSongIsStarred,
38-
rating = selectedSongRating,
43+
starred = if (song == selectedSong) selectedSongIsStarred else false,
44+
rating = if (song == selectedSong) selectedSongRating else 0,
3945
onSelect = { onUpdateSelection(song) },
4046
onDeselect = { onClearSelection() },
4147
onSetStarred = { onSetStarred(it) },
4248
onSetShareId = onSetShareId,
4349
onPlayNext = { onPlayNext(song) },
4450
onAddToQueue = { onAddToQueue(song) },
4551
onClick = { onPlaySong(song) },
46-
onSetRating = onSetRating
52+
onSetRating = onSetRating,
53+
download = download,
54+
onDownload = { onDownload(song) },
55+
onCancelDownload = { onCancelDownload(song) },
56+
onDeleteDownload = { onDeleteDownload(song) }
4757
)
4858
}
4959
} else {

composeApp/src/commonMain/kotlin/paige/navic/ui/screens/song/components/Item.kt

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxSize
66
import androidx.compose.foundation.layout.padding
77
import androidx.compose.foundation.layout.size
88
import androidx.compose.foundation.text.appendInlineContent
9+
import androidx.compose.material3.CircularProgressIndicator
910
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
1011
import androidx.compose.material3.Icon
1112
import androidx.compose.material3.ListItem
@@ -31,16 +32,24 @@ import kotlinx.collections.immutable.persistentListOf
3132
import kotlinx.coroutines.launch
3233
import navic.composeapp.generated.resources.Res
3334
import navic.composeapp.generated.resources.action_add_to_queue
35+
import navic.composeapp.generated.resources.action_play_next
36+
import navic.composeapp.generated.resources.info_download_failed
37+
import navic.composeapp.generated.resources.info_downloaded
3438
import navic.composeapp.generated.resources.info_unknown_album
3539
import navic.composeapp.generated.resources.info_unknown_year
3640
import org.jetbrains.compose.resources.stringResource
3741
import paige.navic.LocalNavStack
42+
import paige.navic.data.database.entities.DownloadEntity
43+
import paige.navic.data.database.entities.DownloadStatus
3844
import paige.navic.data.models.Screen
3945
import paige.navic.data.models.settings.Settings
4046
import paige.navic.domain.models.DomainExplicitStatus
4147
import paige.navic.domain.models.DomainSong
4248
import paige.navic.icons.Icons
49+
import paige.navic.icons.outlined.Check
50+
import paige.navic.icons.outlined.DownloadOff
4351
import paige.navic.icons.outlined.Queue
52+
import paige.navic.icons.outlined.QueuePlayNext
4453
import paige.navic.ui.components.common.CoverArt
4554
import paige.navic.ui.components.common.MarqueeText
4655
import paige.navic.ui.components.sheets.SongSheet
@@ -62,7 +71,11 @@ fun SongListScreenItem(
6271
onPlayNext: () -> Unit,
6372
onAddToQueue: () -> Unit,
6473
onClick: () -> Unit,
65-
onSetRating: (Int) -> Unit
74+
onSetRating: (Int) -> Unit,
75+
download: DownloadEntity?,
76+
onDownload: () -> Unit,
77+
onCancelDownload: () -> Unit,
78+
onDeleteDownload: () -> Unit,
6679
) {
6780
val backStack = LocalNavStack.current
6881
val dismissState = rememberSwipeToDismissBoxState()
@@ -73,10 +86,9 @@ fun SongListScreenItem(
7386
modifier = modifier,
7487
state = dismissState,
7588
onDismiss = {
76-
if (it == SwipeToDismissBoxValue.EndToStart) onAddToQueue()
77-
scope.launch {
78-
dismissState.reset()
79-
}
89+
if (it == SwipeToDismissBoxValue.StartToEnd) onAddToQueue()
90+
if (it == SwipeToDismissBoxValue.EndToStart) onPlayNext()
91+
scope.launch { dismissState.reset() }
8092
},
8193
backgroundContent = {
8294
Box(
@@ -87,15 +99,25 @@ fun SongListScreenItem(
8799
.padding(horizontal = 20.dp),
88100
contentAlignment = Alignment.CenterEnd
89101
) {
90-
Icon(
91-
imageVector = Icons.Outlined.Queue,
92-
contentDescription = stringResource(Res.string.action_add_to_queue),
93-
tint = MaterialTheme.colorScheme.onPrimaryContainer,
94-
modifier = Modifier.align(when (dismissState.dismissDirection) {
95-
SwipeToDismissBoxValue.StartToEnd -> Alignment.CenterStart
96-
else -> Alignment.CenterEnd
97-
})
98-
)
102+
when (dismissState.dismissDirection) {
103+
SwipeToDismissBoxValue.StartToEnd -> {
104+
Icon(
105+
imageVector = Icons.Outlined.Queue,
106+
contentDescription = stringResource(Res.string.action_add_to_queue),
107+
tint = MaterialTheme.colorScheme.onPrimaryContainer,
108+
modifier = Modifier.align(Alignment.CenterStart)
109+
)
110+
}
111+
SwipeToDismissBoxValue.EndToStart -> {
112+
Icon(
113+
imageVector = Icons.Outlined.QueuePlayNext,
114+
contentDescription = stringResource(Res.string.action_play_next),
115+
tint = MaterialTheme.colorScheme.onPrimaryContainer,
116+
modifier = Modifier.align(Alignment.CenterEnd)
117+
)
118+
}
119+
else -> {}
120+
}
99121
}
100122
}
101123
) {
@@ -133,6 +155,36 @@ fun SongListScreenItem(
133155
modifier = Modifier.size(50.dp),
134156
shape = ContinuousRoundedRectangle((Settings.shared.artGridRounding / 1.75f).dp)
135157
)
158+
},
159+
trailingContent = {
160+
if (download != null) {
161+
when (download.status) {
162+
DownloadStatus.DOWNLOADING -> {
163+
CircularProgressIndicator(
164+
progress = { download.progress },
165+
modifier = Modifier.size(16.dp),
166+
strokeWidth = 2.dp
167+
)
168+
}
169+
DownloadStatus.DOWNLOADED -> {
170+
Icon(
171+
Icons.Outlined.Check,
172+
contentDescription = stringResource(Res.string.info_downloaded),
173+
modifier = Modifier.size(16.dp),
174+
tint = MaterialTheme.colorScheme.primary
175+
)
176+
}
177+
DownloadStatus.FAILED -> {
178+
Icon(
179+
Icons.Outlined.DownloadOff,
180+
contentDescription = stringResource(Res.string.info_download_failed),
181+
modifier = Modifier.size(16.dp),
182+
tint = MaterialTheme.colorScheme.error
183+
)
184+
}
185+
else -> {}
186+
}
187+
}
136188
}
137189
)
138190
if (selected) {
@@ -161,7 +213,11 @@ fun SongListScreenItem(
161213
onAddToPlaylist = {
162214
playlistDialogShown = true
163215
},
164-
onSetRating = onSetRating
216+
onSetRating = onSetRating,
217+
downloadStatus = download?.status ?: DownloadStatus.NOT_DOWNLOADED,
218+
onDownload = onDownload,
219+
onCancelDownload = onCancelDownload,
220+
onDeleteDownload = onDeleteDownload,
165221
)
166222
}
167223
}

composeApp/src/commonMain/kotlin/paige/navic/ui/screens/song/viewmodels/SongListViewModel.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,31 @@ import androidx.lifecycle.viewModelScope
55
import kotlinx.collections.immutable.ImmutableList
66
import kotlinx.collections.immutable.persistentListOf
77
import kotlinx.coroutines.flow.MutableStateFlow
8+
import kotlinx.coroutines.flow.SharingStarted
89
import kotlinx.coroutines.flow.asStateFlow
10+
import kotlinx.coroutines.flow.stateIn
911
import kotlinx.coroutines.launch
1012
import paige.navic.data.session.SessionManager
1113
import paige.navic.domain.models.DomainSong
1214
import paige.navic.domain.models.DomainSongListType
1315
import paige.navic.domain.repositories.SongRepository
16+
import paige.navic.managers.DownloadManager
1417
import paige.navic.utils.UiState
1518

1619
class SongListViewModel(
1720
private val artistId: String? = null,
18-
private val repository: SongRepository
21+
private val repository: SongRepository,
22+
private val downloadManager: DownloadManager,
1923
) : ViewModel() {
2024
private val _songsState =
2125
MutableStateFlow<UiState<ImmutableList<DomainSong>>>(UiState.Loading())
2226
val songsState = _songsState.asStateFlow()
27+
val allDownloads = downloadManager.allDownloads
28+
.stateIn(
29+
scope = viewModelScope,
30+
started = SharingStarted.Lazily,
31+
initialValue = emptyList()
32+
)
2333

2434
private val _selectedSong = MutableStateFlow<DomainSong?>(null)
2535
val selectedSong = _selectedSong.asStateFlow()
@@ -104,4 +114,16 @@ class SongListViewModel(
104114
fun clearError() {
105115
_songsState.value = UiState.Success(_songsState.value.data ?: persistentListOf())
106116
}
117+
118+
fun downloadSong(song: DomainSong) {
119+
downloadManager.downloadSong(song)
120+
}
121+
122+
fun cancelDownload(songId: String) {
123+
downloadManager.cancelDownload(songId)
124+
}
125+
126+
fun deleteDownload(songId: String) {
127+
downloadManager.deleteDownload(songId)
128+
}
107129
}

0 commit comments

Comments
 (0)