Compare commits

...

3 Commits

Author SHA1 Message Date
Matthew Witman
3789e13489 Updated tablet mode UI to support two column view 2024-02-18 21:36:17 -05:00
Matthew Witman
30ccce468e Added Key to upcoming lazycolumn 2024-02-18 15:33:08 -05:00
Matthew Witman
361bc4fe2e Fixed Calendar display on wide form factor devices 2024-02-18 15:29:02 -05:00
3 changed files with 238 additions and 31 deletions

View File

@@ -2,6 +2,10 @@ package eu.kanade.presentation.updates
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
@@ -13,7 +17,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBarTitle
@@ -29,12 +36,36 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import java.time.LocalDate
@Composable
fun UpdateUpcomingScreen(
state: UpdateUpcomingScreenModel.State,
modifier: Modifier = Modifier,
isTabletUi: Boolean = false,
onClickUpcoming: (manga: Manga) -> Unit = {},
) {
if (!isTabletUi) {
UpdateUpcomingScreenSmallImpl(
state = state,
modifier = modifier,
onClickUpcoming = onClickUpcoming,
)
} else {
UpdateUpcomingScreenLargeImpl(
state = state,
isTabletUi = isTabletUi,
modifier = modifier,
onClickUpcoming = onClickUpcoming,
)
}
}
@Composable
internal fun UpdateUpcomingScreenSmallImpl(
state: UpdateUpcomingScreenModel.State,
modifier: Modifier = Modifier,
onClickUpcoming: (manga: Manga) -> Unit = {},
@@ -46,7 +77,7 @@ fun UpdateUpcomingScreen(
},
) { paddingValues ->
UpdateUpcomingContent(
UpdateUpcomingSmallContent(
upcoming = state.items,
events = state.events,
contentPadding = paddingValues,
@@ -89,7 +120,7 @@ internal fun UpdateUpcomingToolbar(
}
@Composable
internal fun UpdateUpcomingContent(
internal fun UpdateUpcomingSmallContent(
upcoming: ImmutableList<UpcomingUIModel>,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
@@ -104,14 +135,19 @@ internal fun UpdateUpcomingContent(
.filter { it.value is UpcomingUIModel.Header }
.associate { Pair((it.value as UpcomingUIModel.Header).date, it.index + 1) } // Offset 1 for Calendar
val configuration = LocalConfiguration.current
FastScrollLazyColumn(
contentPadding = contentPadding,
state = listState,
modifier = modifier,
) {
item {
item(
key = "upcoming-calendar",
) {
Calendar(
events = events,
screenWidth = configuration.screenWidthDp.dp,
) { date ->
dateToHeaderMap[date]?.let {
coroutineScope.launch {
@@ -122,7 +158,119 @@ internal fun UpdateUpcomingContent(
}
items(
items = upcoming,
key = null,
key = { "upcoming-${it.hashCode()}" },
contentType = {
when (it) {
is UpcomingUIModel.Header -> "header"
is UpcomingUIModel.Item -> "item"
}
},
) { item ->
when (item) {
is UpcomingUIModel.Item -> {
UpcomingItem(
upcoming = item.item,
onClick = onClickUpcoming,
)
}
is UpcomingUIModel.Header -> {
ListGroupHeader(
modifier = Modifier.animateItemPlacement(),
text = relativeDateText(item.date),
)
}
}
}
}
}
@Composable
internal fun UpdateUpcomingScreenLargeImpl(
state: UpdateUpcomingScreenModel.State,
isTabletUi: Boolean,
modifier: Modifier = Modifier,
onClickUpcoming: (manga: Manga) -> Unit = {},
) {
val layoutDirection = LocalLayoutDirection.current
val listState = rememberLazyListState()
Scaffold(
modifier = modifier,
topBar = { UpdateUpcomingToolbar() },
) { contentPadding ->
TwoPanelBox(
modifier = Modifier.padding(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
),
startContent = {
UpdateUpcomingLargeCalendar(
upcoming = state.items,
listState = listState,
isTabletUi = isTabletUi,
events = state.events,
modifier = Modifier.padding(contentPadding),
)
},
endContent = {
UpdateUpcomingLargeContent(
upcoming = state.items,
listState = listState,
contentPadding = contentPadding,
onClickUpcoming = onClickUpcoming,
)
},
)
}
}
@Composable
internal fun UpdateUpcomingLargeCalendar(
upcoming: ImmutableList<UpcomingUIModel>,
isTabletUi: Boolean,
listState: LazyListState,
modifier: Modifier = Modifier,
events: ImmutableMap<LocalDate, Int> = persistentMapOf(),
) {
val configuration = LocalConfiguration.current
val coroutineScope = rememberCoroutineScope()
val dateToHeaderMap =
upcoming.withIndex()
.filter { it.value is UpcomingUIModel.Header }
.associate { Pair((it.value as UpcomingUIModel.Header).date, it.index) }
Calendar(
modifier = modifier,
events = events,
isTabletUi = isTabletUi,
screenWidth = configuration.screenWidthDp.dp,
) { date ->
dateToHeaderMap[date]?.let {
coroutineScope.launch {
listState.animateScrollToItem(it)
}
}
}
}
@Composable
internal fun UpdateUpcomingLargeContent(
upcoming: ImmutableList<UpcomingUIModel>,
listState: LazyListState,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
onClickUpcoming: (manga: Manga) -> Unit,
) {
FastScrollLazyColumn(
contentPadding = contentPadding,
state = listState,
modifier = modifier,
) {
items(
items = upcoming,
key = { "upcoming-${it.hashCode()}" },
contentType = {
when (it) {
is UpcomingUIModel.Header -> "header"

View File

@@ -1,6 +1,7 @@
package eu.kanade.presentation.updates.components.calendar
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -14,27 +15,35 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableList
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.Month
import java.time.format.TextStyle
import java.util.Locale
import kotlin.math.ceil
private val CalenderPadding = 8.dp
private val FontSize = 16.sp
private val CalculatedHeight = 302.dp
private const val ExtendedScale = 0.31f
private const val MediumScale = 0.60f
private const val HeightMultiplier = 68
private const val DaysOfWeek = 7
@Composable
fun Calendar(
events: ImmutableMap<LocalDate, Int>,
screenWidth: Dp,
modifier: Modifier = Modifier,
isTabletUi: Boolean = false,
labelFormat: (DayOfWeek) -> String = {
it.getDisplayName(
TextStyle.SHORT,
@@ -50,15 +59,19 @@ fun Calendar(
val currentMonth = displayedMonth.value
val currentYear = displayedYear.intValue
val daysInMonth = currentMonth.length(true)
val startDayOfMonth = LocalDate.of(currentYear, currentMonth, 1)
val firstDayOfMonth = startDayOfMonth.dayOfWeek
val widthModifier = when {
isTabletUi -> 1.0f
screenWidth > 840.dp -> ExtendedScale
screenWidth > 600.dp -> MediumScale
else -> 1.0f
}
Column(
modifier = modifier
.wrapContentHeight()
.fillMaxWidth()
.padding(all = CalenderPadding),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CalenderHeader(
month = currentMonth,
@@ -73,31 +86,75 @@ fun Calendar(
},
)
Spacer(modifier = Modifier.padding(vertical = 4.dp))
LazyVerticalGrid(
modifier = Modifier
.fillMaxWidth()
.height(CalculatedHeight),
columns = GridCells.Fixed(DaysOfWeek),
Row(
modifier = Modifier.padding(horizontal = 8.dp),
) {
items(weekValue) { item ->
Text(
modifier = Modifier,
text = labelFormat(item),
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
fontSize = FontSize,
)
}
CalendarGrid(
weekValue = weekValue,
labelFormat = labelFormat,
currentMonth = currentMonth,
currentYear = currentYear,
isTabletUi = isTabletUi,
events = events,
widthModifier = widthModifier,
onClickDay = onClickDay,
modifier = Modifier.padding(horizontal = 8.dp),
)
}
}
}
items((getFirstDayOfMonth(firstDayOfMonth)..daysInMonth).toList()) {
if (it > 0) {
val localDate = LocalDate.of(currentYear, currentMonth, it)
CalendarDay(
date = localDate,
onDayClick = onClickDay,
events = events[localDate] ?: 0,
)
}
@Composable
private fun CalendarGrid(
weekValue: Array<DayOfWeek>,
labelFormat: (DayOfWeek) -> String,
currentMonth: Month,
currentYear: Int,
isTabletUi: Boolean,
events: ImmutableMap<LocalDate, Int>,
modifier: Modifier = Modifier,
onClickDay: (day: LocalDate) -> Unit = {},
widthModifier: Float = 1.0F,
) {
val daysInMonth = currentMonth.length(true)
val startDayOfMonth = LocalDate.of(currentYear, currentMonth, 1)
val firstDayOfMonth = startDayOfMonth.dayOfWeek
val dayEntries = (getFirstDayOfMonth(firstDayOfMonth)..daysInMonth).toImmutableList()
val height = (((((dayEntries.size - 1) / DaysOfWeek) + ceil(1.0f - widthModifier)) * HeightMultiplier)).dp
val modeModifier = if (isTabletUi) {
modifier
.fillMaxWidth(widthModifier)
.wrapContentHeight()
} else {
modifier
.fillMaxWidth(widthModifier)
.height(height)
}
LazyVerticalGrid(
modifier = modeModifier,
columns = GridCells.Fixed(DaysOfWeek),
) {
items(weekValue) { item ->
Text(
modifier = Modifier,
text = labelFormat(item),
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
fontSize = FontSize,
)
}
items(dayEntries) {
if (it > 0) {
val localDate = LocalDate.of(currentYear, currentMonth, it)
CalendarDay(
date = localDate,
onDayClick = onClickDay,
events = events[localDate] ?: 0,
)
}
}
}

View File

@@ -8,6 +8,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.updates.UpdateUpcomingScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.ui.manga.MangaScreen
class UpdateUpcomingScreen : Screen() {
@@ -23,6 +24,7 @@ class UpdateUpcomingScreen : Screen() {
UpdateUpcomingScreen(
state = state,
isTabletUi = isTabletUi(),
onClickUpcoming = { navigator.push(MangaScreen(it.id)) },
)
}