DropdownField (ConfigScreen) and PluginProviderDropdown (PluginsScreen)
were byte-for-byte identical. Extract into ui/common/ExposedDropdownField
and route both call sites to it. No behavior change.
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
* perf(model): optimize model tab load speed with two-phase fetch and loadQuick
* perf: optimize data loading across Config, System, Profiles, and Billing tabs
* chore: remove leftover bottom nav references and unused updatePrimaryScreens
* style: fix ktlint violations in Config/Profiles/Billing ViewModels
- property/argument-list wrapping for async(...) calls
- multiline-expression wrapping for async blocks
- remove unused import in ConfigViewModel
- break long error-message string literals to stay under 120 cols
---------
Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
* refactor: migrate Gson serialization to kotlinx.serialization
* fix(dto): resolve serializing Any? and Map<String, Any?> with JsonElement
* test(refactor): fix unit test issues under kotlinx.serialization and update nullable DTO defaults
* fix(chat): support flexible numeric and string decoding for message timestamps
* fix(chat): sequentialize cache loading to avoid overwriting network state
- Extracted `safeLaunchLoad` inline extension function to `ViewModelExt.kt` to encapsulate API call success/failure handling logic and replaced usages across 18 viewmodels.
- Extracted `ToastHost` interface to standardize `clearToast()` behavior across all ViewModels displaying toasts.
- Extracted `ToastEffect` composable to `SharedComponents.kt` and applied it to 16 screens, reducing repetitive side effect code blocks.
Fixes#286
- HermesScaffold: added isRefreshing param, wraps content in PullToRefreshBox when onRefresh is set
- All 16 data screens: pass isRefreshing = state.isLoading
- Initial loading only shows full-screen LoadingState when data is empty (no spinner over existing content)
- AchievementsScreen already had the correct pattern, just needed isRefreshing param
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
- HermesScaffold: title now @Composable () -> Unit (M3-idiomatic)
- HermesScaffold: added modifier and snackbarHost parameters
- Updated all 18 callers from title="X" to title={Text("X")}
- ChatScreen: converted from raw Scaffold to HermesScaffold
- Removed unused Menu icon import (ktlint fix)
Complete UI/UX rewrite of hermes-mobile with Material You design system.
Phase 1: Color/Theme/Type/Shapes/Spacing/Motion tokens
Phase 2: HermesScaffold, StateViews (skeleton/empty/error), SearchBar, StatusBadge, ToggleRow
Phase 3: Bottom nav + drawer navigation with NavigationController
Phase 4: All 17 screens rewritten with HermesScaffold and Material You patterns
Closes PR #3.