Skip to content

Commit 02e9072

Browse files
committed
browse song enhancements
que clear on login playlist que now loads just in time x3
1 parent a4e2beb commit 02e9072

11 files changed

Lines changed: 328 additions & 68 deletions

File tree

src/app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ android {
1414
minSdk = 21
1515
targetSdk = 35
1616
versionCode = 11
17-
versionName = "1.6.1"
17+
versionName = "1.6.2"
1818

1919
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2020
vectorDrawables {

src/app/src/main/java/com/melodee/autoplayer/data/api/MusicApi.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ interface MusicApi {
99
@Body credentials: Map<String, String>
1010
): AuthResponse
1111

12+
// Fetch current authenticated user profile
13+
@GET("users/me")
14+
suspend fun getCurrentUser(): User
15+
1216
@GET("users/playlists")
1317
suspend fun getPlaylists(
1418
@Query("page") page: Int,
@@ -70,4 +74,4 @@ interface MusicApi {
7074
@Path("songId") songId: String,
7175
@Path("userStarred") userStarred: Boolean
7276
): retrofit2.Response<Unit>
73-
}
77+
}

src/app/src/main/java/com/melodee/autoplayer/data/repository/MusicRepository.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.melodee.autoplayer.domain.model.Playlist
1010
import com.melodee.autoplayer.domain.model.Song
1111
import com.melodee.autoplayer.domain.model.Artist
1212
import com.melodee.autoplayer.domain.model.Album
13+
import com.melodee.autoplayer.domain.model.User
1314
import kotlinx.coroutines.flow.Flow
1415
import kotlinx.coroutines.flow.flow
1516
import retrofit2.HttpException
@@ -47,6 +48,13 @@ class MusicRepository(private val baseUrl: String, private val context: Context)
4748
emit(response)
4849
}
4950

51+
fun getCurrentUser(): Flow<User> = flow {
52+
val response = ErrorHandler.handleOperation(context, "getCurrentUser", "MusicRepository") {
53+
api.getCurrentUser()
54+
}
55+
emit(response)
56+
}
57+
5058
fun getPlaylistSongs(playlistId: String, page: Int): Flow<PaginatedResponse<Song>> = flow {
5159
val response = ErrorHandler.handleOperation(context, "getPlaylistSongs", "MusicRepository") {
5260
api.getPlaylistSongs(playlistId, page)
@@ -115,4 +123,4 @@ class MusicRepository(private val baseUrl: String, private val context: Context)
115123
false
116124
}
117125
}
118-
}
126+
}

src/app/src/main/java/com/melodee/autoplayer/presentation/ui/MainActivity.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,17 @@ fun MainScreen(
237237
}
238238
val isAuthenticated by authenticationManager.isAuthenticated.collectAsStateWithLifecycle()
239239

240+
// Defensive auth restoration if persisted credentials exist
241+
LaunchedEffect(isAuthenticated) {
242+
if (!isAuthenticated) {
243+
val settings = com.melodee.autoplayer.data.SettingsManager(context)
244+
if (settings.isAuthenticated()) {
245+
Log.w("MainActivity", "Auth state false but credentials present; restoring from storage")
246+
authenticationManager.restoreAuthenticationFromStorage()
247+
}
248+
}
249+
}
250+
240251
// Handle navigation based on authentication state changes
241252
LaunchedEffect(isAuthenticated) {
242253
val currentRoute = navController.currentDestination?.route
@@ -498,6 +509,19 @@ fun MainScreen(
498509
LoginScreen(
499510
viewModel = loginViewModel,
500511
onLoginSuccess = { authResponse ->
512+
// Ensure any existing playback is stopped and queue cleared on login
513+
try {
514+
val ctx = context
515+
val stopIntent = android.content.Intent(ctx, com.melodee.autoplayer.service.MusicService::class.java).apply {
516+
action = com.melodee.autoplayer.service.MusicService.ACTION_STOP
517+
}
518+
ctx.startService(stopIntent)
519+
val clearIntent = android.content.Intent(ctx, com.melodee.autoplayer.service.MusicService::class.java).apply {
520+
action = com.melodee.autoplayer.service.MusicService.ACTION_CLEAR_QUEUE
521+
}
522+
ctx.startService(clearIntent)
523+
} catch (_: Exception) {}
524+
501525
// Set base URL for both view models
502526
homeViewModel.setBaseUrl(loginViewModel.serverUrl)
503527
playlistViewModel.setBaseUrl(loginViewModel.serverUrl)
@@ -626,4 +650,4 @@ fun MainScreen(
626650
}
627651
}
628652
}
629-
}
653+
}

src/app/src/main/java/com/melodee/autoplayer/presentation/ui/components/SongItem.kt

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package com.melodee.autoplayer.presentation.ui.components
33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.clickable
55
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.shape.CircleShape
67
import androidx.compose.foundation.shape.RoundedCornerShape
78
import androidx.compose.material.icons.Icons
89
import androidx.compose.material.icons.filled.Favorite
10+
import androidx.compose.material.icons.filled.PlayArrow
911
import androidx.compose.material.icons.outlined.FavoriteBorder
1012
import androidx.compose.material3.*
1113
import androidx.compose.runtime.*
@@ -34,7 +36,6 @@ fun SongItem(
3436
modifier: Modifier = Modifier
3537
) {
3638
val context = LocalContext.current
37-
var showFullImage by remember { mutableStateOf(false) }
3839
var isStarred by remember { mutableStateOf(song.userStarred) }
3940

4041
// Update isStarred when song.userStarred changes
@@ -50,7 +51,6 @@ fun SongItem(
5051
modifier = modifier
5152
.fillMaxWidth()
5253
.height(itemHeight)
53-
.clickable(onClick = onClick)
5454
.background(
5555
if (isCurrentlyPlaying) {
5656
MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
@@ -61,29 +61,49 @@ fun SongItem(
6161
.padding(horizontal = horizontalPadding, vertical = 8.dp),
6262
verticalAlignment = Alignment.CenterVertically
6363
) {
64-
// Thumbnail
65-
AsyncImage(
66-
model = ImageRequest.Builder(context)
67-
.data(song.thumbnailUrl)
68-
.crossfade(true)
69-
.memoryCachePolicy(coil.request.CachePolicy.ENABLED)
70-
.diskCachePolicy(coil.request.CachePolicy.ENABLED)
71-
.build(),
72-
contentDescription = "Song thumbnail",
73-
modifier = Modifier
74-
.size(thumbnailSize)
75-
.clip(RoundedCornerShape(8.dp))
76-
.clickable {
77-
song.imageUrl.let {
78-
showFullImage = true
79-
}
80-
},
81-
contentScale = ContentScale.Crop
82-
)
64+
// Thumbnail with play icon overlay
65+
Box {
66+
AsyncImage(
67+
model = ImageRequest.Builder(context)
68+
.data(song.thumbnailUrl)
69+
.crossfade(true)
70+
.memoryCachePolicy(coil.request.CachePolicy.ENABLED)
71+
.diskCachePolicy(coil.request.CachePolicy.ENABLED)
72+
.build(),
73+
contentDescription = "Song thumbnail",
74+
modifier = Modifier
75+
.size(thumbnailSize)
76+
.clip(RoundedCornerShape(8.dp)),
77+
contentScale = ContentScale.Crop
78+
)
79+
80+
// Play icon overlay (tap to play)
81+
Box(
82+
modifier = Modifier
83+
.size(thumbnailSize)
84+
.clip(RoundedCornerShape(8.dp))
85+
.clickable { onClick() }
86+
.background(Color.Black.copy(alpha = 0.3f)),
87+
contentAlignment = Alignment.Center
88+
) {
89+
Icon(
90+
imageVector = Icons.Filled.PlayArrow,
91+
contentDescription = "Play song",
92+
modifier = Modifier
93+
.size(24.dp)
94+
.background(
95+
MaterialTheme.colorScheme.primary,
96+
CircleShape
97+
)
98+
.padding(4.dp),
99+
tint = MaterialTheme.colorScheme.onPrimary
100+
)
101+
}
102+
}
83103

84104
Spacer(modifier = Modifier.width(12.dp))
85105

86-
// Song details
106+
// Song details (no click-to-play on title)
87107
Column(
88108
modifier = Modifier.weight(1f)
89109
) {
@@ -141,11 +161,4 @@ fun SongItem(
141161
}
142162
}
143163

144-
// Full image viewer
145-
if (showFullImage) {
146-
FullImageViewer(
147-
imageUrl = song.imageUrl,
148-
onDismiss = { showFullImage = false }
149-
)
150-
}
151-
}
164+
}

src/app/src/main/java/com/melodee/autoplayer/presentation/ui/home/HomeScreen.kt

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ fun HomeScreen(
214214
) {
215215
// User header
216216
user?.let { currentUser ->
217+
var attemptedUserAvatarRefresh by remember { mutableStateOf(false) }
218+
// If thumbnail is blank or previous load failed, try fetching user once
219+
if (!attemptedUserAvatarRefresh && currentUser.thumbnailUrl.isBlank()) {
220+
attemptedUserAvatarRefresh = true
221+
viewModel.refreshUserFromApiIfNeeded()
222+
}
217223
Row(
218224
modifier = Modifier
219225
.fillMaxWidth()
@@ -230,6 +236,10 @@ fun HomeScreen(
230236
error = painterResource(id = R.drawable.ic_launcher_foreground),
231237
onError = {
232238
Log.e("HomeScreen", "Failed to load user avatar: ${currentUser.thumbnailUrl}")
239+
if (!attemptedUserAvatarRefresh) {
240+
attemptedUserAvatarRefresh = true
241+
viewModel.refreshUserFromApiIfNeeded()
242+
}
233243
},
234244
contentScale = ContentScale.Crop
235245
)
@@ -336,33 +346,44 @@ fun HomeScreen(
336346
Column(
337347
modifier = Modifier.fillMaxSize()
338348
) {
339-
if (searchQuery.isNotEmpty() && songs.isNotEmpty()) {
340-
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
341-
start = currentPageStart,
342-
end = currentPageEnd,
343-
total = totalSearchResults,
344-
label = "results"
345-
)
346-
}
347-
348-
// Album songs position indicator (above the list)
349-
if (showAlbumSongs && songs.isNotEmpty() && totalSearchResults > 0) {
350-
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
351-
start = currentPageStart,
352-
end = currentPageEnd,
353-
total = totalSearchResults,
354-
label = "songs"
355-
)
356-
}
357-
358-
// Album position indicator (above the list)
359-
if (showAlbums && albums.isNotEmpty() && totalAlbums > 0) {
360-
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
361-
start = currentAlbumsStart,
362-
end = currentAlbumsEnd,
363-
total = totalAlbums,
364-
label = "albums"
365-
)
349+
// Show only one progress indicator at a time
350+
when {
351+
// Album songs browsing
352+
showAlbumSongs && songs.isNotEmpty() && totalSearchResults > 0 -> {
353+
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
354+
start = currentPageStart,
355+
end = currentPageEnd,
356+
total = totalSearchResults,
357+
label = "songs"
358+
)
359+
}
360+
// Albums browsing
361+
showAlbums && albums.isNotEmpty() && totalAlbums > 0 -> {
362+
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
363+
start = currentAlbumsStart,
364+
end = currentAlbumsEnd,
365+
total = totalAlbums,
366+
label = "albums"
367+
)
368+
}
369+
// Search results
370+
searchQuery.isNotEmpty() && songs.isNotEmpty() -> {
371+
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
372+
start = currentPageStart,
373+
end = currentPageEnd,
374+
total = totalSearchResults,
375+
label = "results"
376+
)
377+
}
378+
// Browsing songs for selected artist (no search/albums view)
379+
selectedArtist != null && !showAlbums && !showAlbumSongs && songs.isNotEmpty() -> {
380+
com.melodee.autoplayer.presentation.ui.components.DisplayProgress(
381+
start = currentPageStart,
382+
end = currentPageEnd,
383+
total = totalSearchResults,
384+
label = "songs"
385+
)
386+
}
366387
}
367388
LazyColumn(
368389
state = listState,

src/app/src/main/java/com/melodee/autoplayer/presentation/ui/home/HomeViewModel.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
3232
private var bound = false
3333
private var searchJob: Job? = null
3434
private var performanceMonitor: PerformanceMonitor? = null
35+
private var didAttemptUserRefresh = false
3536

3637
companion object {
3738
private const val MAX_SONGS_IN_MEMORY = 500
@@ -401,6 +402,23 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
401402
// Note: Playlists will be loaded when setUser() is called after authentication
402403
}
403404

405+
fun refreshUserFromApiIfNeeded() {
406+
if (repository == null || didAttemptUserRefresh) return
407+
didAttemptUserRefresh = true
408+
viewModelScope.launch {
409+
try {
410+
repository?.getCurrentUser()
411+
?.catch { e -> Log.w("HomeViewModel", "Failed to fetch current user: ${e.message}") }
412+
?.collect { user ->
413+
_user.value = user
414+
// Optionally trigger playlists reload or other dependent UI updates
415+
}
416+
} catch (e: Exception) {
417+
Log.w("HomeViewModel", "Exception fetching current user", e)
418+
}
419+
}
420+
}
421+
404422
fun loadPlaylists() {
405423
Log.d("HomeViewModel", "Loading playlists, page: $currentPage, hasMore: $hasMorePlaylists")
406424
if (!hasMorePlaylists || repository == null) {
@@ -974,4 +992,4 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
974992
}
975993
}
976994
}
977-
}
995+
}

0 commit comments

Comments
 (0)