Restoring Kotatsu schema backups
This commit is contained in:
4
.idea/deploymentTargetDropDown.xml
generated
4
.idea/deploymentTargetDropDown.xml
generated
@@ -13,12 +13,12 @@
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="C:\Users\xtimms\.android\avd\Small_Phone_API_34.avd" />
|
||||
<value value="C:\Users\xtimms\.android\avd\Pixel_API_27.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2024-03-17T20:36:24.681011800Z" />
|
||||
<timeTargetWasSelectedWithDropDown value="2024-03-27T16:43:21.742325100Z" />
|
||||
</State>
|
||||
</entry>
|
||||
</value>
|
||||
|
||||
@@ -21,124 +21,5 @@ class DatabasePrePopulateCallback(private val resources: Resources) : RoomDataba
|
||||
0L,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`, track, show_in_lib, `deleted_at`) VALUES (?,?,?,?,?,?,?)",
|
||||
arrayOf(
|
||||
System.currentTimeMillis(),
|
||||
1,
|
||||
resources.getString(R.string.reading),
|
||||
SortOrder.NEWEST.name,
|
||||
1,
|
||||
1,
|
||||
0L,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`, track, show_in_lib, `deleted_at`) VALUES (?,?,?,?,?,?,?)",
|
||||
arrayOf(
|
||||
System.currentTimeMillis(),
|
||||
1,
|
||||
resources.getString(R.string.completed),
|
||||
SortOrder.NEWEST.name,
|
||||
1,
|
||||
1,
|
||||
0L,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`, track, show_in_lib, `deleted_at`) VALUES (?,?,?,?,?,?,?)",
|
||||
arrayOf(
|
||||
System.currentTimeMillis(),
|
||||
1,
|
||||
resources.getString(R.string.dropped),
|
||||
SortOrder.NEWEST.name,
|
||||
1,
|
||||
1,
|
||||
0L,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO sources (source, enabled, sort_key) VALUES (?,?,?)",
|
||||
arrayOf(
|
||||
"MANGADEX",
|
||||
1,
|
||||
1,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO sources (source, enabled, sort_key) VALUES (?,?,?)",
|
||||
arrayOf(
|
||||
"DESUME",
|
||||
1,
|
||||
1,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO manga (manga_id, title, alt_title, url, public_url, rating, nsfw, cover_url, large_cover_url, state, author, source) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
arrayOf(
|
||||
4427365311541330000,
|
||||
"Seitokai ni mo Ana wa Aru!",
|
||||
"",
|
||||
"822c9883-385c-4fd0-9523-16e7789cbeae",
|
||||
"https://mangadex.org/title/822c9883-385c-4fd0-9523-16e7789cbeae",
|
||||
-1.0,
|
||||
0,
|
||||
"https://mangadex.org/covers/822c9883-385c-4fd0-9523-16e7789cbeae/f886822a-80c3-484c-ad75-9aa32abedc18.jpg.256.jpg",
|
||||
"https://mangadex.org/covers/822c9883-385c-4fd0-9523-16e7789cbeae/f886822a-80c3-484c-ad75-9aa32abedc18.jpg",
|
||||
"FINISHED",
|
||||
"Muchi Maro",
|
||||
"MANGADEX",
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO manga (manga_id, title, alt_title, url, public_url, rating, nsfw, cover_url, large_cover_url, state, author, source) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
arrayOf(
|
||||
-5513532524243987690,
|
||||
"Тотальный гарем",
|
||||
"Shuumatsu no Harem",
|
||||
"/manga/api/694",
|
||||
"https://desu.me/manga/z-shuumatsu-no-harem.694/",
|
||||
1.0,
|
||||
1,
|
||||
"https://desu.me/data/manga/covers/preview/694.jpg",
|
||||
"https://desu.me/data/manga/covers/original/694.jpg",
|
||||
"ONGOING",
|
||||
"",
|
||||
"DESUME",
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO favourites (manga_id, category_id, sort_key, created_at, deleted_at) VALUES (?,?,?,?,?)",
|
||||
arrayOf(
|
||||
4427365311541330000,
|
||||
1,
|
||||
0,
|
||||
1705944302882,
|
||||
0,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT INTO favourites (manga_id, category_id, sort_key, created_at, deleted_at) VALUES (?,?,?,?,?)",
|
||||
arrayOf(
|
||||
-5513532524243987690,
|
||||
1,
|
||||
0,
|
||||
1705944302882,
|
||||
0,
|
||||
)
|
||||
)
|
||||
db.execSQL(
|
||||
"INSERT into history (manga_id, created_at, updated_at, chapter_id, page, scroll, percent, deleted_at) VALUES (?,?,?,?,?,?,?,?)",
|
||||
arrayOf(
|
||||
-5513532524243987690,
|
||||
1710617414,
|
||||
1710617414,
|
||||
1,
|
||||
3,
|
||||
0.3,
|
||||
0.4,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.xtimms.tokusho.core.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun LoadingScreen(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.MenuBook
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
|
||||
@@ -251,7 +251,7 @@ fun DetailsView(
|
||||
title = it.chapter.name,
|
||||
date = it.chapter.uploadDate,
|
||||
scanlator = it.chapter.scanlator,
|
||||
read = it.isUnread,
|
||||
read = !it.isUnread,
|
||||
bookmark = false,
|
||||
selected = false,
|
||||
onLongClick = { /*TODO*/ },
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.xtimms.tokusho.core.components.effects.updateAnimatedItemsState
|
||||
import org.xtimms.tokusho.core.prefs.AppSettings
|
||||
import org.xtimms.tokusho.core.prefs.SWIPE_TUTORIAL
|
||||
import org.xtimms.tokusho.core.screens.EmptyScreen
|
||||
import org.xtimms.tokusho.core.screens.LoadingScreen
|
||||
import org.xtimms.tokusho.utils.lang.calculateTimeAgo
|
||||
import org.xtimms.tokusho.utils.lang.isSameDay
|
||||
import java.time.Instant
|
||||
@@ -70,11 +71,11 @@ fun HistoryView(
|
||||
val scrollState = rememberScrollState()
|
||||
var isUserTrySwipe by remember { mutableStateOf(false) }
|
||||
|
||||
val history by viewModel.content.collectAsStateWithLifecycle(emptyList())
|
||||
val history by viewModel.content.collectAsStateWithLifecycle(null)
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
if (history.isNotEmpty() && isUserTrySwipe) {
|
||||
if (history?.isNotEmpty() == true && isUserTrySwipe) {
|
||||
AppSettings.updateValue(SWIPE_TUTORIAL, isUserTrySwipe)
|
||||
}
|
||||
}
|
||||
@@ -83,7 +84,7 @@ fun HistoryView(
|
||||
val animatedList = run {
|
||||
val list = emptyList<RowEntity>().toMutableList()
|
||||
var readDate: Instant? = null
|
||||
history.forEach { item ->
|
||||
history?.forEach { item ->
|
||||
|
||||
if (readDate === null || !isSameDay(
|
||||
item.history.updatedAt.toEpochMilli(),
|
||||
@@ -116,6 +117,16 @@ fun HistoryView(
|
||||
Box(
|
||||
Modifier.fillMaxSize()
|
||||
) {
|
||||
history.let {
|
||||
if (it == null) {
|
||||
LoadingScreen(Modifier.padding(padding))
|
||||
} else if (it.isEmpty()) {
|
||||
EmptyScreen(
|
||||
icon = Icons.Outlined.History,
|
||||
title = R.string.empty_history_title,
|
||||
description = R.string.empty_history_description
|
||||
)
|
||||
} else {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
@@ -136,6 +147,7 @@ fun HistoryView(
|
||||
LocalContext.current.resources
|
||||
)
|
||||
)
|
||||
|
||||
RowEntityType.Item -> SwipeActions(
|
||||
startActionsConfig = SwipeActionsConfig(
|
||||
threshold = 0.33f,
|
||||
@@ -231,12 +243,7 @@ fun HistoryView(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (history.isEmpty()) {
|
||||
EmptyScreen(
|
||||
icon = Icons.Outlined.History,
|
||||
title = R.string.empty_history_title,
|
||||
description = R.string.empty_history_description
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ fun BackupRestoreView(
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
|
||||
navigateToRestoreScreen(uri.toString())
|
||||
restoreViewModel.restore(uri)
|
||||
}
|
||||
|
||||
val showDirectoryAlert =
|
||||
|
||||
@@ -9,10 +9,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AccessTime
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -28,7 +30,7 @@ import org.xtimms.tokusho.core.components.ScaffoldWithTopAppBar
|
||||
import org.xtimms.tokusho.sections.settings.about.ProgressIndicatorButton
|
||||
import org.xtimms.tokusho.utils.DeviceUtil
|
||||
|
||||
const val RESTORE_ARGUMENT = "{source}"
|
||||
const val RESTORE_ARGUMENT = "{file}"
|
||||
const val RESTORE_DESTINATION = "restore/?file=${RESTORE_ARGUMENT}"
|
||||
|
||||
@Composable
|
||||
@@ -38,7 +40,8 @@ fun RestoreItemsView(
|
||||
navigateBack: () -> Unit,
|
||||
) {
|
||||
|
||||
val items = restoreViewModel.availableEntries.collectAsStateWithLifecycle()
|
||||
val items by restoreViewModel.availableEntries.collectAsStateWithLifecycle(emptyList())
|
||||
val backupDate by restoreViewModel.backupDate.collectAsStateWithLifecycle(null)
|
||||
|
||||
ScaffoldWithTopAppBar(
|
||||
title = stringResource(R.string.restore_from_backup),
|
||||
@@ -61,17 +64,17 @@ fun RestoreItemsView(
|
||||
item {
|
||||
PreferencesHintCard(
|
||||
title = stringResource(id = R.string.backup_creation_date),
|
||||
description = restoreViewModel.backupDate.value.toString(),
|
||||
description = backupDate.toString(),
|
||||
icon = Icons.Outlined.AccessTime
|
||||
)
|
||||
}
|
||||
items(
|
||||
count = 5
|
||||
) {
|
||||
for (item in items) {
|
||||
item {
|
||||
BackupItem(
|
||||
title = it.toString()
|
||||
title = item.name.name
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
Row(
|
||||
@@ -89,7 +92,7 @@ fun RestoreItemsView(
|
||||
icon = Icons.Outlined.Restore,
|
||||
isLoading = isLoading
|
||||
) {
|
||||
restoreViewModel.restore()
|
||||
// restoreViewModel.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.xtimms.tokusho.sections.settings.backup
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -25,13 +26,14 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class RestoreViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val repository: BackupRepository,
|
||||
@ApplicationContext context: Context,
|
||||
@ApplicationContext val context: Context,
|
||||
) : KotatsuBaseViewModel() {
|
||||
|
||||
private val backupInput = SuspendLazy {
|
||||
val uri = savedStateHandle.get<String>(RESTORE_ARGUMENT)?.toUriOrNull() ?: throw FileNotFoundException()
|
||||
val uri = savedStateHandle.get<String>(RESTORE_ARGUMENT)?.toUriOrNull()
|
||||
?: throw FileNotFoundException()
|
||||
val contentResolver = context.contentResolver
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||
@@ -74,57 +76,68 @@ class RestoreViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun onItemClick(item: BackupEntryModel) {
|
||||
val map = availableEntries.value.associateByTo(EnumMap(BackupEntry.Name::class.java)) { it.name }
|
||||
val map =
|
||||
availableEntries.value.associateByTo(EnumMap(BackupEntry.Name::class.java)) { it.name }
|
||||
map[item.name] = item.copy(isChecked = !item.isChecked)
|
||||
map.validate()
|
||||
availableEntries.value = map.values.sortedBy { it.name.ordinal }
|
||||
}
|
||||
|
||||
fun restore() {
|
||||
fun restore(uri: Uri) {
|
||||
launchLoadingJob {
|
||||
val backup = backupInput.get()
|
||||
val checkedItems = availableEntries.value.mapNotNullTo(EnumSet.noneOf(BackupEntry.Name::class.java)) {
|
||||
val contentResolver = context.contentResolver
|
||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||
(contentResolver.openInputStream(uri) ?: throw FileNotFoundException()).use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
val backupInput = BackupZipInput(tempFile)
|
||||
val backup: BackupZipInput = backupInput
|
||||
val checkedItems =
|
||||
availableEntries.value.mapNotNullTo(EnumSet.noneOf(BackupEntry.Name::class.java)) {
|
||||
if (it.isChecked) it.name else null
|
||||
}
|
||||
val result = CompositeResult()
|
||||
val step = 1f / 5f
|
||||
|
||||
progress.value = 0f
|
||||
if (BackupEntry.Name.HISTORY in checkedItems) {
|
||||
//if (BackupEntry.Name.HISTORY in checkedItems) {
|
||||
backup.getEntry(BackupEntry.Name.HISTORY)?.let {
|
||||
result += repository.restoreHistory(it)
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
progress.value += step
|
||||
if (BackupEntry.Name.CATEGORIES in checkedItems) {
|
||||
//if (BackupEntry.Name.CATEGORIES in checkedItems) {
|
||||
backup.getEntry(BackupEntry.Name.CATEGORIES)?.let {
|
||||
result += repository.restoreCategories(it)
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
progress.value += step
|
||||
if (BackupEntry.Name.FAVOURITES in checkedItems) {
|
||||
//if (BackupEntry.Name.FAVOURITES in checkedItems) {
|
||||
backup.getEntry(BackupEntry.Name.FAVOURITES)?.let {
|
||||
result += repository.restoreFavourites(it)
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
progress.value += step
|
||||
if (BackupEntry.Name.BOOKMARKS in checkedItems) {
|
||||
//if (BackupEntry.Name.BOOKMARKS in checkedItems) {
|
||||
backup.getEntry(BackupEntry.Name.BOOKMARKS)?.let {
|
||||
result += repository.restoreBookmarks(it)
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
progress.value += step
|
||||
if (BackupEntry.Name.SOURCES in checkedItems) {
|
||||
//if (BackupEntry.Name.SOURCES in checkedItems) {
|
||||
backup.getEntry(BackupEntry.Name.SOURCES)?.let {
|
||||
result += repository.restoreSources(it)
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
progress.value = 1f
|
||||
backup.cleanupAsync()
|
||||
onRestoreDone.call(result)
|
||||
}
|
||||
}
|
||||
@@ -142,7 +155,8 @@ class RestoreViewModel @Inject constructor(
|
||||
}
|
||||
} else {
|
||||
if (favorites.isEnabled) {
|
||||
this[BackupEntry.Name.FAVOURITES] = favorites.copy(isEnabled = false, isChecked = false)
|
||||
this[BackupEntry.Name.FAVOURITES] =
|
||||
favorites.copy(isEnabled = false, isChecked = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ fun ShelfPager(
|
||||
coil: ImageLoader,
|
||||
state: PagerState,
|
||||
contentPadding: PaddingValues,
|
||||
searchQuery: String?,
|
||||
getShelfForPage: (Int) -> List<ShelfManga>,
|
||||
navigateToDetails: (ShelfManga) -> Unit,
|
||||
) {
|
||||
@@ -41,7 +40,6 @@ fun ShelfPager(
|
||||
val library = getShelfForPage(page)
|
||||
if (library.isEmpty()) {
|
||||
ShelfPagerEmptyScreen(
|
||||
searchQuery = searchQuery,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
return@HorizontalPager
|
||||
@@ -61,14 +59,8 @@ fun ShelfPager(
|
||||
|
||||
@Composable
|
||||
private fun ShelfPagerEmptyScreen(
|
||||
searchQuery: String?,
|
||||
contentPadding: PaddingValues,
|
||||
) {
|
||||
val msg = when {
|
||||
!searchQuery.isNullOrEmpty() -> R.string.no_results_found
|
||||
else -> R.string.information_no_manga_category
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(contentPadding + PaddingValues(8.dp))
|
||||
@@ -78,7 +70,7 @@ private fun ShelfPagerEmptyScreen(
|
||||
EmptyScreen(
|
||||
icon = Icons.Outlined.Close,
|
||||
title = R.string.empty_here,
|
||||
description = msg,
|
||||
description = R.string.information_no_manga_category,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -110,7 +109,6 @@ fun ShelfViewContent(
|
||||
coil = coil,
|
||||
state = pagerState,
|
||||
contentPadding = PaddingValues(bottom = padding.calculateBottomPadding()),
|
||||
searchQuery = "",
|
||||
getShelfForPage = { mangas },
|
||||
navigateToDetails = onClickManga
|
||||
)
|
||||
|
||||
@@ -3,18 +3,12 @@ package org.xtimms.tokusho.sections.shelf
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import org.xtimms.tokusho.core.base.viewmodel.BaseViewModel
|
||||
import org.xtimms.tokusho.core.base.viewmodel.KotatsuBaseViewModel
|
||||
import org.xtimms.tokusho.data.repository.FavouritesRepository
|
||||
import org.xtimms.tokusho.utils.lang.mapItems
|
||||
@@ -28,11 +22,10 @@ class ShelfViewModel @Inject constructor(
|
||||
private val mangasStateFlow = favouritesRepository.observeAll(1)
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
|
||||
private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
val mangaCount = favouritesRepository.observeMangaCount()
|
||||
val mangaCount = favouritesRepository.observeMangaCountInCategory(1)
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
val categories = categoriesStateFlow
|
||||
|
||||
Reference in New Issue
Block a user