youlookwhat/CloudReader
🗡️ 云阅:一款基于网易云音乐UI,使用玩Android Api,Retrofit2 + RxJava2 + Room + MVVM-databinding架构开发的Android客户端
Solo project — review before adopting
weakest axissingle-maintainer (no co-maintainers visible); no CI workflows detected
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 2mo ago
- ✓Apache-2.0 licensed
- ✓Tests present
Show all 5 evidence items →Show less
- ⚠Solo or near-solo (1 contributor active in recent commits)
- ⚠No CI workflows detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: onboard a second core maintainer
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/youlookwhat/cloudreader)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/youlookwhat/cloudreader on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: youlookwhat/CloudReader
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/youlookwhat/CloudReader shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Solo project — review before adopting
- Last commit 2mo ago
- Apache-2.0 licensed
- Tests present
- ⚠ Solo or near-solo (1 contributor active in recent commits)
- ⚠ No CI workflows detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live youlookwhat/CloudReader
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/youlookwhat/CloudReader.
What it runs against: a local clone of youlookwhat/CloudReader — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in youlookwhat/CloudReader | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 90 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of youlookwhat/CloudReader. If you don't
# have one yet, run these first:
#
# git clone https://github.com/youlookwhat/CloudReader.git
# cd CloudReader
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of youlookwhat/CloudReader and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "youlookwhat/CloudReader(\\.git)?\\b" \\
&& ok "origin remote is youlookwhat/CloudReader" \\
|| miss "origin remote is not youlookwhat/CloudReader (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "app/src/main/java/com/example/jingbin/cloudreader/ui/MainActivity.java" \\
&& ok "app/src/main/java/com/example/jingbin/cloudreader/ui/MainActivity.java" \\
|| miss "missing critical file: app/src/main/java/com/example/jingbin/cloudreader/ui/MainActivity.java"
test -f "app/src/main/java/com/example/jingbin/cloudreader/http/HttpClient.java" \\
&& ok "app/src/main/java/com/example/jingbin/cloudreader/http/HttpClient.java" \\
|| miss "missing critical file: app/src/main/java/com/example/jingbin/cloudreader/http/HttpClient.java"
test -f "app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java" \\
&& ok "app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java" \\
|| miss "missing critical file: app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java"
test -f "app/src/main/java/com/example/jingbin/cloudreader/http/cache/ACache.java" \\
&& ok "app/src/main/java/com/example/jingbin/cloudreader/http/cache/ACache.java" \\
|| miss "missing critical file: app/src/main/java/com/example/jingbin/cloudreader/http/cache/ACache.java"
test -f "app/src/main/java/com/example/jingbin/cloudreader/app/app.kt" \\
&& ok "app/src/main/java/com/example/jingbin/cloudreader/app/app.kt" \\
|| miss "missing critical file: app/src/main/java/com/example/jingbin/cloudreader/app/app.kt"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 90 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~60d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/youlookwhat/CloudReader"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
CloudReader is an Android reading client app styled after Netease Cloud Music's UI, built with MVVM-DataBinding architecture using Retrofit2 + RxJava2 + Room. It aggregates content from multiple APIs (WanAndroid, Gank.io, DouBan) to deliver articles, jokes, films, and Android development resources in a Material Design interface. Single monolithic APK structure: app/src/main/java organized by feature (adapter/, bean/, base/, viewmodel/, ui/) with shared constants in app/ (Constants.kt, ConstantsImageUrl.kt, RxCodeConstants.kt). MVVM separation: ViewModels coordinate with Room entities (bean/) and Retrofit services; adapters (adapter/) bind RecyclerView items via DataBinding. Navigation via NavigationView + DrawerLayout; RxBus replaces EventBus for inter-component messaging.
👥Who it's for
Android developers learning MVVM patterns, DataBinding, and real-world API integration; users who want a stylish, feature-rich reading app combining tech news, entertainment content, and curated Android development materials.
🌱Maturity & risk
Actively maintained; currently at v3.8.3 (53 release builds) with recent development visible. Includes instrumented tests (app/src/androidTest/) and targets modern Android 34 with Gradle 8.0. The project demonstrates production-quality patterns but is primarily an educational showcase rather than a mission-critical app.
Moderate risks: heavy reliance on external APIs (WanAndroid, Gank.io, DouBan) without visible fallbacks or error handling patterns in file list; mixed Java/Kotlin codebase (821KB Java + 67KB Kotlin) increases maintenance surface; no visible CI/CD pipeline (.github/FUNDING.yml present but no Actions workflows shown). Single-maintainer project (youlookwhat) with no visible issue triage discipline.
Active areas of work
Recent focus on Kotlin migration (Film-related adapters in Kotlin: FilmAdapter.kt, FilmComingAdapter.kt, FilmDetailActorAdapter.kt, JokeAdapter.kt, MovieDetailAdapter.kt) alongside legacy Java adapters. Dark mode support (mentioned in features) and CoordinatorLayout+Behavior patterns implemented. Version bump to 3.8.3 suggests active bug fixes and feature polish.
🚀Get running
Clone and import: git clone https://github.com/youlookwhat/CloudReader.git && cd CloudReader. Open in Android Studio Ladybug 2024.2.1+. Sync Gradle (gradle-8.0). Connect emulator or device (API 21+ recommended, targets API 34). Run via ./gradlew installDebug or IDE's Run button. Ensure JDK17 and NDK for armeabi-v7a, arm64-v8a, x86 ABIs.
Daily commands:
After gradle sync: Run via Android Studio (green play button → select emulator/device) or CLI: ./gradlew installDebug && ./gradlew startApp. Debug variant runs unproguarded. Release build (commented signing config in build.gradle) requires keystore setup; ./gradlew assembleRelease after uncommenting signingConfigs.
🗺️Map of the codebase
app/src/main/java/com/example/jingbin/cloudreader/ui/MainActivity.java— Primary entry point and navigation hub; orchestrates fragment management and bottom navigation for all app featuresapp/src/main/java/com/example/jingbin/cloudreader/http/HttpClient.java— Retrofit2 & OkHttp configuration; centralizes all HTTP interceptors, timeout settings, and API client initializationapp/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java— API request implementation facade; mediates between ViewModels and multiple data sources (WanAndroid, Gank, DouBan, Mtime)app/src/main/java/com/example/jingbin/cloudreader/http/cache/ACache.java— Local file-based caching layer; persists API responses to disk for offline-first data access and performanceapp/src/main/java/com/example/jingbin/cloudreader/app/app.kt— Application singleton and global initialization; sets up dependency injection, Kotlin extensions, and app-wide configurationapp/build.gradle— Build configuration and dependency management; defines Kotlin, Retrofit2, RxJava2, Room, and Material Design library versionsapp/src/main/AndroidManifest.xml— Android manifest declaration; defines all activities, permissions, and API endpoints for network/file access
🛠️How to make changes
Add a new API data source (e.g., a new API like Gank or Mtime)
- Define API service interface with Retrofit2 annotations in a new service class (similar to existing Gank, WanAndroid services) (
app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java) - Create data model beans (POJO) in bean/ directory matching the API response structure (
app/src/main/java/com/example/jingbin/cloudreader/bean/) - Add API integration method to RequestImpl using RxJava2 Observable chain with ACache fallback (
app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java) - Create a Repository class (e.g., GankRepository.java) wrapping the RequestImpl call with Room/cache logic (
app/src/main/java/com/example/jingbin/cloudreader/data/model/) - Add constants for endpoints and keys to Constants.kt (
app/src/main/java/com/example/jingbin/cloudreader/app/Constants.kt)
Add a new content listing screen (Fragment with RecyclerView)
- Create new Fragment class extending Android Fragment with ViewModel data binding (
app/src/main/java/com/example/jingbin/cloudreader/ui/) - Create corresponding RecyclerView adapter in adapter/ using data binding patterns (similar to EverydayAdapter.java) (
app/src/main/java/com/example/jingbin/cloudreader/adapter/) - Define layout XML with RecyclerView and bind to ViewModel via data binding expression (
app/src/main/res/layout/) - Add navigation menu item in MainActivity or create new tab in bottom navigation (
app/src/main/java/com/example/jingbin/cloudreader/ui/MainActivity.java) - Wire up RxJava2 subscription in Fragment using RequestImpl API call with error/loading state management (
app/src/main/java/com/example/jingbin/cloudreader/ui/)
Implement offline caching for a new API endpoint
- Add ACache.put() call after successful API response in RequestImpl to serialize JSON to disk (
app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java) - Modify RxJava2 chain to emit cached Observable via ACache.getAsString() as fallback in onError block (
app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java) - Add cache key constant to Constants.kt following naming convention (e.g., CACHE_KEY_GANK) (
app/src/main/java/com/example/jingbin/cloudreader/app/Constants.kt) - Test offline behavior by disabling network and verifying cached data loads in UI (
app/src/main/java/com/example/jingbin/cloudreader/ui/)
Add user authentication & session management
- Create login API endpoint in RequestImpl calling WanAndroid login service (
app/src/main/java/com/example/jingbin/cloudreader/http/RequestImpl.java) - Store login response (token/cookie) using UserUtil.java SharedPreferences wrapper (
app/src/main/java/com/example/jingbin/cloudreader/data/UserUtil.java) - Add OkHttp Interceptor in HttpClient.java to inject auth token into all requests (
app/src/main/java/com/example/jingbin/cloudreader/http/HttpClient.java) - Handle 401 response in interceptor or RxJava error handler to trigger re-login flow (
app/src/main/java/com/example/jingbin/cloudreader/http/HttpClient.java)
🪤Traps & gotchas
API key embedding: Constants likely hardcoded in Constants.kt or resource files (Gank.io, DouBan, WanAndroid public APIs often unguarded—check for leaks). Signing config commented out: Release build signing is disabled by default; uncomment and provide keystore password before release. RenderScript for Glide blur: renderscriptTargetApi 19 and supportModeEnabled required for Glide Gaussian blur—omitting breaks image processing. Fragment lifecycle: Lazy-loading mode (feature #12) requires careful state management in Fragment.setUserVisibleHint() or Fragment.onHiddenChanged()—easy to leak subscriptions. RxJava2 without proper disposal: No visible Composite/DisposeBag pattern documented—check ViewModel/Presenter for proper unsubscribe in onCleared() or onDestroy(). Multi-dex mandatory: multiDexEnabled true forces DEX splitting; removing it will cause method count overflow.
🏗️Architecture
💡Concepts to learn
- MVVM (Model-View-ViewModel) — CloudReader's entire architecture revolves around MVVM with DataBinding; understanding ViewModel lifecycle and separation of UI logic from business logic is essential to navigating this codebase.
- Android Data Binding Library — Two-way binding in XML layouts connects Views to ViewModels; all adapters and BaseActivity use DataBinding's @BindingAdapter annotations for custom view behaviors (Glide image loading, blur).
- RxBus Pattern — CloudReader replaces EventBus with RxJava2 Observables for inter-component communication (Fragment ↔ Activity messaging); critical for understanding event flow without tight coupling.
- CoordinatorLayout + Behavior — Implements smooth title bar gradients and scroll-to-top animations (feature #14); Behavior subclasses intercept scroll events to animate dependent views without manual listeners.
- Fragment Lazy Loading — Feature #12 defers Fragment initialization until user navigates to tab; reduces memory footprint on app launch and prevents premature API calls for off-screen content.
- Transparent Status Bar & System Window Insets — Feature #4 (transparent status bar with version adaptation) requires WindowInsets API handling; critical for achieving Netease Cloud Music–style immersive UI across Android 5.0+.
🔗Related repos
google-developer-training/android-kotlin-fundamentals-apps— Official Google MVVM + DataBinding samples in Kotlin; reference architecture patterns for this codebase.nickbutcher/plaid— Material Design reading app (Google/Pinterest inspiration) with similar CoordinatorLayout + RecyclerView patterns and shared data flow concepts.giarulli/Fresco— Alternative image loading library to Glide with similar blur/cache patterns used in CloudReader; comparison reference for image optimization.square/retrofit— Official Retrofit2 repository; CloudReader's HTTP client backbone—essential for understanding API integration patterns used here.ReactiveX/RxJava— RxJava2 core library underlying CloudReader's reactive pipeline and RxBus event system; required for understanding Observable chains.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add unit tests for Room database entities and DAOs in app/src/test
The repository uses Room for local persistence but there are no visible unit tests for database operations. This is critical for ensuring data integrity in an MVVM architecture that relies on Room caching. Tests should cover entity serialization, DAO query correctness, and migration scenarios.
- [ ] Create app/src/test/java/com/example/jingbin/cloudreader/db/ directory structure
- [ ] Add RobolectricTestRunner-based tests for Room entities (AndroidBean, ArticlesBean, CoinBean, etc.) to verify serialization/deserialization
- [ ] Write DAO integration tests using Room.inMemoryDatabaseBuilder() for CRUD operations on all DAO classes
- [ ] Add migration tests if Room schema has evolved between versions (current versionCode is 53)
Create GitHub Actions CI workflow for Kotlin/Android build and testing
The repository has no visible CI/CD pipeline despite using Kotlin, RxJava2, and Room with androidx dependencies. A GitHub Actions workflow would catch regressions early, especially important given the mix of Java and Kotlin files and multiple API integrations (WanAndroid, DouBan, Gank.io).
- [ ] Create .github/workflows/android-build.yml with steps to: setup JDK 11+, run ./gradlew build, execute androidTest suite
- [ ] Add Kotlin linting via ktlint in the workflow to enforce consistency across mixed Java/Kotlin codebase
- [ ] Configure the workflow to run on push/PR to master/main branches and generate build reports
- [ ] Document in README.md that CI/CD status badge should be added (reference the CRITICAL nature of testing multiple API endpoints)
Refactor mixed Java/Kotlin adapters and convert to unified Kotlin implementation
The adapter package (app/src/main/java/com/example/jingbin/cloudreader/adapter/) contains both Java and Kotlin files (FilmAdapter.kt, JokeAdapter.kt, MovieDetailAdapter.kt alongside Java-based CategoryArticleAdapter.java, EverydayAdapter.java). This inconsistency increases maintenance burden and makes testing harder. Converting remaining Java adapters to Kotlin with sealed classes for ViewHolder state management would improve type safety and leverage Kotlin's null-safety.
- [ ] Identify all .java adapter files: CategoryArticleAdapter.java, CoinAdapter.java, CollectUrlAdapter.java, DouBanTopAdapter.java, EverydayAdapter.java, FilmDetailActorAdapter.kt, FilmDetailImageAdapter.java, GankAndroidAdapter.java, GankAndroidSearchAdapter.java, NavigationAdapter.java, NavigationContentAdapter.java, OneAdapter.java, TreeAdapter.java, WanAndroidAdapter.java, WxArticleAdapter.java
- [ ] Convert Java adapters to Kotlin, replacing nullable types with non-null defaults where appropriate and using object expressions for ViewHolder creation
- [ ] Create a common base adapter class in Kotlin to reduce boilerplate across CategoryArticleAdapter, WanAndroidAdapter, and WxArticleAdapter which likely share similar binding logic
- [ ] Update corresponding test files in app/src/androidTest/java if adapter behavior tests exist
🌿Good first issues
- Add Room unit tests for bean models (AndroidBean, CoinBean, ComingFilmBean) in app/src/androidTest/java/ following the ExampleInstrumentedTest.java template—current test coverage is minimal.
- Extract API endpoints from Constants.kt into a BuildConfig-based configuration system to support staging/production API switching without code changes.
- Migrate remaining Java adapters (GankAndroidAdapter.java, NavigationAdapter.java, WanAndroidAdapter.java) to Kotlin and consolidate DataBinding patterns with newer FilmAdapter.kt and JokeAdapter.kt for consistency.
📝Recent commits
Click to expand
Recent commits
10640f2— Update README.md (youlookwhat)f5b9e67— Update README.md (youlookwhat)99cf2ab— Update README.md (youlookwhat)5d94969— build jdk17 / targetSdkVersion: 34 / gradle-8.0-bin (youlookwhat)bf00685— fix CategoryArticleFragment (youlookwhat)66b487c— update README.md (youlookwhat)be192a6— update README.md (youlookwhat)7719ca8— Merge branch 'master' into develop (youlookwhat)8b9ef8c— build gradle 7.1.0 ; jdk 11; targetSdkVersion 31 (youlookwhat)29b3def— Update strings.xml (youlookwhat)
🔒Security observations
Failed to generate security analysis.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.