Skip to content

Commit

Permalink
New Feature: Introduce Upcoming page to Mihon (#420)
Browse files Browse the repository at this point in the history
* Work in progress upcoming feature

* Checkpointing WIP upcoming feature

* Functional Upcoming Screen

* Rename UpdateCalendar to UpdateUpcoming

* Converted Strings to resources

* Cleanup

* Fixed detekt issues

* Removed Link icon per @AntsyLich's suggestion.

* Detekt

* Fixed Calendar display on wide form factor devices

* Added Key to upcoming lazycolumn

* Updated tablet mode UI to support two column view

* Updated header creation logic

* Updated header creation logic... again

* Moved stray string to resources

* Fixed PR Comments and query refactor

* Tweaks to query, refactored to flow, comments on calendar

* Switched to Date Formatter

* Cleaned up date formatter

* More Refactor work

* Updated Calendar to support localized week formats

* Fixed year format

* Refactored Header animation

* Moved upcoming FAQ

* Completed YearMonth Migration

* Replaced currentYearMonth with delegate

* Even more cleanup

* cleaned up alignment modifiers

* Click Handler and other refactors

* Removed Wrapped Content Height/Size/extra clips

* Huge Refactor for CalendarDay

* Another cleanup attempt

* Migrated to new mihon.feature.* module pattern

* changed access modifier

* A Bunch of changes from the next round of reviews

* Cleanups

* Cleanup 2

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
  • Loading branch information
sirlag and AntsyLich authored Mar 28, 2024
1 parent 0265c16 commit 72222ad
Show file tree
Hide file tree
Showing 22 changed files with 797 additions and 2 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ dependencies {
implementation(libs.compose.materialmotion)
implementation(libs.swipe)
implementation(libs.compose.webview)
implementation(libs.compose.grid)


// Logging
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/eu/kanade/domain/DomainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import mihon.domain.extensionrepo.service.ExtensionRepoService
import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl
import tachiyomi.data.history.HistoryRepositoryImpl
Expand Down Expand Up @@ -117,6 +118,7 @@ class DomainModule : InjektModule {
addFactory { GetMangaByUrlAndSourceId(get()) }
addFactory { GetManga(get()) }
addFactory { GetNextChapters(get(), get(), get()) }
addFactory { GetUpcomingManga(get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) }
addFactory { FetchInterval(get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CalendarMonth
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll
Expand Down Expand Up @@ -47,6 +48,7 @@ fun UpdateScreen(
onClickCover: (UpdatesItem) -> Unit,
onSelectAll: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
onCalendarClicked: () -> Unit,
onUpdateLibrary: () -> Boolean,
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
Expand All @@ -60,6 +62,7 @@ fun UpdateScreen(
Scaffold(
topBar = { scrollBehavior ->
UpdatesAppBar(
onCalendarClicked = { onCalendarClicked() },
onUpdateLibrary = { onUpdateLibrary() },
actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) },
Expand Down Expand Up @@ -126,6 +129,7 @@ fun UpdateScreen(

@Composable
private fun UpdatesAppBar(
onCalendarClicked: () -> Unit,
onUpdateLibrary: () -> Unit,
// For action mode
actionModeCounter: Int,
Expand All @@ -141,6 +145,11 @@ private fun UpdatesAppBar(
actions = {
AppBarActions(
persistentListOf(
AppBar.Action(
title = stringResource(MR.strings.action_view_upcoming),
icon = Icons.Outlined.CalendarMonth,
onClick = onCalendarClicked,
),
AppBar.Action(
title = stringResource(MR.strings.action_update_library),
icon = Icons.Outlined.Refresh,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
import kotlinx.coroutines.flow.collectLatest
import mihon.feature.upcoming.UpcomingScreen
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
Expand Down Expand Up @@ -72,6 +73,7 @@ object UpdatesTab : Tab {
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
context.startActivity(intent)
},
onCalendarClicked = { navigator.push(UpcomingScreen()) },
)

val onDismissDialog = { screenModel.setDialog(null) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ fun Long.toLocalDate(): LocalDate {
return LocalDate.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
}

fun Instant.toLocalDate(zoneId: ZoneId = ZoneId.systemDefault()): LocalDate {
return LocalDate.ofInstant(this, zoneId)
}

fun LocalDate.toRelativeString(
context: Context,
relative: Boolean = true,
Expand All @@ -56,14 +60,12 @@ fun LocalDate.toRelativeString(
difference.toInt().absoluteValue,
difference.toInt().absoluteValue,
)

difference < 1 -> context.stringResource(MR.strings.relative_time_today)
difference < 7 -> context.pluralStringResource(
MR.plurals.relative_time,
difference.toInt(),
difference.toInt(),
)

else -> dateFormat.format(this)
}
}
23 changes: 23 additions & 0 deletions app/src/main/java/mihon/core/designsystem/utils/WindowSize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package mihon.core.designsystem.utils

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp

@Composable
@ReadOnlyComposable
fun isMediumWidthWindow(): Boolean {
val configuration = LocalConfiguration.current
return configuration.screenWidthDp > MediumWidthWindowSize.value
}

@Composable
@ReadOnlyComposable
fun isExpandedWidthWindow(): Boolean {
val configuration = LocalConfiguration.current
return configuration.screenWidthDp > ExpandedWidthWindowSize.value
}

val MediumWidthWindowSize = 600.dp
val ExpandedWidthWindowSize = 840.dp
27 changes: 27 additions & 0 deletions app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package mihon.feature.upcoming

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.manga.MangaScreen

class UpcomingScreen : Screen() {

@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow

val screenModel = rememberScreenModel { UpcomingScreenModel() }
val state by screenModel.state.collectAsState()

UpcomingScreenContent(
state = state,
setSelectedYearMonth = screenModel::setSelectedYearMonth,
onClickUpcoming = { navigator.push(MangaScreen(it.id)) },
)
}
}
198 changes: 198 additions & 0 deletions app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package mihon.feature.upcoming

import androidx.compose.foundation.layout.PaddingValues
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
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.relativeDateText
import eu.kanade.presentation.util.isTabletUi
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.coroutines.launch
import mihon.feature.upcoming.components.UpcomingItem
import mihon.feature.upcoming.components.calendar.Calendar
import tachiyomi.core.common.Constants
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
import java.time.YearMonth

@Composable
fun UpcomingScreenContent(
state: UpcomingScreenModel.State,
setSelectedYearMonth: (YearMonth) -> Unit,
onClickUpcoming: (manga: Manga) -> Unit,
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val onClickDay: (LocalDate, Int) -> Unit = { date, offset ->
state.headerIndexes[date]?.let {
scope.launch {
listState.animateScrollToItem(it + offset)
}
}
}
Scaffold(
topBar = { UpcomingToolbar() },
modifier = modifier,
) { paddingValues ->
if (isTabletUi()) {
UpcomingScreenLargeImpl(
listState = listState,
items = state.items,
events = state.events,
paddingValues = paddingValues,
selectedYearMonth = state.selectedYearMonth,
setSelectedYearMonth = setSelectedYearMonth,
onClickDay = { onClickDay(it, 0) },
onClickUpcoming = onClickUpcoming,
)
} else {
UpcomingScreenSmallImpl(
listState = listState,
items = state.items,
events = state.events,
paddingValues = paddingValues,
selectedYearMonth = state.selectedYearMonth,
setSelectedYearMonth = setSelectedYearMonth,
onClickDay = { onClickDay(it, 1) },
onClickUpcoming = onClickUpcoming,
)
}
}
}

@Composable
private fun UpcomingToolbar() {
val navigator = LocalNavigator.currentOrThrow
val uriHandler = LocalUriHandler.current

AppBar(
title = stringResource(MR.strings.label_upcoming),
navigateUp = navigator::pop,
actions = {
IconButton(onClick = { uriHandler.openUri(Constants.URL_HELP_UPCOMING) }) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
contentDescription = stringResource(MR.strings.upcoming_guide),
)
}
},
)
}

@Composable
private fun UpcomingScreenSmallImpl(
listState: LazyListState,
items: ImmutableList<UpcomingUIModel>,
events: ImmutableMap<LocalDate, Int>,
paddingValues: PaddingValues,
selectedYearMonth: YearMonth,
setSelectedYearMonth: (YearMonth) -> Unit,
onClickDay: (LocalDate) -> Unit,
onClickUpcoming: (manga: Manga) -> Unit,
) {
FastScrollLazyColumn(
contentPadding = paddingValues,
state = listState,
) {
item(key = "upcoming-calendar") {
Calendar(
selectedYearMonth = selectedYearMonth,
events = events,
setSelectedYearMonth = setSelectedYearMonth,
onClickDay = onClickDay,
)
}
items(
items = items,
key = { "upcoming-${it.hashCode()}" },
contentType = {
when (it) {
is UpcomingUIModel.Header -> "header"
is UpcomingUIModel.Item -> "item"
}
},
) { item ->
when (item) {
is UpcomingUIModel.Item -> {
UpcomingItem(
upcoming = item.manga,
onClick = { onClickUpcoming(item.manga) },
)
}
is UpcomingUIModel.Header -> {
ListGroupHeader(text = relativeDateText(item.date))
}
}
}
}
}

@Composable
private fun UpcomingScreenLargeImpl(
listState: LazyListState,
items: ImmutableList<UpcomingUIModel>,
events: ImmutableMap<LocalDate, Int>,
paddingValues: PaddingValues,
selectedYearMonth: YearMonth,
setSelectedYearMonth: (YearMonth) -> Unit,
onClickDay: (LocalDate) -> Unit,
onClickUpcoming: (manga: Manga) -> Unit,
) {
TwoPanelBox(
modifier = Modifier.padding(paddingValues),
startContent = {
Calendar(
selectedYearMonth = selectedYearMonth,
events = events,
setSelectedYearMonth = setSelectedYearMonth,
onClickDay = onClickDay,
)
},
endContent = {
FastScrollLazyColumn(state = listState) {
items(
items = items,
key = { "upcoming-${it.hashCode()}" },
contentType = {
when (it) {
is UpcomingUIModel.Header -> "header"
is UpcomingUIModel.Item -> "item"
}
},
) { item ->
when (item) {
is UpcomingUIModel.Item -> {
UpcomingItem(
upcoming = item.manga,
onClick = { onClickUpcoming(item.manga) },
)
}
is UpcomingUIModel.Header -> {
ListGroupHeader(text = relativeDateText(item.date))
}
}
}
}
},
)
}
Loading

0 comments on commit 72222ad

Please sign in to comment.