Set up macrobenchmark module
parent
0245c04122
commit
774bb84f7f
@ -0,0 +1,57 @@
|
|||||||
|
package org.xtimms.shirizu.core.database
|
||||||
|
|
||||||
|
import androidx.room.testing.MigrationTestHelper
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ShirizuDatabaseTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val helper: MigrationTestHelper = MigrationTestHelper(
|
||||||
|
InstrumentationRegistry.getInstrumentation(),
|
||||||
|
ShirizuDatabase::class.java,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val migrations = getDatabaseMigrations(InstrumentationRegistry.getInstrumentation().targetContext)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun versions() {
|
||||||
|
assertEquals(1, migrations.first().startVersion)
|
||||||
|
repeat(migrations.size) { i ->
|
||||||
|
assertEquals(i + 1, migrations[i].startVersion)
|
||||||
|
assertEquals(i + 2, migrations[i].endVersion)
|
||||||
|
}
|
||||||
|
assertEquals(DATABASE_VERSION, migrations.last().endVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrateAll() {
|
||||||
|
helper.createDatabase(TEST_DB, 1).close()
|
||||||
|
for (migration in migrations) {
|
||||||
|
helper.runMigrationsAndValidate(
|
||||||
|
TEST_DB,
|
||||||
|
migration.endVersion,
|
||||||
|
true,
|
||||||
|
migration,
|
||||||
|
).close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun prePopulate() {
|
||||||
|
val resources = InstrumentationRegistry.getInstrumentation().targetContext.resources
|
||||||
|
helper.createDatabase(TEST_DB, DATABASE_VERSION).use {
|
||||||
|
DatabasePrePopulateCallback(resources).onCreate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
const val TEST_DB = "test-db"
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
|||||||
|
package org.xtimms.shirizu.core
|
||||||
|
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
object HapticFeedback {
|
||||||
|
fun View.slightHapticFeedback() =
|
||||||
|
this.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK)
|
||||||
|
|
||||||
|
fun View.longPressHapticFeedback() =
|
||||||
|
this.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
|
}
|
||||||
@ -0,0 +1,181 @@
|
|||||||
|
package org.xtimms.shirizu.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonColors
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.xtimms.shirizu.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ConfirmButton(
|
||||||
|
text: String = stringResource(R.string.confirm),
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
TextButton(onClick = onClick, enabled = enabled) {
|
||||||
|
Text(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DismissButton(text: String = stringResource(R.string.dismiss), onClick: () -> Unit) {
|
||||||
|
TextButton(onClick = onClick) {
|
||||||
|
Text(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActionButton(
|
||||||
|
title: String,
|
||||||
|
icon: ImageVector,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OutlinedButtonWithIcon(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.primary
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(contentColor = contentColor)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(ButtonDefaults.IconSize),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TextButtonWithIcon(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||||
|
colors = ButtonDefaults.textButtonColors(contentColor = contentColor)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FilledTonalButtonWithIcon(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
colors: ButtonColors = ButtonDefaults.filledTonalButtonColors(),
|
||||||
|
) {
|
||||||
|
FilledTonalButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||||
|
colors = colors
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FilledButtonWithIcon(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||||
|
enabled = enabled
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 6.dp),
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package org.xtimms.shirizu.core.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Check
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SingleChoiceChip(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
selected: Boolean,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
label: String,
|
||||||
|
leadingIcon: ImageVector = Icons.Outlined.Check
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
modifier = modifier.padding(horizontal = 4.dp),
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
label = {
|
||||||
|
Text(text = label)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Row {
|
||||||
|
AnimatedVisibility(visible = selected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = leadingIcon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,222 @@
|
|||||||
|
package org.xtimms.shirizu.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.selection.toggleable
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Check
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
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.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DialogSingleChoiceItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.selectable(
|
||||||
|
selected = selected,
|
||||||
|
enabled = true,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.clearAndSetSemantics { },
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
Text(text = text, style = LocalTextStyle.current.copy(fontSize = 16.sp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SingleChoiceItemPreview() {
|
||||||
|
Surface {
|
||||||
|
Column {
|
||||||
|
DialogSingleChoiceItemWithLabel(
|
||||||
|
text = "Better compatibility", label = "For sharing to other apps", selected = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
DialogSingleChoiceItemWithLabel(
|
||||||
|
text = "Better quality", label = "For watching in compatible apps", selected = true
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
DialogSingleChoiceItem(text = "Preview", selected = true) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DialogSingleChoiceItemWithLabel(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
label: String?,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.selectable(
|
||||||
|
selected = selected,
|
||||||
|
enabled = true,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 8.dp, end = 16.dp)
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.clearAndSetSemantics { },
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
Column {
|
||||||
|
Text(text = text, style = MaterialTheme.typography.bodyLarge)
|
||||||
|
label?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
modifier = Modifier.padding()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CheckBoxItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onValueChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 12.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.toggleable(
|
||||||
|
value = checked, enabled = true, onValueChange = onValueChange
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier, verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
modifier = Modifier.clearAndSetSemantics { },
|
||||||
|
checked = checked, onCheckedChange = onValueChange,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier, text = text, style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DialogSwitchItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
value: Boolean,
|
||||||
|
onValueChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.toggleable(value = value, onValueChange = onValueChange)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
val thumbContent: (@Composable () -> Unit)? = remember(value) {
|
||||||
|
if (value) {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(SwitchDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val density = LocalDensity.current
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalDensity provides Density(
|
||||||
|
density.density * 0.8f,
|
||||||
|
density.fontScale
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = value,
|
||||||
|
onCheckedChange = onValueChange,
|
||||||
|
modifier = Modifier.clearAndSetSemantics { },
|
||||||
|
thumbContent = thumbContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SwitchItemPrev() {
|
||||||
|
var value by remember { mutableStateOf(false) }
|
||||||
|
Surface {
|
||||||
|
DialogSwitchItem(text = "Use cookies", value = value) {
|
||||||
|
value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,327 @@
|
|||||||
|
package org.xtimms.shirizu.core.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.SignalCellularConnectedNoInternet4Bar
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import org.xtimms.shirizu.R
|
||||||
|
import org.xtimms.shirizu.ui.theme.FixedAccentColors
|
||||||
|
import org.xtimms.shirizu.ui.theme.ShirizuTheme
|
||||||
|
|
||||||
|
private val DialogVerticalPadding = PaddingValues(vertical = 24.dp)
|
||||||
|
private val IconPadding = PaddingValues(bottom = 16.dp)
|
||||||
|
private val DialogHorizontalPadding = PaddingValues(horizontal = 24.dp)
|
||||||
|
private val TitlePadding = PaddingValues(bottom = 16.dp)
|
||||||
|
private val TextPadding = PaddingValues(bottom = 24.dp)
|
||||||
|
private val ButtonsMainAxisSpacing = Arrangement.spacedBy(8.dp, Alignment.Start)
|
||||||
|
private val ButtonsCrossAxisSpacing = Arrangement.spacedBy(12.dp, Alignment.Top)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ShirizuDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
confirmButton: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
dismissButton: @Composable (() -> Unit)? = null,
|
||||||
|
icon: @Composable (() -> Unit)? = null,
|
||||||
|
title: @Composable (() -> Unit)? = null,
|
||||||
|
text: @Composable (() -> Unit)? = null,
|
||||||
|
shape: Shape = AlertDialogDefaults.shape,
|
||||||
|
containerColor: Color = AlertDialogDefaults.containerColor,
|
||||||
|
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
|
||||||
|
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
|
||||||
|
textContentColor: Color = AlertDialogDefaults.textContentColor,
|
||||||
|
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
|
||||||
|
properties: DialogProperties = DialogProperties()
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier,
|
||||||
|
properties = properties
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = shape,
|
||||||
|
color = containerColor,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(DialogVerticalPadding)
|
||||||
|
) {
|
||||||
|
icon?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides iconContentColor) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.padding(IconPadding)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides titleContentColor) {
|
||||||
|
val textStyle = MaterialTheme.typography.headlineSmall
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
Box(
|
||||||
|
// Align the title to the center when an icon is present.
|
||||||
|
Modifier
|
||||||
|
.padding(TitlePadding)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
.align(
|
||||||
|
if (icon == null) {
|
||||||
|
Alignment.Start
|
||||||
|
} else {
|
||||||
|
Alignment.CenterHorizontally
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
title()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides textContentColor) {
|
||||||
|
val textStyle =
|
||||||
|
MaterialTheme.typography.bodyMedium
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.weight(weight = 1f, fill = false)
|
||||||
|
.padding(TextPadding)
|
||||||
|
.align(Alignment.Start)
|
||||||
|
) {
|
||||||
|
text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
) {
|
||||||
|
val textStyle =
|
||||||
|
MaterialTheme.typography.labelLarge
|
||||||
|
ProvideTextStyle(value = textStyle) {
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = ButtonsMainAxisSpacing,
|
||||||
|
verticalArrangement = ButtonsCrossAxisSpacing
|
||||||
|
) {
|
||||||
|
dismissButton?.invoke()
|
||||||
|
confirmButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShirizuDialogButtonVariant(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
shape: Shape = MiddleButtonShape,
|
||||||
|
text: String,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Box() {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(48.dp),
|
||||||
|
color = FixedAccentColors.secondaryFixed,
|
||||||
|
shape = shape
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = FixedAccentColors.onSecondaryFixed,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Preview(name = "light", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
private fun ButtonVariantPreview() {
|
||||||
|
ShirizuTheme {
|
||||||
|
ShirizuDialogVariant(
|
||||||
|
onDismissRequest = {}, modifier = Modifier,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.SignalCellularConnectedNoInternet4Bar,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "Download with cellular network?",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
},
|
||||||
|
buttons = {
|
||||||
|
ShirizuDialogButtonVariant(
|
||||||
|
text = stringResource(R.string.allow_always),
|
||||||
|
shape = TopButtonShape
|
||||||
|
) {}
|
||||||
|
ShirizuDialogButtonVariant(
|
||||||
|
text = stringResource(id = R.string.allow_once),
|
||||||
|
shape = MiddleButtonShape
|
||||||
|
) {}
|
||||||
|
ShirizuDialogButtonVariant(
|
||||||
|
text = stringResource(R.string.dont_allow),
|
||||||
|
shape = BottomButtonShape
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val TopButtonShape = RoundedCornerShape(
|
||||||
|
topStart = 12.dp,
|
||||||
|
topEnd = 12.dp,
|
||||||
|
bottomStart = 4.dp,
|
||||||
|
bottomEnd = 4.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
val MiddleButtonShape = RoundedCornerShape(4.dp)
|
||||||
|
|
||||||
|
val BottomButtonShape = RoundedCornerShape(
|
||||||
|
topStart = 4.dp,
|
||||||
|
topEnd = 4.dp,
|
||||||
|
bottomStart = 12.dp,
|
||||||
|
bottomEnd = 12.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ShirizuDialogVariant(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
buttons: @Composable (() -> Unit)? = null,
|
||||||
|
icon: @Composable (() -> Unit)? = null,
|
||||||
|
title: @Composable (() -> Unit)? = null,
|
||||||
|
text: @Composable (() -> Unit)? = null,
|
||||||
|
shape: Shape = AlertDialogDefaults.shape,
|
||||||
|
containerColor: Color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
|
||||||
|
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
|
||||||
|
textContentColor: Color = AlertDialogDefaults.textContentColor,
|
||||||
|
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
|
||||||
|
properties: DialogProperties = DialogProperties()
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier,
|
||||||
|
properties = properties
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = shape,
|
||||||
|
color = containerColor,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(DialogVerticalPadding)
|
||||||
|
) {
|
||||||
|
icon?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides iconContentColor) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.padding(IconPadding)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides titleContentColor) {
|
||||||
|
val textStyle = MaterialTheme.typography.headlineSmall
|
||||||
|
ProvideTextStyle(textStyle.copy(textAlign = TextAlign.Center)) {
|
||||||
|
Box(
|
||||||
|
// Align the title to the center when an icon is present.
|
||||||
|
Modifier
|
||||||
|
.padding(TitlePadding)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
.align(
|
||||||
|
if (icon == null) {
|
||||||
|
Alignment.Start
|
||||||
|
} else {
|
||||||
|
Alignment.CenterHorizontally
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
title()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides textContentColor) {
|
||||||
|
val textStyle =
|
||||||
|
MaterialTheme.typography.bodyMedium
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.weight(weight = 1f, fill = false)
|
||||||
|
.padding(TextPadding)
|
||||||
|
.align(Alignment.Start)
|
||||||
|
) {
|
||||||
|
text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier.padding(DialogHorizontalPadding)
|
||||||
|
) {
|
||||||
|
buttons?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
package org.xtimms.shirizu.core.components
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.SheetState
|
||||||
|
import androidx.compose.material3.SheetValue
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ShirizuModalBottomSheet(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
sheetState: SheetState = SheetState(
|
||||||
|
skipPartiallyExpanded = true,
|
||||||
|
density = LocalDensity.current,
|
||||||
|
initialValue = SheetValue.Hidden
|
||||||
|
),
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
horizontalPadding: PaddingValues = PaddingValues(horizontal = 28.dp),
|
||||||
|
content: @Composable ColumnScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
modifier = modifier,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
sheetState = sheetState,
|
||||||
|
windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(paddingValues = horizontalPadding)) {
|
||||||
|
content()
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
}
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(
|
||||||
|
with(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.asPaddingValues()
|
||||||
|
.calculateBottomPadding()
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
this.value > 30f -> {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: https://issuetracker.google.com/issues/290798798
|
||||||
|
Build.VERSION.SDK_INT < 30 -> {
|
||||||
|
48.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
0.dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DrawerSheetSubtitle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
color: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 4.dp, top = 16.dp, bottom = 8.dp),
|
||||||
|
color = color,
|
||||||
|
style = MaterialTheme.typography.labelLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package org.xtimms.shirizu.core.components.icons
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
public val Icons.Outlined.Creation: ImageVector
|
||||||
|
get() {
|
||||||
|
if (_creation != null) {
|
||||||
|
return _creation!!
|
||||||
|
}
|
||||||
|
_creation = Builder(name = "Creation", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp,
|
||||||
|
viewportWidth = 24.0f, viewportHeight = 24.0f).apply {
|
||||||
|
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||||
|
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||||
|
pathFillType = NonZero) {
|
||||||
|
moveTo(9.0f, 4.0f)
|
||||||
|
lineTo(11.5f, 9.5f)
|
||||||
|
lineTo(17.0f, 12.0f)
|
||||||
|
lineTo(11.5f, 14.5f)
|
||||||
|
lineTo(9.0f, 20.0f)
|
||||||
|
lineTo(6.5f, 14.5f)
|
||||||
|
lineTo(1.0f, 12.0f)
|
||||||
|
lineTo(6.5f, 9.5f)
|
||||||
|
lineTo(9.0f, 4.0f)
|
||||||
|
moveTo(9.0f, 8.83f)
|
||||||
|
lineTo(8.0f, 11.0f)
|
||||||
|
lineTo(5.83f, 12.0f)
|
||||||
|
lineTo(8.0f, 13.0f)
|
||||||
|
lineTo(9.0f, 15.17f)
|
||||||
|
lineTo(10.0f, 13.0f)
|
||||||
|
lineTo(12.17f, 12.0f)
|
||||||
|
lineTo(10.0f, 11.0f)
|
||||||
|
lineTo(9.0f, 8.83f)
|
||||||
|
moveTo(19.0f, 9.0f)
|
||||||
|
lineTo(17.74f, 6.26f)
|
||||||
|
lineTo(15.0f, 5.0f)
|
||||||
|
lineTo(17.74f, 3.75f)
|
||||||
|
lineTo(19.0f, 1.0f)
|
||||||
|
lineTo(20.25f, 3.75f)
|
||||||
|
lineTo(23.0f, 5.0f)
|
||||||
|
lineTo(20.25f, 6.26f)
|
||||||
|
lineTo(19.0f, 9.0f)
|
||||||
|
moveTo(19.0f, 23.0f)
|
||||||
|
lineTo(17.74f, 20.26f)
|
||||||
|
lineTo(15.0f, 19.0f)
|
||||||
|
lineTo(17.74f, 17.75f)
|
||||||
|
lineTo(19.0f, 15.0f)
|
||||||
|
lineTo(20.25f, 17.75f)
|
||||||
|
lineTo(23.0f, 19.0f)
|
||||||
|
lineTo(20.25f, 20.26f)
|
||||||
|
lineTo(19.0f, 23.0f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
return _creation!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _creation: ImageVector? = null
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package org.xtimms.shirizu.core.database.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.MapColumn
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.RawQuery
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||||
|
import androidx.sqlite.db.SupportSQLiteQuery
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.xtimms.shirizu.core.database.entity.MangaEntity
|
||||||
|
import org.xtimms.shirizu.core.database.entity.StatsEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class StatsDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM stats ORDER BY started_at")
|
||||||
|
abstract suspend fun findAll(): List<StatsEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM stats WHERE manga_id = :mangaId ORDER BY started_at")
|
||||||
|
abstract suspend fun findAll(mangaId: Long): List<StatsEntity>
|
||||||
|
|
||||||
|
@Query("SELECT IFNULL(SUM(pages),0) FROM stats WHERE manga_id = :mangaId")
|
||||||
|
abstract suspend fun getReadPagesCount(mangaId: Long): Int
|
||||||
|
|
||||||
|
@Query("SELECT IFNULL(SUM(duration)/SUM(pages), 0) FROM stats WHERE manga_id = :mangaId")
|
||||||
|
abstract suspend fun getAverageTimePerPage(mangaId: Long): Long
|
||||||
|
|
||||||
|
@Query("SELECT IFNULL(SUM(duration)/SUM(pages), 0) FROM stats")
|
||||||
|
abstract suspend fun getAverageTimePerPage(): Long
|
||||||
|
|
||||||
|
@Query("SELECT IFNULL(SUM(duration), 0) FROM stats WHERE manga_id = :mangaId")
|
||||||
|
abstract suspend fun getReadingTime(mangaId: Long): Long
|
||||||
|
|
||||||
|
@Query("SELECT IFNULL(SUM(duration), 0) FROM stats")
|
||||||
|
abstract suspend fun getTotalReadingTime(): Long
|
||||||
|
|
||||||
|
@Query("DELETE FROM stats")
|
||||||
|
abstract suspend fun clear()
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM stats WHERE manga_id = :mangaId")
|
||||||
|
abstract fun observeRowCount(mangaId: Long): Flow<Int>
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
abstract suspend fun upsert(entity: StatsEntity)
|
||||||
|
|
||||||
|
suspend fun getDurationStats(fromDate: Long, isNsfw: Boolean?, favouriteCategories: Set<Long>): Map<MangaEntity, Long> {
|
||||||
|
val conditions = ArrayList<String>()
|
||||||
|
conditions.add("stats.started_at >= $fromDate")
|
||||||
|
if (favouriteCategories.isNotEmpty()) {
|
||||||
|
val ids = favouriteCategories.joinToString(",")
|
||||||
|
conditions.add("stats.manga_id IN (SELECT manga_id FROM favourites WHERE category_id IN ($ids))")
|
||||||
|
}
|
||||||
|
if (isNsfw != null) {
|
||||||
|
val flag = if (isNsfw) 1 else 0
|
||||||
|
conditions.add("manga.nsfw = $flag")
|
||||||
|
}
|
||||||
|
val where = conditions.joinToString(separator = " AND ")
|
||||||
|
val query = SimpleSQLiteQuery(
|
||||||
|
"SELECT manga.*, SUM(duration) AS d FROM stats LEFT JOIN manga ON manga.manga_id = stats.manga_id WHERE $where GROUP BY manga.manga_id ORDER BY d DESC",
|
||||||
|
)
|
||||||
|
return getDurationStatsImpl(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RawQuery
|
||||||
|
protected abstract fun getDurationStatsImpl(
|
||||||
|
query: SupportSQLiteQuery
|
||||||
|
): Map<@MapColumn("manga") MangaEntity, @MapColumn("d") Long>
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue