Set up macrobenchmark module
This commit is contained in:
26
.idea/androidTestResultsUserPreferences.xml
generated
26
.idea/androidTestResultsUserPreferences.xml
generated
@@ -16,6 +16,32 @@
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="83150218">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_5_API_34" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1878637654">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_5_API_34" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
22
.idea/deploymentTargetDropDown.xml
generated
22
.idea/deploymentTargetDropDown.xml
generated
@@ -2,24 +2,14 @@
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="AppBackupAgentTest">
|
||||
<entry key="BaselineProfileGenerator">
|
||||
<State />
|
||||
</entry>
|
||||
<entry key="app">
|
||||
<State>
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="C:\Users\xtimms\.android\avd\Pixel_5_API_34.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2024-04-06T08:29:40.034958200Z" />
|
||||
</State>
|
||||
<entry key="Generate Baseline Profile">
|
||||
<State />
|
||||
</entry>
|
||||
<entry key="android-app.app">
|
||||
<State />
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
|
||||
@@ -15,8 +15,10 @@ plugins {
|
||||
id("dagger.hilt.android.plugin")
|
||||
}
|
||||
|
||||
val acraAuthLogin: String = gradleLocalProperties(rootDir).getProperty("authLogin") ?: "\"acra_login\""
|
||||
val acraAuthPassword: String = gradleLocalProperties(rootDir).getProperty("authPassword") ?: "\"acra_password\""
|
||||
val acraAuthLogin: String =
|
||||
gradleLocalProperties(rootDir).getProperty("authLogin") ?: "\"acra_login\""
|
||||
val acraAuthPassword: String =
|
||||
gradleLocalProperties(rootDir).getProperty("authPassword") ?: "\"acra_password\""
|
||||
|
||||
android {
|
||||
namespace = "org.xtimms.shirizu"
|
||||
@@ -41,27 +43,45 @@ android {
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += mapOf(
|
||||
"room.generateKotlin" to "true",
|
||||
"room.schemaLocation" to "$projectDir/schemas"
|
||||
)
|
||||
}
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
named("debug") {
|
||||
versionNameSuffix = "-${getCommitCount()}"
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
release {
|
||||
named("release") {
|
||||
isShrinkResources = true
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
isDebuggable = false
|
||||
isProfileable = true
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
||||
}
|
||||
flavorDimensions.add("default")
|
||||
productFlavors {
|
||||
create("standard") {
|
||||
dimension = "default"
|
||||
}
|
||||
create("dev") {
|
||||
dimension = "default"
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
@@ -111,6 +131,7 @@ dependencies {
|
||||
implementation("androidx.room:room-runtime:2.6.1")
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
implementation("androidx.room:room-testing:2.6.1")
|
||||
ksp("androidx.room:room-compiler:2.6.1")
|
||||
implementation("ch.acra:acra-http:5.9.7")
|
||||
implementation("com.github.solkin:disk-lru-cache:1.4")
|
||||
@@ -119,6 +140,7 @@ dependencies {
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:0.32.0")
|
||||
implementation("com.google.accompanist:accompanist-pager:0.32.0")
|
||||
implementation("com.google.accompanist:accompanist-pager-indicators:0.32.0")
|
||||
implementation("com.google.accompanist:accompanist-permissions:0.32.0")
|
||||
implementation("com.google.dagger:hilt-android:2.51")
|
||||
kapt("com.google.dagger:hilt-compiler:2.51")
|
||||
implementation("androidx.hilt:hilt-work:1.2.0")
|
||||
@@ -149,6 +171,15 @@ dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Git is needed in your system PATH for these commands to work.
|
||||
// If it's not installed, you can return a random value as a workaround
|
||||
fun Project.getCommitCount(): String {
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -27,16 +27,16 @@
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:backupAgent=".sections.settings.backup.AppBackupAgent"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Shirizu"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<activity
|
||||
@@ -50,7 +50,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".crash.CrashActivity"
|
||||
android:exported="false"
|
||||
@@ -70,7 +69,6 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
@@ -91,7 +89,6 @@
|
||||
<meta-data
|
||||
android:name="com.samsung.android.icon_container.has_icon_container"
|
||||
android:value="@bool/com_samsung_android_icon_container_has_icon_container" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
17808
android-app/app/src/main/baseline-prof.txt
Normal file
17808
android-app/app/src/main/baseline-prof.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,12 @@ package org.xtimms.shirizu
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@@ -29,9 +31,10 @@ val LocalSeedColor = compositionLocalOf { SEED }
|
||||
val LocalDynamicColorSwitch = compositionLocalOf { false }
|
||||
val LocalPaletteStyleIndex = compositionLocalOf { 0 }
|
||||
val LocalWindowInsets = compositionLocalOf { PaddingValues(0.dp) }
|
||||
val LocalWindowWidthState = staticCompositionLocalOf { WindowWidthSizeClass.Compact }
|
||||
|
||||
@Composable
|
||||
fun SettingsProvider(content: @Composable () -> Unit) {
|
||||
fun SettingsProvider(windowWidthSizeClass: WindowWidthSizeClass, content: @Composable () -> Unit) {
|
||||
AppSettings.AppSettingsStateFlow.collectAsState().value.run {
|
||||
CompositionLocalProvider(
|
||||
LocalDarkTheme provides darkTheme,
|
||||
@@ -43,6 +46,7 @@ fun SettingsProvider(content: @Composable () -> Unit) {
|
||||
else Color(seedColor).toTonalPalettes(
|
||||
paletteStyles.getOrElse(paletteStyleIndex) { PaletteStyle.TonalSpot }
|
||||
),
|
||||
LocalWindowWidthState provides windowWidthSizeClass,
|
||||
LocalDynamicColorSwitch provides isDynamicColorEnabled,
|
||||
content = content
|
||||
)
|
||||
@@ -65,7 +65,7 @@ import org.xtimms.shirizu.core.components.NavigationRail
|
||||
import org.xtimms.shirizu.core.components.TopAppBar
|
||||
import org.xtimms.shirizu.core.logs.FileLogger
|
||||
import org.xtimms.shirizu.core.prefs.AppSettings
|
||||
import org.xtimms.shirizu.core.screens.UpdateDialogImpl
|
||||
import org.xtimms.shirizu.core.ui.dialogs.UpdateDialogImpl
|
||||
import org.xtimms.shirizu.core.updates.Updater
|
||||
import org.xtimms.shirizu.ui.theme.ShirizuTheme
|
||||
import org.xtimms.shirizu.utils.system.setLanguage
|
||||
@@ -112,7 +112,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val navController = rememberNavController()
|
||||
val windowSizeClass = calculateWindowSizeClass(this)
|
||||
val isCompactScreen = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact
|
||||
val isCompactScreen = LocalWindowWidthState.current == WindowWidthSizeClass.Compact
|
||||
|
||||
val settings =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
@@ -141,7 +141,7 @@ class MainActivity : ComponentActivity() {
|
||||
isReady.value = true
|
||||
}
|
||||
if (isReady.value) {
|
||||
SettingsProvider {
|
||||
SettingsProvider(windowSizeClass.widthSizeClass) {
|
||||
ShirizuTheme(
|
||||
darkTheme = LocalDarkTheme.current.isDarkTheme(),
|
||||
isDynamicColorEnabled = LocalDynamicColorSwitch.current,
|
||||
@@ -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)
|
||||
}
|
||||
@@ -66,6 +66,10 @@ import org.xtimms.shirizu.sections.settings.backup.RESTORE_DESTINATION
|
||||
import org.xtimms.shirizu.sections.settings.backup.RestoreItemsView
|
||||
import org.xtimms.shirizu.sections.settings.network.NETWORK_DESTINATION
|
||||
import org.xtimms.shirizu.sections.settings.network.NetworkView
|
||||
import org.xtimms.shirizu.sections.settings.services.SERVICES_DESTINATION
|
||||
import org.xtimms.shirizu.sections.settings.services.ServicesView
|
||||
import org.xtimms.shirizu.sections.settings.services.suggestions.SUGGESTIONS_SETTINGS_DESTINATION
|
||||
import org.xtimms.shirizu.sections.settings.services.suggestions.SuggestionsSettingsView
|
||||
import org.xtimms.shirizu.sections.settings.shelf.SHELF_SETTINGS_DESTINATION
|
||||
import org.xtimms.shirizu.sections.settings.shelf.ShelfSettingsView
|
||||
import org.xtimms.shirizu.sections.settings.shelf.categories.CATEGORIES_DESTINATION
|
||||
@@ -232,6 +236,7 @@ fun Navigation(
|
||||
},
|
||||
navigateToMangaSources = { navController.navigate(SOURCES_DESTINATION) },
|
||||
navigateToNetwork = { navController.navigate(NETWORK_DESTINATION) },
|
||||
navigateToServicesSettings = { navController.navigate(SERVICES_DESTINATION) },
|
||||
navigateToShelfSettings = { navController.navigate(SHELF_SETTINGS_DESTINATION) },
|
||||
navigateToStorage = { navController.navigate(STORAGE_DESTINATION) }
|
||||
)
|
||||
@@ -308,6 +313,20 @@ fun Navigation(
|
||||
)
|
||||
}
|
||||
|
||||
composable(SERVICES_DESTINATION) {
|
||||
ServicesView(
|
||||
navigateBack = navigateBack,
|
||||
navigateToSuggestionsSettings = { navController.navigate(SUGGESTIONS_SETTINGS_DESTINATION) },
|
||||
navigateToStatistics = { navController.navigate(STATS_DESTINATION) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(SUGGESTIONS_SETTINGS_DESTINATION) {
|
||||
SuggestionsSettingsView(
|
||||
navigateBack = navigateBack
|
||||
)
|
||||
}
|
||||
|
||||
composable(NETWORK_DESTINATION) {
|
||||
NetworkView(
|
||||
navigateBack = navigateBack,
|
||||
@@ -63,6 +63,13 @@ abstract class KotatsuBaseViewModel : ViewModel() {
|
||||
loadingCounter.decrement()
|
||||
}
|
||||
|
||||
protected inline suspend fun <T> withLoading(block: () -> T): T = try {
|
||||
loadingCounter.increment()
|
||||
block()
|
||||
} finally {
|
||||
loadingCounter.decrement()
|
||||
}
|
||||
|
||||
protected fun <T> Flow<T>.withErrorHandling() = catch { error ->
|
||||
errorEvent.call(error)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,17 @@ import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.xtimms.shirizu.ui.theme.applyOpacity
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ExploreButton(
|
||||
text: String,
|
||||
icon: ImageVector,
|
||||
icon: Any? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
@@ -42,6 +43,8 @@ fun ExploreButton(
|
||||
.height(40.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
when (icon) {
|
||||
is ImageVector -> {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = text,
|
||||
@@ -50,6 +53,19 @@ fun ExploreButton(
|
||||
.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
is Painter -> {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = text,
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -3,10 +3,6 @@ package org.xtimms.shirizu.core.components
|
||||
import android.graphics.Path
|
||||
import android.view.animation.PathInterpolator
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
@@ -14,39 +10,28 @@ import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.border
|
||||
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.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.QueryStats
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.outlined.RssFeed
|
||||
import androidx.compose.material.icons.outlined.Search
|
||||
import androidx.compose.material.icons.outlined.SentimentSatisfiedAlt
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -54,35 +39,25 @@ import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SuggestionChip
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.graphics.Color.Companion.Blue
|
||||
import androidx.compose.ui.graphics.Color.Companion.DarkGray
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import org.xtimms.shirizu.R
|
||||
import org.xtimms.shirizu.core.DURATION_ENTER
|
||||
@@ -95,11 +70,10 @@ import org.xtimms.shirizu.sections.history.HISTORY_DESTINATION
|
||||
import org.xtimms.shirizu.sections.search.SEARCH_DESTINATION
|
||||
import org.xtimms.shirizu.sections.settings.SETTINGS_DESTINATION
|
||||
import org.xtimms.shirizu.sections.shelf.SHELF_DESTINATION
|
||||
import org.xtimms.shirizu.ui.theme.ShirizuTheme
|
||||
import org.xtimms.shirizu.sections.stats.STATS_DESTINATION
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TopAppBar(
|
||||
navController: NavController,
|
||||
@@ -108,6 +82,7 @@ fun TopAppBar(
|
||||
searchBarColorProvider: () -> Color,
|
||||
) {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
val isVisible by remember {
|
||||
derivedStateOf {
|
||||
@@ -203,15 +178,43 @@ fun TopAppBar(
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { navController.navigate(SETTINGS_DESTINATION) },
|
||||
onClick = { expanded = true },
|
||||
modifier = Modifier.padding(0.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.Settings,
|
||||
contentDescription = stringResource(id = R.string.settings),
|
||||
Icons.Outlined.MoreVert,
|
||||
contentDescription = stringResource(id = R.string.open_menu),
|
||||
tint = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(id = R.string.statistics)) },
|
||||
onClick = {
|
||||
navController.navigate(STATS_DESTINATION)
|
||||
expanded = false
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.QueryStats,
|
||||
contentDescription = stringResource(id = R.string.statistics)
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(id = R.string.settings)) },
|
||||
onClick = {
|
||||
navController.navigate(SETTINGS_DESTINATION)
|
||||
expanded = false
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = stringResource(id = R.string.settings)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -5,6 +5,7 @@ import androidx.room.Database
|
||||
import androidx.room.InvalidationTracker
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -16,6 +17,7 @@ import org.xtimms.shirizu.core.database.dao.FavouritesDao
|
||||
import org.xtimms.shirizu.core.database.dao.HistoryDao
|
||||
import org.xtimms.shirizu.core.database.dao.MangaDao
|
||||
import org.xtimms.shirizu.core.database.dao.MangaSourcesDao
|
||||
import org.xtimms.shirizu.core.database.dao.StatsDao
|
||||
import org.xtimms.shirizu.core.database.dao.SuggestionDao
|
||||
import org.xtimms.shirizu.core.database.dao.TagsDao
|
||||
import org.xtimms.shirizu.core.database.dao.TrackLogsDao
|
||||
@@ -27,13 +29,15 @@ import org.xtimms.shirizu.core.database.entity.HistoryEntity
|
||||
import org.xtimms.shirizu.core.database.entity.MangaEntity
|
||||
import org.xtimms.shirizu.core.database.entity.MangaSourceEntity
|
||||
import org.xtimms.shirizu.core.database.entity.MangaTagsEntity
|
||||
import org.xtimms.shirizu.core.database.entity.StatsEntity
|
||||
import org.xtimms.shirizu.core.database.entity.SuggestionEntity
|
||||
import org.xtimms.shirizu.core.database.entity.TagEntity
|
||||
import org.xtimms.shirizu.core.database.entity.TrackEntity
|
||||
import org.xtimms.shirizu.core.database.entity.TrackLogEntity
|
||||
import org.xtimms.shirizu.core.database.migrations.Migration1To2
|
||||
import org.xtimms.shirizu.utils.lang.processLifecycleScope
|
||||
|
||||
const val DATABASE_VERSION = 1
|
||||
const val DATABASE_VERSION = 2
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
@@ -47,7 +51,8 @@ const val DATABASE_VERSION = 1
|
||||
BookmarkEntity::class,
|
||||
SuggestionEntity::class,
|
||||
TrackEntity::class,
|
||||
TrackLogEntity::class
|
||||
TrackLogEntity::class,
|
||||
StatsEntity::class,
|
||||
],
|
||||
version = DATABASE_VERSION
|
||||
)
|
||||
@@ -73,10 +78,17 @@ abstract class ShirizuDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun getTrackLogsDao(): TrackLogsDao
|
||||
|
||||
abstract fun getStatsDao(): StatsDao
|
||||
|
||||
}
|
||||
|
||||
fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
|
||||
Migration1To2()
|
||||
)
|
||||
|
||||
fun ShirizuDatabase(context: Context): ShirizuDatabase = Room
|
||||
.databaseBuilder(context, ShirizuDatabase::class.java, "shirizu-db")
|
||||
.addMigrations(*getDatabaseMigrations(context))
|
||||
.addCallback(DatabasePrePopulateCallback(context.resources))
|
||||
.build()
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user