Skip to content

Commit

Permalink
feat: Add error handling and reload functionality to MediaStore
Browse files Browse the repository at this point in the history
Added error handling and reload functionality to the MediaStore page.
- Displayed a loading page while
 fetching songs from the MediaStore.
- Showed an error page if an error occurred while fetching songs.
- Added a reload button to the error page to allow users to retry fetching songs.

Other changes:
- Updated the date format for albums in the Spotify metadata details screen.
- Removed unnecessary progress
 indicator from the metadata editor page.
- Updated dependencies to include kotlinx.collections.immutable.
- Added a safeDrawingPadding modifier to the LoadingPage composable.
- Updated the PermissionNotGrantedDialog composable to use PersistentList for neededPermissions.

Signed-off-by: Bobby Espinoza <[email protected]>
Signed-off-by: Gabriel Fontán <[email protected]>
  • Loading branch information
BobbyESP committed Jul 29, 2024
1 parent 2abdf03 commit 42e98e6
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 61 deletions.
23 changes: 23 additions & 0 deletions app/src/main/java/com/bobbyesp/metadator/ext/ReleaseDate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.bobbyesp.metadator.ext

import com.adamratzman.spotify.models.ReleaseDate

fun ReleaseDate.format(precision: String?): String {
return when (precision) {
"year" -> {
"$year"
}

"month" -> {
"$month - $year"
}

"day" -> {
"$day-$month-$year"
}

else -> {
"Unknown"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.bobbyesp.metadator.R
import com.bobbyesp.metadator.presentation.components.cards.songs.HorizontalSongCard
import com.bobbyesp.metadator.presentation.components.cards.songs.VerticalSongCard
import com.bobbyesp.metadator.presentation.components.others.status.EmptyMediaStore
import com.bobbyesp.metadator.presentation.pages.home.LayoutType
import com.bobbyesp.ui.common.pages.ErrorPage
import com.bobbyesp.ui.common.pages.LoadingPage
import com.bobbyesp.utilities.model.Song
import com.bobbyesp.utilities.states.ResourceState
import my.nanihadesuka.compose.LazyColumnScrollbar
Expand All @@ -36,6 +39,7 @@ fun MediaStorePage(
lazyGridState: LazyGridState,
lazyListState: LazyListState,
desiredLayout: LayoutType,
onReloadMediaStore: () -> Unit,
onItemClicked: (Song) -> Unit
) {
Box(
Expand All @@ -45,13 +49,11 @@ fun MediaStorePage(
targetState = desiredLayout, label = "List item transition", animationSpec = tween(200)
) { type ->
when (songs.value) {
is ResourceState.Loading -> {
CircularProgressIndicator()
}

is ResourceState.Error -> {
is ResourceState.Loading -> LoadingPage(text = stringResource(R.string.loading_mediastore))

}
is ResourceState.Error -> ErrorPage(
error = songs.value.message ?: "Unknown"
) { onReloadMediaStore() }

is ResourceState.Success -> {
if (songs.value.data!!.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.bobbyesp.metadator.presentation.pages

import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bobbyesp.utilities.mediastore.MediaStoreReceiver.Advanced.observeSongs
Expand All @@ -28,31 +27,34 @@ class MediaStorePageViewModel @Inject constructor(
private val mediaStoreSongsFlow =
applicationContext.contentResolver.observeSongs()

fun reloadMediaStore() {

private fun songsCollection() {
viewModelScope.launch(Dispatchers.IO) {
_songs.update { ResourceState.Loading() }
mediaStoreSongsFlow.collectLatest { songs ->
_songs.update { ResourceState.Success(songs) }
}
}
}

private fun reloadMediaStore() {
_songs.update { ResourceState.Loading() }
songsCollection()
}

fun onEvent(event: Events) {
when (event) {
is Events.StartObservingMediaStore -> {
Log.i("MediaStorePageViewModel", "Start observing media store")
viewModelScope.launch(Dispatchers.IO) {
mediaStoreSongsFlow.collectLatest { songs ->
_songs.update { ResourceState.Success(songs) }
}
}
songsCollection()
}

is Events.ReloadMediaStore -> reloadMediaStore()
}
}

companion object {
interface Events {
data object StartObservingMediaStore : Events
data object ReloadMediaStore : Events
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import com.bobbyesp.utilities.ui.rememberForeverLazyGridState
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch

Expand Down Expand Up @@ -225,7 +226,7 @@ fun HomePage(
permissionState = storagePermissionState,
deniedContent = { shouldShowRationale ->
PermissionNotGrantedDialog(
neededPermissions = listOf(readAudioFiles.toPermissionType()),
neededPermissions = persistentListOf(readAudioFiles.toPermissionType()),
onGrantRequest = {
storagePermissionState.launchPermissionRequest()
},
Expand All @@ -242,6 +243,9 @@ fun HomePage(
lazyGridState = mediaStoreLazyGridState,
lazyListState = mediaStoreLazyColumnState,
desiredLayout = desiredLayout,
onReloadMediaStore = {
onEvent(MediaStorePageViewModel.Companion.Events.ReloadMediaStore)
},
onItemClicked = { song ->
navController.navigate(
Route.UtilitiesNavigator.TagEditor(song.toParcelableSong())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text
Expand Down Expand Up @@ -64,6 +63,8 @@ import com.bobbyesp.metadator.presentation.common.LocalOrientation
import com.bobbyesp.metadator.presentation.components.image.AsyncImage
import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify.MetadataBsVM
import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify.SpMetadataBottomSheetContent
import com.bobbyesp.ui.common.pages.ErrorPage
import com.bobbyesp.ui.common.pages.LoadingPage
import com.bobbyesp.ui.components.button.CloseButton
import com.bobbyesp.ui.components.others.MetadataTag
import com.bobbyesp.ui.components.text.LargeCategoryTitle
Expand Down Expand Up @@ -179,8 +180,16 @@ fun MetadataEditorPage(
.navigationBarsPadding()
) { state ->
when (state) {
is ScreenState.Error -> TODO()
ScreenState.Loading -> LoadingState(modifier = Modifier.fillMaxSize())
is ScreenState.Error -> ErrorPage(error = state.exception.stackTrace.toString()) {
onEvent(
MetadataEditorVM.Event.LoadMetadata(receivedAudio.localPath)
)
}

ScreenState.Loading -> LoadingPage(
modifier = Modifier.fillMaxSize(),
text = stringResource(id = R.string.loading_metadata)
)

is ScreenState.Success -> {
val scrollState = rememberScrollState()
Expand Down Expand Up @@ -515,22 +524,4 @@ fun SongProperties(mutablePropertiesMap: SnapshotStateMap<String, String>) {
}
}
}
}

@Composable
private fun LoadingState(modifier: Modifier = Modifier) {
Column(
modifier = modifier, verticalArrangement = Arrangement.spacedBy(
8.dp, alignment = Alignment.CenterVertically
), horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.loading_audio_information),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold
)
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(0.7f)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bobbyesp.utilities.ext.toModifiableMap
Expand Down Expand Up @@ -38,6 +39,7 @@ import javax.inject.Inject
@HiltViewModel
class MetadataEditorVM @Inject constructor(
@ApplicationContext private val context: Context,
private val stateHandle: SavedStateHandle
) : ViewModel() {
private val mutableState = MutableStateFlow(PageViewState())
val state = mutableState.asStateFlow()
Expand All @@ -47,17 +49,35 @@ class MetadataEditorVM @Inject constructor(

private var latestLoadedSongPath: String? = null

init {
stateHandle.get<String>("path")?.let {
onEvent(Event.LoadMetadata(it))
}
}

data class PageViewState(
val metadata: ResourceState<Metadata?> = ResourceState.Loading(),
val audioProperties: ResourceState<AudioProperties?> = ResourceState.Loading(),
val pageState: ScreenState<Nothing> = ScreenState.Loading,
val mutablePropertiesMap: SnapshotStateMap<String, String> = mutableStateMapOf()
)

override fun onCleared() {
super.onCleared()
updateState(ScreenState.Loading)
mutableState.update {
it.copy(
metadata = ResourceState.Loading(),
audioProperties = ResourceState.Loading()
)
}
}

private suspend fun loadTrackMetadata(path: String) {
updateState(ScreenState.Loading)
mutableState.value.mutablePropertiesMap.clear()
runCatching {
stateHandle["path"] = path
MediaStoreReceiver.getFileDescriptorFromPath(context, path, mode = "r")?.use { songFd ->

val metadata = loadAudioMetadata(songFd)
Expand Down Expand Up @@ -371,7 +391,6 @@ class MetadataEditorVM @Inject constructor(
data class RequestPermission(val intent: PendingIntent) : UiEvent
data class SaveSuccess(val pictures: Boolean? = null, val properties: Boolean? = null) :
UiEvent

data object SaveFailed : UiEvent
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify.stages

import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -28,6 +29,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
Expand All @@ -38,6 +40,7 @@ import com.adamratzman.spotify.models.Track
import com.bobbyesp.metadator.R
import com.bobbyesp.metadator.ext.TagLib.toImageVector
import com.bobbyesp.metadator.ext.TagLib.toLocalizedName
import com.bobbyesp.metadator.ext.format
import com.bobbyesp.metadator.ext.formatArtistsName
import com.bobbyesp.metadator.presentation.components.image.AsyncImage
import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify.MetadataBsVM
Expand Down Expand Up @@ -99,7 +102,8 @@ fun SpMetadataBsDetails(
}
}
pageViewState.value.selectedTrack?.let { track ->
val metadataMap = createMetadataMap(track)
val context = LocalContext.current
val metadataMap = createMetadataMap(context, track)

TrackInfo(
modifier = Modifier.padding(vertical = 6.dp, horizontal = 8.dp),
Expand Down Expand Up @@ -154,15 +158,16 @@ fun SpMetadataBsDetails(
}

@Composable
fun createMetadataMap(track: Track) = rememberSaveable {
fun createMetadataMap(context: Context, track: Track) = rememberSaveable {
mutableMapOf(
"TITLE" to track.name,
"ARTIST" to track.artists.formatArtistsName(),
"ALBUM" to track.album.name,
"ALBUMARTIST" to track.album.artists.formatArtistsName(),
"TRACKNUMBER" to track.trackNumber.toString(),
"DISCNUMBER" to track.discNumber.toString(),
"DATE" to track.album.releaseDate.toString(),
"DATE" to (track.album.releaseDate?.format(track.album.releaseDatePrecisionString)
?: context.getString(R.string.unknown)),
)
}

Expand Down
45 changes: 29 additions & 16 deletions app/ui/src/main/java/com/bobbyesp/ui/common/pages/LoadingPage.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
package com.bobbyesp.ui.common.pages

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp

@Composable
fun LoadingPage() {
Column(
modifier = Modifier
fun LoadingPage(
modifier: Modifier = Modifier,
text: String
) {
Box(
modifier = modifier
.fillMaxSize()
.padding(16.dp)
.systemBarsPadding(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
.safeDrawingPadding(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
.align(Alignment.CenterHorizontally)
)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold
)
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(0.7f)
)
}
}
}
1 change: 1 addition & 0 deletions app/utilities/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
implementation(libs.paging.compose)
implementation(libs.paging.runtime)
implementation(libs.coil)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.bundles.coroutines)
//Accompanist libraries
implementation(libs.bundles.accompanist)
Expand Down
Loading

0 comments on commit 42e98e6

Please sign in to comment.