M12 — the Android client, end to end #2

Merged
bvandeusen merged 86 commits from dev into main 2026-08-21 08:53:58 -04:00
4 changed files with 49 additions and 8 deletions
Showing only changes of commit dee71dffb3 - Show all commits
+15
View File
@@ -0,0 +1,15 @@
root = true
[*.{kt,kts}]
# ktlint's standard function-naming rule doesn't know about Compose, where
# PascalCase @Composable functions are the universal convention — every
# mainstream Compose codebase would fail it. This is ktlint's own supported
# exemption, and it mirrors the equivalent detekt override in config/detekt.yml.
ktlint_function_naming_ignore_when_annotated_with = Composable
# 120 rather than ktlint's looser default: this is a phone UI with deeply nested
# Compose calls, and a hard-ish ceiling is what keeps the nesting from becoming
# unreadable rather than merely long.
max_line_length = 120
indent_size = 4
insert_final_newline = true
@@ -2,6 +2,7 @@ package com.fabledsword.thoughtsync.ui
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -106,7 +107,7 @@ private fun CaptureField(
private fun NoteList(notes: List<Note>) { private fun NoteList(notes: List<Note>) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
// Keyed by id so Compose reuses rows across a refresh instead of // Keyed by id so Compose reuses rows across a refresh instead of
@@ -31,7 +31,9 @@ data class BoardState(
* (The sync methods are the exception: those are `suspend` on the Kotlin side * (The sync methods are the exception: those are `suspend` on the Kotlin side
* already, because uniffi bridges the Rust async to coroutines.) * already, because uniffi bridges the Rust async to coroutines.)
*/ */
class BoardViewModel(private val core: ThoughtSync) : ViewModel() { class BoardViewModel(
private val core: ThoughtSync,
) : ViewModel() {
var state by mutableStateOf(BoardState()) var state by mutableStateOf(BoardState())
private set private set
+29 -6
View File
@@ -1,19 +1,42 @@
# Per-rule overrides layered on top of detekt's defaults # Per-rule overrides layered on top of detekt's defaults
# (`buildUponDefaultConfig = true` in the :app `detekt {}` block). # (`--build-upon-default-config` on the CLI invocation in the Android lane).
# #
# The pre-2.0 `build:` top-level was removed; failure-on-finding is controlled by # The pre-2.0 `build:` top-level was removed; failure is controlled by the CLI's
# the Gradle DSL's `failOnSeverity` option instead. # exit code instead.
naming: naming:
# Composables conventionally use PascalCase function names. Matches every # Composables conventionally use PascalCase function names. Matches every
# mainstream Compose codebase, and Minstrel's config for the same reason. # mainstream Compose codebase, and mirrors the ktlint exemption in
# android/.editorconfig — the two tools have to agree or one of them is always
# wrong.
FunctionNaming: FunctionNaming:
ignoreAnnotated: ignoreAnnotated:
- "Composable" - "Composable"
style: style:
# The generated uniffi bindings are excluded at the Gradle level, but a
# magic-number complaint about a Compose dp value is noise rather than a smell.
MagicNumber: MagicNumber:
ignoreAnnotated: ignoreAnnotated:
- "Composable" - "Composable"
# Colour literals and dp constants are declared as named properties, which is
# exactly the "define it as a well-named constant" the rule asks for — the
# number simply appears in the declaration itself. Flagging
# `private val Brand = Color(0xFFF5C518)` would demand a constant holding the
# constant.
ignorePropertyDeclaration: true
exceptions:
TooGenericExceptionCaught:
# Catching broadly is DELIBERATE in these two places, and each site says so.
#
# * the ViewModel — a note that fails to save must become a visible error
# banner, never a crash. Narrowing this would mean an unanticipated
# failure takes the app down instead of being reported, which is strictly
# worse for the user.
# * the Application — the store failing to open is the one thing that must
# still let the app start, so it can explain itself.
#
# Scoped to those paths rather than disabled globally: elsewhere the rule is
# right and still applies.
excludes:
- "**/ui/**"
- "**/ThoughtSyncApplication.kt"