mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-18 17:50:56 -05:00
add editing to lists
This commit is contained in:
@@ -3,8 +3,8 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4.model
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class ListUpdateBody(
|
||||
@SerializedName("description") val description: String?,
|
||||
@SerializedName("name") val name: String?,
|
||||
@SerializedName("public") val isPublic: Boolean?
|
||||
// @SerializedName("sort_by")
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("description") val description: String,
|
||||
@SerializedName("public") val isPublic: Boolean,
|
||||
@SerializedName("sort_by") val sortBy: SortOrder
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v4.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.owenlejeune.tvtime.R
|
||||
|
||||
class MediaList(
|
||||
@SerializedName("poster_path") val posterPath: String?,
|
||||
@@ -17,5 +18,52 @@ class MediaList(
|
||||
@SerializedName("average_rating") val averageRating: Float,
|
||||
@SerializedName("runtime") val runtime: Int,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("revenue") val revenue: Int
|
||||
)
|
||||
@SerializedName("revenue") val revenue: Int,
|
||||
@SerializedName("sort_by") val sortBy: SortOrder
|
||||
)
|
||||
|
||||
enum class SortOrder(
|
||||
val stringKey: Int,
|
||||
val sort: (List<ListItem>) -> List<ListItem>
|
||||
) {
|
||||
@SerializedName("original_order.asc")
|
||||
ORIGINAL_ASCENDING(
|
||||
R.string.sort_order_original_ascending,
|
||||
{ it }
|
||||
),
|
||||
@SerializedName("original_order.desc")
|
||||
ORIGINAL_DESCENDING(
|
||||
R.string.sort_order_original_descending,
|
||||
{ ORIGINAL_ASCENDING.sort(it).reversed() }
|
||||
),
|
||||
@SerializedName("vote_average.asc")
|
||||
RATING_ASCENDING(
|
||||
R.string.sort_order_rating_ascending,
|
||||
{ lin -> lin.sortedBy { it.voteAverage } }
|
||||
),
|
||||
@SerializedName("vote_average.desc")
|
||||
RATING_DESCENDING(
|
||||
R.string.sort_order_rating_descending,
|
||||
{ lin -> RATING_ASCENDING.sort(lin).reversed() }
|
||||
),
|
||||
@SerializedName("primary_release_date.asc")
|
||||
RELEASE_DATE_ASCENDING(
|
||||
R.string.sort_order_release_date_ascending,
|
||||
{ lin -> lin.sortedBy { it.releaseDate } }
|
||||
),
|
||||
@SerializedName("primary_release_date.desc")
|
||||
RELEASE_DATE_DESCENDING(
|
||||
R.string.sort_order_release_date_descending,
|
||||
{ lin -> RELEASE_DATE_ASCENDING.sort(lin).reversed() }
|
||||
),
|
||||
@SerializedName("title.asc")
|
||||
TITLE_ASCENDING(
|
||||
R.string.sort_order_title_ascending,
|
||||
{ lin -> lin.sortedBy { it.title } }
|
||||
),
|
||||
@SerializedName("title.desc")
|
||||
TITLE_DESCENDING(
|
||||
R.string.sort_order_title_descending,
|
||||
{ lin -> TITLE_ASCENDING.sort(lin).reversed() }
|
||||
)
|
||||
}
|
||||
@@ -16,15 +16,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -60,8 +58,6 @@ import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.navigation.NavHostController
|
||||
import coil.compose.AsyncImage
|
||||
@@ -73,9 +69,6 @@ import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
@@ -83,6 +76,7 @@ import org.intellij.markdown.html.HtmlGenerator
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
import org.koin.java.KoinJavaComponent
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TopLevelSwitch(
|
||||
text: String,
|
||||
@@ -94,14 +88,16 @@ fun TopLevelSwitch(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.padding(12.dp),
|
||||
shape = RoundedCornerShape(30.dp),
|
||||
backgroundColor = when {
|
||||
isSystemInDarkTheme() && checkedState.value -> MaterialTheme.colorScheme.primary
|
||||
isSystemInDarkTheme() && !checkedState.value -> MaterialTheme.colorScheme.secondary
|
||||
checkedState.value -> MaterialTheme.colorScheme.primaryContainer
|
||||
else -> MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
.padding(12.dp)
|
||||
.background(
|
||||
color = when {
|
||||
isSystemInDarkTheme() && checkedState.value -> MaterialTheme.colorScheme.primary
|
||||
isSystemInDarkTheme() && !checkedState.value -> MaterialTheme.colorScheme.secondary
|
||||
checkedState.value -> MaterialTheme.colorScheme.primaryContainer
|
||||
else -> MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
),
|
||||
shape = RoundedCornerShape(30.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -962,17 +958,40 @@ fun <T> Spinner(
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = selected.first,
|
||||
onValueChange = { },
|
||||
trailingIcon = { Icon(Icons.Outlined.ArrowDropDown, null) },
|
||||
readOnly = true,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = {
|
||||
expanded = true
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
.clickable {
|
||||
expanded = !expanded
|
||||
}
|
||||
)
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 8.dp, top = 16.dp, bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = selected.first,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Filled.ArrowDropUp else Icons.Filled.ArrowDropDown,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = {
|
||||
expanded = !expanded
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
|
||||
@@ -42,6 +42,8 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||
import com.owenlejeune.tvtime.extensions.unlessEmpty
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.Spinner
|
||||
import com.owenlejeune.tvtime.ui.components.SwitchPreference
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.theme.*
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
@@ -55,31 +57,6 @@ import kotlinx.coroutines.withContext
|
||||
import org.koin.java.KoinJavaComponent
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
enum class SortOrder(val stringKey: Int) {
|
||||
ORIGINAL(R.string.sort_order_original) {
|
||||
override fun sort(listIn: List<ListItem>): List<ListItem> {
|
||||
return listIn
|
||||
}
|
||||
},
|
||||
RATING(R.string.sort_order_rating) {
|
||||
override fun sort(listIn: List<ListItem>): List<ListItem> {
|
||||
return listIn.sortedBy { it.voteAverage }.reversed()
|
||||
}
|
||||
},
|
||||
RELEASE_DATE(R.string.sort_order_release_date) {
|
||||
override fun sort(listIn: List<ListItem>): List<ListItem> {
|
||||
return listIn.sortedBy { it.releaseDate }.reversed()
|
||||
}
|
||||
},
|
||||
TITLE(R.string.sort_order_title) {
|
||||
override fun sort(listIn: List<ListItem>): List<ListItem> {
|
||||
return listIn.sortedBy { it.title }
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun sort(listIn: List<ListItem>): List<ListItem>
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ListDetailView(
|
||||
@@ -136,10 +113,16 @@ fun ListDetailView(
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
val selectedSortOrder = remember { mutableStateOf(SortOrder.ORIGINAL) }
|
||||
ListHeader(list = mediaList, selectedSortOrder = selectedSortOrder)
|
||||
val selectedSortOrder = remember { mutableStateOf(mediaList.sortBy) }
|
||||
ListHeader(
|
||||
list = mediaList,
|
||||
selectedSortOrder = selectedSortOrder,
|
||||
service = service,
|
||||
parentList = parentList
|
||||
)
|
||||
|
||||
selectedSortOrder.value.sort(mediaList.results).forEach { listItem ->
|
||||
val sortedResults = selectedSortOrder.value.sort(mediaList.results)
|
||||
sortedResults.forEach { listItem ->
|
||||
ListItemView(
|
||||
appNavController = appNavController,
|
||||
listItem = listItem,
|
||||
@@ -152,11 +135,12 @@ fun ListDetailView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ListHeader(
|
||||
list: MediaList,
|
||||
selectedSortOrder: MutableState<SortOrder>
|
||||
selectedSortOrder: MutableState<SortOrder>,
|
||||
service: ListV4Service,
|
||||
parentList: MutableState<MediaList?>
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -222,14 +206,14 @@ private fun ListHeader(
|
||||
}
|
||||
|
||||
val showSortByOrderDialog = remember { mutableStateOf(false) }
|
||||
|
||||
val showEditListDialog = remember { mutableStateOf(false) }
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
onClick = { Toast.makeText(context, "Edit", Toast.LENGTH_SHORT).show() }
|
||||
onClick = { showEditListDialog.value = true }
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_edit))
|
||||
}
|
||||
@@ -250,7 +234,20 @@ private fun ListHeader(
|
||||
}
|
||||
|
||||
if (showSortByOrderDialog.value) {
|
||||
SortOrderDialog(showSortByOrderDialog = showSortByOrderDialog, selectedSortOrder = selectedSortOrder)
|
||||
SortOrderDialog(
|
||||
showSortByOrderDialog = showSortByOrderDialog,
|
||||
selectedSortOrder = selectedSortOrder
|
||||
)
|
||||
}
|
||||
|
||||
if (showEditListDialog.value) {
|
||||
EditListDialog(
|
||||
showEditListDialog = showEditListDialog,
|
||||
list = list,
|
||||
service = service,
|
||||
parentList = parentList,
|
||||
selectedSortOrder = selectedSortOrder
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,13 +259,19 @@ private fun SortOrderDialog(
|
||||
selectedSortOrder: MutableState<SortOrder>
|
||||
) {
|
||||
AlertDialog(
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
onDismissRequest = { showSortByOrderDialog.value = false },
|
||||
confirmButton = {},
|
||||
title = { Text(text = "Sort By") },
|
||||
dismissButton = { Text(text = "Dismiss") },
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = { showSortByOrderDialog.value = false }
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.action_dismiss))
|
||||
}
|
||||
},
|
||||
title = { Text(text = stringResource(id = R.string.action_sort_by)) },
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
SortOrder.values().forEach {
|
||||
Row(
|
||||
@@ -280,14 +283,14 @@ private fun SortOrderDialog(
|
||||
},
|
||||
role = Role.RadioButton
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedSortOrder.value == it,
|
||||
onClick = null
|
||||
)
|
||||
Text(text = stringResource(id = it.stringKey), fontSize = 20.sp)
|
||||
Text(text = stringResource(id = it.stringKey), fontSize = 18.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,6 +298,86 @@ private fun SortOrderDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditListDialog(
|
||||
showEditListDialog: MutableState<Boolean>,
|
||||
list: MediaList,
|
||||
service: ListV4Service,
|
||||
parentList: MutableState<MediaList?>,
|
||||
selectedSortOrder: MutableState<SortOrder>
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var listTitle by remember { mutableStateOf(list.name) }
|
||||
var listDescription by remember { mutableStateOf(list.description) }
|
||||
var isPublicList by remember { mutableStateOf(list.isPublic) }
|
||||
var editSelectedSortOrder by remember { mutableStateOf(list.sortBy) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { },
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = { showEditListDialog.value = false }
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.action_dismiss))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
val listUpdateBody = ListUpdateBody(listTitle, listDescription, isPublicList, editSelectedSortOrder)
|
||||
coroutineScope.launch {
|
||||
val response = service.updateList(list.id, listUpdateBody)
|
||||
if (response.isSuccessful) {
|
||||
fetchList(list.id, service, parentList)
|
||||
selectedSortOrder.value = editSelectedSortOrder
|
||||
}
|
||||
showEditListDialog.value = false
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.action_save))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = listTitle,
|
||||
onValueChange = { listTitle = it },
|
||||
singleLine = true,
|
||||
label = { Text(text = stringResource(id = R.string.label_name)) }
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = listDescription,
|
||||
onValueChange = { listDescription = it },
|
||||
modifier = Modifier.heightIn(min = 100.dp),
|
||||
label = { Text(text = stringResource(id = R.string.label_description)) }
|
||||
)
|
||||
|
||||
SwitchPreference(
|
||||
titleText = stringResource(id = R.string.label_public_list),
|
||||
checkState = isPublicList,
|
||||
onCheckedChange = { isPublicList = it }
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.action_sort_by),
|
||||
fontSize = 16.sp
|
||||
)
|
||||
Spinner(
|
||||
list = SortOrder.values().map { stringResource(id = it.stringKey) to it },
|
||||
preselected = Pair(stringResource(id = editSelectedSortOrder.stringKey), editSelectedSortOrder),
|
||||
onSelectionChanged = { editSelectedSortOrder = it.second }
|
||||
)
|
||||
}
|
||||
},
|
||||
title = { Text(text = stringResource(id = R.string.title_edit_list)) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.OverviewStatCard(
|
||||
top: String,
|
||||
|
||||
@@ -93,6 +93,9 @@ object TmdbUtils {
|
||||
}
|
||||
|
||||
fun convertRuntimeToHoursMinutes(series: DetailedTv): String {
|
||||
if (series.episodeRuntime.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
return convertRuntimeToHoursAndMinutes(series.episodeRuntime[0])
|
||||
}
|
||||
|
||||
|
||||
@@ -193,8 +193,18 @@
|
||||
<string name="action_sort_by">Sort By</string>
|
||||
<string name="action_share">Share</string>
|
||||
<string name="remove_from_list_cd">Remove from list</string>
|
||||
<string name="sort_order_original">Original</string>
|
||||
<string name="sort_order_rating">Rating</string>
|
||||
<string name="sort_order_release_date">Release Date</string>
|
||||
<string name="sort_order_title">Title</string>
|
||||
<string name="sort_order_original_ascending">Original (Ascending)</string>
|
||||
<string name="sort_order_original_descending">Original (Descending)</string>
|
||||
<string name="sort_order_rating_ascending">Rating (Ascending)</string>
|
||||
<string name="sort_order_rating_descending">Rating (Descending)</string>
|
||||
<string name="sort_order_release_date_ascending">Release Date (Ascending)</string>
|
||||
<string name="sort_order_release_date_descending">Release Date (Ascending)</string>
|
||||
<string name="sort_order_title_ascending">Title (A-Z)</string>
|
||||
<string name="sort_order_title_descending">Title (Z-A)</string>
|
||||
<string name="action_dismiss">Dismiss</string>
|
||||
<string name="action_save">Save</string>
|
||||
<string name="label_name">Name</string>
|
||||
<string name="label_description">Description</string>
|
||||
<string name="label_public_list">Public list?</string>
|
||||
<string name="title_edit_list">Edit List</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user