diff --git a/app/src/main/java/com/bobbyesp/metadator/ext/Song.kt b/app/src/main/java/com/bobbyesp/metadator/ext/Song.kt index 8fb350e..93554e6 100644 --- a/app/src/main/java/com/bobbyesp/metadator/ext/Song.kt +++ b/app/src/main/java/com/bobbyesp/metadator/ext/Song.kt @@ -4,11 +4,9 @@ import com.bobbyesp.metadator.model.ParcelableSong import com.bobbyesp.utilities.model.Song fun Song.toParcelableSong(): ParcelableSong { - val artistsList = this.artist.toList() - val mainArtist = artistsList.first().toString() return ParcelableSong( name = this.title, - mainArtist = mainArtist, + mainArtist = this.artist, localPath = this.path, artworkPath = this.artworkPath, filename = this.fileName diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt index bc8efc8..2709213 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt @@ -91,8 +91,8 @@ import com.bobbyesp.metadator.presentation.pages.mediaplayer.player.CollapsedPla import com.bobbyesp.metadator.presentation.pages.mediaplayer.player.PlayerAnimationSpec import com.bobbyesp.metadator.presentation.pages.settings.SettingsPage import com.bobbyesp.metadator.presentation.pages.settings.modules.GeneralSettingsPage -import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.rework.MetadataEditorPage -import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.rework.MetadataEditorVM +import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.MetadataEditorPage +import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.MetadataEditorVM import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify.MetadataBsVM import com.bobbyesp.ui.components.bottomsheet.draggable.rememberDraggableBottomSheetState import com.bobbyesp.ui.components.tags.RoundedTag diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt index 0c07c63..53cfe4d 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt @@ -63,8 +63,7 @@ fun MediaStorePage( when (type) { LayoutType.Grid -> { LazyVerticalGridScrollbar( - state = lazyGridState, - settings = ScrollbarSettings( + state = lazyGridState, settings = ScrollbarSettings( thumbUnselectedColor = MaterialTheme.colorScheme.onSurfaceVariant, thumbSelectedColor = MaterialTheme.colorScheme.primary, selectionActionable = ScrollbarSelectionActionable.WhenVisible, @@ -98,8 +97,7 @@ fun MediaStorePage( LayoutType.List -> { LazyColumnScrollbar( - state = lazyListState, - settings = ScrollbarSettings( + state = lazyListState, settings = ScrollbarSettings( thumbUnselectedColor = MaterialTheme.colorScheme.onSurfaceVariant, thumbSelectedColor = MaterialTheme.colorScheme.primary, selectionActionable = ScrollbarSelectionActionable.WhenVisible, @@ -115,15 +113,13 @@ fun MediaStorePage( key = { index -> songsList[index].id }, contentType = { index -> songsList[index].id.toString() }) { index -> val song = songsList[index] - HorizontalSongCard( - song = song, + HorizontalSongCard(song = song, modifier = Modifier.animateItem( fadeInSpec = null, fadeOutSpec = null ), onClick = { onItemClicked(song) - } - ) + }) } } } diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/ID3MetadataEditorPage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/ID3MetadataEditorPage.kt deleted file mode 100644 index cefc6d6..0000000 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/ID3MetadataEditorPage.kt +++ /dev/null @@ -1,552 +0,0 @@ -package com.bobbyesp.metadator.presentation.pages.utilities.tageditor -// -//import android.app.Activity -//import android.net.Uri -//import androidx.activity.compose.BackHandler -//import androidx.activity.compose.rememberLauncherForActivityResult -//import androidx.activity.result.IntentSenderRequest -//import androidx.activity.result.PickVisualMediaRequest -//import androidx.activity.result.contract.ActivityResultContracts -//import androidx.compose.animation.Crossfade -//import androidx.compose.animation.core.animateDpAsState -//import androidx.compose.animation.core.tween -//import androidx.compose.foundation.layout.Arrangement -//import androidx.compose.foundation.layout.Box -//import androidx.compose.foundation.layout.Column -//import androidx.compose.foundation.layout.Row -//import androidx.compose.foundation.layout.Spacer -//import androidx.compose.foundation.layout.aspectRatio -//import androidx.compose.foundation.layout.fillMaxSize -//import androidx.compose.foundation.layout.fillMaxWidth -//import androidx.compose.foundation.layout.height -//import androidx.compose.foundation.layout.navigationBarsPadding -//import androidx.compose.foundation.layout.padding -//import androidx.compose.foundation.layout.size -//import androidx.compose.foundation.layout.width -//import androidx.compose.foundation.rememberScrollState -//import androidx.compose.foundation.verticalScroll -//import androidx.compose.material.icons.Icons -//import androidx.compose.material.icons.rounded.Downloading -//import androidx.compose.material.icons.rounded.Edit -//import androidx.compose.material.icons.rounded.Warning -//import androidx.compose.material3.AlertDialog -//import androidx.compose.material3.BottomSheetScaffold -//import androidx.compose.material3.Button -//import androidx.compose.material3.ButtonDefaults -//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 -//import androidx.compose.material3.TextButton -//import androidx.compose.material3.TopAppBar -//import androidx.compose.material3.TopAppBarDefaults -//import androidx.compose.material3.rememberBottomSheetScaffoldState -//import androidx.compose.material3.rememberStandardBottomSheetState -//import androidx.compose.runtime.Composable -//import androidx.compose.runtime.LaunchedEffect -//import androidx.compose.runtime.getValue -//import androidx.compose.runtime.mutableStateOf -//import androidx.compose.runtime.remember -//import androidx.compose.runtime.rememberCoroutineScope -//import androidx.compose.runtime.setValue -//import androidx.compose.ui.Alignment -//import androidx.compose.ui.Modifier -//import androidx.compose.ui.draw.clip -//import androidx.compose.ui.graphics.Color -//import androidx.compose.ui.res.stringResource -//import androidx.compose.ui.text.font.FontWeight -//import androidx.compose.ui.unit.dp -//import androidx.compose.ui.unit.sp -//import androidx.lifecycle.compose.collectAsStateWithLifecycle -//import com.bobbyesp.metadator.R -//import com.bobbyesp.metadator.model.ParcelableSong -//import com.bobbyesp.metadator.presentation.common.LocalNavController -//import com.bobbyesp.metadator.presentation.components.image.AsyncImage -//import com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify.SpMetadataBottomSheetContent -//import com.bobbyesp.ui.components.button.CloseButton -//import com.bobbyesp.ui.components.others.MetadataTag -//import com.bobbyesp.ui.components.text.LargeCategoryTitle -//import com.bobbyesp.ui.components.text.MarqueeText -//import com.bobbyesp.ui.components.text.PreConfiguredOutlinedTextField -//import com.bobbyesp.utilities.ext.toAudioFileMetadata -//import com.bobbyesp.utilities.ext.toMinutes -//import com.bobbyesp.utilities.ext.toModifiableMap -//import com.bobbyesp.utilities.mediastore.AudioFileMetadata.Companion.toAudioFileMetadata -//import com.bobbyesp.utilities.mediastore.AudioFileMetadata.Companion.toPropertyMap -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.launch -// -//@OptIn(ExperimentalMaterial3Api::class) -//@Composable -//fun ID3MetadataEditorPage( -// viewModel: ID3MetadataEditorPageViewModel, parcelableSong: ParcelableSong -//) { -// val viewState = viewModel.pageViewState.collectAsStateWithLifecycle().value -// val navController = LocalNavController.current -// -// val path = parcelableSong.localPath -// -// val scope = rememberCoroutineScope() -// val scaffoldState = rememberBottomSheetScaffoldState( -// bottomSheetState = rememberStandardBottomSheetState( -// initialValue = SheetValue.Hidden, skipHiddenState = false -// ) -// ) -// -// val metadata = viewState.metadata -// val modifiablePropertyMap = viewState.metadata?.propertyMap?.toModifiableMap() -// -// var newArtworkAddress by remember { -// mutableStateOf(null) -// } -// -// LaunchedEffect(parcelableSong.localPath) { -// newArtworkAddress = null -// viewModel.loadTrackMetadata( -// path = parcelableSong.localPath -// ) -// } -// -// val sendActivityIntent = -// rememberLauncherForActivityResult(contract = ActivityResultContracts.StartIntentSenderForResult()) { result -> -// if (result.resultCode == Activity.RESULT_OK) { -// scope.launch(Dispatchers.IO) { -// modifiablePropertyMap?.let { newMetadata -> -// viewModel.saveMetadata( -// newMetadata = viewState.metadata.copy( -// propertyMap = newMetadata.toAudioFileMetadata().toPropertyMap() -// ), path = path, imageUri = newArtworkAddress -// ) -// } -// } -// navController.popBackStack() -// } -// } -// -// var showNotSavedChangesDialog by remember { mutableStateOf(false) } -// -// fun saveInMediaStore(): Boolean = viewModel.saveMetadata( -// newMetadata = viewState.metadata?.copy( -// propertyMap = modifiablePropertyMap!!.toAudioFileMetadata().toPropertyMap() -// )!!, path = path, imageUri = newArtworkAddress -// ) { -// val intent = IntentSenderRequest.Builder(it).build() -// sendActivityIntent.launch(intent) -// } -// -// val singleImagePickerLauncher = -// rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia(), -// onResult = { uri -> -// newArtworkAddress = uri -// }) -// -// -// BackHandler { -// if (scaffoldState.bottomSheetState.isVisible) { -// scope.launch { -// scaffoldState.bottomSheetState.hide() -// } -// } else { -// navController.popBackStack() -// } -// } -// -// -// BottomSheetScaffold( -// topBar = { -// TopAppBar(title = { -// Column(modifier = Modifier.fillMaxWidth()) { -// MarqueeText( -// text = stringResource(id = R.string.viewing_metadata), -// style = MaterialTheme.typography.bodyLarge, -// fontSize = 20.sp, -// fontWeight = FontWeight.Bold -// ) -// } -// }, navigationIcon = { -// CloseButton { navController.popBackStack() } -// }, actions = { -// IconButton(onClick = { -// scope.launch { -// scaffoldState.bottomSheetState.partialExpand() -// } -// }) { -// Icon( -// imageVector = Icons.Rounded.Downloading, -// contentDescription = stringResource( -// id = R.string.retrieve_song_info -// ) -// ) -// } -// TextButton(onClick = { -// val isInfoSavedInMediaStore = saveInMediaStore() -// if (isInfoSavedInMediaStore) { -// navController.popBackStack() -// } -// }) { -// Text(text = stringResource(id = R.string.save)) -// } -// }, scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()) -// }, -// modifier = Modifier.fillMaxSize(), -// scaffoldState = scaffoldState, -// sheetPeekHeight = 148.dp, -// sheetShadowElevation = 8.dp, -// sheetContent = { -// SpMetadataBottomSheetContent( -// name = modifiablePropertyMap?.get("TITLE") ?: "", -// artist = modifiablePropertyMap?.get("ARTIST") ?: "", -// sheetState = scaffoldState.bottomSheetState, -// onCloseSheet = { -// scope.launch { -// scaffoldState.bottomSheetState.hide() -// } -// } -// ) -// }) { innerPadding -> -// Crossfade( -// targetState = viewState.state, -// animationSpec = tween(175), -// label = "Fade between pages (ID3MetadataEditorPage)", -// modifier = Modifier -// .fillMaxSize() -// .navigationBarsPadding() -// ) { actualPageState -> -// when (actualPageState) { -// is ID3MetadataEditorPageViewModel.Companion.ID3MetadataEditorPageState.Loading -> { -// Column( -// modifier = Modifier.fillMaxSize(), -// verticalArrangement = Arrangement.spacedBy( -// 8.dp, alignment = Alignment.CenterVertically -// ), -// horizontalAlignment = Alignment.CenterHorizontally -// ) { -// Text( -// text = stringResource(id = R.string.loading_metadata), -// style = MaterialTheme.typography.bodyMedium, -// fontWeight = FontWeight.SemiBold -// ) -// LinearProgressIndicator( -// modifier = Modifier.fillMaxWidth(0.7f) -// ) -// } -// } -// -// is ID3MetadataEditorPageViewModel.Companion.ID3MetadataEditorPageState.Success -> { -// var showMediaStoreInfoDialog by remember { mutableStateOf(false) } -// -// val artworkUri = newArtworkAddress ?: parcelableSong.artworkPath -// -// val songProperties by remember { -// mutableStateOf(metadata!!.propertyMap.toAudioFileMetadata()) -// } -// val audioStats by remember { -// mutableStateOf(viewState.audioProperties!!) -// } -// -// val scrollState = rememberScrollState() -// -// Column( -// modifier = Modifier -// .fillMaxSize() -// .verticalScroll(scrollState) -// .padding(horizontal = 16.dp) -// ) { -// Box( -// modifier = Modifier -// .size(250.dp) -// .padding(8.dp) -// .padding(bottom = 8.dp) -// .aspectRatio(1f) -// .align(Alignment.CenterHorizontally), -// ) { -// AsyncImage( -// modifier = Modifier -// .fillMaxSize() -// .clip(MaterialTheme.shapes.small) -// .align(Alignment.Center), -// imageModel = artworkUri, -// ) -// Box( -// modifier = Modifier -// .fillMaxSize() -// .padding(8.dp), -// contentAlignment = Alignment.BottomEnd -// ) { -// IconButton(colors = IconButtonDefaults.iconButtonColors( -// containerColor = Color.Black.copy(alpha = 0.5f) -// ), onClick = { -// singleImagePickerLauncher.launch( -// PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) -// ) -// }) { -// Icon( -// imageVector = Icons.Rounded.Edit, -// contentDescription = stringResource(id = R.string.edit_image) -// ) -// } -// } -// } -// LargeCategoryTitle( -// modifier = Modifier.padding(vertical = 6.dp), -// text = stringResource(id = R.string.audio_details) -// ) -// -// Column( -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 12.dp), -// verticalArrangement = Arrangement.spacedBy(8.dp), -// horizontalAlignment = Alignment.CenterHorizontally -// ) { -// Row( -// modifier = Modifier -// .fillMaxWidth() -// .padding(bottom = 8.dp), -// ) { -// MetadataTag( -// modifier = Modifier.weight(0.5f), -// typeOfMetadata = stringResource(id = R.string.bitrate), -// metadata = audioStats.bitrate.toString() + " kbps" -// ) -// MetadataTag( -// modifier = Modifier.weight(0.5f), -// typeOfMetadata = stringResource(id = R.string.sample_rate), -// metadata = audioStats.sampleRate.toString() + " Hz" -// ) -// } -// Row( -// modifier = Modifier -// .fillMaxWidth() -// .padding(bottom = 8.dp), -// ) { -// MetadataTag( -// modifier = Modifier.weight(0.5f), -// typeOfMetadata = stringResource(id = R.string.channels), -// metadata = audioStats.channels.toString() -// ) -// MetadataTag( -// modifier = Modifier.weight(0.5f), -// typeOfMetadata = stringResource(id = R.string.duration), -// metadata = audioStats.length.toMinutes() -// ) -// } -// } -// -// -// LargeCategoryTitle( -// modifier = Modifier.padding(vertical = 6.dp), -// text = stringResource(id = R.string.general_tags) -// ) -// PreConfiguredOutlinedTextField( -// value = songProperties.title, -// label = stringResource(id = R.string.title), -// modifier = Modifier.fillMaxWidth() -// ) { title -> -// modifiablePropertyMap?.put("TITLE", title) -// } -// -// PreConfiguredOutlinedTextField( -// value = songProperties.artist, -// label = stringResource(id = R.string.artist), -// modifier = Modifier.fillMaxWidth() -// ) { artists -> -// modifiablePropertyMap?.put("ARTIST", artists) -// } -// -// PreConfiguredOutlinedTextField( -// value = songProperties.album, -// label = stringResource(id = R.string.album), -// modifier = Modifier.fillMaxWidth() -// ) { album -> -// modifiablePropertyMap?.put("ALBUM", album) -// } -// -// PreConfiguredOutlinedTextField( -// value = songProperties.albumArtist, -// label = stringResource(id = R.string.album_artist), -// modifier = Modifier.fillMaxWidth() -// ) { artists -> -// modifiablePropertyMap?.put("ALBUMARTIST", artists) -// } -// -// Column( -// modifier = Modifier.fillMaxWidth() -// ) { -// Row( -// modifier = Modifier.fillMaxWidth(), -// ) { -// PreConfiguredOutlinedTextField( -// value = songProperties.trackNumber, -// label = stringResource(id = R.string.track_number), -// modifier = Modifier.weight(0.5f) -// ) { trackNumber -> -// modifiablePropertyMap?.put("TRACKNUMBER", trackNumber) -// } -// Spacer(modifier = Modifier.width(8.dp)) -// PreConfiguredOutlinedTextField( -// value = songProperties.discNumber, -// label = stringResource(id = R.string.disc_number), -// modifier = Modifier.weight(0.5f) -// ) { discNumber -> -// modifiablePropertyMap?.put("DISCNUMBER", discNumber) -// } -// } -// Row( -// modifier = Modifier.fillMaxWidth(), -// ) { -// PreConfiguredOutlinedTextField( -// value = songProperties.date, -// label = stringResource(id = R.string.date), -// modifier = Modifier.weight(0.5f) -// ) { date -> -// modifiablePropertyMap?.put("DATE", date) -// } -// Spacer(modifier = Modifier.width(8.dp)) -// PreConfiguredOutlinedTextField( -// value = songProperties.genre, -// label = stringResource(id = R.string.genre), -// modifier = Modifier.weight(0.5f) -// ) { genre -> -// modifiablePropertyMap?.put("GENRE", genre) -// } -// } -// } -// LargeCategoryTitle( -// modifier = Modifier.padding(vertical = 6.dp), -// text = stringResource(id = R.string.credits) -// ) -// -// Column( -// modifier = Modifier.fillMaxWidth() -// ) { -// Row( -// modifier = Modifier.fillMaxWidth(), -// ) { -// PreConfiguredOutlinedTextField( -// value = songProperties.composer, -// label = stringResource(id = R.string.composer), -// modifier = Modifier.weight(0.5f) -// ) { composer -> -// modifiablePropertyMap?.put("COMPOSER", composer) -// } -// Spacer(modifier = Modifier.width(8.dp)) -// PreConfiguredOutlinedTextField( -// value = songProperties.lyricist, -// label = stringResource(id = R.string.lyricist), -// modifier = Modifier.weight(0.5f) -// ) { lyricist -> -// modifiablePropertyMap?.put("LYRICIST", lyricist) -// } -// } -// Row( -// modifier = Modifier.fillMaxWidth(), -// ) { -// PreConfiguredOutlinedTextField( -// value = songProperties.conductor, -// label = stringResource(id = R.string.conductor), -// modifier = Modifier.weight(0.5f) -// ) { conductor -> -// modifiablePropertyMap?.put("CONDUCTOR", conductor) -// } -// Spacer(modifier = Modifier.width(8.dp)) -// PreConfiguredOutlinedTextField( -// value = songProperties.remixer, -// label = stringResource(id = R.string.remixer), -// modifier = Modifier.weight(0.5f) -// ) { remixer -> -// modifiablePropertyMap?.put("REMIXER", remixer) -// } -// } -// PreConfiguredOutlinedTextField( -// value = songProperties.performer, -// label = stringResource(id = R.string.performer), -// modifier = Modifier.fillMaxWidth() -// ) { performer -> -// modifiablePropertyMap?.put("PERFORMER", performer) -// } -// } -// LargeCategoryTitle( -// modifier = Modifier.padding(vertical = 6.dp), -// text = stringResource(id = R.string.others) -// ) -// -// Column( -// modifier = Modifier.fillMaxWidth() -// ) { -// PreConfiguredOutlinedTextField( -// value = songProperties.comment, -// label = stringResource(id = R.string.comment), -// modifier = Modifier.fillMaxWidth(), -// maxLines = 3 -// ) { comment -> -// modifiablePropertyMap?.put("COMMENT", comment) -// } -// } -// -// val animatedBottomPadding by animateDpAsState( -// targetValue = if (scaffoldState.bottomSheetState.isVisible) innerPadding.calculateBottomPadding() + 6.dp else 0.dp, -// label = "animatedBottomPadding" -// ) -// Spacer(modifier = Modifier.height(animatedBottomPadding)) -// } -// -// if (showMediaStoreInfoDialog) { -// MediaStoreInfoDialog(onDismissRequest = { -// showMediaStoreInfoDialog = false -// }) -// } -// } -// -// is ID3MetadataEditorPageViewModel.Companion.ID3MetadataEditorPageState.Error -> Text( -// text = actualPageState.throwable.message ?: "Unknown error" -// ) -// } -// } -// } -// -// if (showNotSavedChangesDialog) { -// NotSavedChanges(onDismissChanges = { -// showNotSavedChangesDialog = false -// navController.popBackStack() -// }, onReturnToPage = { -// showNotSavedChangesDialog = false -// }) -// } -//} -// -//@Composable -//private fun NotSavedChanges( -// onDismissChanges: () -> Unit = {}, onReturnToPage: () -> Unit = {} -//) { -// AlertDialog(onDismissRequest = onReturnToPage, icon = { -// Icon( -// imageVector = Icons.Rounded.Warning, -// contentDescription = stringResource(id = R.string.warning) -// ) -// }, title = { -// Text(text = stringResource(id = R.string.unsaved_changes)) -// }, text = { -// Text( -// text = stringResource(id = R.string.unsaved_changes_info), -// style = MaterialTheme.typography.bodyMedium -// ) -// }, dismissButton = { -// TextButton( -// onClick = onReturnToPage, -// ) { -// Text(text = stringResource(id = R.string.return_str)) -// } -// }, confirmButton = { -// Button( -// onClick = onDismissChanges, -// colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error) -// ) { -// Text(text = stringResource(id = R.string.discard_changes)) -// } -// }) -//} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/ID3MetadataEditorPageViewModel.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/ID3MetadataEditorPageViewModel.kt deleted file mode 100644 index ed41ff0..0000000 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/ID3MetadataEditorPageViewModel.kt +++ /dev/null @@ -1,196 +0,0 @@ -package com.bobbyesp.metadator.presentation.pages.utilities.tageditor - -import android.app.PendingIntent -import android.app.RecoverableSecurityException -import android.content.Context -import android.net.Uri -import android.os.Build -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.bobbyesp.utilities.ext.toModifiableMap -import com.bobbyesp.utilities.mediastore.AudioFileMetadata.Companion.toAudioFileMetadata -import com.bobbyesp.utilities.mediastore.AudioFileMetadata.Companion.toPropertyMap -import com.bobbyesp.utilities.mediastore.MediaStoreReceiver -import com.kyant.taglib.AudioProperties -import com.kyant.taglib.AudioPropertiesReadStyle -import com.kyant.taglib.Metadata -import com.kyant.taglib.Picture -import com.kyant.taglib.TagLib -import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import javax.inject.Inject - -@HiltViewModel -class ID3MetadataEditorPageViewModel @Inject constructor( - @ApplicationContext private val context: Context, -) : ViewModel() { - private val mutablePageViewState = MutableStateFlow(PageViewState()) - val pageViewState = mutablePageViewState.asStateFlow() - - data class PageViewState( - val metadata: Metadata? = null, - val audioProperties: AudioProperties? = null, - val state: ID3MetadataEditorPageState = ID3MetadataEditorPageState.Loading, - ) - - suspend fun loadTrackMetadata(path: String) { - updateState(ID3MetadataEditorPageState.Loading) - runCatching { - MediaStoreReceiver.getFileDescriptorFromPath(context, path, mode = "r")?.use { songFd -> - val fd = songFd.dup()?.detachFd() - ?: throw IllegalStateException("File descriptor is null") - - val metadata = - withContext(viewModelScope.coroutineContext + Dispatchers.IO) { - async { - TagLib.getMetadata( - fd = fd, - ) - }.await() - } ?: throw IllegalStateException("Metadata is null") - - val fd2 = songFd.dup()?.detachFd() - ?: throw IllegalStateException("File descriptor is null") - val audioProperties = - withContext(viewModelScope.coroutineContext + Dispatchers.IO) { - async { - TagLib.getAudioProperties( - fd = fd2, - readStyle = AudioPropertiesReadStyle.Fast - ) - }.await() - } ?: throw IllegalStateException("Audio properties are null") - - updateStateMetadata(metadata, audioProperties) - - updateState(ID3MetadataEditorPageState.Success) - } - }.onFailure { error -> - Log.e( - "ID3MetadataEditorPageViewModel", - "Error while trying to load metadata: ${error.message}" - ) - updateState(ID3MetadataEditorPageState.Error(error)) - } - } - - fun saveMetadata( - context: Context = this.context, - newMetadata: Metadata, - path: String, - imageUri: Uri?, - intentPassthrough: (PendingIntent) -> Unit = {} - ): Boolean { - return try { - val fd = MediaStoreReceiver.getFileDescriptorFromPath(context, path, mode = "w") - ?: throw IOException("File descriptor is null") - - viewModelScope.launch(Dispatchers.IO) { - fd.dup()?.detachFd()?.let { - TagLib.savePropertyMap( - it, - propertyMap = newMetadata.propertyMap - ) - } - - imageUri?.let { - savePicture(context, it, fd.detachFd()) - } - } - true - } catch (securityException: SecurityException) { - handleSecurityException(securityException, intentPassthrough) - false - } catch (e: IOException) { - Log.e( - "ID3MetadataEditorPageViewModel", - "Error while trying to save metadata: ${e.message}" - ) - updateState(ID3MetadataEditorPageState.Error(e)) - false - } - } - - private fun handleSecurityException( - securityException: SecurityException, - intentPassthrough: (PendingIntent) -> Unit - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val recoverableSecurityException = securityException as? RecoverableSecurityException - ?: throw RuntimeException(securityException.message, securityException) - - intentPassthrough(recoverableSecurityException.userAction.actionIntent) - } else { - throw RuntimeException(securityException.message, securityException) - } - } - - private fun savePicture(context: Context, imageUri: Uri, fileDescriptorId: Int) { - val byteArray = context.contentResolver.openInputStream(imageUri)?.readBytes() ?: return - val mimeType = context.contentResolver.getType(imageUri) ?: return - val picture = Picture( - data = byteArray, - mimeType = mimeType, - description = "Song cover - Metadator", - pictureType = "Cover (front)" - ) - viewModelScope.launch(Dispatchers.IO) { - TagLib.savePictures( - fileDescriptorId, - pictures = arrayOf(picture) - ) - } - } - - fun updateStatePropertyMap(propertyMap: Map) { - val mutableStateMap = mutablePageViewState.value.metadata?.propertyMap?.toModifiableMap() - val updatedPropertyMap = mutableStateMap?.apply { - putAll(propertyMap) - } ?: propertyMap - - mutablePageViewState.update { - it.copy( - metadata = it.metadata?.copy( - propertyMap = updatedPropertyMap.toAudioFileMetadata().toPropertyMap() - ) - ) - } - } - - private fun updateState(state: ID3MetadataEditorPageState) { - mutablePageViewState.update { - it.copy( - state = state - ) - } - } - - private fun updateStateMetadata( - metadata: Metadata? = null, - audioProperties: AudioProperties? = null - ) { - mutablePageViewState.update { - it.copy( - metadata = metadata, - audioProperties = audioProperties - ) - } - } - - companion object { - sealed class ID3MetadataEditorPageState { - data object Loading : ID3MetadataEditorPageState() - data object Success : ID3MetadataEditorPageState() - data class Error(val throwable: Throwable) : ID3MetadataEditorPageState() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/rework/MetadataEditorPage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/MetadataEditorPage.kt similarity index 99% rename from app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/rework/MetadataEditorPage.kt rename to app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/MetadataEditorPage.kt index 553ce9a..a9195b6 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/rework/MetadataEditorPage.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/MetadataEditorPage.kt @@ -1,4 +1,4 @@ -package com.bobbyesp.metadator.presentation.pages.utilities.tageditor.rework +package com.bobbyesp.metadator.presentation.pages.utilities.tageditor import android.content.res.Configuration import android.net.Uri diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/rework/MetadataEditorVM.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/MetadataEditorVM.kt similarity index 99% rename from app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/rework/MetadataEditorVM.kt rename to app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/MetadataEditorVM.kt index 09d0d1c..d99f631 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/rework/MetadataEditorVM.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/MetadataEditorVM.kt @@ -1,4 +1,4 @@ -package com.bobbyesp.metadator.presentation.pages.utilities.tageditor.rework +package com.bobbyesp.metadator.presentation.pages.utilities.tageditor import android.app.PendingIntent import android.app.RecoverableSecurityException diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/spotify/SpMetadataBottomSheetContent.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/spotify/SpMetadataBottomSheetContent.kt index c545b5b..5f809b6 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/spotify/SpMetadataBottomSheetContent.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/utilities/tageditor/spotify/SpMetadataBottomSheetContent.kt @@ -1,5 +1,6 @@ package com.bobbyesp.metadator.presentation.pages.utilities.tageditor.spotify +import android.util.Log import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -46,6 +47,7 @@ fun SpMetadataBottomSheetContent( LaunchedEffect(sheetState.isVisible, name, artist) { val query = "$name $artist" + Log.i("SpMetadataBottomSheetContent", "Query: $query") if (sheetState.isVisible && bsViewState.value.lastQuery != query) { search(query) }