mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Use Stable interface for History screen (#7586)
- Adds Stable interface - Move last Dialog into Compose - Make History screen be full Compose screen
This commit is contained in:
		@@ -1,220 +1,89 @@
 | 
			
		||||
package eu.kanade.presentation.history
 | 
			
		||||
 | 
			
		||||
import androidx.compose.animation.core.LinearEasing
 | 
			
		||||
import androidx.compose.animation.core.animateFloat
 | 
			
		||||
import androidx.compose.animation.core.infiniteRepeatable
 | 
			
		||||
import androidx.compose.animation.core.rememberInfiniteTransition
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
import androidx.compose.foundation.layout.asPaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.navigationBars
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.lazy.rememberLazyListState
 | 
			
		||||
import androidx.compose.foundation.selection.toggleable
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.Checkbox
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.TextButton
 | 
			
		||||
import androidx.compose.foundation.layout.safeContentPadding
 | 
			
		||||
import androidx.compose.material3.Scaffold
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.geometry.Offset
 | 
			
		||||
import androidx.compose.ui.graphics.Brush.Companion.linearGradient
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import androidx.paging.LoadState
 | 
			
		||||
import androidx.paging.compose.LazyPagingItems
 | 
			
		||||
import androidx.paging.compose.collectAsLazyPagingItems
 | 
			
		||||
import androidx.paging.compose.items
 | 
			
		||||
import eu.kanade.domain.history.model.HistoryWithRelations
 | 
			
		||||
import eu.kanade.presentation.components.EmptyScreen
 | 
			
		||||
import eu.kanade.presentation.components.LoadingScreen
 | 
			
		||||
import eu.kanade.presentation.components.RelativeDateHeader
 | 
			
		||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
 | 
			
		||||
import eu.kanade.presentation.history.components.HistoryItem
 | 
			
		||||
import eu.kanade.presentation.history.components.HistoryItemShimmer
 | 
			
		||||
import eu.kanade.presentation.util.bottomNavPaddingValues
 | 
			
		||||
import eu.kanade.presentation.util.plus
 | 
			
		||||
import eu.kanade.presentation.util.shimmerGradient
 | 
			
		||||
import eu.kanade.presentation.util.topPaddingValues
 | 
			
		||||
import eu.kanade.presentation.history.components.HistoryContent
 | 
			
		||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
 | 
			
		||||
import eu.kanade.presentation.history.components.HistoryDeleteDialog
 | 
			
		||||
import eu.kanade.presentation.history.components.HistoryToolbar
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 | 
			
		||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryState
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.text.DateFormat
 | 
			
		||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter.Dialog
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryScreen(
 | 
			
		||||
    nestedScrollInterop: NestedScrollConnection,
 | 
			
		||||
    presenter: HistoryPresenter,
 | 
			
		||||
    onClickCover: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    onClickResume: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    onClickDelete: (HistoryWithRelations, Boolean) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val state by presenter.state.collectAsState()
 | 
			
		||||
    when (state) {
 | 
			
		||||
        is HistoryState.Loading -> LoadingScreen()
 | 
			
		||||
        is HistoryState.Error -> Text(text = (state as HistoryState.Error).error.message!!)
 | 
			
		||||
        is HistoryState.Success ->
 | 
			
		||||
            HistoryContent(
 | 
			
		||||
                nestedScroll = nestedScrollInterop,
 | 
			
		||||
                history = (state as HistoryState.Success).uiModels.collectAsLazyPagingItems(),
 | 
			
		||||
    val context = LocalContext.current
 | 
			
		||||
    Scaffold(
 | 
			
		||||
        modifier = Modifier.safeContentPadding(),
 | 
			
		||||
        topBar = {
 | 
			
		||||
            HistoryToolbar(state = presenter)
 | 
			
		||||
        },
 | 
			
		||||
    ) {
 | 
			
		||||
        val items = presenter.getLazyHistory()
 | 
			
		||||
        when {
 | 
			
		||||
            items.loadState.refresh is LoadState.Loading && items.itemCount < 1 -> LoadingScreen()
 | 
			
		||||
            items.loadState.refresh is LoadState.NotLoading && items.itemCount < 1 -> EmptyScreen(textResource = R.string.information_no_recent_manga)
 | 
			
		||||
            else -> HistoryContent(
 | 
			
		||||
                history = items,
 | 
			
		||||
                contentPadding = it,
 | 
			
		||||
                onClickCover = onClickCover,
 | 
			
		||||
                onClickResume = onClickResume,
 | 
			
		||||
                onClickDelete = onClickDelete,
 | 
			
		||||
                onClickDelete = { presenter.dialog = Dialog.Delete(it) },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryContent(
 | 
			
		||||
    history: LazyPagingItems<HistoryUiModel>,
 | 
			
		||||
    onClickCover: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    onClickResume: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    onClickDelete: (HistoryWithRelations, Boolean) -> Unit,
 | 
			
		||||
    preferences: PreferencesHelper = Injekt.get(),
 | 
			
		||||
    nestedScroll: NestedScrollConnection,
 | 
			
		||||
) {
 | 
			
		||||
    if (history.loadState.refresh is LoadState.NotLoading && history.itemCount == 0) {
 | 
			
		||||
        EmptyScreen(textResource = R.string.information_no_recent_manga)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val relativeTime: Int = remember { preferences.relativeTime().get() }
 | 
			
		||||
    val dateFormat: DateFormat = remember { preferences.dateFormat() }
 | 
			
		||||
 | 
			
		||||
    var removeState by remember { mutableStateOf<HistoryWithRelations?>(null) }
 | 
			
		||||
 | 
			
		||||
    val scrollState = rememberLazyListState()
 | 
			
		||||
 | 
			
		||||
    ScrollbarLazyColumn(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .nestedScroll(nestedScroll),
 | 
			
		||||
        contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
 | 
			
		||||
        state = scrollState,
 | 
			
		||||
    ) {
 | 
			
		||||
        items(history) { item ->
 | 
			
		||||
            when (item) {
 | 
			
		||||
                is HistoryUiModel.Header -> {
 | 
			
		||||
                    RelativeDateHeader(
 | 
			
		||||
                        modifier = Modifier
 | 
			
		||||
                            .animateItemPlacement(),
 | 
			
		||||
                        date = item.date,
 | 
			
		||||
                        relativeTime = relativeTime,
 | 
			
		||||
                        dateFormat = dateFormat,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                is HistoryUiModel.Item -> {
 | 
			
		||||
                    val value = item.item
 | 
			
		||||
                    HistoryItem(
 | 
			
		||||
                        modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                        history = value,
 | 
			
		||||
                        onClickCover = { onClickCover(value) },
 | 
			
		||||
                        onClickResume = { onClickResume(value) },
 | 
			
		||||
                        onClickDelete = { removeState = value },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                null -> {
 | 
			
		||||
                    val transition = rememberInfiniteTransition()
 | 
			
		||||
                    val translateAnimation = transition.animateFloat(
 | 
			
		||||
                        initialValue = 0f,
 | 
			
		||||
                        targetValue = 1000f,
 | 
			
		||||
                        animationSpec = infiniteRepeatable(
 | 
			
		||||
                            animation = tween(
 | 
			
		||||
                                durationMillis = 1000,
 | 
			
		||||
                                easing = LinearEasing,
 | 
			
		||||
                            ),
 | 
			
		||||
                        ),
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    val brush = remember {
 | 
			
		||||
                        linearGradient(
 | 
			
		||||
                            colors = shimmerGradient,
 | 
			
		||||
                            start = Offset(0f, 0f),
 | 
			
		||||
                            end = Offset(
 | 
			
		||||
                                x = translateAnimation.value,
 | 
			
		||||
                                y = 00f,
 | 
			
		||||
                            ),
 | 
			
		||||
                        )
 | 
			
		||||
    val onDismissRequest = { presenter.dialog = null }
 | 
			
		||||
    when (val dialog = presenter.dialog) {
 | 
			
		||||
        is Dialog.Delete -> {
 | 
			
		||||
            HistoryDeleteDialog(
 | 
			
		||||
                onDismissRequest = onDismissRequest,
 | 
			
		||||
                onDelete = { all ->
 | 
			
		||||
                    if (all) {
 | 
			
		||||
                        presenter.removeAllFromHistory(dialog.history.mangaId)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        presenter.removeFromHistory(dialog.history)
 | 
			
		||||
                    }
 | 
			
		||||
                    HistoryItemShimmer(brush = brush)
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        Dialog.DeleteAll -> {
 | 
			
		||||
            HistoryDeleteAllDialog(
 | 
			
		||||
                onDismissRequest = onDismissRequest,
 | 
			
		||||
                onDelete = {
 | 
			
		||||
                    presenter.deleteAllHistory()
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        else -> {}
 | 
			
		||||
    }
 | 
			
		||||
    LaunchedEffect(Unit) {
 | 
			
		||||
        presenter.events.collectLatest { event ->
 | 
			
		||||
            when (event) {
 | 
			
		||||
                HistoryPresenter.Event.InternalError -> context.toast(R.string.internal_error)
 | 
			
		||||
                HistoryPresenter.Event.NoNextChapterFound -> context.toast(R.string.no_next_chapter)
 | 
			
		||||
                is HistoryPresenter.Event.OpenChapter -> {
 | 
			
		||||
                    val intent = ReaderActivity.newIntent(context, event.chapter.mangaId, event.chapter.id)
 | 
			
		||||
                    context.startActivity(intent)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (removeState != null) {
 | 
			
		||||
        RemoveHistoryDialog(
 | 
			
		||||
            onPositive = { all ->
 | 
			
		||||
                onClickDelete(removeState!!, all)
 | 
			
		||||
                removeState = null
 | 
			
		||||
            },
 | 
			
		||||
            onNegative = { removeState = null },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun RemoveHistoryDialog(
 | 
			
		||||
    onPositive: (Boolean) -> Unit,
 | 
			
		||||
    onNegative: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    var removeEverything by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
    AlertDialog(
 | 
			
		||||
        title = {
 | 
			
		||||
            Text(text = stringResource(R.string.action_remove))
 | 
			
		||||
        },
 | 
			
		||||
        text = {
 | 
			
		||||
            Column {
 | 
			
		||||
                Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
 | 
			
		||||
                Row(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .padding(top = 16.dp)
 | 
			
		||||
                        .toggleable(
 | 
			
		||||
                            interactionSource = remember { MutableInteractionSource() },
 | 
			
		||||
                            indication = null,
 | 
			
		||||
                            value = removeEverything,
 | 
			
		||||
                            onValueChange = { removeEverything = it },
 | 
			
		||||
                        ),
 | 
			
		||||
                    verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
                ) {
 | 
			
		||||
                    Checkbox(
 | 
			
		||||
                        checked = removeEverything,
 | 
			
		||||
                        onCheckedChange = null,
 | 
			
		||||
                    )
 | 
			
		||||
                    Text(
 | 
			
		||||
                        modifier = Modifier.padding(start = 4.dp),
 | 
			
		||||
                        text = stringResource(R.string.dialog_with_checkbox_reset),
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        onDismissRequest = onNegative,
 | 
			
		||||
        confirmButton = {
 | 
			
		||||
            TextButton(onClick = { onPositive(removeEverything) }) {
 | 
			
		||||
                Text(text = stringResource(R.string.action_remove))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dismissButton = {
 | 
			
		||||
            TextButton(onClick = onNegative) {
 | 
			
		||||
                Text(text = stringResource(R.string.action_cancel))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sealed class HistoryUiModel {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
package eu.kanade.presentation.history.components
 | 
			
		||||
 | 
			
		||||
import androidx.compose.animation.core.LinearEasing
 | 
			
		||||
import androidx.compose.animation.core.animateFloat
 | 
			
		||||
import androidx.compose.animation.core.infiniteRepeatable
 | 
			
		||||
import androidx.compose.animation.core.rememberInfiniteTransition
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.lazy.rememberLazyListState
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.geometry.Offset
 | 
			
		||||
import androidx.compose.ui.graphics.Brush
 | 
			
		||||
import androidx.paging.compose.LazyPagingItems
 | 
			
		||||
import androidx.paging.compose.items
 | 
			
		||||
import eu.kanade.domain.history.model.HistoryWithRelations
 | 
			
		||||
import eu.kanade.presentation.components.RelativeDateHeader
 | 
			
		||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
 | 
			
		||||
import eu.kanade.presentation.history.HistoryUiModel
 | 
			
		||||
import eu.kanade.presentation.util.bottomNavPaddingValues
 | 
			
		||||
import eu.kanade.presentation.util.plus
 | 
			
		||||
import eu.kanade.presentation.util.shimmerGradient
 | 
			
		||||
import eu.kanade.presentation.util.topPaddingValues
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.text.DateFormat
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryContent(
 | 
			
		||||
    history: LazyPagingItems<HistoryUiModel>,
 | 
			
		||||
    contentPadding: PaddingValues,
 | 
			
		||||
    onClickCover: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    onClickResume: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    onClickDelete: (HistoryWithRelations) -> Unit,
 | 
			
		||||
    preferences: PreferencesHelper = Injekt.get(),
 | 
			
		||||
) {
 | 
			
		||||
    val relativeTime: Int = remember { preferences.relativeTime().get() }
 | 
			
		||||
    val dateFormat: DateFormat = remember { preferences.dateFormat() }
 | 
			
		||||
 | 
			
		||||
    ScrollbarLazyColumn(
 | 
			
		||||
        contentPadding = contentPadding + bottomNavPaddingValues + topPaddingValues,
 | 
			
		||||
        state = rememberLazyListState(),
 | 
			
		||||
    ) {
 | 
			
		||||
        items(history) { item ->
 | 
			
		||||
            when (item) {
 | 
			
		||||
                is HistoryUiModel.Header -> {
 | 
			
		||||
                    RelativeDateHeader(
 | 
			
		||||
                        modifier = Modifier
 | 
			
		||||
                            .animateItemPlacement(),
 | 
			
		||||
                        date = item.date,
 | 
			
		||||
                        relativeTime = relativeTime,
 | 
			
		||||
                        dateFormat = dateFormat,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                is HistoryUiModel.Item -> {
 | 
			
		||||
                    val value = item.item
 | 
			
		||||
                    HistoryItem(
 | 
			
		||||
                        modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                        history = value,
 | 
			
		||||
                        onClickCover = { onClickCover(value) },
 | 
			
		||||
                        onClickResume = { onClickResume(value) },
 | 
			
		||||
                        onClickDelete = { onClickDelete(value) },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                null -> {
 | 
			
		||||
                    val transition = rememberInfiniteTransition()
 | 
			
		||||
                    val translateAnimation = transition.animateFloat(
 | 
			
		||||
                        initialValue = 0f,
 | 
			
		||||
                        targetValue = 1000f,
 | 
			
		||||
                        animationSpec = infiniteRepeatable(
 | 
			
		||||
                            animation = tween(
 | 
			
		||||
                                durationMillis = 1000,
 | 
			
		||||
                                easing = LinearEasing,
 | 
			
		||||
                            ),
 | 
			
		||||
                        ),
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    val brush = remember {
 | 
			
		||||
                        Brush.linearGradient(
 | 
			
		||||
                            colors = shimmerGradient,
 | 
			
		||||
                            start = Offset(0f, 0f),
 | 
			
		||||
                            end = Offset(
 | 
			
		||||
                                x = translateAnimation.value,
 | 
			
		||||
                                y = 00f,
 | 
			
		||||
                            ),
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    HistoryItemShimmer(brush = brush)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,103 @@
 | 
			
		||||
package eu.kanade.presentation.history.components
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.selection.toggleable
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.Checkbox
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.TextButton
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryDeleteDialog(
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onDelete: (Boolean) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    var removeEverything by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
    AlertDialog(
 | 
			
		||||
        title = {
 | 
			
		||||
            Text(text = stringResource(R.string.action_remove))
 | 
			
		||||
        },
 | 
			
		||||
        text = {
 | 
			
		||||
            Column {
 | 
			
		||||
                Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
 | 
			
		||||
                Row(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .padding(top = 16.dp)
 | 
			
		||||
                        .toggleable(
 | 
			
		||||
                            interactionSource = remember { MutableInteractionSource() },
 | 
			
		||||
                            indication = null,
 | 
			
		||||
                            value = removeEverything,
 | 
			
		||||
                            onValueChange = { removeEverything = it },
 | 
			
		||||
                        ),
 | 
			
		||||
                    verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
                ) {
 | 
			
		||||
                    Checkbox(
 | 
			
		||||
                        checked = removeEverything,
 | 
			
		||||
                        onCheckedChange = null,
 | 
			
		||||
                    )
 | 
			
		||||
                    Text(
 | 
			
		||||
                        modifier = Modifier.padding(start = 4.dp),
 | 
			
		||||
                        text = stringResource(R.string.dialog_with_checkbox_reset),
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        onDismissRequest = onDismissRequest,
 | 
			
		||||
        confirmButton = {
 | 
			
		||||
            TextButton(onClick = {
 | 
			
		||||
                onDelete(removeEverything)
 | 
			
		||||
                onDismissRequest()
 | 
			
		||||
            },) {
 | 
			
		||||
                Text(text = stringResource(R.string.action_remove))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dismissButton = {
 | 
			
		||||
            TextButton(onClick = onDismissRequest) {
 | 
			
		||||
                Text(text = stringResource(android.R.string.cancel))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryDeleteAllDialog(
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onDelete: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    AlertDialog(
 | 
			
		||||
        title = {
 | 
			
		||||
            Text(text = stringResource(R.string.action_remove_everything))
 | 
			
		||||
        },
 | 
			
		||||
        text = {
 | 
			
		||||
            Text(text = stringResource(R.string.clear_history_confirmation))
 | 
			
		||||
        },
 | 
			
		||||
        onDismissRequest = onDismissRequest,
 | 
			
		||||
        confirmButton = {
 | 
			
		||||
            TextButton(onClick = {
 | 
			
		||||
                onDelete()
 | 
			
		||||
                onDismissRequest()
 | 
			
		||||
            },) {
 | 
			
		||||
                Text(text = stringResource(android.R.string.ok))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        dismissButton = {
 | 
			
		||||
            TextButton(onClick = onDismissRequest) {
 | 
			
		||||
                Text(text = stringResource(android.R.string.cancel))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
package eu.kanade.presentation.history.components
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.text.BasicTextField
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.outlined.ArrowBack
 | 
			
		||||
import androidx.compose.material.icons.outlined.DeleteSweep
 | 
			
		||||
import androidx.compose.material.icons.outlined.Search
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.IconButton
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.SmallTopAppBar
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.focus.FocusRequester
 | 
			
		||||
import androidx.compose.ui.focus.focusRequester
 | 
			
		||||
import androidx.compose.ui.graphics.SolidColor
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryState
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryToolbar(
 | 
			
		||||
    state: HistoryState,
 | 
			
		||||
) {
 | 
			
		||||
    if (state.searchQuery == null) {
 | 
			
		||||
        HistoryRegularToolbar(
 | 
			
		||||
            onClickSearch = { state.searchQuery = "" },
 | 
			
		||||
            onClickDelete = { state.dialog = HistoryPresenter.Dialog.DeleteAll },
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
        HistorySearchToolbar(
 | 
			
		||||
            searchQuery = state.searchQuery!!,
 | 
			
		||||
            onChangeSearchQuery = { state.searchQuery = it },
 | 
			
		||||
            onClickCloseSearch = { state.searchQuery = null },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistoryRegularToolbar(
 | 
			
		||||
    onClickSearch: () -> Unit,
 | 
			
		||||
    onClickDelete: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    SmallTopAppBar(
 | 
			
		||||
        title = {
 | 
			
		||||
            Text(text = stringResource(id = R.string.history))
 | 
			
		||||
        },
 | 
			
		||||
        actions = {
 | 
			
		||||
            IconButton(onClick = onClickSearch) {
 | 
			
		||||
                Icon(Icons.Outlined.Search, contentDescription = "search")
 | 
			
		||||
            }
 | 
			
		||||
            IconButton(onClick = onClickDelete) {
 | 
			
		||||
                Icon(Icons.Outlined.DeleteSweep, contentDescription = "delete")
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun HistorySearchToolbar(
 | 
			
		||||
    searchQuery: String,
 | 
			
		||||
    onChangeSearchQuery: (String) -> Unit,
 | 
			
		||||
    onClickCloseSearch: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val focusRequester = remember { FocusRequester.Default }
 | 
			
		||||
    SmallTopAppBar(
 | 
			
		||||
        navigationIcon = {
 | 
			
		||||
            IconButton(onClick = onClickCloseSearch) {
 | 
			
		||||
                Icon(Icons.Outlined.ArrowBack, contentDescription = "delete")
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        title = {
 | 
			
		||||
            BasicTextField(
 | 
			
		||||
                value = searchQuery,
 | 
			
		||||
                onValueChange = onChangeSearchQuery,
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .fillMaxWidth()
 | 
			
		||||
                    .focusRequester(focusRequester),
 | 
			
		||||
                textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onBackground),
 | 
			
		||||
                singleLine = true,
 | 
			
		||||
                cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
 | 
			
		||||
            )
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    LaunchedEffect(focusRequester) {
 | 
			
		||||
        // TODO: https://issuetracker.google.com/issues/204502668
 | 
			
		||||
        delay(100)
 | 
			
		||||
        focusRequester.requestFocus()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.recent.history
 | 
			
		||||
 | 
			
		||||
import android.app.Dialog
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
 | 
			
		||||
 | 
			
		||||
class ClearHistoryDialogController : DialogController() {
 | 
			
		||||
    override fun onCreateDialog(savedViewState: Bundle?): Dialog {
 | 
			
		||||
        return MaterialAlertDialogBuilder(activity!!)
 | 
			
		||||
            .setMessage(R.string.clear_history_confirmation)
 | 
			
		||||
            .setPositiveButton(android.R.string.ok) { _, _ ->
 | 
			
		||||
                (targetController as? HistoryController)
 | 
			
		||||
                    ?.presenter
 | 
			
		||||
                    ?.deleteAllHistory()
 | 
			
		||||
            }
 | 
			
		||||
            .setNegativeButton(android.R.string.cancel, null)
 | 
			
		||||
            .create()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,37 +1,19 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.recent.history
 | 
			
		||||
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.MenuInflater
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import androidx.appcompat.widget.SearchView
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import eu.kanade.domain.chapter.model.Chapter
 | 
			
		||||
import eu.kanade.presentation.history.HistoryScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
import kotlinx.coroutines.flow.filter
 | 
			
		||||
import kotlinx.coroutines.flow.launchIn
 | 
			
		||||
import kotlinx.coroutines.flow.onEach
 | 
			
		||||
import reactivecircus.flowbinding.appcompat.queryTextChanges
 | 
			
		||||
 | 
			
		||||
class HistoryController : ComposeController<HistoryPresenter>(), RootController {
 | 
			
		||||
 | 
			
		||||
    private var query = ""
 | 
			
		||||
 | 
			
		||||
    override fun getTitle() = resources?.getString(R.string.label_recent_manga)
 | 
			
		||||
class HistoryController : FullComposeController<HistoryPresenter>(), RootController {
 | 
			
		||||
 | 
			
		||||
    override fun createPresenter() = HistoryPresenter()
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
 | 
			
		||||
    override fun ComposeContent() {
 | 
			
		||||
        HistoryScreen(
 | 
			
		||||
            nestedScrollInterop = nestedScrollInterop,
 | 
			
		||||
            presenter = presenter,
 | 
			
		||||
            onClickCover = { history ->
 | 
			
		||||
                router.pushController(MangaController(history.mangaId))
 | 
			
		||||
@@ -39,59 +21,9 @@ class HistoryController : ComposeController<HistoryPresenter>(), RootController
 | 
			
		||||
            onClickResume = { history ->
 | 
			
		||||
                presenter.getNextChapterForManga(history.mangaId, history.chapterId)
 | 
			
		||||
            },
 | 
			
		||||
            onClickDelete = { history, all ->
 | 
			
		||||
                if (all) {
 | 
			
		||||
                    // Reset last read of chapter to 0L
 | 
			
		||||
                    presenter.removeAllFromHistory(history.mangaId)
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Remove all chapters belonging to manga from library
 | 
			
		||||
                    presenter.removeFromHistory(history)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
			
		||||
        inflater.inflate(R.menu.history, menu)
 | 
			
		||||
        val searchItem = menu.findItem(R.id.action_search)
 | 
			
		||||
        val searchView = searchItem.actionView as SearchView
 | 
			
		||||
        searchView.maxWidth = Int.MAX_VALUE
 | 
			
		||||
        if (query.isNotEmpty()) {
 | 
			
		||||
            searchItem.expandActionView()
 | 
			
		||||
            searchView.setQuery(query, true)
 | 
			
		||||
            searchView.clearFocus()
 | 
			
		||||
        }
 | 
			
		||||
        searchView.queryTextChanges()
 | 
			
		||||
            .filter { router.backstack.lastOrNull()?.controller == this }
 | 
			
		||||
            .onEach {
 | 
			
		||||
                query = it.toString()
 | 
			
		||||
                presenter.search(query)
 | 
			
		||||
            }
 | 
			
		||||
            .launchIn(viewScope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
        return when (item.itemId) {
 | 
			
		||||
            R.id.action_clear_history -> {
 | 
			
		||||
                val dialog = ClearHistoryDialogController()
 | 
			
		||||
                dialog.targetController = this@HistoryController
 | 
			
		||||
                dialog.showDialog(router)
 | 
			
		||||
                true
 | 
			
		||||
            }
 | 
			
		||||
            else -> super.onOptionsItemSelected(item)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun openChapter(chapter: Chapter?) {
 | 
			
		||||
        val activity = activity ?: return
 | 
			
		||||
        if (chapter != null) {
 | 
			
		||||
            val intent = ReaderActivity.newIntent(activity, chapter.mangaId, chapter.id)
 | 
			
		||||
            startActivity(intent)
 | 
			
		||||
        } else {
 | 
			
		||||
            activity.toast(R.string.no_next_chapter)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun resumeLastChapterRead() {
 | 
			
		||||
        presenter.resumeLastChapterRead()
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,19 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.recent.history
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.Stable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.paging.PagingData
 | 
			
		||||
import androidx.paging.cachedIn
 | 
			
		||||
import androidx.paging.compose.LazyPagingItems
 | 
			
		||||
import androidx.paging.compose.collectAsLazyPagingItems
 | 
			
		||||
import androidx.paging.insertSeparators
 | 
			
		||||
import androidx.paging.map
 | 
			
		||||
import eu.kanade.domain.chapter.model.Chapter
 | 
			
		||||
import eu.kanade.domain.history.interactor.DeleteHistoryTable
 | 
			
		||||
import eu.kanade.domain.history.interactor.GetHistory
 | 
			
		||||
import eu.kanade.domain.history.interactor.GetNextChapter
 | 
			
		||||
@@ -17,53 +26,46 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.launchIO
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.launchUI
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.toDateKey
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
import kotlinx.coroutines.channels.Channel
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.catch
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.receiveAsFlow
 | 
			
		||||
import logcat.LogPriority
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Presenter of HistoryFragment.
 | 
			
		||||
 * Contains information and data for fragment.
 | 
			
		||||
 * Observable updates should be called from here.
 | 
			
		||||
 */
 | 
			
		||||
class HistoryPresenter(
 | 
			
		||||
    private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
 | 
			
		||||
    private val getHistory: GetHistory = Injekt.get(),
 | 
			
		||||
    private val getNextChapter: GetNextChapter = Injekt.get(),
 | 
			
		||||
    private val deleteHistoryTable: DeleteHistoryTable = Injekt.get(),
 | 
			
		||||
    private val removeHistoryById: RemoveHistoryById = Injekt.get(),
 | 
			
		||||
    private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(),
 | 
			
		||||
) : BasePresenter<HistoryController>() {
 | 
			
		||||
) : BasePresenter<HistoryController>(), HistoryState by state {
 | 
			
		||||
 | 
			
		||||
    private val _query: MutableStateFlow<String> = MutableStateFlow("")
 | 
			
		||||
    private val _state: MutableStateFlow<HistoryState> = MutableStateFlow(HistoryState.Loading)
 | 
			
		||||
    val state: StateFlow<HistoryState> = _state.asStateFlow()
 | 
			
		||||
    private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
 | 
			
		||||
    val events: Flow<Event> = _events.receiveAsFlow()
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            _query.collectLatest { query ->
 | 
			
		||||
                getHistory.subscribe(query)
 | 
			
		||||
                    .catch { exception ->
 | 
			
		||||
                        _state.value = HistoryState.Error(exception)
 | 
			
		||||
                    }
 | 
			
		||||
                    .map { pagingData ->
 | 
			
		||||
                        pagingData.toHistoryUiModels()
 | 
			
		||||
                    }
 | 
			
		||||
                    .cachedIn(presenterScope)
 | 
			
		||||
                    .let { uiModelsPagingDataFlow ->
 | 
			
		||||
                        _state.value = HistoryState.Success(uiModelsPagingDataFlow)
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
    @Composable
 | 
			
		||||
    fun getLazyHistory(): LazyPagingItems<HistoryUiModel> {
 | 
			
		||||
        val scope = rememberCoroutineScope()
 | 
			
		||||
        val query = searchQuery ?: ""
 | 
			
		||||
        val flow = remember(query) {
 | 
			
		||||
            getHistory.subscribe(query)
 | 
			
		||||
                .catch { error ->
 | 
			
		||||
                    logcat(LogPriority.ERROR, error)
 | 
			
		||||
                    _events.send(Event.InternalError)
 | 
			
		||||
                }
 | 
			
		||||
                .map { pagingData ->
 | 
			
		||||
                    pagingData.toHistoryUiModels()
 | 
			
		||||
                }
 | 
			
		||||
                .cachedIn(scope)
 | 
			
		||||
        }
 | 
			
		||||
        return flow.collectAsLazyPagingItems()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun PagingData<HistoryWithRelations>.toHistoryUiModels(): PagingData<HistoryUiModel> {
 | 
			
		||||
@@ -81,12 +83,6 @@ class HistoryPresenter(
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun search(query: String) {
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            _query.emit(query)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun removeFromHistory(history: HistoryWithRelations) {
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            removeHistoryById.await(history)
 | 
			
		||||
@@ -102,9 +98,7 @@ class HistoryPresenter(
 | 
			
		||||
    fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            val chapter = getNextChapter.await(mangaId, chapterId)
 | 
			
		||||
            launchUI {
 | 
			
		||||
                view?.openChapter(chapter)
 | 
			
		||||
            }
 | 
			
		||||
            _events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -121,15 +115,33 @@ class HistoryPresenter(
 | 
			
		||||
    fun resumeLastChapterRead() {
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            val chapter = getNextChapter.await()
 | 
			
		||||
            launchUI {
 | 
			
		||||
                view?.openChapter(chapter)
 | 
			
		||||
            }
 | 
			
		||||
            _events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sealed class Dialog {
 | 
			
		||||
        object DeleteAll : Dialog()
 | 
			
		||||
        data class Delete(val history: HistoryWithRelations) : Dialog()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sealed class Event {
 | 
			
		||||
        object InternalError : Event()
 | 
			
		||||
        object NoNextChapterFound : Event()
 | 
			
		||||
        data class OpenChapter(val chapter: Chapter) : Event()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sealed class HistoryState {
 | 
			
		||||
    object Loading : HistoryState()
 | 
			
		||||
    data class Error(val error: Throwable) : HistoryState()
 | 
			
		||||
    data class Success(val uiModels: Flow<PagingData<HistoryUiModel>>) : HistoryState()
 | 
			
		||||
@Stable
 | 
			
		||||
interface HistoryState {
 | 
			
		||||
    var searchQuery: String?
 | 
			
		||||
    var dialog: HistoryPresenter.Dialog?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun HistoryState(): HistoryState {
 | 
			
		||||
    return HistoryStateImpl()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class HistoryStateImpl : HistoryState {
 | 
			
		||||
    override var searchQuery: String? by mutableStateOf(null)
 | 
			
		||||
    override var dialog: HistoryPresenter.Dialog? by mutableStateOf(null)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user