8c8a4ab9e8
- 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>
220 lines
6.1 KiB
Markdown
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
|