Mobile UI pass: responsive tables, deck-view drill-down, dialog fixes

- Library/Card Search/Deck tables stack into dense card-style rows below
  720px instead of scrolling horizontally (floated thumbnail, wrapping
  chips for MV/Owned/color pips).
- Deck view: fix desktop min-widths and a non-wrapping button row that
  forced horizontal scroll on phones; split the deck list and the
  deck-building pane into separate drill-down screens with a back button
  so a long deck list doesn't bury the builder.
- Suggested Cards modal: fix action buttons crushing the card name down to
  one word per line by giving them their own full-width row.
- Add a close (X) button to the shared dialog title bar — mobile has no
  Escape key and wide dialogs leave little backdrop to tap.
- Info tab, bottom-nav loading/safe-area fixes, and Gradle NDK_HOME
  inheritance fix carried over from the Android bring-up work.
This commit is contained in:
2026-08-02 16:10:25 -05:00
parent 94e4f25ec4
commit 663af32562
10 changed files with 646 additions and 99 deletions

View File

@@ -1,4 +1,5 @@
import java.io.File
import java.util.Properties
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
@@ -50,9 +51,23 @@ open class BuildTask : DefaultTask() {
val release = release ?: throw GradleException("release cannot be null")
val args = listOf("tauri", "android", "android-studio-script");
// `project.exec` inherits whatever environment the Gradle daemon has —
// which is whatever launched Android Studio, not necessarily a shell
// where ANDROID_HOME/NDK_HOME were exported. Without NDK_HOME, cargo's
// build script for `ring` (and rusqlite's bundled SQLite) can't find
// the NDK's clang and rustBuild*Debug/Release fails outright. Read
// local.properties instead — the same file AGP itself reads sdk.dir
// from — so this works regardless of how Studio was started.
val localProps = Properties().apply {
val f = project.rootProject.file("local.properties")
if (f.exists()) f.inputStream().use { load(it) }
}
project.exec {
workingDir(File(project.projectDir, rootDirRel))
executable(executable)
localProps.getProperty("sdk.dir")?.let { environment("ANDROID_HOME", it) }
localProps.getProperty("ndk.dir")?.let { environment("NDK_HOME", it) }
args(args)
if (project.logger.isEnabled(LogLevel.DEBUG)) {
args("-vv")

View File

@@ -53,7 +53,17 @@ fn status_of(s: &AppState) -> Status {
#[tauri::command]
pub fn get_status(db: Db) -> Res<Status> {
Ok(status_of(&db.lock().unwrap()))
let status = status_of(&db.lock().unwrap());
// Only ever called once per launch (from the frontend's one status-fetch
// effect, retried on failure/timeout) — cheap to log unconditionally, and
// it's the one way to tell "this invoke never reached Rust" (nothing
// logged at all) apart from "it reached Rust and returned not-ready".
crate::mlog!(
"get_status: reference_ready={} card_count={}",
status.reference_ready,
status.card_count
);
Ok(status)
}
#[tauri::command]

View File

@@ -58,8 +58,20 @@ impl AppState {
index: None,
ingest_cancel: None,
};
// Best-effort open; failures just leave the catalog unloaded.
let _ = s.reopen();
crate::mlog!(
"AppState::new: library={} reference={} (exists={})",
s.library_path.display(),
s.reference_path.display(),
s.reference_path.exists()
);
// Best-effort open; failures just leave the catalog unloaded (the UI
// falls back to "no card data yet" and offers Download/Import again).
// That fallback previously made a real open failure indistinguishable
// from a first run — log it so it shows up in `adb logcat -s
// RustStdoutStderr` instead of silently reappearing every launch.
if let Err(e) = s.reopen() {
crate::mlog!("AppState::new: reopen failed: {e}");
}
s
}