Skip to content
Open
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
10 changes: 10 additions & 0 deletions mobile/app/src/main/java/com/bytecats/metanoia/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ import com.bytecats.metanoia.ui.theme.MetanoiaTheme
import com.bytecats.metanoia.viewmodel.MainViewModel
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen

/**
* Main activity for the Metanoia Bible Reader app.
*
* Handles deep linking, navigation, and the main Compose UI setup.
* The activity is configured with launchMode="singleTask" to handle
* deep links properly when the app is already running.
*
* Deep links are processed through [DeepLink.parse] and consumed
* once to prevent replay when navigating back.
*/
class MainActivity : ComponentActivity() {
// Set from onCreate's initial intent and from onNewIntent (the activity
// is launchMode="singleTask" — see AndroidManifest.xml — specifically so
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,33 @@ import com.bytecats.metanoia.bible.dao.*
import com.bytecats.metanoia.models.*
import java.io.File

/**
* Bible database manager providing access to all Bible-related DAOs.
*
* Manages the SQLite database file and provides typed access to different data access objects:
* - [VerseDao]: Bible verse queries and storage
* - [FavoritesDao]: User's favorite Strong's numbers
* - [HighlightsDao]: Verse highlights with colors
* - [NotesDao]: User notes on passages
* - [ReadingAnalyticsDao]: Reading progress tracking
* - [InterlinearDao]: Interlinear word-by-word translations
* - [LexiconDao]: Strong's lexicon definitions
*
* Database location: /data/data/com.bytecats.metanoia/files/bible.db

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching BibleDatabase.kt:"
fd -a 'BibleDatabase\.kt$' . || true

file="$(fd 'BibleDatabase\.kt$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  echo
  echo "Line count:"
  wc -l "$file"
  echo
  echo "Relevant contents:"
  cat -n "$file" | sed -n '1,80p'
fi

echo
echo "Search for dbFile/filesDir/bible.db documents:"
rg -n "dbFile|filesDir|bible\.db|/data/data" . -g '!build' -g '!node_modules' -g '!dist' || true

Repository: 4cecoder/metanoia

Length of output: 18194


Document the database path from context.filesDir.

dbFile is constructed with File(context.filesDir, "bible.db"), so the /data/data/... KDoc path does not describe the actual API contract. Describe the location as context.filesDir/bible.db in app-private storage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/src/main/java/com/bytecats/metanoia/bible/BibleDatabase.kt` at
line 22, Update the database location KDoc near dbFile to describe the path as
context.filesDir/bible.db in app-private storage, matching the
File(context.filesDir, "bible.db") construction.

*
* @property context Android context for file system access
*/
class BibleDatabase(private val context: Context) {
private val dbFile = File(context.filesDir, "bible.db")

/**
* Open the Bible database with specified access mode.
*
* @param readOnly If true, opens in read-only mode; otherwise opens in read-write mode
* @return SQLiteDatabase instance
*/
fun openDb(readOnly: Boolean = false): SQLiteDatabase =
SQLiteDatabase.openDatabase(dbFile.absolutePath, null, openFlags(readOnly))

val verse = VerseDao(::openDb)
val favorites = FavoritesDao(::openDb)
val highlights = HighlightsDao(::openDb)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.bytecats.metanoia.bible

import com.bytecats.metanoia.models.strongsLanguagePrefix
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ import com.bytecats.metanoia.gateway.GatewayClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
* Primary Bible data manager for the Metanoia app.
*
* Manages Bible content, scraping from web sources, database operations,
* and provides high-level APIs for the UI layer.
*
* Features:
* - Verse retrieval from local SQLite database or web scrapers
* - Full-text and reference-based search
* - Favorites, highlights, and notes management
* - Interlinear data access
* - Strong's lexicon integration
* - Reading progress tracking
* - Multiple scraping sources with fallback (BibleGateway, BibleHub)
*
* @property context Android context for database and cache access
*/
class BibleManager(private val context: Context) {
private val dbFile = File(context.filesDir, "bible.db")
private val client = OkHttpClient()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.bytecats.metanoia.bible

import okhttp3.Call
import java.io.IOException

/**
Expand Down
20 changes: 18 additions & 2 deletions mobile/app/src/main/java/com/bytecats/metanoia/bible/DeepLink.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import com.bytecats.metanoia.models.BOOKS

/**
* A resolved deep-link target: a specific book/chapter, optionally a verse.
* `book` is always the canonical BOOKS entry's exact name (e.g. "SongofSolomon",
* "1Samuel") — never the raw path segment from the incoming URI.
*
* @property book The canonical BOOKS entry's exact name (e.g., "SongofSolomon", "1Samuel")
* @property chapter The chapter number (1-based)
* @property verse Optional verse number (null if only chapter specified)
*/
data class VerseReference(val book: String, val chapter: Int, val verse: Int?)

Expand Down Expand Up @@ -47,9 +49,23 @@ data class VerseReference(val book: String, val chapter: Int, val verse: Int?)
*/
object DeepLink {

/**
* Parse a deep-link URI into a VerseReference.
*
* @param uri The Android Uri to parse (either metanoia:// or https:// scheme)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep parse documentation aligned with accepted schemes.

DeepLink.parseParts accepts http, https, and metanoia, but the parse KDoc documents only metanoia and https. If http is supported, include it in @param uri. Otherwise, reject it in parseParts.

Proposed documentation fix
-     * `@param` uri The Android Uri to parse (either metanoia:// or https:// scheme)
+     * `@param` uri The Android Uri to parse (metanoia://, http://, or https:// scheme)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @param uri The Android Uri to parse (either metanoia:// or https:// scheme)
* `@param` uri The Android Uri to parse (metanoia://, http://, or https:// scheme)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/src/main/java/com/bytecats/metanoia/bible/DeepLink.kt` at line 55,
Update the `@param` uri KDoc for DeepLink.parse to document all schemes accepted
by DeepLink.parseParts, including http, https, and metanoia; preserve the
existing parsing behavior.

* @return A VerseReference if the URI is valid, null otherwise
*/
fun parse(uri: Uri): VerseReference? =
parseParts(uri.scheme, uri.host, uri.pathSegments?.toList() ?: emptyList())

/**
* Parse deep-link components into a VerseReference.
*
* @param scheme The URI scheme ("metanoia", "https", or "http")
* @param host The URI host (for custom scheme URIs)
* @param pathSegments The path segments from the URI
* @return A VerseReference if the components are valid, null otherwise
*/
fun parseParts(scheme: String?, host: String?, pathSegments: List<String>): VerseReference? {
// Custom-scheme URIs (metanoia://bible/...) put "bible" in the host,
// not the path -- Uri parses "bible" as the authority there, so the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.bytecats.metanoia.settings

import android.content.Context
import android.content.SharedPreferences
import com.bytecats.metanoia.update.ReleaseChannel

class SettingsManager(context: Context) {
private val prefs: SharedPreferences = context.getSharedPreferences("metanoia_settings", Context.MODE_PRIVATE)
Expand Down Expand Up @@ -140,9 +141,26 @@ class SettingsManager(context: Context) {
set(value) = prefs.edit().putString("scraper_user_agent", value).apply()

// --- Updates ---
/** Release channel for updates: STABLE, BETA, ALPHA, or NIGHTLY */
var releaseChannel: ReleaseChannel
get() = ReleaseChannel.fromString(prefs.getString("release_channel", ReleaseChannel.STABLE.name))
set(value) = prefs.edit().putString("release_channel", value.name).apply()
Comment on lines +145 to +147

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Migrate the legacy nightly preference when the new key is absent.

Existing installations can contain nightly_updates_enabled=true without release_channel. This getter then returns STABLE, so the user loses the prior nightly selection. Read the legacy flag only when release_channel is absent, then persist the migrated NIGHTLY value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/src/main/java/com/bytecats/metanoia/settings/SettingsManager.kt`
around lines 145 - 147, Update the releaseChannel getter to detect when the
"release_channel" preference is absent and, only in that case, read the legacy
"nightly_updates_enabled" flag; return and persist ReleaseChannel.NIGHTLY when
the flag is true, otherwise preserve the existing default and parsing behavior.
Keep the setter unchanged.


/** Enable/disable automatic update checking for the selected channel */
var updatesEnabled: Boolean
get() = prefs.getBoolean("updates_enabled", true)
set(value) = prefs.edit().putBoolean("updates_enabled", value).apply()
Comment on lines +149 to +152

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline mobile/app/src/main/java/com/bytecats/metanoia/viewmodel/MainViewModel.kt \
  --items all --view expanded

rg -n -C 5 \
  'performAutoUpdate|fetchLatestForChannel\s*\(|fetchLatest\s*\(|availableUpdate|releaseChannel|updatesEnabled' \
  mobile/app/src/main/java/com/bytecats/metanoia

Repository: 4cecoder/metanoia

Length of output: 33848


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '80,125p' mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt
sed -n '330,390p' mobile/app/src/main/java/com/bytecats/metanoia/update/UpdateChecker.kt
rg -n -C 3 'class ReleaseChannel|enum class ReleaseChannel|releaseChannel|updatesEnabled|startAutoUpdateLoop' mobile/app/src/main/java/com/bytecats/metanoia

Repository: 4cecoder/metanoia

Length of output: 17185


Make the auto-update loop honor updateSettings.

MainViewModel.performAutoUpdate() calls the deprecated channel-agnostic fetchLatest(), while the Settings UI uses fetchLatestForChannel(releaseChannel) and reads updatesEnabled. Add updatesEnabled checks before starting and continuing startAutoUpdateLoop(), and replace the loop fetch with channel-aware checks to keep stable/beta/alpha installs from pulling nightly updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/app/src/main/java/com/bytecats/metanoia/settings/SettingsManager.kt`
around lines 149 - 152, Update MainViewModel.performAutoUpdate and
startAutoUpdateLoop to check SettingsManager.updateSettings.updatesEnabled
before starting the loop and before each continued iteration. Replace the
deprecated channel-agnostic fetchLatest() call with channel-aware update
checking via fetchLatestForChannel(releaseChannel), preserving the selected
stable, beta, or alpha channel.


@Deprecated("Use releaseChannel instead", level = DeprecationLevel.WARNING)
var nightlyUpdatesEnabled: Boolean
get() = prefs.getBoolean("nightly_updates_enabled", false)
set(value) = prefs.edit().putBoolean("nightly_updates_enabled", value).apply()
set(value) {
prefs.edit().putBoolean("nightly_updates_enabled", value).apply()
// If enabling nightly, also switch to nightly channel for consistency
if (value) {
releaseChannel = ReleaseChannel.NIGHTLY
}
}

var lastUpdateCheckMillis: Long
get() = prefs.getLong("last_update_check_millis", 0L)
Expand All @@ -151,4 +169,9 @@ class SettingsManager(context: Context) {
var dismissedUpdateSha: String
get() = prefs.getString("dismissed_update_sha", "") ?: ""
set(value) = prefs.edit().putString("dismissed_update_sha", value).apply()
}

/** Last checked version for the current release channel */
var lastCheckedVersion: String
get() = prefs.getString("last_checked_version", "") ?: ""
set(value) = prefs.edit().putString("last_checked_version", value).apply()
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,42 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.concurrent.atomic.AtomicReference

/**
* Audio player state for TTS playback.
*/
enum class AudioPlayerState {
IDLE, PLAYING, STOPPED, ERROR
/** Player is idle and ready to play */
IDLE,
/** Currently playing audio */
PLAYING,
/** Playback was stopped */
STOPPED,
/** An error occurred during playback */
ERROR
}

/**
* TTS audio player - handles playback of generated speech audio.
*
* Supports both MediaPlayer (for WAV files) and AudioTrack (for PCM audio),
* with automatic fallback and state management.
*/
class TTSAudioPlayer {
private var mediaPlayer: MediaPlayer? = null
private var audioTrack: AudioTrack? = null
private val tag = "TTSAudioPlayer"

private val _state = AtomicReference(AudioPlayerState.IDLE)
/** Current playback state (thread-safe) */
val state: AudioPlayerState get() = _state.get()

/**
* Play audio from a file.
* Automatically handles WAV file playback via MediaPlayer,
* with fallback to PCM playback if MediaPlayer fails.
*
* @param file Audio file to play (WAV format recommended)
*/
fun play(file: File) {
stop()
try {
Expand Down Expand Up @@ -53,6 +77,9 @@ class TTSAudioPlayer {
}
}

/**
* Stop current playback and reset state.
*/
fun stop() {
val currentState = _state.get()
if (currentState == AudioPlayerState.STOPPED || currentState == AudioPlayerState.IDLE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ class GraphicsQualityManager(
* Check if device supports compute shaders
*/
private fun supportsComputeShaders(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
return true // Always true for minSdkVersion 28+

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Trace capability consumers and runtime graphics probes.
rg -n -C 5 '\bsupportsComputeShaders\b|GL_COMPUTE_SHADER|GLES31|compute[[:space:]-]?shader|computeShader' mobile/app/src/main/java
rg -n -C 3 'minSdk|compileSdk|targetSdk' mobile

Repository: 4cecoder/metanoia

Length of output: 7454


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files containing DeviceCapabilities/reference =="
rg -n -C 4 'DeviceCapabilities|int\.kotlin|deviceCapabilities|graphicsCapabilities|supportsComputeShaders|\bsupportsComputeShaders\b' mobile/app/src/main/java mobile/app/src/main/kotlin mobile -g '!**/README.md' || true

echo
echo "== GraphicsQualityManager relevant sections =="
wc -l mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt
sed -n '1,130p' mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt
sed -n '300,470p' mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt
sed -n '600,660p' mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt

Repository: 4cecoder/metanoia

Length of output: 40764


Probe compute-shader support instead of always returning true.

minSdkVersion 28 does not guarantee compute-shader capability. supportsComputeShaders() currently reports every device as supported via DeviceCapabilities.supportsComputeShaders, so later graphics paths may select compute-based rendering on devices that do not support it. Base this value on an actual GPU/GL probe before assigning it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@mobile/app/src/main/java/com/bytecats/metanoia/ui/effects/core/GraphicsQualityManager.kt`
at line 439, Update supportsComputeShaders() to determine compute-shader support
through an actual GPU/OpenGL capability probe instead of unconditionally
returning true for minSdkVersion 28+. Assign
DeviceCapabilities.supportsComputeShaders from the probe result, preserving the
existing capability flow so compute-based rendering is selected only when the
device genuinely supports it.

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fun BibleScreen(viewModel: MainViewModel, onNavigateToSettings: () -> Unit = {})

var step by remember { mutableStateOf("book") }
var selectedBook by remember { mutableStateOf<BibleBook?>(null) }
var selectedChapter by remember { mutableStateOf(1) }
var selectedChapter by remember { mutableIntStateOf(1) }
var currentChapterContent by remember { mutableStateOf<List<Pair<Int, String>>>(emptyList()) }
var interlinearData by remember(selectedBook, selectedChapter) { mutableStateOf<Map<Int, List<InterlinearWord>>>(emptyMap()) }
var highlights by remember(selectedBook, selectedChapter) { mutableStateOf<Map<Int, Int>>(emptyMap()) }
Expand Down Expand Up @@ -221,7 +221,7 @@ fun BibleScreen(viewModel: MainViewModel, onNavigateToSettings: () -> Unit = {})
}
}
) { innerPadding ->
var dragOffset by remember { mutableStateOf(0f) }
var dragOffset by remember { mutableFloatStateOf(0f) }
var hasTriggered by remember { mutableStateOf(false) }
Column(modifier = Modifier.padding(innerPadding).fillMaxSize().pointerInput(Unit) {
detectDragGestures(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import com.bytecats.metanoia.viewmodel.MainViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CollectionScreen(navController: NavController, viewModel: MainViewModel) {
var tabIndex by remember { mutableStateOf(0) }
var tabIndex by remember { mutableIntStateOf(0) }
val favs = remember { viewModel.bibleManager.getFavorites() }

Scaffold(topBar = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import com.bytecats.metanoia.settings.SettingsManager
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReaderSettingsPage(navController: NavController, settings: SettingsManager) {
var engSize by remember { mutableStateOf(settings.englishFontSize.toFloat()) }
var ancSize by remember { mutableStateOf(settings.ancientFontSize.toFloat()) }
var engSize by remember { mutableFloatStateOf(settings.englishFontSize.toFloat()) }
var ancSize by remember { mutableFloatStateOf(settings.ancientFontSize.toFloat()) }
var showEthiopian by remember { mutableStateOf(settings.showEthiopianCanon) }
var showApocrypha by remember { mutableStateOf(settings.showApocrypha) }
Scaffold(topBar = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
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.vector.ImageVector
Expand All @@ -32,7 +36,7 @@ fun SettingsDashboard(navController: NavController) {
SettingsLink("Data & Library", "Database Management", Icons.Default.Storage) {
navController.navigate("data_management")
}
SettingsLink("Updates", "Nightly / experimental builds", Icons.Default.SystemUpdate) {
SettingsLink("Updates", "Release channels and update checking", Icons.Default.SystemUpdate) {
navController.navigate("settings_updates")
}
SettingsLink("Changelog", "Recent commits and version history", Icons.Default.History) {
Expand Down Expand Up @@ -72,6 +76,78 @@ fun SettingToggle(title: String, sub: String, state: Boolean, onToggle: (Boolean
}
}

@Composable
fun SettingSection(title: String, content: @Composable ColumnScope.() -> Unit) {
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
title,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
content()
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T> SettingsDropdown(
label: String,
description: String,
options: List<T>,
selectedOption: T,
optionLabel: (T) -> String,
onOptionSelected: (T) -> Unit
) {
var expanded by remember { mutableStateOf(false) }

Column(modifier = Modifier.fillMaxWidth()) {
Text(
label,
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Medium
)
Text(
description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline
)

ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = optionLabel(selectedOption),
onValueChange = {},
readOnly = true,
modifier = Modifier
.fillMaxWidth()
.menuAnchor(),
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors()
)

ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.fillMaxWidth()
) {
options.forEach { option ->
DropdownMenuItem(
text = { Text(optionLabel(option)) },
onClick = {
onOptionSelected(option)
expanded = false
}
)
}
}
}
}
}

@Composable
fun ServiceItem(name: String, description: String) {
Row(
Expand All @@ -85,4 +161,4 @@ fun ServiceItem(name: String, description: String) {
Text(description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.outline)
}
}
}
}
Loading
Loading