Skip to content

feat(news): port the cu-12 / cu-13 news flow on top of NewsAPI#27

Merged
netqo merged 2 commits into
devfrom
feat/news-screen
May 27, 2026
Merged

feat(news): port the cu-12 / cu-13 news flow on top of NewsAPI#27
netqo merged 2 commits into
devfrom
feat/news-screen

Conversation

@netqo
Copy link
Copy Markdown
Owner

@netqo netqo commented May 27, 2026

Summary

  • Replaces the News and NewsDetail placeholders with the cu-12 / cu-13 mockup port driven end-to-end by Retrofit + Moshi + OkHttp against NewsAPI.org.
  • Wires Glide-compose for image rendering, a HorizontalPager carousel for the FEATURED block and a LazyColumn for the LATEST list — covers the TPO's Listado Dinámico / Loading-Success-Error / Búsqueda y Filtrado / Detalle / Renderizado de Imágenes requirements.
  • Search and source filtering run in-memory over the cached fetch so neither burns the 100 req / day NewsAPI quota.

Architecture

  • domain/news: NewsArticle + NewsRepository interface.
  • data/news: NewsApiService Retrofit binding, Moshi DTOs, NewsArticleMapper (drops rows missing required fields or flagged with NewsAPI's [Removed] sentinel) and an in-memory NewsRepositoryImpl that keys the cache by article URL so the detail screen finds articles without a second round-trip. Room-backed offline-first ships in the final entrega.
  • di/NewsModule: provides Moshi, an OkHttp client whose interceptor attaches X-Api-Key, the configured Retrofit + MoshiConverterFactory and the NewsApiService; binds the repository.
  • feature/news: sealed NewsUiState (Loading / Success / Error) + SourceFilter sealed type, Hilt MVVM ViewModel exposing a StateFlow with collectAsStateWithLifecycle in the UI.
  • NewsScreen: header, reactive search, scrollable source chips (driven dynamically off the loaded articles' source set), FEATURED HorizontalPager carousel with violet/grey dot indicators, LATEST LazyColumn with Glide thumbnails, and a centred ErrorState card on failure.
  • NewsDetailScreen: hero (Glide), title, published-at metadata, optional description, "READ FULL ARTICLE" CTA (Intent.ACTION_VIEW) and a header share chip (Intent.ACTION_SEND with title + URL).

Notes

  • Route.NewsDetail.build(id) URL-encodes the article id. NewsAPI's only stable id is the URL itself, and raw colons + slashes break Compose Navigation's path matching without encoding.
  • The INTERNET permission is already declared (added in the assistant PR for the Gemini SDK); Retrofit reuses it.

Test plan

  • ./gradlew assembleDebug
  • ./gradlew ktlintCheck
  • ./gradlew detekt
  • ./gradlew testDebugUnitTest (NewsViewModelTest: 9/9 + NewsArticleMapperTest: 7/7 passing)
  • Installed on device, opened the News tab and walked Loading -> Success -> filter by source and search -> open an article via the carousel and via the LATEST list -> Share intent and Read full article intent both fire.

Local setup

Add to stack/local.properties (gitignored):

NEWSAPI_KEY=<key from https://newsapi.org/register>

Then ./gradlew installDebug.

netqo added 2 commits May 27, 2026 18:27
Wires the News tab and the article detail screen end-to-end through
Retrofit + Moshi + OkHttp against NewsAPI.org, with Glide-rendered
images and a HorizontalPager carousel sitting above the LATEST
LazyColumn.

  * domain/news: NewsArticle data class + NewsRepository interface,
    nullable image and description because real-world NewsAPI rows
    skip them on some sources.
  * data/news: NewsApiService Retrofit binding + the Moshi DTOs,
    NewsArticleMapper that drops rows missing required fields or
    flagged with NewsAPI's "[Removed]" sentinel, and an in-memory
    repository implementation that caches the last successful fetch
    by article URL so the detail screen can look an article up
    without a second round-trip. Room-backed offline-first lands in
    the final entrega; the in-memory cache is the smallest thing
    that satisfies the second-submission contract.
  * di/NewsModule: provides Moshi, an OkHttp client whose interceptor
    attaches `X-Api-Key`, the configured Retrofit + MoshiConverterFactory,
    the NewsApiService and binds the repository.
  * feature/news: sealed NewsUiState (Loading / Success / Error) + a
    SourceFilter sealed type, a Hilt MVVM ViewModel that triggers the
    first refresh on init and exposes a StateFlow, search + source
    filtering applied in-memory so neither burns the 100 req/day quota.
  * NewsScreen renders the header, the reactive search input, the
    horizontally-scrollable source chips, the FEATURED carousel
    (HorizontalPager over the top 5 articles with violet/grey dot
    indicators), the LATEST LazyColumn with Glide thumbnails, and a
    centred ErrorState card on failure.
  * NewsDetailScreen renders the hero (Glide), title, published-at
    metadata, optional description, a "Read full article" CTA that
    fires `Intent.ACTION_VIEW` and a share chip on the header that
    fires `Intent.ACTION_SEND` with the title + URL.

Routes through `Route.NewsDetail.build(id)` which URL-encodes the
article id; NewsAPI's only stable id is the article URL itself, and
the raw colons + slashes break Compose Navigation's path matching
without encoding.

16 unit tests cover the ViewModel state machine (initial Loading,
success → filtered, search by title and source case-insensitive,
filter composition with search, retry after error) and the mapper
contract (drops the row when title / url / publishedAt / source are
missing or "[Removed]", keeps it and drops the field for description
or image individually).
android.net.Uri.encode returns null under plain JVM unit tests where
the Android framework is stubbed, which broke the pre-existing
RouteTest.newsDetail_build_inserts_id_into_path_pattern assertion.

URLEncoder lives in java.net so it works in tests and on device alike;
the trailing replace("+", "%20") brings the output to RFC 3986 path
encoding (URLEncoder defaults to form encoding where space becomes +).

Extends the test to also lock the encoding behaviour against a real
article URL with colons and slashes, so the next change to the helper
fails fast if someone drops the encoding.
@netqo netqo merged commit bfdf778 into dev May 27, 2026
3 checks passed
@netqo netqo deleted the feat/news-screen branch May 27, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant