Set up macrobenchmark module
This commit is contained in:
26
.idea/androidTestResultsUserPreferences.xml
generated
26
.idea/androidTestResultsUserPreferences.xml
generated
@@ -16,6 +16,32 @@
|
|||||||
</AndroidTestResultsTableState>
|
</AndroidTestResultsTableState>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
22
.idea/deploymentTargetDropDown.xml
generated
22
.idea/deploymentTargetDropDown.xml
generated
@@ -2,24 +2,14 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="deploymentTargetDropDown">
|
<component name="deploymentTargetDropDown">
|
||||||
<value>
|
<value>
|
||||||
<entry key="AppBackupAgentTest">
|
<entry key="BaselineProfileGenerator">
|
||||||
<State />
|
<State />
|
||||||
</entry>
|
</entry>
|
||||||
<entry key="app">
|
<entry key="Generate Baseline Profile">
|
||||||
<State>
|
<State />
|
||||||
<targetSelectedWithDropDown>
|
</entry>
|
||||||
<Target>
|
<entry key="android-app.app">
|
||||||
<type value="QUICK_BOOT_TARGET" />
|
<State />
|
||||||
<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>
|
</entry>
|
||||||
</value>
|
</value>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ plugins {
|
|||||||
id("dagger.hilt.android.plugin")
|
id("dagger.hilt.android.plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
val acraAuthLogin: String = gradleLocalProperties(rootDir).getProperty("authLogin") ?: "\"acra_login\""
|
val acraAuthLogin: String =
|
||||||
val acraAuthPassword: String = gradleLocalProperties(rootDir).getProperty("authPassword") ?: "\"acra_password\""
|
gradleLocalProperties(rootDir).getProperty("authLogin") ?: "\"acra_login\""
|
||||||
|
val acraAuthPassword: String =
|
||||||
|
gradleLocalProperties(rootDir).getProperty("authPassword") ?: "\"acra_password\""
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.xtimms.shirizu"
|
namespace = "org.xtimms.shirizu"
|
||||||
@@ -41,27 +43,45 @@ android {
|
|||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
}
|
}
|
||||||
javaCompileOptions {
|
ksp {
|
||||||
annotationProcessorOptions {
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
arguments += mapOf(
|
|
||||||
"room.generateKotlin" to "true",
|
|
||||||
"room.schemaLocation" to "$projectDir/schemas"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
named("debug") {
|
||||||
|
versionNameSuffix = "-${getCommitCount()}"
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
}
|
}
|
||||||
release {
|
named("release") {
|
||||||
|
isShrinkResources = true
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"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 {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
@@ -111,6 +131,7 @@ dependencies {
|
|||||||
implementation("androidx.room:room-runtime:2.6.1")
|
implementation("androidx.room:room-runtime:2.6.1")
|
||||||
implementation("androidx.room:room-ktx:2.6.1")
|
implementation("androidx.room:room-ktx:2.6.1")
|
||||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
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")
|
ksp("androidx.room:room-compiler:2.6.1")
|
||||||
implementation("ch.acra:acra-http:5.9.7")
|
implementation("ch.acra:acra-http:5.9.7")
|
||||||
implementation("com.github.solkin:disk-lru-cache:1.4")
|
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-systemuicontroller:0.32.0")
|
||||||
implementation("com.google.accompanist:accompanist-pager: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-pager-indicators:0.32.0")
|
||||||
|
implementation("com.google.accompanist:accompanist-permissions:0.32.0")
|
||||||
implementation("com.google.dagger:hilt-android:2.51")
|
implementation("com.google.dagger:hilt-android:2.51")
|
||||||
kapt("com.google.dagger:hilt-compiler:2.51")
|
kapt("com.google.dagger:hilt-compiler:2.51")
|
||||||
implementation("androidx.hilt:hilt-work:1.2.0")
|
implementation("androidx.hilt:hilt-work:1.2.0")
|
||||||
@@ -149,6 +171,15 @@ dependencies {
|
|||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
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.
|
// 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
|
// If it's not installed, you can return a random value as a workaround
|
||||||
fun Project.getCommitCount(): String {
|
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:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:backupAgent=".sections.settings.backup.AppBackupAgent"
|
android:backupAgent=".sections.settings.backup.AppBackupAgent"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Shirizu"
|
android:theme="@style/Theme.Shirizu"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -50,7 +50,6 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".crash.CrashActivity"
|
android:name=".crash.CrashActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -70,7 +69,6 @@
|
|||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
@@ -91,7 +89,6 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.samsung.android.icon_container.has_icon_container"
|
android:name="com.samsung.android.icon_container.has_icon_container"
|
||||||
android:value="@bool/com_samsung_android_icon_container_has_icon_container" />
|
android:value="@bool/com_samsung_android_icon_container_has_icon_container" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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 android.os.Build
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
@@ -29,9 +31,10 @@ val LocalSeedColor = compositionLocalOf { SEED }
|
|||||||
val LocalDynamicColorSwitch = compositionLocalOf { false }
|
val LocalDynamicColorSwitch = compositionLocalOf { false }
|
||||||
val LocalPaletteStyleIndex = compositionLocalOf { 0 }
|
val LocalPaletteStyleIndex = compositionLocalOf { 0 }
|
||||||
val LocalWindowInsets = compositionLocalOf { PaddingValues(0.dp) }
|
val LocalWindowInsets = compositionLocalOf { PaddingValues(0.dp) }
|
||||||
|
val LocalWindowWidthState = staticCompositionLocalOf { WindowWidthSizeClass.Compact }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsProvider(content: @Composable () -> Unit) {
|
fun SettingsProvider(windowWidthSizeClass: WindowWidthSizeClass, content: @Composable () -> Unit) {
|
||||||
AppSettings.AppSettingsStateFlow.collectAsState().value.run {
|
AppSettings.AppSettingsStateFlow.collectAsState().value.run {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalDarkTheme provides darkTheme,
|
LocalDarkTheme provides darkTheme,
|
||||||
@@ -43,6 +46,7 @@ fun SettingsProvider(content: @Composable () -> Unit) {
|
|||||||
else Color(seedColor).toTonalPalettes(
|
else Color(seedColor).toTonalPalettes(
|
||||||
paletteStyles.getOrElse(paletteStyleIndex) { PaletteStyle.TonalSpot }
|
paletteStyles.getOrElse(paletteStyleIndex) { PaletteStyle.TonalSpot }
|
||||||
),
|
),
|
||||||
|
LocalWindowWidthState provides windowWidthSizeClass,
|
||||||
LocalDynamicColorSwitch provides isDynamicColorEnabled,
|
LocalDynamicColorSwitch provides isDynamicColorEnabled,
|
||||||
content = content
|
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.components.TopAppBar
|
||||||
import org.xtimms.shirizu.core.logs.FileLogger
|
import org.xtimms.shirizu.core.logs.FileLogger
|
||||||
import org.xtimms.shirizu.core.prefs.AppSettings
|
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.core.updates.Updater
|
||||||
import org.xtimms.shirizu.ui.theme.ShirizuTheme
|
import org.xtimms.shirizu.ui.theme.ShirizuTheme
|
||||||
import org.xtimms.shirizu.utils.system.setLanguage
|
import org.xtimms.shirizu.utils.system.setLanguage
|
||||||
@@ -112,7 +112,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val windowSizeClass = calculateWindowSizeClass(this)
|
val windowSizeClass = calculateWindowSizeClass(this)
|
||||||
val isCompactScreen = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact
|
val isCompactScreen = LocalWindowWidthState.current == WindowWidthSizeClass.Compact
|
||||||
|
|
||||||
val settings =
|
val settings =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
@@ -141,7 +141,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
isReady.value = true
|
isReady.value = true
|
||||||
}
|
}
|
||||||
if (isReady.value) {
|
if (isReady.value) {
|
||||||
SettingsProvider {
|
SettingsProvider(windowSizeClass.widthSizeClass) {
|
||||||
ShirizuTheme(
|
ShirizuTheme(
|
||||||
darkTheme = LocalDarkTheme.current.isDarkTheme(),
|
darkTheme = LocalDarkTheme.current.isDarkTheme(),
|
||||||
isDynamicColorEnabled = LocalDynamicColorSwitch.current,
|
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.backup.RestoreItemsView
|
||||||
import org.xtimms.shirizu.sections.settings.network.NETWORK_DESTINATION
|
import org.xtimms.shirizu.sections.settings.network.NETWORK_DESTINATION
|
||||||
import org.xtimms.shirizu.sections.settings.network.NetworkView
|
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.SHELF_SETTINGS_DESTINATION
|
||||||
import org.xtimms.shirizu.sections.settings.shelf.ShelfSettingsView
|
import org.xtimms.shirizu.sections.settings.shelf.ShelfSettingsView
|
||||||
import org.xtimms.shirizu.sections.settings.shelf.categories.CATEGORIES_DESTINATION
|
import org.xtimms.shirizu.sections.settings.shelf.categories.CATEGORIES_DESTINATION
|
||||||
@@ -232,6 +236,7 @@ fun Navigation(
|
|||||||
},
|
},
|
||||||
navigateToMangaSources = { navController.navigate(SOURCES_DESTINATION) },
|
navigateToMangaSources = { navController.navigate(SOURCES_DESTINATION) },
|
||||||
navigateToNetwork = { navController.navigate(NETWORK_DESTINATION) },
|
navigateToNetwork = { navController.navigate(NETWORK_DESTINATION) },
|
||||||
|
navigateToServicesSettings = { navController.navigate(SERVICES_DESTINATION) },
|
||||||
navigateToShelfSettings = { navController.navigate(SHELF_SETTINGS_DESTINATION) },
|
navigateToShelfSettings = { navController.navigate(SHELF_SETTINGS_DESTINATION) },
|
||||||
navigateToStorage = { navController.navigate(STORAGE_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) {
|
composable(NETWORK_DESTINATION) {
|
||||||
NetworkView(
|
NetworkView(
|
||||||
navigateBack = navigateBack,
|
navigateBack = navigateBack,
|
||||||
@@ -63,6 +63,13 @@ abstract class KotatsuBaseViewModel : ViewModel() {
|
|||||||
loadingCounter.decrement()
|
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 ->
|
protected fun <T> Flow<T>.withErrorHandling() = catch { error ->
|
||||||
errorEvent.call(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.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import org.xtimms.shirizu.ui.theme.applyOpacity
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExploreButton(
|
fun ExploreButton(
|
||||||
text: String,
|
text: String,
|
||||||
icon: ImageVector,
|
icon: Any? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -42,14 +43,29 @@ fun ExploreButton(
|
|||||||
.height(40.dp),
|
.height(40.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
when (icon) {
|
||||||
imageVector = icon,
|
is ImageVector -> {
|
||||||
contentDescription = text,
|
Icon(
|
||||||
modifier = Modifier
|
imageVector = icon,
|
||||||
.padding(end = 16.dp)
|
contentDescription = text,
|
||||||
.size(24.dp),
|
modifier = Modifier
|
||||||
tint = MaterialTheme.colorScheme.primary
|
.padding(end = 16.dp)
|
||||||
)
|
.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 = text,
|
text = text,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
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.graphics.Path
|
||||||
import android.view.animation.PathInterpolator
|
import android.view.animation.PathInterpolator
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
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.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@@ -14,39 +10,28 @@ import androidx.compose.animation.slideInHorizontally
|
|||||||
import androidx.compose.animation.slideOutHorizontally
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.basicMarquee
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
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.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.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.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
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.RssFeed
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material.icons.outlined.SentimentSatisfiedAlt
|
import androidx.compose.material.icons.outlined.SentimentSatisfiedAlt
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -54,35 +39,25 @@ import androidx.compose.material3.LargeTopAppBar
|
|||||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.MediumTopAppBar
|
import androidx.compose.material3.MediumTopAppBar
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SuggestionChip
|
import androidx.compose.material3.SuggestionChip
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarColors
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
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.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import org.xtimms.shirizu.R
|
import org.xtimms.shirizu.R
|
||||||
import org.xtimms.shirizu.core.DURATION_ENTER
|
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.search.SEARCH_DESTINATION
|
||||||
import org.xtimms.shirizu.sections.settings.SETTINGS_DESTINATION
|
import org.xtimms.shirizu.sections.settings.SETTINGS_DESTINATION
|
||||||
import org.xtimms.shirizu.sections.shelf.SHELF_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.LocalDate
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TopAppBar(
|
fun TopAppBar(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
@@ -108,6 +82,7 @@ fun TopAppBar(
|
|||||||
searchBarColorProvider: () -> Color,
|
searchBarColorProvider: () -> Color,
|
||||||
) {
|
) {
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val isVisible by remember {
|
val isVisible by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -203,15 +178,43 @@ fun TopAppBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { navController.navigate(SETTINGS_DESTINATION) },
|
onClick = { expanded = true },
|
||||||
modifier = Modifier.padding(0.dp),
|
modifier = Modifier.padding(0.dp),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Settings,
|
Icons.Outlined.MoreVert,
|
||||||
contentDescription = stringResource(id = R.string.settings),
|
contentDescription = stringResource(id = R.string.open_menu),
|
||||||
tint = MaterialTheme.colorScheme.outline
|
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.InvalidationTracker
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
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.HistoryDao
|
||||||
import org.xtimms.shirizu.core.database.dao.MangaDao
|
import org.xtimms.shirizu.core.database.dao.MangaDao
|
||||||
import org.xtimms.shirizu.core.database.dao.MangaSourcesDao
|
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.SuggestionDao
|
||||||
import org.xtimms.shirizu.core.database.dao.TagsDao
|
import org.xtimms.shirizu.core.database.dao.TagsDao
|
||||||
import org.xtimms.shirizu.core.database.dao.TrackLogsDao
|
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.MangaEntity
|
||||||
import org.xtimms.shirizu.core.database.entity.MangaSourceEntity
|
import org.xtimms.shirizu.core.database.entity.MangaSourceEntity
|
||||||
import org.xtimms.shirizu.core.database.entity.MangaTagsEntity
|
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.SuggestionEntity
|
||||||
import org.xtimms.shirizu.core.database.entity.TagEntity
|
import org.xtimms.shirizu.core.database.entity.TagEntity
|
||||||
import org.xtimms.shirizu.core.database.entity.TrackEntity
|
import org.xtimms.shirizu.core.database.entity.TrackEntity
|
||||||
import org.xtimms.shirizu.core.database.entity.TrackLogEntity
|
import org.xtimms.shirizu.core.database.entity.TrackLogEntity
|
||||||
|
import org.xtimms.shirizu.core.database.migrations.Migration1To2
|
||||||
import org.xtimms.shirizu.utils.lang.processLifecycleScope
|
import org.xtimms.shirizu.utils.lang.processLifecycleScope
|
||||||
|
|
||||||
const val DATABASE_VERSION = 1
|
const val DATABASE_VERSION = 2
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
@@ -47,7 +51,8 @@ const val DATABASE_VERSION = 1
|
|||||||
BookmarkEntity::class,
|
BookmarkEntity::class,
|
||||||
SuggestionEntity::class,
|
SuggestionEntity::class,
|
||||||
TrackEntity::class,
|
TrackEntity::class,
|
||||||
TrackLogEntity::class
|
TrackLogEntity::class,
|
||||||
|
StatsEntity::class,
|
||||||
],
|
],
|
||||||
version = DATABASE_VERSION
|
version = DATABASE_VERSION
|
||||||
)
|
)
|
||||||
@@ -73,10 +78,17 @@ abstract class ShirizuDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
abstract fun getTrackLogsDao(): TrackLogsDao
|
abstract fun getTrackLogsDao(): TrackLogsDao
|
||||||
|
|
||||||
|
abstract fun getStatsDao(): StatsDao
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
|
||||||
|
Migration1To2()
|
||||||
|
)
|
||||||
|
|
||||||
fun ShirizuDatabase(context: Context): ShirizuDatabase = Room
|
fun ShirizuDatabase(context: Context): ShirizuDatabase = Room
|
||||||
.databaseBuilder(context, ShirizuDatabase::class.java, "shirizu-db")
|
.databaseBuilder(context, ShirizuDatabase::class.java, "shirizu-db")
|
||||||
|
.addMigrations(*getDatabaseMigrations(context))
|
||||||
.addCallback(DatabasePrePopulateCallback(context.resources))
|
.addCallback(DatabasePrePopulateCallback(context.resources))
|
||||||
.build()
|
.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