Compare commits
4 Commits
746934d421
...
774bb84f7f
| Author | SHA1 | Date |
|---|---|---|
|
|
774bb84f7f | 2 years ago |
|
|
0245c04122 | 2 years ago |
|
|
df158db1e9 | 2 years ago |
|
|
44b6f1b52f | 2 years ago |
@ -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