Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
output-metadata.json

.kotlin/sessions/

# OCR ONNX models
app/src/main/assets/ocr/model*.onnx
app/src/main/assets/ocr/model*.ort
Expand Down
2 changes: 1 addition & 1 deletion app/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ xyz.sevive.arcaeaoffline/
│ └── screens/<feature>/[<subfeature>/]
├── database/ app-private Room 3 databases: AppDatabase, OcrQueueDatabase
├── datastore/ DataStore stores and their repositories
├── data/ Paths, BuildFlavor, maintenance tasks, notification channels
├── data/ Paths, BuildFlavor, maintenance tasks, notification channels, ImportLog, etc.
├── jobs/ WorkManager workers
├── helpers/ validators, OCR and resource helpers, diagnostics, activity/context extensions
├── di/AppModule.kt the Koin module
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package xyz.sevive.arcaeaoffline.ui.screens.database.manage
package xyz.sevive.arcaeaoffline.data.imports

import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
Expand All @@ -10,7 +10,7 @@ import kotlin.time.Clock
import kotlin.time.Instant
import kotlin.uuid.Uuid

internal sealed interface ImportLogEvent {
sealed interface ImportLogEvent {
data class Plural(
@PluralsRes val resId: Int,
val quantity: Int,
Expand All @@ -25,14 +25,14 @@ internal sealed interface ImportLogEvent {
) : ImportLogEvent
}

internal data class ImportLogObject(
data class ImportLogObject(
val uuid: Uuid = Uuid.generateV4(),
val timestamp: Instant,
val tag: String? = null,
val event: ImportLogEvent,
)

internal class ImportLogManager {
class ImportLogManager {
private val lock = Mutex()
private val _logs = MutableStateFlow(emptyList<ImportLogObject>())
val logs: StateFlow<List<ImportLogObject>> = _logs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package xyz.sevive.arcaeaoffline.ui.screens.database.manage
package xyz.sevive.arcaeaoffline.data.imports

import co.touchlab.kermit.Logger
import kotlinx.coroutines.CancellationException
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/java/xyz/sevive/arcaeaoffline/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import xyz.sevive.arcaeaoffline.core.database.repositories.SongLocalizedReposito
import xyz.sevive.arcaeaoffline.core.database.repositories.SongRepository
import xyz.sevive.arcaeaoffline.core.di.coreModule
import xyz.sevive.arcaeaoffline.core.search.ArcaeaCatalogStateHolder
import xyz.sevive.arcaeaoffline.data.imports.ImportLogManager
import xyz.sevive.arcaeaoffline.database.AppDatabase
import xyz.sevive.arcaeaoffline.database.OcrQueueDatabase
import xyz.sevive.arcaeaoffline.database.daos.OcrHistoryDao
Expand All @@ -47,7 +48,7 @@ import xyz.sevive.arcaeaoffline.ui.screens.database.DatabaseNavEntryViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.addplayresult.DatabaseAddPlayResultViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.bestlist.DatabaseBestListViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.deduplicator.DatabaseDeduplicatorViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.manage.DatabaseManageViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.playdata.DatabasePlayDataViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.playresultlist.DatabasePlayResultListViewModel
import xyz.sevive.arcaeaoffline.ui.screens.database.r30list.DatabaseR30ListViewModel
import xyz.sevive.arcaeaoffline.ui.screens.ocr.dependencies.OcrDependenciesScreenViewModel
Expand All @@ -56,6 +57,7 @@ import xyz.sevive.arcaeaoffline.ui.screens.ocr.queue.OcrQueueScreenViewModel
import xyz.sevive.arcaeaoffline.ui.screens.ocr.queue.preferences.OcrQueuePreferencesViewModel
import xyz.sevive.arcaeaoffline.ui.screens.ocr.queue.staging.OcrQueueStagingViewModel
import xyz.sevive.arcaeaoffline.ui.screens.overview.OverviewViewModel
import xyz.sevive.arcaeaoffline.ui.screens.resources.ResourcesViewModel
import xyz.sevive.arcaeaoffline.ui.screens.settings.SettingsViewModel
import xyz.sevive.arcaeaoffline.ui.screens.settings.unstablealert.SettingsUnstableAlertScreenViewModel
import xyz.sevive.arcaeaoffline.ui.screens.utilities.UtilitiesChartRecommendScreenViewModel
Expand Down Expand Up @@ -156,11 +158,13 @@ val appModule =
single<ArcaeaCatalogStateHolder> { create(::createArcaeaCatalogStateHolder) }

single<DiagnosticsCollector> { create(::createDiagnosticsCollector) }
single<ImportLogManager>()

viewModel<EmergencyModeActivityViewModel>()
viewModel<OverviewViewModel>()
viewModel<DatabaseNavEntryViewModel>()
viewModel<DatabaseManageViewModel>()
viewModel<ResourcesViewModel>()
viewModel<DatabasePlayDataViewModel>()
viewModel<DatabaseAddPlayResultViewModel>()
viewModel<DatabasePlayResultListViewModel>()
viewModel<DatabaseBestListViewModel>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ fun SubScreenContainer(
title: String,
modifier: Modifier = Modifier,
actions: @Composable RowScope.() -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
content: @Composable () -> Unit,
) {
Expand All @@ -121,6 +122,7 @@ fun SubScreenContainer(
actions = actions,
)
},
floatingActionButton = floatingActionButton,
snackbarHost = snackbarHost,
content = content,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package xyz.sevive.arcaeaoffline.ui.components

import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
Expand Down Expand Up @@ -31,15 +30,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.materialkolor.hct.Hct
import kotlinx.coroutines.flow.combine
import org.koin.compose.koinInject
import xyz.sevive.arcaeaoffline.core.constants.ArcaeaRatingClass
Expand All @@ -50,8 +45,6 @@ import xyz.sevive.arcaeaoffline.core.database.repositories.ChartInfoRepository
import xyz.sevive.arcaeaoffline.core.database.repositories.DifficultyWithSongRepository
import xyz.sevive.arcaeaoffline.ui.helpers.ArcaeaFormatters
import xyz.sevive.arcaeaoffline.ui.theme.ArcaeaOfflineTheme
import xyz.sevive.arcaeaoffline.ui.theme.isSystemInDarkTheme
import xyz.sevive.arcaeaoffline.ui.theme.ratingClassColor
import xyz.sevive.arcaeaoffline.ui.theme.spacing

// Display data resolved as one atomic unit. Swapping the pair together is
Expand Down Expand Up @@ -97,36 +90,10 @@ fun ArcaeaChartCard(
var artistOverflowed by remember(difficultyWithSong) { mutableStateOf(false) }
val canExpand = expanded || titleOverflowed || artistOverflowed

// TODO: change to local when theme settings added
val isDarkTheme = isSystemInDarkTheme()
val textTone = if (isDarkTheme) 80.0 else 32.0
val accentColor by animateColorAsState(
targetValue =
ratingClassColor(
ArcaeaRatingClassDisplay.of(difficultyWithSong.ratingClass, difficultyWithSong.ratingClassAlias),
),
label = "accentColor",
)
val (backgroundBrush, accentTextColor) =
remember(accentColor, isDarkTheme) {
val hct = Hct.fromInt(accentColor.toArgb())
if (isDarkTheme) {
val backgroundEnd = Color(hct.withTone(20.0).toInt())
Brush.horizontalGradient(
0.48f to backgroundEnd.copy(alpha = 0f),
0.68f to backgroundEnd.copy(alpha = 0.12f),
0.84f to backgroundEnd.copy(alpha = 0.45f),
0.94f to backgroundEnd.copy(alpha = 0.78f),
1.00f to backgroundEnd.copy(alpha = 0.90f),
) to Color(hct.withTone(textTone).toInt())
} else {
Brush.horizontalGradient(
0.00f to accentColor.copy(alpha = 0f),
0.40f to accentColor.copy(alpha = 0f),
1.00f to accentColor.copy(alpha = 0.22f),
) to Color(hct.withTone(textTone).toInt())
}
}
val accent =
rememberRatingClassAccent(
ArcaeaRatingClassDisplay.of(difficultyWithSong.ratingClass, difficultyWithSong.ratingClassAlias),
)
val enabledCardColors = CardDefaults.cardColors()

Card(
Expand All @@ -143,7 +110,7 @@ fun ArcaeaChartCard(
Row(
// TODO: sync this to spacing...?
Modifier
.background(backgroundBrush)
.background(accent.backgroundBrush)
.padding(horizontal = MaterialTheme.spacing.pagePadding, vertical = MaterialTheme.spacing.cardPadding),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.sm),
verticalAlignment = Alignment.CenterVertically,
Expand Down Expand Up @@ -187,12 +154,12 @@ fun ArcaeaChartCard(
value,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Medium,
color = accentTextColor,
color = accent.textColor,
)
Text(
ratingClass,
style = MaterialTheme.typography.bodyMedium,
color = accentTextColor,
color = accent.textColor,
)
}
}
Expand All @@ -203,7 +170,7 @@ fun ArcaeaChartCard(
if (it) Icons.Default.UnfoldLess else Icons.Default.UnfoldMore,
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = accentTextColor,
tint = accent.textColor,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package xyz.sevive.arcaeaoffline.ui.screens.database.manage
package xyz.sevive.arcaeaoffline.ui.components

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
Expand Down Expand Up @@ -41,6 +41,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import xyz.sevive.arcaeaoffline.data.imports.ImportLogEvent
import xyz.sevive.arcaeaoffline.data.imports.ImportLogObject
import xyz.sevive.arcaeaoffline.helpers.context.findActivity
import xyz.sevive.arcaeaoffline.helpers.formatAsLocalizedDate
import xyz.sevive.arcaeaoffline.helpers.formatAsLocalizedDateTime
Expand All @@ -52,15 +54,13 @@ import xyz.sevive.arcaeaoffline.ui.theme.spacing
import kotlin.time.Instant
import kotlin.uuid.Uuid

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Composable
private fun ImportLogObjectUi(
log: ImportLogObject,
widthSizeClass: WindowWidthSizeClass,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val resources = LocalResources.current
val widthSizeClass = context.findActivity()?.let { calculateWindowSizeClass(it) }?.widthSizeClass ?: WindowWidthSizeClass.Compact

val timestampText =
remember(widthSizeClass) {
Expand Down Expand Up @@ -102,13 +102,19 @@ private fun ImportLogObjectUi(
}
}

@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3WindowSizeClassApi::class)
@Composable
internal fun ImportLogBottomSheet(
onDismissRequest: () -> Unit,
logs: List<ImportLogObject>,
) {
val coroutineScope = rememberCoroutineScope()
val widthSizeClass =
LocalContext.current
.findActivity()
?.let { calculateWindowSizeClass(it) }
?.widthSizeClass
?: WindowWidthSizeClass.Compact

val lazyColumnState = rememberLazyListState()
val showScrollToTopButton by remember {
Expand Down Expand Up @@ -150,6 +156,7 @@ internal fun ImportLogBottomSheet(
items(logs, key = { it.uuid }) {
ImportLogObjectUi(
it,
widthSizeClass,
Modifier
.fillMaxWidth()
.padding(vertical = MaterialTheme.spacing.sm)
Expand All @@ -161,22 +168,34 @@ internal fun ImportLogBottomSheet(
}
}

private fun previewImportLogObject() =
ImportLogObject(
uuid = Uuid.generateV4(),
timestamp = Instant.fromEpochMilliseconds(0),
tag = "P-VIEW",
event = ImportLogEvent.Raw("Wow this is a log wow"),
)

@Preview
@Composable
private fun ImportLogObjectUiPreview() {
val log =
remember {
ImportLogObject(
uuid = Uuid.generateV4(),
timestamp = Instant.fromEpochMilliseconds(0),
tag = "P-VIEW",
event = ImportLogEvent.Raw("Wow this is a log wow"),
)
private fun ImportLogObjectUiCompactPreview() {
val log = remember { previewImportLogObject() }

ArcaeaOfflineTheme {
Card {
ImportLogObjectUi(log, WindowWidthSizeClass.Compact)
}
}
}

@Preview
@Composable
private fun ImportLogObjectUiMediumPreview() {
val log = remember { previewImportLogObject() }

ArcaeaOfflineTheme {
Card {
ImportLogObjectUi(log)
ImportLogObjectUi(log, WindowWidthSizeClass.Medium)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package xyz.sevive.arcaeaoffline.ui.components

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import xyz.sevive.arcaeaoffline.ui.theme.spacing

@Composable
fun LabeledCheckbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
label: String,
modifier: Modifier = Modifier,
) {
val interactionSource = remember { MutableInteractionSource() }

Row(
modifier.toggleable(
value = checked,
interactionSource = interactionSource,
indication = null,
role = Role.Checkbox,
onValueChange = onCheckedChange,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.xs),
) {
Checkbox(
checked = checked,
onCheckedChange = null,
interactionSource = interactionSource,
)
Text(label)
}
}
Loading
Loading