Files
amethyst/docs/shared-ui-analysis.md
T
nrobi144 8c8a4ab9e8 feat: Add relay connections, login, and feed view to desktop app
- Add DesktopHttpClient and DesktopRelayConnectionManager for relay WebSocket connections
- Implement AccountManager with key generation and nsec/npub login support
- Create LoginScreen with key import and new key generation UI
- Build FeedScreen with live global feed subscription and note cards
- Add ProfileScreen showing account info and logout
- Update RelaySettingsScreen with connection status and relay management
- Configure JDK 21 toolchain for desktop builds
- Add shared UI analysis documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 14:02:44 +02:00

220 lines
6.1 KiB
Markdown

# Shared UI Components Analysis
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