Commit Graph

14 Commits

Author SHA1 Message Date
Adrian Kuta
2a419df43e feat(characters:presentation): UI-agnostic MVI ViewModel (REDI-87)
- CharacterListState (characters, isLoading, isLoadingNextPage, currentPage, endReached, error: UiText?),
  CharacterListAction (OnCharacterClick/OnRetry/OnLoadNextPage), CharacterListEvent (NavigateToDetail/ShowSnackbar).
- CharacterListViewModel: state via .update, one-time events via Channel, DataError -> UiText on failure,
  pagination persisted in SavedStateHandle (rebuilds list up to the saved page after process death).
- CharacterUi + Character.toCharacterUi().
- NO Compose/Views deps: verified no androidx.compose on the compile classpath. Stability via
  ImmutableList instead of @Stable (which would require compose-runtime) — the only compose-named
  transitive is kotlinx-immutable's annotations-only stub, not the Compose framework.
2026-06-10 12:43:30 +02:00
Adrian Kuta
0bb96baa4d feat(characters:data): DTOs, mappers, data source, repo, Koin module (REDI-86)
- @Serializable CharacterDto/CharactersResponseDto/PageInfoDto in dto/.
- mappers/CharacterMapper.kt: internal, pure toDomain()/toCharacter()/toCharacterDetails();
  nextPage parsed from info.next URL. No mapping inside DTO/data-source classes.
- KtorCharacterDataSource via the typed HttpClient.get helpers (errors -> DataError.Network).
- NetworkCharacterRepository (not *Impl) maps DTO -> domain; DataError.Network widens to DataError.
- charactersDataModule: singleOf(::KtorCharacterDataSource) + singleOf(::NetworkCharacterRepository) { bind<CharacterRepository>() }.
- core:data: expose ktor-client-core as api (public inline helpers are inlined into consumers) and
  move Timber logging into a @PublishedApi internal fn so Timber doesn't leak across modules.
2026-06-10 12:37:40 +02:00
Adrian Kuta
600f12259d feat(characters:domain): models + CharacterRepository interface (REDI-85)
- Character, CharacterStatus, CharactersPage(characters, nextPage), CharacterDetails.
- CharacterRepository interface returning Result<CharactersPage, DataError> and
  Result<CharacterDetails, DataError>. Pure Kotlin, no serialization annotations, no Android.
2026-06-10 12:31:59 +02:00
Adrian Kuta
7af99f91f3 Merge pull request #1 from AdrianKuta/feat/core-infrastructure
Core Infrastructure (REDI-80…84)
2026-06-10 12:29:56 +02:00
Adrian Kuta
6a1842ae96 refactor(logging): use Timber instead of Kermit
Android-only project, so Timber (the de-facto Android logging lib) fits better than the
KMP-oriented Kermit.

- Catalog: kermit -> timber (com.jakewharton.timber:timber 5.0.1).
- core:data: Ktor Logging bridged to Timber; safeCall logs via Timber.
- app: plant Timber.DebugTree() in debug only (buildConfig enabled for BuildConfig.DEBUG).
2026-06-10 12:23:43 +02:00
Adrian Kuta
b7ccf2fefa fix(core:data): catch deserialization errors in safeCall (review)
Deserialization happens in responseToResult via response.body<T>() on the 2xx branch.
That call was outside safeCall's try/catch, so a malformed 2xx body threw an uncaught
SerializationException (crash) instead of mapping to DataError.Network.SERIALIZATION.
Move responseToResult(execute()) inside the try so both transport and parse errors are typed.

Found by the milestone review.
2026-06-10 11:57:08 +02:00
Adrian Kuta
070ffde49c feat(app): Koin bootstrap + AppTheme + Material3 XML theme (REDI-84)
- ArchitectureApp.Application: startKoin { androidLogger; androidContext; modules(coreDataModule) }.
  Modules are assembled only here; feature modules will append to the list.
- MainActivity hosts a themed empty screen via AppTheme + AppScaffold (design-system).
- Activity XML theme upgraded to Theme.Material3.DayNight.NoActionBar (Compose themes via AppTheme;
  the Material3 XML theme lets the later Views renderer inherit Material3 styling).
- :app depends on :core:data + :core:design-system; applies the koin convention.
2026-06-10 11:48:19 +02:00
Adrian Kuta
5f3cc51195 feat(core:data): Ktor network core + coreDataModule (REDI-83)
- HttpClientFactory.create(engine) with the engine injected (MockEngine seam for tests):
  ContentNegotiation JSON (ignoreUnknownKeys), Kermit-backed Ktor logging, default JSON request.
- safeCall / responseToResult (status -> DataError.Network, extended with 400/403/404/503) /
  constructRoute (reads BuildConfig.BASE_URL) and typed HttpClient.get/post/delete.
- BASE_URL BuildConfig field = Rick & Morty API.
- coreDataModule: single<HttpClient> via factory lambda (the one sanctioned lambda-DSL binding).
2026-06-10 11:45:53 +02:00
Adrian Kuta
709c7d6ff5 feat(core:presentation): UiText, ObserveAsEvents, DataError -> UiText (REDI-82)
- UiText sealed interface (DynamicString / StringResource) — Compose-free type so the
  UI-agnostic presentation module can hold UiText? in state without depending on Compose.
- Two resolvers: @Composable UiText.asString() (Compose renderer) and
  UiText.asString(context) (Views renderer).
- ObserveAsEvents: lifecycle-aware one-time event collection on Main.immediate.
- DataError.toUiText() covering all displayed cases with else -> unknown; error strings here.
2026-06-10 11:42:38 +02:00
Adrian Kuta
3a155beb3c feat(core:design-system): AppTheme + reusable composables (REDI-81)
- AppTheme wraps Material3 (color scheme, typography, shapes); all previews use it.
- Slot-API AppCard (header + content slots, optional click); AppScaffold.
- LoadingIndicator, ErrorState (optional retry), Coil-backed NetworkImage.
- Modifier.shimmerEffect() animated placeholder (Modifier extension, not @Composable).
- Add androidx-compose-foundation to the version catalog + compose bundle.
2026-06-10 11:39:51 +02:00
Adrian Kuta
6bc4027cbb feat(core:domain): typed Result / Error / DataError core (REDI-80)
- Error marker interface; Result<D, E: Error> (Success/Error) + EmptyResult typealias.
- Inline chainable helpers: map / onSuccess / onFailure / asEmptyResult.
- DataError sealed interface with full Network + Local case sets.
- Pure Kotlin, zero Android imports.
2026-06-10 11:31:13 +02:00
Adrian Kuta
a3bda3b601 chore: gitignore the whole .idea/ directory
Untrack IDE-generated .idea/ files that a Gradle sync added; ignore the directory
entirely to keep machine-specific config out of the repo.
2026-06-10 11:28:14 +02:00
Adrian Kuta
9ef0719447 chore: use src/main/kotlin source directories in :app
Kotlin-only reference repo — move the app's main/test/androidTest source roots
from src/main/java to src/main/kotlin (the core/feature modules already use kotlin).
Verified: :app:compileDebugKotlin produces MainActivity from src/main/kotlin.
2026-06-10 11:27:32 +02:00
Adrian Kuta
10fa6dc9eb chore: scaffold multi-module project, version catalog, and build-logic
Foundation milestone (REDI-78, REDI-79):

- Multi-module skeleton: :app, :core:{domain,data,presentation,design-system},
  :feature:characters:{domain,data,presentation,presentation-compose,presentation-views},
  :feature:about:presentation, plus the :build-logic composite build.
- gradle/libs.versions.toml as the single source of truth ([versions]/[libraries]/
  [bundles]/[plugins]); no inline versions in any build file.
- Convention plugins: architecture.android.{application,library,feature,feature.views},
  domain.module, compose, koin, ktor, kotlinx.serialization.
- Pure-Kotlin domain modules; presentation-compose uses android.feature;
  presentation-views uses android.feature.views (ViewBinding on, Compose off);
  the UI-agnostic :presentation has neither Compose nor Views deps.
- Toolchain: AGP 9.0.1, Kotlin 2.3.20, Gradle 9.1.0, compileSdk 36, minSdk 24, Java 17.
- Minimal MainActivity placeholder; CI (assembleDebug) via GitHub Actions.

Verified: ./gradlew projects lists the full tree and ./gradlew assemble is green.
2026-06-10 10:52:03 +02:00