Restoring Kotatsu schema backups
This commit is contained in:
4
.idea/deploymentTargetDropDown.xml
generated
4
.idea/deploymentTargetDropDown.xml
generated
@@ -13,12 +13,12 @@
|
|||||||
<deviceKey>
|
<deviceKey>
|
||||||
<Key>
|
<Key>
|
||||||
<type value="VIRTUAL_DEVICE_PATH" />
|
<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>
|
</Key>
|
||||||
</deviceKey>
|
</deviceKey>
|
||||||
</Target>
|
</Target>
|
||||||
</targetSelectedWithDropDown>
|
</targetSelectedWithDropDown>
|
||||||
<timeTargetWasSelectedWithDropDown value="2024-03-17T20:36:24.681011800Z" />
|
<timeTargetWasSelectedWithDropDown value="2024-03-27T16:43:21.742325100Z" />
|
||||||
</State>
|
</State>
|
||||||
</entry>
|
</entry>
|
||||||
</value>
|
</value>
|
||||||
|
|||||||
@@ -21,124 +21,5 @@ class DatabasePrePopulateCallback(private val resources: Resources) : RoomDataba
|
|||||||
0L,
|
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.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.MenuBook
|
import androidx.compose.material.icons.automirrored.outlined.MenuBook
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.Block
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ fun DetailsView(
|
|||||||
title = it.chapter.name,
|
title = it.chapter.name,
|
||||||
date = it.chapter.uploadDate,
|
date = it.chapter.uploadDate,
|
||||||
scanlator = it.chapter.scanlator,
|
scanlator = it.chapter.scanlator,
|
||||||
read = it.isUnread,
|
read = !it.isUnread,
|
||||||
bookmark = false,
|
bookmark = false,
|
||||||
selected = false,
|
selected = false,
|
||||||
onLongClick = { /*TODO*/ },
|
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.AppSettings
|
||||||
import org.xtimms.tokusho.core.prefs.SWIPE_TUTORIAL
|
import org.xtimms.tokusho.core.prefs.SWIPE_TUTORIAL
|
||||||
import org.xtimms.tokusho.core.screens.EmptyScreen
|
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.calculateTimeAgo
|
||||||
import org.xtimms.tokusho.utils.lang.isSameDay
|
import org.xtimms.tokusho.utils.lang.isSameDay
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -70,11 +71,11 @@ fun HistoryView(
|
|||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
var isUserTrySwipe by remember { mutableStateOf(false) }
|
var isUserTrySwipe by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val history by viewModel.content.collectAsStateWithLifecycle(emptyList())
|
val history by viewModel.content.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
if (history.isNotEmpty() && isUserTrySwipe) {
|
if (history?.isNotEmpty() == true && isUserTrySwipe) {
|
||||||
AppSettings.updateValue(SWIPE_TUTORIAL, isUserTrySwipe)
|
AppSettings.updateValue(SWIPE_TUTORIAL, isUserTrySwipe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ fun HistoryView(
|
|||||||
val animatedList = run {
|
val animatedList = run {
|
||||||
val list = emptyList<RowEntity>().toMutableList()
|
val list = emptyList<RowEntity>().toMutableList()
|
||||||
var readDate: Instant? = null
|
var readDate: Instant? = null
|
||||||
history.forEach { item ->
|
history?.forEach { item ->
|
||||||
|
|
||||||
if (readDate === null || !isSameDay(
|
if (readDate === null || !isSameDay(
|
||||||
item.history.updatedAt.toEpochMilli(),
|
item.history.updatedAt.toEpochMilli(),
|
||||||
@@ -116,114 +117,127 @@ fun HistoryView(
|
|||||||
Box(
|
Box(
|
||||||
Modifier.fillMaxSize()
|
Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxSize()) {
|
history.let {
|
||||||
LazyColumn(
|
if (it == null) {
|
||||||
modifier = Modifier
|
LoadingScreen(Modifier.padding(padding))
|
||||||
.collapsable(
|
} else if (it.isEmpty()) {
|
||||||
state = scrollState,
|
EmptyScreen(
|
||||||
topBarHeightPx = topBarHeightPx,
|
icon = Icons.Outlined.History,
|
||||||
topBarOffsetY = topBarOffsetY
|
title = R.string.empty_history_title,
|
||||||
)
|
description = R.string.empty_history_description
|
||||||
.padding(padding)
|
)
|
||||||
) {
|
} else {
|
||||||
animatedItemsIndexed(
|
Column(Modifier.fillMaxSize()) {
|
||||||
state = animatedList.value,
|
LazyColumn(
|
||||||
key = { rowItem -> rowItem.key },
|
modifier = Modifier
|
||||||
) { index, item ->
|
.collapsable(
|
||||||
when (item.type) {
|
state = scrollState,
|
||||||
RowEntityType.Header -> ListGroupHeader(
|
topBarHeightPx = topBarHeightPx,
|
||||||
calculateTimeAgo(item.day).format(
|
topBarOffsetY = topBarOffsetY
|
||||||
LocalContext.current.resources
|
|
||||||
)
|
)
|
||||||
)
|
.padding(padding)
|
||||||
RowEntityType.Item -> SwipeActions(
|
) {
|
||||||
startActionsConfig = SwipeActionsConfig(
|
animatedItemsIndexed(
|
||||||
threshold = 0.33f,
|
state = animatedList.value,
|
||||||
background = MaterialTheme.colorScheme.errorContainer,
|
key = { rowItem -> rowItem.key },
|
||||||
backgroundActive = MaterialTheme.colorScheme.error,
|
) { index, item ->
|
||||||
iconTint = MaterialTheme.colorScheme.onError,
|
when (item.type) {
|
||||||
icon = Icons.Outlined.DeleteForever,
|
RowEntityType.Header -> ListGroupHeader(
|
||||||
stayDismissed = true,
|
calculateTimeAgo(item.day).format(
|
||||||
onDismiss = {
|
LocalContext.current.resources
|
||||||
viewModel.removeFromHistory(item.historyItemModel!!)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
endActionsConfig = SwipeActionsConfig(
|
|
||||||
threshold = 0.33f,
|
|
||||||
background = MaterialTheme.colorScheme.tertiaryContainer,
|
|
||||||
backgroundActive = MaterialTheme.colorScheme.tertiary,
|
|
||||||
iconTint = MaterialTheme.colorScheme.onTertiary,
|
|
||||||
icon = Icons.Outlined.PlayArrow,
|
|
||||||
stayDismissed = false,
|
|
||||||
onDismiss = {
|
|
||||||
navigateToReader()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onTried = { isUserTrySwipe = true },
|
|
||||||
showTutorial = false,
|
|
||||||
) { state ->
|
|
||||||
val size = with(LocalDensity.current) {
|
|
||||||
java.lang.Float.max(
|
|
||||||
java.lang.Float.min(
|
|
||||||
16.dp.toPx(),
|
|
||||||
abs(state.offset.value)
|
|
||||||
), 0f
|
|
||||||
).toDp()
|
|
||||||
}
|
|
||||||
|
|
||||||
val animateCorners by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
state.offset.value.absoluteValue > 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val startCorners by animateDpAsState(
|
|
||||||
targetValue = when {
|
|
||||||
state.dismissDirection == DismissDirection.StartToEnd &&
|
|
||||||
animateCorners -> 8.dp
|
|
||||||
|
|
||||||
else -> 0.dp
|
|
||||||
}, label = "startCorners"
|
|
||||||
)
|
|
||||||
val endCorners by animateDpAsState(
|
|
||||||
targetValue = when {
|
|
||||||
state.dismissDirection == DismissDirection.EndToStart &&
|
|
||||||
animateCorners -> 8.dp
|
|
||||||
|
|
||||||
else -> 0.dp
|
|
||||||
}, label = "endCorners"
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.height(IntrinsicSize.Min)
|
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(
|
|
||||||
vertical = min(
|
|
||||||
size / 4f,
|
|
||||||
4.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.clip(RoundedCornerShape(size)),
|
|
||||||
color = MaterialTheme.colorScheme.surface,
|
|
||||||
shape = RoundedCornerShape(
|
|
||||||
topStart = startCorners,
|
|
||||||
bottomStart = startCorners,
|
|
||||||
topEnd = endCorners,
|
|
||||||
bottomEnd = endCorners,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
|
||||||
) {
|
|
||||||
HistoryItem(
|
|
||||||
coil = coil,
|
|
||||||
history = item.historyItemModel!!,
|
|
||||||
onClick = { navigateToDetails(item.historyItemModel!!.manga.id) },
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
RowEntityType.Item -> SwipeActions(
|
||||||
|
startActionsConfig = SwipeActionsConfig(
|
||||||
|
threshold = 0.33f,
|
||||||
|
background = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
backgroundActive = MaterialTheme.colorScheme.error,
|
||||||
|
iconTint = MaterialTheme.colorScheme.onError,
|
||||||
|
icon = Icons.Outlined.DeleteForever,
|
||||||
|
stayDismissed = true,
|
||||||
|
onDismiss = {
|
||||||
|
viewModel.removeFromHistory(item.historyItemModel!!)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
endActionsConfig = SwipeActionsConfig(
|
||||||
|
threshold = 0.33f,
|
||||||
|
background = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
backgroundActive = MaterialTheme.colorScheme.tertiary,
|
||||||
|
iconTint = MaterialTheme.colorScheme.onTertiary,
|
||||||
|
icon = Icons.Outlined.PlayArrow,
|
||||||
|
stayDismissed = false,
|
||||||
|
onDismiss = {
|
||||||
|
navigateToReader()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
onTried = { isUserTrySwipe = true },
|
||||||
|
showTutorial = false,
|
||||||
|
) { state ->
|
||||||
|
val size = with(LocalDensity.current) {
|
||||||
|
java.lang.Float.max(
|
||||||
|
java.lang.Float.min(
|
||||||
|
16.dp.toPx(),
|
||||||
|
abs(state.offset.value)
|
||||||
|
), 0f
|
||||||
|
).toDp()
|
||||||
|
}
|
||||||
|
|
||||||
|
val animateCorners by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
state.offset.value.absoluteValue > 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val startCorners by animateDpAsState(
|
||||||
|
targetValue = when {
|
||||||
|
state.dismissDirection == DismissDirection.StartToEnd &&
|
||||||
|
animateCorners -> 8.dp
|
||||||
|
|
||||||
|
else -> 0.dp
|
||||||
|
}, label = "startCorners"
|
||||||
|
)
|
||||||
|
val endCorners by animateDpAsState(
|
||||||
|
targetValue = when {
|
||||||
|
state.dismissDirection == DismissDirection.EndToStart &&
|
||||||
|
animateCorners -> 8.dp
|
||||||
|
|
||||||
|
else -> 0.dp
|
||||||
|
}, label = "endCorners"
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.height(IntrinsicSize.Min)
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
vertical = min(
|
||||||
|
size / 4f,
|
||||||
|
4.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.clip(RoundedCornerShape(size)),
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
shape = RoundedCornerShape(
|
||||||
|
topStart = startCorners,
|
||||||
|
bottomStart = startCorners,
|
||||||
|
topEnd = endCorners,
|
||||||
|
bottomEnd = endCorners,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
HistoryItem(
|
||||||
|
coil = coil,
|
||||||
|
history = item.historyItemModel!!,
|
||||||
|
onClick = { navigateToDetails(item.historyItemModel!!.manga.id) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,12 +245,5 @@ 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
|
return@rememberLauncherForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToRestoreScreen(uri.toString())
|
restoreViewModel.restore(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
val showDirectoryAlert =
|
val showDirectoryAlert =
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.AccessTime
|
import androidx.compose.material.icons.outlined.AccessTime
|
||||||
import androidx.compose.material.icons.outlined.Restore
|
import androidx.compose.material.icons.outlined.Restore
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.sections.settings.about.ProgressIndicatorButton
|
||||||
import org.xtimms.tokusho.utils.DeviceUtil
|
import org.xtimms.tokusho.utils.DeviceUtil
|
||||||
|
|
||||||
const val RESTORE_ARGUMENT = "{source}"
|
const val RESTORE_ARGUMENT = "{file}"
|
||||||
const val RESTORE_DESTINATION = "restore/?file=${RESTORE_ARGUMENT}"
|
const val RESTORE_DESTINATION = "restore/?file=${RESTORE_ARGUMENT}"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -38,7 +40,8 @@ fun RestoreItemsView(
|
|||||||
navigateBack: () -> Unit,
|
navigateBack: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val items = restoreViewModel.availableEntries.collectAsStateWithLifecycle()
|
val items by restoreViewModel.availableEntries.collectAsStateWithLifecycle(emptyList())
|
||||||
|
val backupDate by restoreViewModel.backupDate.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
ScaffoldWithTopAppBar(
|
ScaffoldWithTopAppBar(
|
||||||
title = stringResource(R.string.restore_from_backup),
|
title = stringResource(R.string.restore_from_backup),
|
||||||
@@ -61,16 +64,16 @@ fun RestoreItemsView(
|
|||||||
item {
|
item {
|
||||||
PreferencesHintCard(
|
PreferencesHintCard(
|
||||||
title = stringResource(id = R.string.backup_creation_date),
|
title = stringResource(id = R.string.backup_creation_date),
|
||||||
description = restoreViewModel.backupDate.value.toString(),
|
description = backupDate.toString(),
|
||||||
icon = Icons.Outlined.AccessTime
|
icon = Icons.Outlined.AccessTime
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(
|
for (item in items) {
|
||||||
count = 5
|
item {
|
||||||
) {
|
BackupItem(
|
||||||
BackupItem(
|
title = item.name.name
|
||||||
title = it.toString()
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
var isLoading by remember { mutableStateOf(false) }
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
@@ -89,7 +92,7 @@ fun RestoreItemsView(
|
|||||||
icon = Icons.Outlined.Restore,
|
icon = Icons.Outlined.Restore,
|
||||||
isLoading = isLoading
|
isLoading = isLoading
|
||||||
) {
|
) {
|
||||||
restoreViewModel.restore()
|
// restoreViewModel.restore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.xtimms.tokusho.sections.settings.backup
|
package org.xtimms.tokusho.sections.settings.backup
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
@@ -25,13 +26,14 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RestoreViewModel @Inject constructor(
|
class RestoreViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
private val repository: BackupRepository,
|
private val repository: BackupRepository,
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext val context: Context,
|
||||||
) : KotatsuBaseViewModel() {
|
) : KotatsuBaseViewModel() {
|
||||||
|
|
||||||
private val backupInput = SuspendLazy {
|
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
|
val contentResolver = context.contentResolver
|
||||||
runInterruptible(Dispatchers.IO) {
|
runInterruptible(Dispatchers.IO) {
|
||||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||||
@@ -74,57 +76,68 @@ class RestoreViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onItemClick(item: BackupEntryModel) {
|
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[item.name] = item.copy(isChecked = !item.isChecked)
|
||||||
map.validate()
|
map.validate()
|
||||||
availableEntries.value = map.values.sortedBy { it.name.ordinal }
|
availableEntries.value = map.values.sortedBy { it.name.ordinal }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore() {
|
fun restore(uri: Uri) {
|
||||||
launchLoadingJob {
|
launchLoadingJob {
|
||||||
val backup = backupInput.get()
|
val contentResolver = context.contentResolver
|
||||||
val checkedItems = availableEntries.value.mapNotNullTo(EnumSet.noneOf(BackupEntry.Name::class.java)) {
|
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||||
if (it.isChecked) it.name else null
|
(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 result = CompositeResult()
|
||||||
val step = 1f / 5f
|
val step = 1f / 5f
|
||||||
|
|
||||||
progress.value = 0f
|
progress.value = 0f
|
||||||
if (BackupEntry.Name.HISTORY in checkedItems) {
|
//if (BackupEntry.Name.HISTORY in checkedItems) {
|
||||||
backup.getEntry(BackupEntry.Name.HISTORY)?.let {
|
backup.getEntry(BackupEntry.Name.HISTORY)?.let {
|
||||||
result += repository.restoreHistory(it)
|
result += repository.restoreHistory(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
progress.value += step
|
progress.value += step
|
||||||
if (BackupEntry.Name.CATEGORIES in checkedItems) {
|
//if (BackupEntry.Name.CATEGORIES in checkedItems) {
|
||||||
backup.getEntry(BackupEntry.Name.CATEGORIES)?.let {
|
backup.getEntry(BackupEntry.Name.CATEGORIES)?.let {
|
||||||
result += repository.restoreCategories(it)
|
result += repository.restoreCategories(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
progress.value += step
|
progress.value += step
|
||||||
if (BackupEntry.Name.FAVOURITES in checkedItems) {
|
//if (BackupEntry.Name.FAVOURITES in checkedItems) {
|
||||||
backup.getEntry(BackupEntry.Name.FAVOURITES)?.let {
|
backup.getEntry(BackupEntry.Name.FAVOURITES)?.let {
|
||||||
result += repository.restoreFavourites(it)
|
result += repository.restoreFavourites(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
progress.value += step
|
progress.value += step
|
||||||
if (BackupEntry.Name.BOOKMARKS in checkedItems) {
|
//if (BackupEntry.Name.BOOKMARKS in checkedItems) {
|
||||||
backup.getEntry(BackupEntry.Name.BOOKMARKS)?.let {
|
backup.getEntry(BackupEntry.Name.BOOKMARKS)?.let {
|
||||||
result += repository.restoreBookmarks(it)
|
result += repository.restoreBookmarks(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
progress.value += step
|
progress.value += step
|
||||||
if (BackupEntry.Name.SOURCES in checkedItems) {
|
//if (BackupEntry.Name.SOURCES in checkedItems) {
|
||||||
backup.getEntry(BackupEntry.Name.SOURCES)?.let {
|
backup.getEntry(BackupEntry.Name.SOURCES)?.let {
|
||||||
result += repository.restoreSources(it)
|
result += repository.restoreSources(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
progress.value = 1f
|
progress.value = 1f
|
||||||
|
backup.cleanupAsync()
|
||||||
onRestoreDone.call(result)
|
onRestoreDone.call(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +155,8 @@ class RestoreViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (favorites.isEnabled) {
|
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,
|
coil: ImageLoader,
|
||||||
state: PagerState,
|
state: PagerState,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String?,
|
|
||||||
getShelfForPage: (Int) -> List<ShelfManga>,
|
getShelfForPage: (Int) -> List<ShelfManga>,
|
||||||
navigateToDetails: (ShelfManga) -> Unit,
|
navigateToDetails: (ShelfManga) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -41,7 +40,6 @@ fun ShelfPager(
|
|||||||
val library = getShelfForPage(page)
|
val library = getShelfForPage(page)
|
||||||
if (library.isEmpty()) {
|
if (library.isEmpty()) {
|
||||||
ShelfPagerEmptyScreen(
|
ShelfPagerEmptyScreen(
|
||||||
searchQuery = searchQuery,
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
)
|
)
|
||||||
return@HorizontalPager
|
return@HorizontalPager
|
||||||
@@ -61,14 +59,8 @@ fun ShelfPager(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ShelfPagerEmptyScreen(
|
private fun ShelfPagerEmptyScreen(
|
||||||
searchQuery: String?,
|
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
) {
|
) {
|
||||||
val msg = when {
|
|
||||||
!searchQuery.isNullOrEmpty() -> R.string.no_results_found
|
|
||||||
else -> R.string.information_no_manga_category
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(contentPadding + PaddingValues(8.dp))
|
.padding(contentPadding + PaddingValues(8.dp))
|
||||||
@@ -78,7 +70,7 @@ private fun ShelfPagerEmptyScreen(
|
|||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
icon = Icons.Outlined.Close,
|
icon = Icons.Outlined.Close,
|
||||||
title = R.string.empty_here,
|
title = R.string.empty_here,
|
||||||
description = msg,
|
description = R.string.information_no_manga_category,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -110,7 +109,6 @@ fun ShelfViewContent(
|
|||||||
coil = coil,
|
coil = coil,
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
contentPadding = PaddingValues(bottom = padding.calculateBottomPadding()),
|
contentPadding = PaddingValues(bottom = padding.calculateBottomPadding()),
|
||||||
searchQuery = "",
|
|
||||||
getShelfForPage = { mangas },
|
getShelfForPage = { mangas },
|
||||||
navigateToDetails = onClickManga
|
navigateToDetails = onClickManga
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,18 +3,12 @@ package org.xtimms.tokusho.sections.shelf
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.xtimms.tokusho.core.base.viewmodel.BaseViewModel
|
|
||||||
import org.xtimms.tokusho.core.base.viewmodel.KotatsuBaseViewModel
|
import org.xtimms.tokusho.core.base.viewmodel.KotatsuBaseViewModel
|
||||||
import org.xtimms.tokusho.data.repository.FavouritesRepository
|
import org.xtimms.tokusho.data.repository.FavouritesRepository
|
||||||
import org.xtimms.tokusho.utils.lang.mapItems
|
import org.xtimms.tokusho.utils.lang.mapItems
|
||||||
@@ -28,11 +22,10 @@ class ShelfViewModel @Inject constructor(
|
|||||||
private val mangasStateFlow = favouritesRepository.observeAll(1)
|
private val mangasStateFlow = favouritesRepository.observeAll(1)
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
|
|
||||||
private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()
|
private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
val mangaCount = favouritesRepository.observeMangaCount()
|
val mangaCount = favouritesRepository.observeMangaCountInCategory(1)
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
val categories = categoriesStateFlow
|
val categories = categoriesStateFlow
|
||||||
|
|||||||
Reference in New Issue
Block a user