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.
36
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Assemble (debug)
|
||||
run: ./gradlew assembleDebug --no-daemon --stacktrace
|
||||
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
*.iml
|
||||
.DS_Store
|
||||
|
||||
# Gradle
|
||||
**/.gradle/
|
||||
**/build/
|
||||
/captures
|
||||
|
||||
# Kotlin
|
||||
.kotlin/
|
||||
|
||||
# Native
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
|
||||
# Local config / secrets
|
||||
local.properties
|
||||
|
||||
# IDE
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea/deploymentTargetSelector.xml
|
||||
/.idea/deploymentTargetDropDown.xml
|
||||
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Android Architecture Showcase
|
||||
|
||||
A single runnable **Android-only (Jetpack Compose)** reference app that demonstrates good
|
||||
architecture conventions — each in its own module/example. Teaching repo: every module is meant to
|
||||
be minimal but complete and idiomatic.
|
||||
|
||||
> **Status:** built milestone-by-milestone from the
|
||||
> [Linear backlog](https://linear.app/adrian-kuta/project/android-architecture-showcase-b5ecdeddda6c).
|
||||
> **Foundation** (scaffold, version catalog, `build-logic` convention plugins) is complete and the
|
||||
> project assembles green. Full architecture docs land with the *Quality & Docs* milestone.
|
||||
|
||||
## Stack
|
||||
|
||||
Multi-module Gradle + `build-logic` convention plugins · Koin (constructor DSL) · Ktor ·
|
||||
KotlinX Serialization · Coil · Kermit · type-safe Compose Navigation. Data comes from the no-key
|
||||
[Rick & Morty API](https://rickandmortyapi.com/).
|
||||
|
||||
What it will showcase: **MVI** as the primary presentation pattern (flagship *characters* feature),
|
||||
an **MVVM** contrast screen, and the same MVI `ViewModel` driven by **two renderers** — Jetpack
|
||||
Compose and classic **XML + ViewBinding + RecyclerView** — proving the presentation logic is
|
||||
UI-toolkit-agnostic.
|
||||
|
||||
## Module structure
|
||||
|
||||
```
|
||||
:app → wires everything; single Activity, Compose host
|
||||
:build-logic → Gradle convention plugins (the only place versions/config live)
|
||||
:core:domain → Result/error types, shared domain models (pure Kotlin)
|
||||
:core:data → Ktor HttpClient factory, safe-call helpers
|
||||
:core:presentation → UiText, ObserveAsEvents, DataError → UiText
|
||||
:core:design-system → AppTheme + reusable composables
|
||||
:feature:characters:domain → models + repository interface (pure Kotlin)
|
||||
:feature:characters:data → DTOs, mappers, data source, repository impl
|
||||
:feature:characters:presentation → MVI ViewModel/State/Action/Event (UI-agnostic: no Compose, no Views)
|
||||
:feature:characters:presentation-compose → Compose renderer
|
||||
:feature:characters:presentation-views → Views/XML renderer (same ViewModel)
|
||||
:feature:about:presentation → MVVM contrast screen
|
||||
```
|
||||
|
||||
**Dependency rules:** `presentation → domain ← data`; `domain` depends only on `:core:domain`;
|
||||
features never depend on other features; `:app` wires the graph.
|
||||
|
||||
## Build & run
|
||||
|
||||
```bash
|
||||
./gradlew assembleDebug # build the APK
|
||||
./gradlew projects # print the module tree
|
||||
./gradlew check # tests + lint (added in the Quality & Docs milestone)
|
||||
```
|
||||
|
||||
Requires JDK 17+ (the Gradle build pins a Java 17 toolchain) and the Android SDK
|
||||
(`compileSdk 36`, `minSdk 24`).
|
||||
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
14
app/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.application)
|
||||
alias(libs.plugins.architecture.compose)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.bundles.lifecycle.compose)
|
||||
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
2
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Add project-specific ProGuard rules here.
|
||||
# Minification is disabled for the release build type in this teaching project.
|
||||
22
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/Theme.AndroidArchitectureShowcase">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
36
app/src/main/java/com/example/architecture/MainActivity.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.example.architecture
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
// Placeholder content. The real navigation host + AppTheme are wired in later
|
||||
// milestones (design-system, characters graph, Koin bootstrap).
|
||||
MaterialTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(text = "Android Architecture Showcase")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
3
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Android Architecture Showcase</string>
|
||||
</resources>
|
||||
9
app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--
|
||||
Compose drives the in-app theming via AppTheme (core:design-system). This XML theme only
|
||||
styles the Activity window. It is upgraded to a Material3 (Theme.Material3.*) parent in the
|
||||
Koin-bootstrap milestone so the later hosted Views renderer inherits Material3 styling.
|
||||
-->
|
||||
<style name="Theme.AndroidArchitectureShowcase" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older than API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
67
build-logic/convention/build.gradle.kts
Normal file
@@ -0,0 +1,67 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
group = "com.example.architecture.buildlogic"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The convention plugins apply these by id, so they only need them at compile time.
|
||||
compileOnly(libs.android.gradlePlugin)
|
||||
compileOnly(libs.kotlin.gradlePlugin)
|
||||
compileOnly(libs.compose.compiler.gradlePlugin)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
register("androidApplication") {
|
||||
id = "architecture.android.application"
|
||||
implementationClass = "com.example.architecture.convention.AndroidApplicationConventionPlugin"
|
||||
}
|
||||
register("androidLibrary") {
|
||||
id = "architecture.android.library"
|
||||
implementationClass = "com.example.architecture.convention.AndroidLibraryConventionPlugin"
|
||||
}
|
||||
register("androidFeature") {
|
||||
id = "architecture.android.feature"
|
||||
implementationClass = "com.example.architecture.convention.AndroidFeatureConventionPlugin"
|
||||
}
|
||||
register("androidFeatureViews") {
|
||||
id = "architecture.android.feature.views"
|
||||
implementationClass = "com.example.architecture.convention.AndroidFeatureViewsConventionPlugin"
|
||||
}
|
||||
register("domainModule") {
|
||||
id = "architecture.domain.module"
|
||||
implementationClass = "com.example.architecture.convention.DomainModuleConventionPlugin"
|
||||
}
|
||||
register("compose") {
|
||||
id = "architecture.compose"
|
||||
implementationClass = "com.example.architecture.convention.ComposeConventionPlugin"
|
||||
}
|
||||
register("koin") {
|
||||
id = "architecture.koin"
|
||||
implementationClass = "com.example.architecture.convention.KoinConventionPlugin"
|
||||
}
|
||||
register("ktor") {
|
||||
id = "architecture.ktor"
|
||||
implementationClass = "com.example.architecture.convention.KtorConventionPlugin"
|
||||
}
|
||||
register("kotlinxSerialization") {
|
||||
id = "architecture.kotlinx.serialization"
|
||||
implementationClass = "com.example.architecture.convention.KotlinxSerializationConventionPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
|
||||
/**
|
||||
* Configures the single `:app` module: applicationId, SDK levels, Java 17, and the release
|
||||
* build type. Compose is added separately by [ComposeConventionPlugin].
|
||||
*/
|
||||
class AndroidApplicationConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("com.android.application")
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
namespace = "com.example.architecture"
|
||||
compileSdk = COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.architecture"
|
||||
minSdk = MIN_SDK
|
||||
targetSdk = TARGET_SDK
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = false
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configureKotlinJvmToolchain()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* A Compose-based feature presentation module: Android library + Compose + Koin, plus the common
|
||||
* feature stack (lifecycle, type-safe navigation, coroutines, Coil). Used by every
|
||||
* `:feature:*:presentation-compose` and the MVVM `:feature:about:presentation`.
|
||||
*/
|
||||
class AndroidFeatureConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("architecture.android.library")
|
||||
pluginManager.apply("architecture.compose")
|
||||
pluginManager.apply("architecture.koin")
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("androidx-core-ktx").get())
|
||||
add("implementation", libs.findBundle("lifecycle-compose").get())
|
||||
add("implementation", libs.findLibrary("androidx-navigation-compose").get())
|
||||
add("implementation", libs.findLibrary("kotlinx-coroutines-android").get())
|
||||
add("implementation", libs.findLibrary("koin-androidx-compose").get())
|
||||
add("implementation", libs.findLibrary("coil-compose").get())
|
||||
add("implementation", libs.findLibrary("coil-network-okhttp").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* A classic Views feature renderer: Android library + Koin, ViewBinding ON, Compose OFF.
|
||||
* Brings Fragment / RecyclerView / Material / AppCompat and Coil's ImageView loader so the
|
||||
* Views renderer can drive the same ViewModel as the Compose one.
|
||||
*/
|
||||
class AndroidFeatureViewsConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("architecture.android.library")
|
||||
pluginManager.apply("architecture.koin")
|
||||
|
||||
extensions.configure<LibraryExtension> {
|
||||
buildFeatures.viewBinding = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("androidx-core-ktx").get())
|
||||
add("implementation", libs.findBundle("views").get())
|
||||
add("implementation", libs.findLibrary("androidx-lifecycle-runtime-ktx").get())
|
||||
add("implementation", libs.findLibrary("androidx-lifecycle-viewmodel-ktx").get())
|
||||
add("implementation", libs.findLibrary("kotlinx-coroutines-android").get())
|
||||
add("implementation", libs.findLibrary("coil-core").get())
|
||||
add("implementation", libs.findLibrary("coil-network-okhttp").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
|
||||
/**
|
||||
* Base configuration shared by every Android library module. Each module still declares its own
|
||||
* `namespace` in its build file.
|
||||
*/
|
||||
class AndroidLibraryConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("com.android.library")
|
||||
|
||||
extensions.configure<LibraryExtension> {
|
||||
compileSdk = COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
minSdk = MIN_SDK
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
configureKotlinJvmToolchain()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Enables Jetpack Compose on an Android application or library module: applies the Compose
|
||||
* compiler plugin, turns on the `compose` build feature, and wires the BOM-aligned Compose deps.
|
||||
*
|
||||
* Order-independent: `withPlugin` enables the build feature whenever the Android plugin is applied,
|
||||
* regardless of whether this plugin runs before or after it.
|
||||
*/
|
||||
class ComposeConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.plugin.compose")
|
||||
|
||||
pluginManager.withPlugin("com.android.library") {
|
||||
extensions.configure<LibraryExtension> { buildFeatures.compose = true }
|
||||
}
|
||||
pluginManager.withPlugin("com.android.application") {
|
||||
extensions.configure<ApplicationExtension> { buildFeatures.compose = true }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val bom = platform(libs.findLibrary("androidx-compose-bom").get())
|
||||
add("implementation", bom)
|
||||
add("androidTestImplementation", bom)
|
||||
add("implementation", libs.findBundle("compose").get())
|
||||
add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
|
||||
/**
|
||||
* Pure-Kotlin (JVM) module for the domain layer: no Android dependencies. Adds Coroutines (for
|
||||
* `Flow`-returning repository interfaces) and runs unit tests on the JUnit 5 platform.
|
||||
*/
|
||||
class DomainModuleConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.jvm")
|
||||
|
||||
configureKotlinJvmToolchain()
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("kotlinx-coroutines-core").get())
|
||||
add("testImplementation", libs.findLibrary("junit-jupiter-api").get())
|
||||
add("testImplementation", libs.findLibrary("assertk").get())
|
||||
add("testRuntimeOnly", libs.findLibrary("junit-jupiter-engine").get())
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Adds the Koin BOM + core/android dependencies. Compose-specific Koin (`koin-androidx-compose`)
|
||||
* is added only by [AndroidFeatureConventionPlugin] so UI-agnostic modules stay Compose-free.
|
||||
*/
|
||||
class KoinConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
dependencies {
|
||||
val bom = platform(libs.findLibrary("koin-bom").get())
|
||||
add("implementation", bom)
|
||||
add("implementation", libs.findBundle("koin").get())
|
||||
add("testImplementation", libs.findLibrary("koin-test").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Applies the KotlinX Serialization compiler plugin + JSON runtime for modules that hold
|
||||
* `@Serializable` DTOs or navigation routes but do not need the full Ktor stack.
|
||||
*/
|
||||
class KotlinxSerializationConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.plugin.serialization")
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("kotlinx-serialization-json").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Wires the Ktor client bundle (OkHttp engine, content negotiation, JSON, logging) and the
|
||||
* KotlinX Serialization runtime. Applies the serialization compiler plugin so `@Serializable`
|
||||
* DTOs compile.
|
||||
*/
|
||||
class KtorConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.plugin.serialization")
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findBundle("ktor").get())
|
||||
add("implementation", libs.findLibrary("kotlinx-serialization-json").get())
|
||||
add("testImplementation", libs.findLibrary("ktor-client-mock").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.VersionCatalog
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
|
||||
internal const val COMPILE_SDK = 36
|
||||
internal const val MIN_SDK = 24
|
||||
internal const val TARGET_SDK = 36
|
||||
internal const val JVM_TARGET = 17
|
||||
|
||||
/** Type-safe accessor for the shared `libs` version catalog from inside a convention plugin. */
|
||||
internal val Project.libs: VersionCatalog
|
||||
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
/**
|
||||
* Pins the Kotlin JVM toolchain. Works for both Android modules and pure-Kotlin (`jvm`) modules
|
||||
* because [KotlinProjectExtension] is the common supertype of both kotlin extensions.
|
||||
*/
|
||||
internal fun Project.configureKotlinJvmToolchain() {
|
||||
extensions.configure<KotlinProjectExtension> {
|
||||
jvmToolchain(JVM_TARGET)
|
||||
}
|
||||
}
|
||||
26
build-logic/settings.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
versionCatalogs {
|
||||
// Reuse the single source of truth for versions.
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "build-logic"
|
||||
include(":convention")
|
||||
9
build.gradle.kts
Normal file
@@ -0,0 +1,9 @@
|
||||
// Top-level build file. Plugins are declared here `apply false` so their markers are
|
||||
// on the classpath and the :build-logic convention plugins can apply them by id.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.jvm) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
alias(libs.plugins.kotlin.serialization) apply false
|
||||
}
|
||||
13
core/data/build.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.library)
|
||||
alias(libs.plugins.architecture.ktor)
|
||||
alias(libs.plugins.architecture.koin)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.architecture.core.data"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:domain"))
|
||||
}
|
||||
8
core/design-system/build.gradle.kts
Normal file
@@ -0,0 +1,8 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.library)
|
||||
alias(libs.plugins.architecture.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.architecture.core.design.system"
|
||||
}
|
||||
3
core/domain/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.domain.module)
|
||||
}
|
||||
12
core/presentation/build.gradle.kts
Normal file
@@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.library)
|
||||
alias(libs.plugins.architecture.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.architecture.core.presentation"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:domain"))
|
||||
}
|
||||
14
feature/about/presentation/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.feature)
|
||||
}
|
||||
|
||||
// MVVM contrast screen (StateFlow + plain VM methods, no Action/Event funnel). Static content,
|
||||
// so it has no data/domain modules.
|
||||
android {
|
||||
namespace = "com.example.architecture.feature.about.presentation"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:presentation"))
|
||||
implementation(project(":core:design-system"))
|
||||
}
|
||||
15
feature/characters/data/build.gradle.kts
Normal file
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.library)
|
||||
alias(libs.plugins.architecture.koin)
|
||||
alias(libs.plugins.architecture.kotlinx.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.architecture.feature.characters.data"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:domain"))
|
||||
implementation(project(":core:data"))
|
||||
implementation(project(":feature:characters:domain"))
|
||||
}
|
||||
7
feature/characters/domain/build.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.domain.module)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:domain"))
|
||||
}
|
||||
14
feature/characters/presentation-compose/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.feature)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.architecture.feature.characters.presentation.compose"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:presentation"))
|
||||
implementation(project(":core:design-system"))
|
||||
implementation(project(":feature:characters:domain"))
|
||||
implementation(project(":feature:characters:presentation"))
|
||||
}
|
||||
15
feature/characters/presentation-views/build.gradle.kts
Normal file
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.feature.views)
|
||||
}
|
||||
|
||||
// Classic Views renderer (Fragment + ViewBinding + RecyclerView) driving the SAME ViewModel from
|
||||
// :feature:characters:presentation. ViewBinding ON, Compose OFF.
|
||||
android {
|
||||
namespace = "com.example.architecture.feature.characters.presentation.views"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:presentation"))
|
||||
implementation(project(":feature:characters:domain"))
|
||||
implementation(project(":feature:characters:presentation"))
|
||||
}
|
||||
20
feature/characters/presentation/build.gradle.kts
Normal file
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
alias(libs.plugins.architecture.android.library)
|
||||
alias(libs.plugins.architecture.koin)
|
||||
}
|
||||
|
||||
// UI-agnostic presentation: the MVI ViewModel + State/Action/Event live here and are shared by
|
||||
// BOTH the Compose and the Views renderers. No Compose, no Views dependencies on purpose.
|
||||
android {
|
||||
namespace = "com.example.architecture.feature.characters.presentation"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:domain"))
|
||||
implementation(project(":core:presentation"))
|
||||
implementation(project(":feature:characters:domain"))
|
||||
|
||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
}
|
||||
23
gradle.properties
Normal file
@@ -0,0 +1,23 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
164
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,164 @@
|
||||
[versions]
|
||||
# Build / language
|
||||
agp = "9.0.1"
|
||||
kotlin = "2.3.20"
|
||||
|
||||
# AndroidX – core / lifecycle / activity / views
|
||||
androidxCore = "1.18.0"
|
||||
androidxLifecycle = "2.10.0"
|
||||
androidxActivity = "1.13.0"
|
||||
androidxAppcompat = "1.7.0"
|
||||
androidxFragment = "1.8.5"
|
||||
androidxRecyclerview = "1.4.0"
|
||||
androidxNavigation = "2.9.0"
|
||||
|
||||
# Compose (BOM-managed)
|
||||
composeBom = "2026.03.01"
|
||||
|
||||
# Async / serialization
|
||||
coroutines = "1.10.2"
|
||||
kotlinxSerialization = "1.8.1"
|
||||
|
||||
# DI
|
||||
koin = "4.1.0"
|
||||
|
||||
# Networking
|
||||
ktor = "3.1.3"
|
||||
|
||||
# Image loading
|
||||
coil = "3.1.0"
|
||||
|
||||
# Logging
|
||||
kermit = "2.0.5"
|
||||
|
||||
# Material Components (Views renderer)
|
||||
material = "1.12.0"
|
||||
|
||||
# Testing
|
||||
junit4 = "4.13.2"
|
||||
junitJupiter = "5.11.4"
|
||||
androidJunit5 = "1.11.4"
|
||||
turbine = "1.2.0"
|
||||
assertk = "0.28.1"
|
||||
androidxTest = "1.7.0"
|
||||
androidxTestExt = "1.3.0"
|
||||
androidxTestRunner = "1.7.0"
|
||||
androidxEspresso = "3.7.0"
|
||||
|
||||
[libraries]
|
||||
# --- Gradle plugin artifacts (consumed by :build-logic convention plugins) ---
|
||||
android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
|
||||
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
compose-compiler-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
|
||||
|
||||
# --- AndroidX core / lifecycle / activity ---
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" }
|
||||
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidxLifecycle" }
|
||||
|
||||
# --- Navigation (type-safe Compose Navigation) ---
|
||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigation" }
|
||||
|
||||
# --- Views renderer (Fragment / RecyclerView / Material / AppCompat) ---
|
||||
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppcompat" }
|
||||
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidxFragment" }
|
||||
androidx-fragment-compose = { module = "androidx.fragment:fragment-compose", version.ref = "androidxFragment" }
|
||||
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "androidxRecyclerview" }
|
||||
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||
|
||||
# --- Compose (versions via BOM) ---
|
||||
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
||||
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
|
||||
androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
|
||||
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
||||
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
||||
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
||||
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
||||
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
|
||||
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
|
||||
|
||||
# --- Coroutines / serialization ---
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||
|
||||
# --- Koin (versions via BOM) ---
|
||||
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin" }
|
||||
koin-core = { module = "io.insert-koin:koin-core" }
|
||||
koin-android = { module = "io.insert-koin:koin-android" }
|
||||
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose" }
|
||||
koin-test = { module = "io.insert-koin:koin-test" }
|
||||
koin-test-junit5 = { module = "io.insert-koin:koin-test-junit5" }
|
||||
|
||||
# --- Ktor ---
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
|
||||
|
||||
# --- Coil (image loading) ---
|
||||
coil-core = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||
|
||||
# --- Logging ---
|
||||
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
|
||||
|
||||
# --- Testing ---
|
||||
junit4 = { module = "junit:junit", version.ref = "junit4" }
|
||||
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitJupiter" }
|
||||
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitJupiter" }
|
||||
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junitJupiter" }
|
||||
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
|
||||
assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" }
|
||||
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTest" }
|
||||
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTestRunner" }
|
||||
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExt" }
|
||||
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspresso" }
|
||||
|
||||
[bundles]
|
||||
compose = [
|
||||
"androidx-compose-ui",
|
||||
"androidx-compose-ui-graphics",
|
||||
"androidx-compose-ui-tooling-preview",
|
||||
"androidx-compose-material3",
|
||||
]
|
||||
koin = ["koin-core", "koin-android"]
|
||||
ktor = [
|
||||
"ktor-client-core",
|
||||
"ktor-client-okhttp",
|
||||
"ktor-client-content-negotiation",
|
||||
"ktor-serialization-kotlinx-json",
|
||||
"ktor-client-logging",
|
||||
]
|
||||
lifecycle-compose = ["androidx-lifecycle-runtime-compose", "androidx-lifecycle-viewmodel-compose"]
|
||||
views = ["androidx-appcompat", "material", "androidx-recyclerview", "androidx-fragment-ktx"]
|
||||
unit-test = ["junit-jupiter-api", "kotlinx-coroutines-test", "turbine", "assertk"]
|
||||
|
||||
[plugins]
|
||||
# Upstream plugins
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
# Declared for milestone 5 (ViewModel/Compose tests on Android); wired when tests land.
|
||||
android-junit5 = { id = "de.mannodermaus.android-junit5", version.ref = "androidJunit5" }
|
||||
|
||||
# Convention plugins (defined in :build-logic, resolved from the included build)
|
||||
architecture-android-application = { id = "architecture.android.application" }
|
||||
architecture-android-library = { id = "architecture.android.library" }
|
||||
architecture-android-feature = { id = "architecture.android.feature" }
|
||||
architecture-android-feature-views = { id = "architecture.android.feature.views" }
|
||||
architecture-domain-module = { id = "architecture.domain.module" }
|
||||
architecture-compose = { id = "architecture.compose" }
|
||||
architecture-koin = { id = "architecture.koin" }
|
||||
architecture-ktor = { id = "architecture.ktor" }
|
||||
architecture-kotlinx-serialization = { id = "architecture.kotlinx.serialization" }
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
172
gradlew
vendored
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
45
settings.gradle.kts
Normal file
@@ -0,0 +1,45 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
pluginManagement {
|
||||
// Convention plugins live in a composite build.
|
||||
includeBuild("build-logic")
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// Auto-provisions the JDK 17 toolchain used by every module.
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Android Architecture Showcase"
|
||||
|
||||
// --- App ---
|
||||
include(":app")
|
||||
|
||||
// --- Core (shared across features) ---
|
||||
include(":core:domain")
|
||||
include(":core:data")
|
||||
include(":core:presentation")
|
||||
include(":core:design-system")
|
||||
|
||||
// --- Feature: characters (flagship MVI; one ViewModel, two renderers) ---
|
||||
include(":feature:characters:domain")
|
||||
include(":feature:characters:data")
|
||||
include(":feature:characters:presentation")
|
||||
include(":feature:characters:presentation-compose")
|
||||
include(":feature:characters:presentation-views")
|
||||
|
||||
// --- Feature: about (MVVM contrast) ---
|
||||
include(":feature:about:presentation")
|
||||