Commit graph

102 commits

Author SHA1 Message Date
Paweł Orzech
e71d15805c merge: integrate Phase 4b (Email-only Posts) - all 8 features complete 2026-03-20 00:59:58 +01:00
Paweł Orzech
5c931b138c feat: handle email-only posts in PostUploadWorker, Feed, and Detail screens
Phase 4b.3: Sent status in Feed + PostUploadWorker handling.
- PostUploadWorker: handle QUEUED_EMAIL_ONLY with email_only=true on GhostPost,
  pass newsletter slug to repository.createPost()
- FeedViewModel: map GhostPost email_only/sent status to FeedPost.emailOnly
- FeedScreen FilterChipsBar: add "Sent" chip (magenta, only when newsletter enabled)
- FeedScreen PostCardContent: show envelope icon + "Sent" in magenta for sent posts,
  replace "Share" with "Copy content" for email-only posts
- FeedScreen StatusBadge: handle sent/emailOnly status
- DetailScreen: show email-only info card with errorContainer color when post is
  sent via email only, noting it's not visible on the blog
2026-03-20 00:58:50 +01:00
Paweł Orzech
0c43dc173c merge: integrate Phase 5 (Media Upload) with existing phases 2026-03-20 00:57:01 +01:00
Paweł Orzech
f93a21e743 feat: add email-only post option in Composer with confirmation dialog
Phase 4b.2: Email-only option in Composer.
- Add "Send via Email Only" dropdown menu item (visible when newsletter enabled)
- Add showEmailOnlyConfirmation state to ComposerUiState
- Add sendEmailOnly(), confirmEmailOnly(), cancelEmailOnly() to ViewModel
- submitEmailOnlyPost() saves with emailOnly=true, QUEUED_EMAIL_ONLY status
- Add EmailOnlyConfirmationDialog with warning icon, post preview,
  newsletter picker (if multiple), bold warning about irreversibility,
  and error-colored confirm button
2026-03-20 00:56:04 +01:00
Paweł Orzech
f9d060ed7d feat: add SENT status, QUEUED_EMAIL_ONLY queue status, and email_only field for email-only posts
Phase 4b.1: Add data model support for email-only posts.
- PostStatus: add SENT enum value
- QueueStatus: add QUEUED_EMAIL_ONLY enum value
- PostFilter: add SENT filter with "status:sent" ghost filter
- GhostPost: add email_only Boolean field
- FeedPost: add emailOnly Boolean field
- LocalPostDao: include QUEUED_EMAIL_ONLY in queued posts query
- OverallStats: handle SENT status in stats calculation
- FeedScreen: show "Pending email send" for QUEUED_EMAIL_ONLY queue status
- Update tests for new enum values and fields
2026-03-20 00:54:34 +01:00
Paweł Orzech
3b1061694d merge: integrate Phase 4a (Newsletter) with existing phases 2026-03-20 00:50:17 +01:00
Paweł Orzech
a1aae661c9 feat: add video/audio playback in Feed and Detail screens
- Add Media3 ExoPlayer dependencies (media3-exoplayer, media3-ui 1.2.1)
- Extend FeedPost with videoUrl and audioUrl fields
- Parse video/audio card URLs from mobiledoc JSON in FeedViewModel
- Map LocalPost video/audio URIs to FeedPost in toFeedPost()
- Create VideoPlayer composable: ExoPlayer in AndroidView, play button overlay, tap to play
- Create AudioPlayer composable: play/pause button, progress slider, duration text
- Integrate compact VideoPlayer and AudioPlayer in FeedScreen post cards
- Integrate full-size VideoPlayer and AudioPlayer in DetailScreen with reveal animations
- Load video/audio URIs when editing a post in ComposerViewModel
2026-03-20 00:49:24 +01:00
Paweł Orzech
39a51e5d4b feat: add newsletter sending toggle in Composer publish dialog
- Add newsletter fields to ComposerUiState (enabled, newsletters list,
  selected newsletter, sendAsNewsletter, emailSegment, subscriber count,
  confirmation dialog state)
- Load newsletter data on init when newsletter features are enabled
- Add newsletter options in publish dropdown: send-as-newsletter switch,
  newsletter picker (radio buttons), email segment picker (All/Free/Paid)
- Show warning about irreversible email send with subscriber count
- Change publish button to tertiaryContainer color and email icon when
  newsletter sending is active
- Add NewsletterConfirmationDialog requiring "WYSLIJ" typed input to
  confirm, with summary of newsletter name, segment, count, and title
- Pass newsletter slug and email segment through to PostRepository
- Store newsletter slug in LocalPost for offline queue support
2026-03-20 00:48:18 +01:00
Paweł Orzech
c55881e7a8 feat: display file attachments in Feed and Detail screens
- Extend FeedPost with fileUrl and fileName fields
- Parse mobiledoc file cards in FeedViewModel.extractFileCardFromMobiledoc()
- Map LocalPost file fields to FeedPost in toFeedPost()
- Create FileAttachmentCard composable with file type icon colors and tap-to-download
- Integrate file card into PostCardContent (FeedScreen) and DetailScreen
2026-03-20 00:48:01 +01:00
Paweł Orzech
27782893dc feat: add video and audio picker buttons and upload support in Composer
- Add videoUri, audioUri, uploadedVideoUrl, uploadedAudioUrl, isUploadingMedia to ComposerUiState
- Add setVideo/removeVideo/setAudio/removeAudio methods to ComposerViewModel
- Update submitPost to upload video/audio via uploadMediaFile and pass URLs to MobiledocBuilder
- Save videoUri/audioUri to LocalPost for offline queue
- Add Video and Audio picker buttons to composer toolbar
- Add MediaPreviewCard composable showing filename, file size, and remove button
- Update PostUploadWorker to upload video/audio before building mobiledoc
2026-03-20 00:46:27 +01:00
Paweł Orzech
74dac1db6f feat: add file attachment support in Composer, MobiledocBuilder, and PostUploadWorker
- Add file card support to MobiledocBuilder with Ghost's native file card format
- Add file card tests to MobiledocBuilderTest
- Add file state fields to ComposerUiState (fileUri, fileName, fileSize, fileMimeType, uploadedFileUrl)
- Add addFile()/removeFile() methods to ComposerViewModel with 50MB size validation
- Add file picker button and FileAttachmentComposerCard in ComposerScreen
- Update PostUploadWorker to upload files via repository.uploadFile() and include in mobiledoc
2026-03-20 00:46:06 +01:00
Paweł Orzech
bbe991b027 feat: add newsletter toggle in Settings screen
- Add "Newsletter" section after Content/Tags section
- Switch to enable/disable newsletter features per account
- Info text explaining the toggle's effect on composer
- Best-effort API validation when toggling ON (fetches newsletters count)
- Animated validation status display
2026-03-20 00:44:58 +01:00
Paweł Orzech
96e2799787 feat: add video and audio card support to MobiledocBuilder
- Add 8-param build() overload accepting videoUrl and audioUrl
- Video card: ["video",{"src":"url","loop":false}]
- Audio card: ["audio",{"src":"url"}]
- Card order: images -> video -> audio -> bookmark
- Add tests for video only, audio only, and all media types combined
2026-03-20 00:44:02 +01:00
Paweł Orzech
ed11577be1 feat: add newsletter model, API endpoint, and per-account preferences
- Create NewsletterModels.kt with GhostNewsletter and NewslettersResponse
- Add getNewsletters() endpoint to GhostApiService
- Add optional newsletter/emailSegment query params to createPost/updatePost
- Create NewsletterPreferences for per-account newsletter toggle
- Add fetchNewsletters() and fetchSubscriberCount() to PostRepository
- Pass newsletter params through PostRepository to API service
- Add Robolectric tests for NewsletterPreferences
2026-03-20 00:43:53 +01:00
Paweł Orzech
2f9b7dac09 feat: add file upload API endpoint, model, and repository method
- Create FileModels.kt with FileUploadResponse and UploadedFile data classes
- Add uploadFile() multipart endpoint to GhostApiService
- Add uploadFile(uri) method to PostRepository following the same pattern as uploadImage()
2026-03-20 00:43:10 +01:00
Paweł Orzech
2410d05bd6 feat: add media upload API endpoints and repository method for video/audio
- Create MediaModels.kt with MediaUploadResponse and UploadedMedia data classes
- Add uploadMedia() and uploadMediaThumbnail() to GhostApiService
- Add uploadMediaFile(uri) to PostRepository with MIME detection
2026-03-20 00:42:58 +01:00
Paweł Orzech
807c6d559e merge: integrate Phase 2 (Tags CRUD) with existing phases 2026-03-20 00:39:31 +01:00
Paweł Orzech
7d199e9fe9 merge: integrate Phase 3 (Members API) with existing phases 2026-03-20 00:37:02 +01:00
Paweł Orzech
a81a65281f feat: add tag statistics section in Stats screen
StatsViewModel fetches tags from TagRepository, computes most used tag
and posts-without-tags count. StatsScreen shows "Tags" section with
horizontal progress bars (LinearProgressIndicator per tag, colored by
accent_color), most used tag, total tags, and posts without tags count.
2026-03-20 00:35:23 +01:00
Paweł Orzech
33647d41d6 feat: add Member detail screen with profile, subscriptions, activity, and labels
MemberDetailScreen shows scrollable profile: large avatar header with name
and email, 3 quick stat tiles (status, open rate, emails sent), subscription
details for paid members (tier, price, renewal date, cancellation status),
activity section (joined date, last seen, geolocation), newsletters list
with read-only checkboxes, labels as FlowRow of AssistChips, email activity
with open rate progress bar, and member notes.
2026-03-20 00:35:01 +01:00
Paweł Orzech
0752238578 merge: integrate Phase 1 (Site Metadata) with Phase 7 (Pages) 2026-03-20 00:34:46 +01:00
Paweł Orzech
aaf29f1512 feat: add tag filter chips in Feed with popular tags LazyRow
FeedViewModel fetches tags on refresh(), takes top 10 by post count.
FeedScreen shows LazyRow of FilterChip below status filter: "All tags"
first, then popular tags with post counts. Tapping filters posts by tag.
Post cards now show tags in compact labelSmall format joined by dots.
2026-03-20 00:33:57 +01:00
Paweł Orzech
ac461c3e6f feat: show "Publishing to" chip in Composer for multi-account users
When more than one account is configured, display an informational
AssistChip at the top of the Composer showing the active blog name
and site icon. Uses SiteMetadataCache for the blog title, falls back
to account name. Non-clickable, only shown for disambiguation.
2026-03-20 00:33:16 +01:00
Paweł Orzech
afa0005a47 feat: add Members list screen with search, filter, pagination, and nav routes
MembersViewModel manages members list state with loading, pagination,
filter (All/Free/Paid), and debounced search. MembersScreen shows
TopAppBar with total count, search field, segmented filter buttons,
and LazyColumn with member rows (avatar via Coil or colored initial,
name, email, open rate progress bar, relative time, PAID/NEW badges).
Add Routes.MEMBERS and Routes.MEMBER_DETAIL to NavGraph (not in
bottomBarRoutes). Wire "See all members" button from Stats screen.
2026-03-20 00:32:45 +01:00
Paweł Orzech
0679b18b8e feat: show blog name and site icon in Feed top bar
Replace account name with blog title from SiteMetadataCache in the Feed
TopAppBar. Show site icon (24dp, circular) before the title. Truncate
blog name to 20 characters with ellipsis. Falls back to account name
or "Swoosh" if no cached site data exists.
2026-03-20 00:32:24 +01:00
Paweł Orzech
11b20fd42a feat: add Tags management screen with list/edit modes
TagsViewModel manages tag CRUD state. TagsScreen shows searchable list
of OutlinedCards with accent dot, name, count, description. Edit mode
supports name, slug (read-only), description, accent_color hex,
visibility radio. Wired into NavGraph via Routes.TAGS and accessible
from Settings screen via "Tags" row.
2026-03-20 00:31:53 +01:00
Paweł Orzech
b829ff5963 Merge branch 'worktree-agent-a5a483ec' into claude/ghost-microblog-android-utau1 2026-03-20 00:31:46 +01:00
Paweł Orzech
471fea6183 feat: add blog info section in Settings with site metadata
Display cached Ghost site metadata (logo/icon, title, description, URL,
version, locale) in a card above the Current Account section. Add "Open
Ghost Admin" button that launches the blog's admin panel in browser.
Show version warning banner if Ghost version is older than v5.
2026-03-20 00:31:22 +01:00
Paweł Orzech
83b779155e feat: add Pages list and editor screen with Settings navigation
Add PagesViewModel with CRUD operations and edit/create state management.
Add PagesScreen with dual-mode UI (list with long-press context menu and
editor with title/content/slug/status fields). Wire navigation from
Settings via "Static Pages" row. Pages use slide-in-horizontal transition
consistent with other detail screens.
2026-03-20 00:31:22 +01:00
Paweł Orzech
e99d88e10a feat: show member stats tiles in Stats screen with animated counters
StatsViewModel now fetches members via MemberRepository and computes
MemberStats. StatsScreen shows a 2x3 grid of ElevatedCard tiles when
memberStats is available: Total, New this week, Open rate, Free, Paid,
MRR. Includes animated counters and a "See all members" navigation button.
Member fetch failure is non-fatal (tiles simply hidden).
2026-03-20 00:29:50 +01:00
Paweł Orzech
be37f6284f feat: fetch site metadata on setup and show confirmation card
After successful connection test, fetch Ghost /site/ endpoint to get
blog name, description, icon, and version. Show a confirmation card
with site details before completing setup. Warn if Ghost version < 5.
Cache site metadata per account via SiteMetadataCache. Falls back to
existing behavior if site fetch fails.
2026-03-20 00:29:09 +01:00
Paweł Orzech
532e04e571 feat: add tag autocomplete in Composer with suggestions and chips
ComposerViewModel fetches available tags from TagRepository on init,
filters suggestions as user types, supports addTag/removeTag. ComposerScreen
shows tag input field with dropdown suggestions (name + post count),
"Create new" option, and FlowRow of InputChip tags with close icons.
2026-03-20 00:28:21 +01:00
Paweł Orzech
64a573a95c feat: add MemberRepository with fetchMembers, fetchAllMembers, and getMemberStats
MemberRepository follows the same pattern as PostRepository: Context constructor,
AccountManager, ApiClient. Includes fetchMembers (paged), fetchMember (single),
fetchAllMembers (all pages, max 20), and getMemberStats (pure function computing
total/free/paid/newThisWeek/avgOpenRate/MRR). Comprehensive tests for getMemberStats.
2026-03-20 00:28:10 +01:00
Paweł Orzech
a558a2f289 feat: add PageRepository for Ghost Pages CRUD operations
Follows PostRepository pattern with AccountManager-based auth,
Dispatchers.IO coroutine context, and Result<T> return types.
Exposes fetchPages, createPage, updatePage, deletePage methods
plus getBlogUrl for constructing page URLs in the UI.
2026-03-20 00:28:01 +01:00
Paweł Orzech
d83309f8bc feat: add Pages API model, endpoints, and model tests
Introduce GhostPage, PagesResponse, PageWrapper data classes for
Ghost CMS static pages. Add CRUD endpoints (getPages, createPage,
updatePage, deletePage) to GhostApiService. Include comprehensive
unit tests for serialization and default values.
2026-03-20 00:27:33 +01:00
Paweł Orzech
492ee1ca11 feat: add SiteMetadataCache for per-account site metadata storage
SharedPreferences-based cache for GhostSite metadata keyed by account ID.
Supports save/get/getVersion/remove operations with Gson serialization.
Includes Robolectric tests for round-trip, overwrite, multi-account
isolation, and removal.
2026-03-20 00:26:46 +01:00
Paweł Orzech
689b8cc8c2 feat: add Member model, API endpoints, and model parsing tests
Add MemberModels.kt with GhostMember, MemberLabel, MemberNewsletter,
MemberSubscription, SubscriptionPrice, and SubscriptionTier data classes.
Add getMembers() and getMember() endpoints to GhostApiService.
Add comprehensive JSON parsing tests for all member model types.
2026-03-20 00:26:32 +01:00
Paweł Orzech
2dbb4ad005 feat: add TagRepository for tag CRUD operations
Follows PostRepository pattern: constructor takes Context, creates
AccountManager, uses ApiClient.getService, wraps calls in
withContext(Dispatchers.IO), returns Result<T>.
2026-03-20 00:26:15 +01:00
Paweł Orzech
d0019947f8 feat: add extended tag model (GhostTagFull) and tag CRUD API endpoints
Add TagModels.kt with GhostTagFull, TagsResponse, TagWrapper, TagCount
data classes for full Ghost tag management. Add getTags, getTag,
createTag, updateTag, deleteTag endpoints to GhostApiService.
2026-03-20 00:25:34 +01:00
Paweł Orzech
6761eae351 feat: add GhostSite model and /site/ API endpoint
Add GhostSite data class for Ghost CMS site metadata (title, description,
logo, icon, accent color, URL, version, locale). Add getSite() endpoint
to GhostApiService. Include unit tests for Gson deserialization and
version parsing.
2026-03-20 00:24:50 +01:00
Paweł Orzech
8326d06861 feat: add DB migration v3→v4 with new LocalPost columns for email, media, files 2026-03-20 00:21:28 +01:00
Paweł Orzech
c9f77d8e25
fix: add proguard rule to suppress missing errorprone annotations in release build 2026-03-19 15:37:24 +01:00
Paweł Orzech
11f4e2f8f6
fix: extract avatar from post authors instead of /users/me/ (404)
Ghost Admin API /users/me/ returns 404. Instead, extract
profile_image from the first post's authors array which is
already fetched on every refresh.
2026-03-19 15:33:42 +01:00
Paweł Orzech
05f5518bdb
fix: fetch Ghost avatar for existing accounts on app launch 2026-03-19 15:30:28 +01:00
Paweł Orzech
ccd729e82f
feat: show app version in Settings, fix composer NPE crash, bump to v0.2.0
- Settings: shows "Swoosh v0.2.0" at bottom
- Fix: NPE crash in composer error display (AnimatedVisibility exit)
- Version bumped to 0.2.0 (versionCode 2)
- CLAUDE.md: added versioning process documentation
2026-03-19 15:27:58 +01:00
Paweł Orzech
dcb9c50c02
feat: fetch Ghost profile avatar for account icon, fallback to colored initial 2026-03-19 15:25:47 +01:00
Paweł Orzech
edca4dd0c5
feat: add refresh button to stats screen top bar 2026-03-19 15:22:23 +01:00
Paweł Orzech
4a2a18282c
fix: stats screen shows layout instantly, only numbers animate (no stagger entrance) 2026-03-19 14:59:28 +01:00
Paweł Orzech
3da3e97e77
feat: move search to top bar, replace with Stats tab in bottom nav
- Bottom tabs: Home / Stats / Settings (was Home / Search / Settings)
- Search icon back in feed top bar
- Stats screen: no back button, tab-style fade transitions
- Removed Stats link from Settings screen
2026-03-19 14:57:44 +01:00
Paweł Orzech
c91ccd0afb
fix: code review findings - @Stable on FeedPost, derivedStateOf, deduplicate dismiss logic 2026-03-19 14:54:30 +01:00