11package com.kelsos.mbrc.features.library
22
3+ import androidx.annotation.StringRes
34import androidx.compose.animation.core.animateFloatAsState
5+ import androidx.compose.foundation.layout.Arrangement
46import androidx.compose.foundation.layout.Column
57import androidx.compose.foundation.layout.Row
8+ import androidx.compose.foundation.layout.Spacer
69import androidx.compose.foundation.layout.fillMaxSize
710import androidx.compose.foundation.layout.fillMaxWidth
811import androidx.compose.foundation.layout.height
12+ import androidx.compose.foundation.layout.padding
13+ import androidx.compose.foundation.shape.RoundedCornerShape
14+ import androidx.compose.material.Checkbox
15+ import androidx.compose.material.DropdownMenu
16+ import androidx.compose.material.DropdownMenuItem
17+ import androidx.compose.material.Icon
18+ import androidx.compose.material.IconButton
919import androidx.compose.material.LinearProgressIndicator
1020import androidx.compose.material.MaterialTheme
1121import androidx.compose.material.ProgressIndicatorDefaults
@@ -14,15 +24,26 @@ import androidx.compose.material.Tab
1424import androidx.compose.material.TabRow
1525import androidx.compose.material.TabRowDefaults
1626import androidx.compose.material.Text
27+ import androidx.compose.material.TextButton
28+ import androidx.compose.material.icons.Icons
29+ import androidx.compose.material.icons.filled.MoreVert
30+ import androidx.compose.material.icons.filled.Refresh
31+ import androidx.compose.material.icons.filled.Search
1732import androidx.compose.runtime.Composable
33+ import androidx.compose.runtime.collectAsState
1834import androidx.compose.runtime.getValue
1935import androidx.compose.runtime.livedata.observeAsState
36+ import androidx.compose.runtime.mutableStateOf
37+ import androidx.compose.runtime.remember
38+ import androidx.compose.runtime.setValue
2039import androidx.compose.ui.Alignment
2140import androidx.compose.ui.Modifier
41+ import androidx.compose.ui.draw.clip
2242import androidx.compose.ui.res.stringResource
2343import androidx.compose.ui.tooling.preview.Devices
2444import androidx.compose.ui.tooling.preview.Preview
2545import androidx.compose.ui.unit.dp
46+ import androidx.compose.ui.window.Dialog
2647import androidx.lifecycle.LiveData
2748import com.google.accompanist.pager.ExperimentalPagerApi
2849import com.google.accompanist.pager.HorizontalPager
@@ -35,27 +56,103 @@ import com.kelsos.mbrc.features.library.presentation.LibraryViewModel
3556import com.kelsos.mbrc.features.library.sync.LibrarySyncProgress
3657import com.kelsos.mbrc.features.library.sync.SyncCategory
3758import com.kelsos.mbrc.features.queue.Queue
59+ import com.kelsos.mbrc.metrics.SyncedData
3860import com.kelsos.mbrc.theme.Accent
3961import com.kelsos.mbrc.theme.DarkBackground
4062import com.kelsos.mbrc.theme.RemoteTheme
4163import kotlinx.coroutines.CoroutineScope
4264import kotlinx.coroutines.MainScope
65+ import kotlinx.coroutines.flow.Flow
4366import kotlinx.coroutines.launch
4467import org.koin.androidx.compose.getViewModel
4568
4669@Composable
47- fun LibraryScreen (openDrawer : () -> Unit , coroutineScope : CoroutineScope ) {
48- val vm = getViewModel<LibraryViewModel >()
70+ fun LibraryScreen (
71+ openDrawer : () -> Unit ,
72+ coroutineScope : CoroutineScope ,
73+ vm : LibraryViewModel = getViewModel()
74+ ) {
4975 LibraryScreen (
5076 openDrawer = openDrawer, sync = { vm.refresh() },
5177 action = { queue, meta, id ->
5278 vm.queue(id, meta, queue)
5379 },
5480 syncProgress = vm.syncProgress,
81+ albumArtistsOnly = vm.albumArtistOnly,
82+ setAlbumArtistOnly = { vm.setAlbumArtistOnly(it) },
83+ metrics = vm.syncState,
5584 coroutineScope
5685 )
5786}
5887
88+ @Composable
89+ fun SyncMetricsDialog (metrics : Flow <SyncedData >, showDialog : Boolean , dismiss : () -> Unit ) {
90+ if (showDialog) {
91+ Dialog (
92+ onDismissRequest = { dismiss() },
93+ ) {
94+ Surface (
95+ modifier = Modifier .clip(RoundedCornerShape (8 .dp))
96+ ) {
97+ val syncedData by metrics.collectAsState(initial = SyncedData (0 , 0 , 0 , 0 , 0 ))
98+ SyncMetricsContent (syncedData, dismiss)
99+ }
100+ }
101+ }
102+ }
103+
104+ @Composable
105+ fun SyncMetricsContent (metrics : SyncedData , dismiss : () -> Unit ) =
106+ Column (modifier = Modifier .padding(16 .dp)) {
107+ Row (modifier = Modifier .fillMaxWidth()) {
108+ Text (
109+ text = stringResource(id = R .string.library_stats__title),
110+ style = MaterialTheme .typography.h6
111+ )
112+ }
113+ Row (
114+ modifier = Modifier
115+ .padding(vertical = 16 .dp)
116+ .fillMaxWidth()
117+ ) {
118+ Text (
119+ text = stringResource(id = R .string.library_stats__description),
120+ style = MaterialTheme .typography.caption,
121+ color = MaterialTheme .colors.onSurface.copy(0.7f )
122+ )
123+ }
124+
125+ MetricsRow (R .string.media__genres, metrics.genres)
126+ MetricsRow (R .string.media__artists, metrics.artists)
127+ MetricsRow (R .string.media__albums, metrics.albums)
128+ MetricsRow (R .string.media__tracks, metrics.tracks)
129+ MetricsRow (R .string.media__playlists, metrics.playlists)
130+ Row (modifier = Modifier .padding(top = 16 .dp)) {
131+ Spacer (modifier = Modifier .weight(1f ))
132+ TextButton (onClick = dismiss) {
133+ Text (text = stringResource(id = android.R .string.ok))
134+ }
135+ }
136+ }
137+
138+ @Composable
139+ private fun MetricsRow (@StringRes resId : Int , items : Long ) {
140+ Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .SpaceBetween ) {
141+ Column {
142+ Text (text = stringResource(id = resId), color = MaterialTheme .colors.primary)
143+ }
144+ Column {
145+ Text (text = items.toString())
146+ }
147+ }
148+ }
149+
150+ @Preview
151+ @Composable
152+ fun SyncMetricsContentPreview () {
153+ SyncMetricsContent (metrics = SyncedData (10 , 40 , 100 , 1000 , 4 )) {}
154+ }
155+
59156@Composable
60157fun LibrarySyncIndicator (syncProgress : LiveData <LibrarySyncProgress >) {
61158 val progress by syncProgress.observeAsState(initial = LibrarySyncProgress (0 , 0 , 0 , false ))
@@ -81,7 +178,7 @@ fun LibrarySyncIndicator(syncProgress: LibrarySyncProgress) = Surface(
81178 SyncCategory .TRACKS -> R .string.library__category_tracks
82179 SyncCategory .PLAYLISTS -> R .string.library__category_playlists
83180 SyncCategory .COVERS -> R .string.library__category_covers
84- else -> throw IllegalArgumentException ()
181+ else -> R .string.media__genres
85182 }
86183 Column (modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment .CenterHorizontally ) {
87184 Text (
@@ -115,13 +212,62 @@ fun LibrarySyncIndicatorPreview() {
115212 LibrarySyncIndicator (syncProgress = LibrarySyncProgress (12000 , 120000 , 2 , true ))
116213}
117214
215+ @Composable
216+ fun SyncButton (sync : () -> Unit ) {
217+ IconButton (onClick = { sync() }) {
218+ Icon (
219+ imageVector = Icons .Filled .Refresh ,
220+ contentDescription = stringResource(id = R .string.press_to_sync)
221+ )
222+ }
223+ }
224+
225+ @Composable
226+ fun LibraryScreenOverflow (
227+ showAlbumArtistsOnly : Flow <Boolean >,
228+ setAlbumArtistOnly : (enabled: Boolean ) -> Unit ,
229+ metrics : Flow <SyncedData >
230+ ) {
231+ var showMenu by remember { mutableStateOf(false ) }
232+ var showMetricsDialog by remember { mutableStateOf(false ) }
233+ IconButton (onClick = { showMenu = ! showMenu }) {
234+ Icon (
235+ imageVector = Icons .Filled .MoreVert ,
236+ contentDescription = stringResource(id = R .string.press_to_sync)
237+ )
238+ }
239+ DropdownMenu (expanded = showMenu, onDismissRequest = { showMenu = false }) {
240+ val albumArtistsOnly by showAlbumArtistsOnly.collectAsState(initial = false )
241+ DropdownMenuItem (onClick = { setAlbumArtistOnly(! albumArtistsOnly) }) {
242+ Checkbox (checked = albumArtistsOnly, onCheckedChange = setAlbumArtistOnly)
243+ Text (
244+ text = stringResource(id = R .string.library__action_only_album_artists),
245+ modifier = Modifier .padding(start = 16 .dp)
246+ )
247+ }
248+
249+ DropdownMenuItem (onClick = {
250+ showMenu = false
251+ showMetricsDialog = true
252+ }) {
253+ Text (
254+ text = stringResource(id = R .string.library_menu__sync_state),
255+ )
256+ }
257+ }
258+ SyncMetricsDialog (metrics, showDialog = showMetricsDialog) { showMetricsDialog = false }
259+ }
260+
118261@OptIn(ExperimentalPagerApi ::class )
119262@Composable
120263fun LibraryScreen (
121264 openDrawer : () -> Unit ,
122265 sync : () -> Unit ,
123266 action : (queue: Queue , meta: Meta , id: Long ) -> Unit ,
124267 syncProgress : LiveData <LibrarySyncProgress >,
268+ albumArtistsOnly : Flow <Boolean >,
269+ setAlbumArtistOnly : (enabled: Boolean ) -> Unit ,
270+ metrics : Flow <SyncedData >,
125271 coroutineScope : CoroutineScope
126272) = Surface {
127273 val tabs = listOf (
@@ -135,6 +281,16 @@ fun LibraryScreen(
135281
136282 Column (modifier = Modifier .fillMaxSize()) {
137283 RemoteTopAppBar (openDrawer = openDrawer) {
284+ Row {
285+ IconButton (onClick = { /* TODO*/ }) {
286+ Icon (
287+ imageVector = Icons .Filled .Search ,
288+ contentDescription = stringResource(id = R .string.library_search_hint)
289+ )
290+ }
291+ SyncButton (sync = sync)
292+ LibraryScreenOverflow (albumArtistsOnly, setAlbumArtistOnly, metrics)
293+ }
138294 }
139295 TabRow (
140296 selectedTabIndex = pagerState.currentPage,
0 commit comments