add editing to lists

This commit is contained in:
Owen LeJeune
2023-05-30 21:31:15 -04:00
parent df38b4f679
commit 269acf707e
6 changed files with 239 additions and 76 deletions

View File

@@ -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
)

View File

@@ -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() }
)
}

View File

@@ -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 }

View File

@@ -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,

View File

@@ -93,6 +93,9 @@ object TmdbUtils {
}
fun convertRuntimeToHoursMinutes(series: DetailedTv): String {
if (series.episodeRuntime.isEmpty()) {
return ""
}
return convertRuntimeToHoursAndMinutes(series.episodeRuntime[0])
}

View File

@@ -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>