mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
Document the commons module's purpose, source-set layout, and the CLI-safe vs UI boundary in commons/ARCHITECTURE.md, then clean up the clearest package overlaps that had accumulated: - merge duplicate util/utils -> util (all source sets) - unify service/services -> service (jvmAndroid) - move data/UserMetadataCache -> model/cache - fold compose/ into ui/ (ui/article, editor, elements, layouts, markdown, nip53LiveActivities, and Compose helpers in ui/state + ui/text) - move ProfileBroadcastBanner composable into profile/ui All changes are whole-file/whole-package moves with import rewrites; no logic changed. The chess logic/UI split is documented as deferred debt (it needs file-level surgery, not moves). Marks docs/shared-ui-analysis.md superseded. https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
226 lines
6.4 KiB
Markdown
226 lines
6.4 KiB
Markdown
# Shared UI Components Analysis
|
|
|
|
> **Superseded (2026-05-30).** This was the original feasibility analysis that
|
|
> motivated the `commons` module (then called "shared-ui"). The module now
|
|
> exists and is documented authoritatively in **`commons/ARCHITECTURE.md`**,
|
|
> which defines the package taxonomy, the CLI-safe / UI boundary, and the
|
|
> "where does my code go?" guide. Keep this file only as historical context.
|
|
|
|
Analysis of Android vs Desktop UI to identify reusable Compose Multiplatform components.
|
|
|
|
## Executive Summary
|
|
|
|
| Category | Android Files | Shareable As-Is | Needs Abstraction |
|
|
|----------|---------------|-----------------|-------------------|
|
|
| Formatters | 4 | 2 | 2 |
|
|
| Theme | 4 | 4 | 0 |
|
|
| Note Components | 35 | ~10 | ~25 |
|
|
| Total Potential | ~450+ | ~100+ | ~200+ |
|
|
|
|
## Immediately Shareable Components
|
|
|
|
These have **zero Android dependencies** and can be moved to a shared module today:
|
|
|
|
### Formatters (Pure Kotlin)
|
|
```
|
|
amethyst/ui/note/PubKeyFormatter.kt -> shared/ui/formatters/
|
|
amethyst/ui/note/ZapFormatter.kt -> shared/ui/formatters/
|
|
amethyst/ui/note/ZapFormatterNoDecimals.kt -> shared/ui/formatters/
|
|
```
|
|
|
|
### Theme (Pure Compose)
|
|
```
|
|
amethyst/ui/theme/Color.kt -> shared/ui/theme/
|
|
amethyst/ui/theme/Shape.kt -> shared/ui/theme/
|
|
amethyst/ui/theme/Type.kt -> shared/ui/theme/
|
|
```
|
|
|
|
### Layout Primitives
|
|
```
|
|
amethyst/ui/theme/ButtonBorder (padding values)
|
|
amethyst/ui/theme/Size* (dimension constants)
|
|
```
|
|
|
|
## Components Requiring Abstraction
|
|
|
|
### 1. String Resources (R.string.*)
|
|
|
|
**Problem:** Android uses `R.string.xxx` for localized strings.
|
|
|
|
**Solution:** Create `StringProvider` interface:
|
|
|
|
```kotlin
|
|
// commonMain
|
|
interface StringProvider {
|
|
fun get(key: String): String
|
|
fun get(key: String, vararg args: Any): String
|
|
}
|
|
|
|
// androidMain
|
|
class AndroidStringProvider(private val context: Context) : StringProvider {
|
|
override fun get(key: String) = context.getString(keyToId(key))
|
|
}
|
|
|
|
// jvmMain
|
|
class DesktopStringProvider : StringProvider {
|
|
private val strings = mapOf(
|
|
"post_not_found" to "Post not found",
|
|
"now" to "now",
|
|
// ...
|
|
)
|
|
override fun get(key: String) = strings[key] ?: key
|
|
}
|
|
```
|
|
|
|
**Affected files:**
|
|
- TimeAgoFormatter.kt
|
|
- BlankNote.kt
|
|
- All UI components with text
|
|
|
|
### 2. AccountViewModel
|
|
|
|
**Problem:** Most UI components depend on `AccountViewModel` for:
|
|
- User profile data
|
|
- Settings (image loading, NSFW, etc.)
|
|
- Actions (follow, mute, report)
|
|
|
|
**Solution:** Create interface in shared module:
|
|
|
|
```kotlin
|
|
// commonMain
|
|
interface IAccountState {
|
|
val userPubKey: String
|
|
val userNpub: String
|
|
val settings: IUserSettings
|
|
}
|
|
|
|
interface IUserSettings {
|
|
val showImages: Boolean
|
|
val showSensitiveContent: Boolean
|
|
}
|
|
```
|
|
|
|
### 3. Navigation (INav)
|
|
|
|
**Problem:** Android uses custom `INav` interface tied to Compose Navigation.
|
|
|
|
**Solution:** Already abstracted! `INav` is an interface - just needs to be in shared module.
|
|
|
|
### 4. Image Loading
|
|
|
|
**Problem:** Uses Coil with Android-specific caching.
|
|
|
|
**Solution:** Coil 3.x supports Compose Multiplatform. Create shared wrapper:
|
|
|
|
```kotlin
|
|
// commonMain
|
|
@Composable
|
|
expect fun AsyncImage(
|
|
url: String,
|
|
contentDescription: String?,
|
|
modifier: Modifier,
|
|
)
|
|
```
|
|
|
|
## Recommended Shared Module Structure
|
|
|
|
```
|
|
shared-ui/
|
|
├── src/
|
|
│ ├── commonMain/kotlin/
|
|
│ │ ├── formatters/
|
|
│ │ │ ├── PubKeyFormatter.kt
|
|
│ │ │ ├── ZapFormatter.kt
|
|
│ │ │ └── TimeAgoFormatter.kt (abstracted)
|
|
│ │ ├── theme/
|
|
│ │ │ ├── Color.kt
|
|
│ │ │ ├── Shape.kt
|
|
│ │ │ ├── Type.kt
|
|
│ │ │ └── Dimensions.kt
|
|
│ │ ├── components/
|
|
│ │ │ ├── NoteCard.kt
|
|
│ │ │ ├── UserAvatar.kt
|
|
│ │ │ └── LoadingIndicator.kt
|
|
│ │ └── providers/
|
|
│ │ ├── StringProvider.kt
|
|
│ │ └── ImageProvider.kt
|
|
│ ├── androidMain/kotlin/
|
|
│ │ └── providers/
|
|
│ │ └── AndroidStringProvider.kt
|
|
│ └── jvmMain/kotlin/
|
|
│ └── providers/
|
|
│ └── DesktopStringProvider.kt
|
|
└── build.gradle.kts
|
|
```
|
|
|
|
## Implementation Priority
|
|
|
|
### Phase 1: Low-Hanging Fruit (1-2 days)
|
|
1. Create `shared-ui` module
|
|
2. Move pure Kotlin formatters (PubKeyFormatter, ZapFormatter)
|
|
3. Move theme colors and dimensions
|
|
4. Update both apps to use shared module
|
|
|
|
### Phase 2: String Abstraction (2-3 days)
|
|
1. Create StringProvider interface
|
|
2. Implement for Android and Desktop
|
|
3. Migrate TimeAgoFormatter
|
|
4. Create composable wrappers for common strings
|
|
|
|
### Phase 3: Core UI Components (1 week)
|
|
1. Abstract AccountViewModel to IAccountState
|
|
2. Move simple components (BlankNote, LoadingIndicator)
|
|
3. Create NoteCard shared component
|
|
4. Integrate with both apps
|
|
|
|
### Phase 4: Complex Components (2+ weeks)
|
|
1. Image loading abstraction
|
|
2. RichTextViewer
|
|
3. ReactionsRow
|
|
4. Full NoteCompose migration
|
|
|
|
## Desktop vs Android Comparison
|
|
|
|
| Feature | Android | Desktop | Share Strategy |
|
|
|---------|---------|---------|----------------|
|
|
| Note display | NoteCompose.kt (51KB) | NoteCard in FeedScreen | Extract common layout |
|
|
| Time formatting | TimeAgoFormatter.kt | SimpleDateFormat inline | Abstract with interface |
|
|
| Key display | PubKeyFormatter.kt | Manual truncation | Use shared formatter |
|
|
| Colors | Color.kt | darkColorScheme() | Share Color.kt |
|
|
| Navigation | INav + Compose Nav | enum Screen | Abstract navigation |
|
|
| Images | Coil + OkHttp | Not implemented | Coil Multiplatform |
|
|
|
|
## Current Desktop Implementation Gaps
|
|
|
|
Desktop currently has basic implementations that could benefit from sharing:
|
|
|
|
1. **NoteCard** - Has inline date formatting, could use TimeAgoFormatter
|
|
2. **Key display** - Manual truncation, should use PubKeyFormatter
|
|
3. **Theme** - Uses default darkColorScheme, could use shared theme
|
|
4. **No image loading** - Needs Coil integration
|
|
|
|
## Quick Wins
|
|
|
|
Immediate improvements to desktop using patterns from Android:
|
|
|
|
```kotlin
|
|
// Instead of:
|
|
val npub = event.pubKey.take(16) + "..."
|
|
|
|
// Use shared formatter:
|
|
val npub = event.pubKey.toDisplayHexKey()
|
|
|
|
// Instead of:
|
|
val format = SimpleDateFormat("MMM d, HH:mm", Locale.getDefault())
|
|
|
|
// Use shared formatter:
|
|
val timeAgo = timeAgoNoDot(event.createdAt, stringProvider)
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
1. **Create shared-ui module** in project structure
|
|
2. **Move formatters** as proof of concept
|
|
3. **Create StringProvider** abstraction
|
|
4. **Iterate** on more complex components
|