Skip to content

Commit d0980f9

Browse files
authored
Merge pull request #1613 from theovilardo/impr/artist-choose-bottomsheet
Implement a dedicated artist picker bottom sheet and enhance artist i…
2 parents 6a9c9f0 + 39a27b0 commit d0980f9

5 files changed

Lines changed: 330 additions & 47 deletions

File tree

app/src/main/java/com/theveloper/pixelplay/data/repository/MusicRepositoryImpl.kt

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import kotlinx.coroutines.flow.flatMapLatest
6262
import kotlinx.coroutines.flow.flow
6363
import kotlinx.coroutines.flow.flowOf
6464
import kotlinx.coroutines.flow.flowOn
65+
import kotlinx.coroutines.flow.onEach
6566
import kotlinx.coroutines.runBlocking
6667
import kotlinx.coroutines.sync.Mutex
6768
import kotlinx.coroutines.sync.withLock
@@ -107,6 +108,7 @@ class MusicRepositoryImpl @Inject constructor(
107108
private val repositoryScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
108109
// Tracks the active prefetch job so a new flow emission cancels the previous one.
109110
@Volatile private var prefetchJob: Job? = null
111+
@Volatile private var currentSongArtistPrefetchJob: Job? = null
110112
@Volatile private var telegramDownloadSyncObserverStarted = false
111113
private val telegramCacheManager: com.theveloper.pixelplay.data.telegram.TelegramCacheManager
112114
get() = telegramCacheManagerProvider.get()
@@ -129,6 +131,13 @@ class MusicRepositoryImpl @Inject constructor(
129131
}
130132
}
131133

134+
private fun List<Artist>.missingImageCandidates(): List<Pair<Long, String>> =
135+
asSequence()
136+
.filter { it.effectiveImageUrl.isNullOrBlank() && it.name.isNotBlank() }
137+
.map { it.id to it.name }
138+
.distinctBy { (_, name) -> name.trim().lowercase() }
139+
.toList()
140+
132141
@OptIn(ExperimentalCoroutinesApi::class)
133142
override fun getAudioFiles(): Flow<List<Song>> {
134143
return combine(
@@ -269,11 +278,7 @@ class MusicRepositoryImpl @Inject constructor(
269278
.map { entities ->
270279
val artists = entities.map { it.toArtist() }
271280
// Trigger prefetch for missing images (non-blocking)
272-
val missingImages = artists.asSequence()
273-
.filter { it.imageUrl.isNullOrEmpty() && it.name.isNotBlank() }
274-
.map { it.id to it.name }
275-
.distinctBy { (_, name) -> name.trim().lowercase() }
276-
.toList()
281+
val missingImages = artists.missingImageCandidates()
277282
if (missingImages.isNotEmpty()) {
278283
// Cancel any in-flight prefetch before starting a new one — the flow
279284
// can emit multiple times during sync, and concurrent launches would
@@ -299,9 +304,19 @@ class MusicRepositoryImpl @Inject constructor(
299304
}
300305

301306
override fun getArtistsForSong(songId: Long): Flow<List<Artist>> {
302-
return musicDao.getArtistsForSong(songId).map { entities ->
303-
entities.map { it.toArtist() }
304-
}.flowOn(Dispatchers.IO)
307+
return musicDao.getArtistsForSong(songId)
308+
.map { entities -> entities.map { it.toArtist() } }
309+
.distinctUntilChanged()
310+
.onEach { artists ->
311+
val missingImages = artists.missingImageCandidates()
312+
if (missingImages.isNotEmpty()) {
313+
currentSongArtistPrefetchJob?.cancel()
314+
currentSongArtistPrefetchJob = repositoryScope.launch {
315+
artistImageRepository.prefetchArtistImages(missingImages)
316+
}
317+
}
318+
}
319+
.flowOn(Dispatchers.IO)
305320
}
306321

307322
override fun getSongsForArtist(artistId: Long): Flow<List<Song>> {

app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,17 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
4949
import androidx.compose.material3.ColorScheme
5050
import androidx.compose.material3.FilledIconButton
5151
import androidx.compose.material3.Icon
52-
import androidx.compose.material3.HorizontalDivider
5352
import androidx.compose.material3.IconButtonDefaults
5453
import androidx.compose.material3.MaterialTheme
5554
import androidx.compose.material3.CircularWavyProgressIndicator
5655
import androidx.compose.material3.CircularProgressIndicator
5756
import androidx.compose.material3.LoadingIndicator
5857
// import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults // Removed
5958
// import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState // Removed
60-
import androidx.compose.material3.ModalBottomSheet
6159
import androidx.compose.material3.Scaffold
6260
import androidx.compose.material3.Surface
6361
import androidx.compose.material3.Text
6462
import androidx.compose.material3.TopAppBar
65-
import androidx.compose.material3.SheetState
6663
import androidx.compose.material3.TopAppBarDefaults
6764
import androidx.compose.runtime.Composable
6865
import androidx.compose.runtime.LaunchedEffect
@@ -958,43 +955,18 @@ fun FullPlayerContent(
958955
)
959956
}
960957

961-
val artistPickerSheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
958+
val artistPickerSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
962959
if (showArtistPicker && currentSongArtists.isNotEmpty()) {
963-
ModalBottomSheet(
964-
onDismissRequest = { showArtistPicker = false },
965-
sheetState = artistPickerSheetState
966-
) {
967-
Column(
968-
modifier = Modifier
969-
.fillMaxWidth()
970-
.padding(horizontal = 20.dp, vertical = 12.dp),
971-
verticalArrangement = Arrangement.spacedBy(4.dp)
972-
) {
973-
Text(
974-
text = stringResource(R.string.artist_picker_title), // short label; keep UI minimal
975-
style = MaterialTheme.typography.titleMedium,
976-
color = LocalMaterialTheme.current.onPrimaryContainer,
977-
modifier = Modifier.padding(bottom = 8.dp)
978-
)
979-
currentSongArtists.forEachIndexed { index, artistItem ->
980-
Text(
981-
text = artistItem.name,
982-
style = MaterialTheme.typography.bodyLarge,
983-
color = LocalMaterialTheme.current.onPrimaryContainer,
984-
modifier = Modifier
985-
.fillMaxWidth()
986-
.padding(vertical = 10.dp)
987-
.clickable {
988-
playerViewModel.triggerArtistNavigationFromPlayer(artistItem.id)
989-
showArtistPicker = false
990-
}
991-
)
992-
if (index != currentSongArtists.lastIndex) {
993-
HorizontalDivider(color = LocalMaterialTheme.current.outlineVariant)
994-
}
995-
}
960+
PlayerArtistPickerBottomSheet(
961+
song = song,
962+
artists = currentSongArtists,
963+
sheetState = artistPickerSheetState,
964+
onDismiss = { showArtistPicker = false },
965+
onArtistClick = { artist ->
966+
playerViewModel.triggerArtistNavigationFromPlayer(artist.id)
967+
showArtistPicker = false
996968
}
997-
}
969+
)
998970
}
999971
}
1000972

@@ -1878,11 +1850,13 @@ private fun EfficientTimeLabels(
18781850
Text(
18791851
posStr,
18801852
style = MaterialTheme.typography.bodySmall.copy(fontSize = 12.sp),
1853+
fontWeight = FontWeight.SemiBold,
18811854
color = textColor
18821855
)
18831856
Text(
18841857
durStr,
18851858
style = MaterialTheme.typography.bodySmall.copy(fontSize = 12.sp),
1859+
fontWeight = FontWeight.SemiBold,
18861860
color = textColor
18871861
)
18881862
}

0 commit comments

Comments
 (0)