Support delete

This commit is contained in:
Damontecres 2026-03-21 16:38:11 -04:00
parent 803780894c
commit 64c6e4db79
No known key found for this signature in database
6 changed files with 216 additions and 22 deletions

View file

@ -1,5 +1,6 @@
package com.github.damontecres.wholphin.ui.detail package com.github.damontecres.wholphin.ui.detail
import android.content.Context
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.focusGroup import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
@ -60,8 +61,10 @@ import com.github.damontecres.wholphin.data.filter.ItemFilterBy
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaManagementService
import com.github.damontecres.wholphin.services.MediaReportService import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.MusicService import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.MusicServiceState import com.github.damontecres.wholphin.services.MusicServiceState
@ -70,6 +73,7 @@ import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.TimeFormatter import com.github.damontecres.wholphin.ui.TimeFormatter
import com.github.damontecres.wholphin.ui.cards.ItemCardImage import com.github.damontecres.wholphin.ui.cards.ItemCardImage
import com.github.damontecres.wholphin.ui.components.BasicDialog import com.github.damontecres.wholphin.ui.components.BasicDialog
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
@ -108,6 +112,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -128,16 +133,18 @@ import kotlin.time.Duration
class PlaylistViewModel class PlaylistViewModel
@AssistedInject @AssistedInject
constructor( constructor(
@ApplicationContext context: Context,
api: ApiClient, api: ApiClient,
navigationManager: NavigationManager, navigationManager: NavigationManager,
musicService: MusicService, musicService: MusicService,
mediaManagementService: MediaManagementService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
private val serverRepository: ServerRepository, private val serverRepository: ServerRepository,
private val libraryDisplayInfoDao: LibraryDisplayInfoDao, private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val mediaReportService: MediaReportService, private val mediaReportService: MediaReportService,
@Assisted val itemId: UUID, @Assisted itemId: UUID,
) : MusicViewModel(api, musicService, navigationManager) { ) : MusicViewModel(itemId, context, api, musicService, navigationManager, mediaManagementService) {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(itemId: UUID): PlaylistViewModel fun create(itemId: UUID): PlaylistViewModel
@ -147,6 +154,10 @@ class PlaylistViewModel
val musicState = musicService.state val musicState = musicService.state
init { init {
init()
}
override fun init() {
state.update { it.copy(loading = LoadingState.Loading) } state.update { it.copy(loading = LoadingState.Loading) }
viewModelScope.launchDefault { viewModelScope.launchDefault {
try { try {
@ -347,6 +358,7 @@ data class PlaylistDetailsState(
@Composable @Composable
fun PlaylistDetails( fun PlaylistDetails(
preferences: UserPreferences,
destination: Destination.MediaItem, destination: Destination.MediaItem,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: PlaylistViewModel = viewModel: PlaylistViewModel =
@ -392,7 +404,7 @@ fun PlaylistDetails(
} }
} }
} }
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
val musicMoreActions = val musicMoreActions =
MusicMoreDialogActions( MusicMoreDialogActions(
onNavigate = { viewModel.navigationManager.navigateTo(it) }, onNavigate = { viewModel.navigationManager.navigateTo(it) },
@ -405,6 +417,7 @@ fun PlaylistDetails(
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onClickRemoveFromQueue = {}, onClickRemoveFromQueue = {},
onClickDelete = { showDeleteDialog = it },
) )
val moreActions = val moreActions =
MoreDialogActions( MoreDialogActions(
@ -416,7 +429,7 @@ fun PlaylistDetails(
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onSendMediaInfo = viewModel::sendMediaReport, onSendMediaInfo = viewModel::sendMediaReport,
onClickDelete = {}, onClickDelete = { showDeleteDialog = it },
) )
PlaylistDetailsContent( PlaylistDetailsContent(
@ -446,6 +459,7 @@ fun PlaylistDetails(
item = item, item = item,
index = index, index = index,
canRemove = false, canRemove = false,
canDelete = viewModel.canDelete(item, preferences.appPreferences),
) )
} else { } else {
buildMoreDialogItemsForHome( buildMoreDialogItemsForHome(
@ -455,7 +469,7 @@ fun PlaylistDetails(
playbackPosition = item.playbackPosition, playbackPosition = item.playbackPosition,
watched = item.played, watched = item.played,
favorite = item.favorite, favorite = item.favorite,
canDelete = false, canDelete = viewModel.canDelete(item, preferences.appPreferences),
actions = moreActions, actions = moreActions,
) )
}, },
@ -495,6 +509,16 @@ fun PlaylistDetails(
elevation = 3.dp, elevation = 3.dp,
) )
} }
showDeleteDialog?.let { item ->
ConfirmDeleteDialog(
itemTitle = item.title ?: "",
onCancel = { showDeleteDialog = null },
onConfirm = {
viewModel.deleteItem(item)
showDeleteDialog = null
},
)
}
} }
@Composable @Composable

View file

@ -33,6 +33,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -46,9 +47,11 @@ import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.MediaManagementService
import com.github.damontecres.wholphin.services.MediaReportService import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.MusicService import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
@ -58,6 +61,7 @@ import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
@ -107,18 +111,19 @@ import java.util.UUID
class AlbumViewModel class AlbumViewModel
@AssistedInject @AssistedInject
constructor( constructor(
@ApplicationContext context: Context,
api: ApiClient, api: ApiClient,
musicService: MusicService, musicService: MusicService,
navigationManager: NavigationManager, navigationManager: NavigationManager,
@param:ApplicationContext private val context: Context, mediaManagementService: MediaManagementService,
val serverRepository: ServerRepository, val serverRepository: ServerRepository,
val mediaReportService: MediaReportService, val mediaReportService: MediaReportService,
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
private val imageUrlService: ImageUrlService, private val imageUrlService: ImageUrlService,
@Assisted val itemId: UUID, @Assisted itemId: UUID,
) : MusicViewModel(api, musicService, navigationManager) { ) : MusicViewModel(itemId, context, api, musicService, navigationManager, mediaManagementService) {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(itemId: UUID): AlbumViewModel fun create(itemId: UUID): AlbumViewModel
@ -130,6 +135,10 @@ class AlbumViewModel
val currentMusic = musicService.state val currentMusic = musicService.state
init { init {
init()
}
override fun init() {
viewModelScope.launchIO { viewModelScope.launchIO {
try { try {
val itemDeferred = val itemDeferred =
@ -280,6 +289,7 @@ private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1
@Composable @Composable
fun AlbumDetailsPage( fun AlbumDetailsPage(
itemId: UUID, itemId: UUID,
preferences: UserPreferences,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: AlbumViewModel = viewModel: AlbumViewModel =
hiltViewModel<AlbumViewModel, AlbumViewModel.Factory>( hiltViewModel<AlbumViewModel, AlbumViewModel.Factory>(
@ -292,9 +302,14 @@ fun AlbumDetailsPage(
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
val currentMusic by viewModel.currentMusic.collectAsState() val currentMusic by viewModel.currentMusic.collectAsState()
var position by rememberPosition(0, 0)
val focusRequesters =
remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
val focusManager = LocalFocusManager.current
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) } var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending) val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
var moreDialog by remember { mutableStateOf<DialogParams?>(null) } var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
val moreDialogActions = val moreDialogActions =
remember { remember {
MusicMoreDialogActions( MusicMoreDialogActions(
@ -308,6 +323,7 @@ fun AlbumDetailsPage(
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onClickRemoveFromQueue = {}, onClickRemoveFromQueue = {},
onClickDelete = { showDeleteDialog = it },
) )
} }
when (val loading = state.loading) { when (val loading = state.loading) {
@ -323,15 +339,13 @@ fun AlbumDetailsPage(
LoadingState.Success -> { LoadingState.Success -> {
val album = state.album!! val album = state.album!!
var position by rememberPosition(0, 0)
val firstFocusRequester = remember { FocusRequester() } val firstFocusRequester = remember { FocusRequester() }
val firstBringIntoViewRequester = remember { BringIntoViewRequester() } val firstBringIntoViewRequester = remember { BringIntoViewRequester() }
val bringIntoViewRequester = remember { BringIntoViewRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() }
val listState = rememberLazyListState() val listState = rememberLazyListState()
val itemsBefore = 2 val itemsBefore = 2
val focusRequesters =
remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
val songFocusRequester = remember { FocusRequester() } val songFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (position.row == SONG_ROW) { if (position.row == SONG_ROW) {
@ -399,6 +413,11 @@ fun AlbumDetailsPage(
item = album, item = album,
index = 0, index = 0,
canRemove = false, canRemove = false,
canDelete =
viewModel.canDelete(
album,
preferences.appPreferences,
),
), ),
) )
}, },
@ -451,6 +470,11 @@ fun AlbumDetailsPage(
item = song, item = song,
index = index, index = index,
canRemove = false, canRemove = false,
canDelete =
viewModel.canDelete(
song,
preferences.appPreferences,
),
), ),
) )
} }
@ -468,6 +492,11 @@ fun AlbumDetailsPage(
item = song, item = song,
index = index, index = index,
canRemove = false, canRemove = false,
canDelete =
viewModel.canDelete(
song,
preferences.appPreferences,
),
), ),
) )
} }
@ -527,7 +556,24 @@ fun AlbumDetailsPage(
viewModel.navigationManager.navigateTo(item.destination()) viewModel.navigationManager.navigateTo(item.destination())
}, },
onLongClickItem = { index, item -> onLongClickItem = { index, item ->
// TODO moreDialog =
DialogParams(
fromLongClick = true,
title = item.name ?: "",
items =
buildMoreDialogForMusic(
context = context,
actions = moreDialogActions,
item = item,
index = index,
canRemove = false,
canDelete =
viewModel.canDelete(
item,
preferences.appPreferences,
),
),
)
}, },
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
BannerCardWithTitle( BannerCardWithTitle(
@ -575,6 +621,17 @@ fun AlbumDetailsPage(
elevation = 3.dp, elevation = 3.dp,
) )
} }
showDeleteDialog?.let { item ->
ConfirmDeleteDialog(
itemTitle = item.title ?: "",
onCancel = { showDeleteDialog = null },
onConfirm = {
viewModel.deleteItem(item)
focusRequesters.getOrNull(position.row)?.tryRequestFocus()
showDeleteDialog = null
},
)
}
} }
@Composable @Composable

View file

@ -31,6 +31,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -44,9 +45,11 @@ import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.MediaManagementService
import com.github.damontecres.wholphin.services.MediaReportService import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.MusicService import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
@ -56,6 +59,7 @@ import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.components.ConfirmDeleteDialog
import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
@ -106,18 +110,19 @@ import java.util.UUID
class ArtistViewModel class ArtistViewModel
@AssistedInject @AssistedInject
constructor( constructor(
@ApplicationContext context: Context,
api: ApiClient, api: ApiClient,
musicService: MusicService, musicService: MusicService,
navigationManager: NavigationManager, navigationManager: NavigationManager,
@param:ApplicationContext private val context: Context, mediaManagementService: MediaManagementService,
val serverRepository: ServerRepository, val serverRepository: ServerRepository,
val mediaReportService: MediaReportService, val mediaReportService: MediaReportService,
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
private val imageUrlService: ImageUrlService, private val imageUrlService: ImageUrlService,
@Assisted val itemId: UUID, @Assisted itemId: UUID,
) : MusicViewModel(api, musicService, navigationManager) { ) : MusicViewModel(itemId, context, api, musicService, navigationManager, mediaManagementService) {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(itemId: UUID): ArtistViewModel fun create(itemId: UUID): ArtistViewModel
@ -129,6 +134,10 @@ class ArtistViewModel
val currentMusic = musicService.state val currentMusic = musicService.state
init { init {
init()
}
override fun init() {
viewModelScope.launchIO { viewModelScope.launchIO {
try { try {
val itemDeferred = val itemDeferred =
@ -230,7 +239,7 @@ class ArtistViewModel
} }
} }
fun init() { fun refresh() {
viewModelScope.launchDefault { viewModelScope.launchDefault {
state.value.artist?.let { state.value.artist?.let {
backdropService.submit(it) backdropService.submit(it)
@ -283,6 +292,7 @@ private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1
@Composable @Composable
fun ArtistDetailsPage( fun ArtistDetailsPage(
preferences: UserPreferences,
itemId: UUID, itemId: UUID,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: ArtistViewModel = viewModel: ArtistViewModel =
@ -292,13 +302,18 @@ fun ArtistDetailsPage(
playlistViewModel: AddPlaylistViewModel = hiltViewModel(), playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
) { ) {
val context = LocalContext.current val context = LocalContext.current
val focusManager = LocalFocusManager.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
val currentMusic by viewModel.currentMusic.collectAsState() val currentMusic by viewModel.currentMusic.collectAsState()
var position by rememberPosition(0, 0)
val focusRequesters =
remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) } var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending) val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
var moreDialog by remember { mutableStateOf<DialogParams?>(null) } var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
val moreDialogActions = val moreDialogActions =
remember { remember {
MusicMoreDialogActions( MusicMoreDialogActions(
@ -312,6 +327,7 @@ fun ArtistDetailsPage(
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onClickRemoveFromQueue = {}, onClickRemoveFromQueue = {},
onClickDelete = { showDeleteDialog = it },
) )
} }
@ -327,10 +343,7 @@ fun ArtistDetailsPage(
} }
LoadingState.Success -> { LoadingState.Success -> {
var position by rememberPosition(0, 0)
val artist = state.artist!! val artist = state.artist!!
val focusRequesters =
remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
val songFocusRequester = remember { FocusRequester() } val songFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (position.row == SONG_ROW) { if (position.row == SONG_ROW) {
@ -338,7 +351,7 @@ fun ArtistDetailsPage(
} else { } else {
focusRequesters.getOrNull(position.row)?.tryRequestFocus() focusRequesters.getOrNull(position.row)?.tryRequestFocus()
} }
viewModel.init() viewModel.refresh()
} }
val bringIntoViewRequester = remember { BringIntoViewRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() }
Box(modifier = modifier) { Box(modifier = modifier) {
@ -389,6 +402,11 @@ fun ArtistDetailsPage(
item = artist, item = artist,
index = 0, index = 0,
canRemove = false, canRemove = false,
canDelete =
viewModel.canDelete(
artist,
preferences.appPreferences,
),
), ),
) )
}, },
@ -438,6 +456,11 @@ fun ArtistDetailsPage(
item = song, item = song,
index = index, index = index,
canRemove = false, canRemove = false,
canDelete =
viewModel.canDelete(
song,
preferences.appPreferences,
),
), ),
) )
} }
@ -455,6 +478,11 @@ fun ArtistDetailsPage(
item = song, item = song,
index = index, index = index,
canRemove = false, canRemove = false,
canDelete =
viewModel.canDelete(
song,
preferences.appPreferences,
),
), ),
) )
} }
@ -482,7 +510,24 @@ fun ArtistDetailsPage(
viewModel.navigationManager.navigateTo(album.destination()) viewModel.navigationManager.navigateTo(album.destination())
}, },
onLongClickItem = { index, album -> onLongClickItem = { index, album ->
// TODO moreDialog =
DialogParams(
fromLongClick = true,
title = album.name ?: "",
items =
buildMoreDialogForMusic(
context = context,
actions = moreDialogActions,
item = album,
index = index,
canRemove = false,
canDelete =
viewModel.canDelete(
album,
preferences.appPreferences,
),
),
)
}, },
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
BannerCardWithTitle( BannerCardWithTitle(
@ -534,7 +579,24 @@ fun ArtistDetailsPage(
viewModel.navigationManager.navigateTo(item.destination()) viewModel.navigationManager.navigateTo(item.destination())
}, },
onLongClickItem = { index, item -> onLongClickItem = { index, item ->
// TODO moreDialog =
DialogParams(
fromLongClick = true,
title = item.name ?: "",
items =
buildMoreDialogForMusic(
context = context,
actions = moreDialogActions,
item = item,
index = index,
canRemove = false,
canDelete =
viewModel.canDelete(
item,
preferences.appPreferences,
),
),
)
}, },
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
BannerCardWithTitle( BannerCardWithTitle(
@ -582,6 +644,17 @@ fun ArtistDetailsPage(
elevation = 3.dp, elevation = 3.dp,
) )
} }
showDeleteDialog?.let { item ->
ConfirmDeleteDialog(
itemTitle = item.title ?: "",
onCancel = { showDeleteDialog = null },
onConfirm = {
viewModel.deleteItem(item)
focusRequesters.getOrNull(position.row)?.tryRequestFocus()
showDeleteDialog = null
},
)
}
} }
@Composable @Composable

View file

@ -1,10 +1,14 @@
package com.github.damontecres.wholphin.ui.detail.music package com.github.damontecres.wholphin.ui.detail.music
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.services.MediaManagementService
import com.github.damontecres.wholphin.services.MusicService import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.deleteItem
import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
@ -17,9 +21,12 @@ import timber.log.Timber
import java.util.UUID import java.util.UUID
abstract class MusicViewModel( abstract class MusicViewModel(
internal val itemId: UUID,
internal val context: Context,
internal val api: ApiClient, internal val api: ApiClient,
internal val musicService: MusicService, internal val musicService: MusicService,
internal val navigationManager: NavigationManager, internal val navigationManager: NavigationManager,
internal val mediaManagementService: MediaManagementService,
) : ViewModel() { ) : ViewModel() {
fun play( fun play(
pager: ApiRequestPager<*>, pager: ApiRequestPager<*>,
@ -115,4 +122,21 @@ abstract class MusicViewModel(
navigationManager.navigateTo(Destination.NowPlaying) navigationManager.navigateTo(Destination.NowPlaying)
} }
} }
fun canDelete(
item: BaseItem,
appPreferences: AppPreferences,
): Boolean = mediaManagementService.canDelete(item, appPreferences)
fun deleteItem(item: BaseItem) {
deleteItem(context, mediaManagementService, item) {
if (item.id == itemId) {
navigationManager.goBack()
} else {
init()
}
}
}
internal abstract fun init()
} }

View file

@ -37,6 +37,7 @@ data class MusicMoreDialogActions(
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST)) onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
}, },
val onClickRemoveFromQueue: (Int) -> Unit, val onClickRemoveFromQueue: (Int) -> Unit,
val onClickDelete: (BaseItem) -> Unit,
) )
fun buildMoreDialogForMusic( fun buildMoreDialogForMusic(
@ -45,6 +46,7 @@ fun buildMoreDialogForMusic(
item: BaseItem, item: BaseItem,
index: Int, index: Int,
canRemove: Boolean, canRemove: Boolean,
canDelete: Boolean,
): List<DialogItem> = ): List<DialogItem> =
buildList { buildList {
add( add(
@ -91,6 +93,17 @@ fun buildMoreDialogForMusic(
actions.onClickAddPlaylist.invoke(item.id) actions.onClickAddPlaylist.invoke(item.id)
}, },
) )
if (canDelete) {
add(
DialogItem(
context.getString(R.string.delete),
Icons.Default.Delete,
iconColor = Color.Red.copy(alpha = .8f),
) {
actions.onClickDelete.invoke(item)
},
)
}
add( add(
DialogItem( DialogItem(
text = if (item.favorite) R.string.remove_favorite else R.string.add_favorite, text = if (item.favorite) R.string.remove_favorite else R.string.add_favorite,

View file

@ -158,6 +158,7 @@ fun DestinationContent(
BaseItemKind.PLAYLIST -> { BaseItemKind.PLAYLIST -> {
LaunchedEffect(Unit) { onClearBackdrop.invoke() } LaunchedEffect(Unit) { onClearBackdrop.invoke() }
PlaylistDetails( PlaylistDetails(
preferences = preferences,
destination = destination, destination = destination,
modifier = modifier, modifier = modifier,
) )
@ -221,6 +222,7 @@ fun DestinationContent(
BaseItemKind.MUSIC_ALBUM -> { BaseItemKind.MUSIC_ALBUM -> {
LaunchedEffect(Unit) { onClearBackdrop.invoke() } LaunchedEffect(Unit) { onClearBackdrop.invoke() }
AlbumDetailsPage( AlbumDetailsPage(
preferences = preferences,
itemId = destination.itemId, itemId = destination.itemId,
modifier = modifier, modifier = modifier,
) )
@ -229,6 +231,7 @@ fun DestinationContent(
BaseItemKind.MUSIC_ARTIST -> { BaseItemKind.MUSIC_ARTIST -> {
LaunchedEffect(Unit) { onClearBackdrop.invoke() } LaunchedEffect(Unit) { onClearBackdrop.invoke() }
ArtistDetailsPage( ArtistDetailsPage(
preferences = preferences,
itemId = destination.itemId, itemId = destination.itemId,
modifier = modifier, modifier = modifier,
) )