diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d7631ff..7aa875e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.compose.compiler) + alias(libs.plugins.ksp) alias(libs.plugins.ktlint) } @@ -9,12 +10,25 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.navigation) + implementation(libs.androidx.compose.navigation.common) + implementation(libs.androidx.compose.navigation.common.ktx) + implementation(libs.androidx.compose.icons.extended) + implementation(libs.androidx.material3) + implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) - implementation(libs.androidx.material3) + + implementation(platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.koin.android) + implementation(libs.koin.android.compose) + implementation(libs.koin.annotations) + ksp(libs.koin.ksp) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -75,3 +89,7 @@ android { } } } + +ksp { + arg("KOIN_CONFIG_CHECK", "true") +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7418bb0..d95c1f9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,11 +11,11 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.NewsApp" + android:name=".main.MainApplication" tools:targetApi="31"> @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/com/germanautolabs/acaraus/MainActivity.kt b/app/src/main/java/com/germanautolabs/acaraus/main/MainActivity.kt similarity index 51% rename from app/src/main/java/com/germanautolabs/acaraus/MainActivity.kt rename to app/src/main/java/com/germanautolabs/acaraus/main/MainActivity.kt index f5575a0..155951c 100644 --- a/app/src/main/java/com/germanautolabs/acaraus/MainActivity.kt +++ b/app/src/main/java/com/germanautolabs/acaraus/main/MainActivity.kt @@ -1,47 +1,28 @@ -package com.germanautolabs.acaraus +package com.germanautolabs.acaraus.main import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import com.germanautolabs.acaraus.ui.theme.NewsAppTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + render() + } + + private fun render() { enableEdgeToEdge() setContent { NewsAppTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", - modifier = Modifier.padding(innerPadding), - ) + MainNavHost(innerPadding = innerPadding) } } } } } - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier, - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - NewsAppTheme { - Greeting("Android") - } -} diff --git a/app/src/main/java/com/germanautolabs/acaraus/main/MainApplication.kt b/app/src/main/java/com/germanautolabs/acaraus/main/MainApplication.kt new file mode 100644 index 0000000..174c220 --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/main/MainApplication.kt @@ -0,0 +1,10 @@ +package com.germanautolabs.acaraus.main + +import android.app.Application + +class MainApplication : Application() { + override fun onCreate() { + super.onCreate() + setupMainDi(this@MainApplication) + } +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/main/MainDi.kt b/app/src/main/java/com/germanautolabs/acaraus/main/MainDi.kt new file mode 100644 index 0000000..9d5735a --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/main/MainDi.kt @@ -0,0 +1,22 @@ +package com.germanautolabs.acaraus.main + +import android.content.Context +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module +import org.koin.core.context.startKoin +import org.koin.core.logger.Level +import org.koin.ksp.generated.module + +@ComponentScan(value = "com.germanautolabs.acaraus") +@Module +class MainDi + +fun setupMainDi(applicationContext: Context) = startKoin { + androidLogger(Level.WARNING) + androidContext(applicationContext) + modules( + MainDi().module, + ) +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/main/MainNavHost.kt b/app/src/main/java/com/germanautolabs/acaraus/main/MainNavHost.kt new file mode 100644 index 0000000..034918e --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/main/MainNavHost.kt @@ -0,0 +1,35 @@ +package com.germanautolabs.acaraus.main + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import com.germanautolabs.acaraus.screens.articles.details.articleDetailScreen +import com.germanautolabs.acaraus.screens.articles.details.onNavigateToDetails +import com.germanautolabs.acaraus.screens.articles.list.ARTICLE_LIST_ROUTE +import com.germanautolabs.acaraus.screens.articles.list.articleListScreen + +@Composable +fun MainNavHost( + modifier: Modifier = Modifier, + navController: NavHostController = rememberNavController(), + innerPadding: PaddingValues, + route: String = ARTICLE_LIST_ROUTE, +) = NavHost( + navController = navController, + startDestination = route, + modifier = modifier.padding(paddingValues = innerPadding), +) { + articleListScreen( + modifier = modifier, + onNavigateToDetails = navController::onNavigateToDetails, + ) + + articleDetailScreen( + modifier = modifier, + onNavigateBack = navController::popBackStack, + ) +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/models/Article.kt b/app/src/main/java/com/germanautolabs/acaraus/models/Article.kt new file mode 100644 index 0000000..647c6e1 --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/models/Article.kt @@ -0,0 +1,9 @@ +package com.germanautolabs.acaraus.models + +data class Article( + val id: String, + val source: String, + val title: String, + val description: String?, + val imageURL: String?, +) diff --git a/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailNav.kt b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailNav.kt new file mode 100644 index 0000000..f1f9cb3 --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailNav.kt @@ -0,0 +1,30 @@ +package com.germanautolabs.acaraus.screens.articles.details + +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.germanautolabs.acaraus.models.Article +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf + +const val ARTICLE_DETAIL_ROUTE = "article-detail" +private const val ARTICLE_ID_PARAM = "article-id" + +fun NavController.onNavigateToDetails(article: Article) = + navigate("$ARTICLE_DETAIL_ROUTE/?$ARTICLE_ID_PARAM=${article.id}") + +fun NavGraphBuilder.articleDetailScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit, +) { + composable(route = "$ARTICLE_DETAIL_ROUTE/?$ARTICLE_ID_PARAM={$ARTICLE_ID_PARAM}") { backStack -> + val articleId = backStack.arguments?.getString(ARTICLE_ID_PARAM) + val vm = koinViewModel { parametersOf(articleId) } + ArticleDetailScreen( + modifier = modifier, + vm = vm, + onNavigateBack = onNavigateBack, + ) + } +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailScreen.kt b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailScreen.kt new file mode 100644 index 0000000..66a6abb --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailScreen.kt @@ -0,0 +1,19 @@ +package com.germanautolabs.acaraus.screens.articles.details + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun ArticleDetailScreen( + modifier: Modifier = Modifier, + vm: ArticleDetailViewModel, + onNavigateBack: () -> Unit, +) = Column(modifier = modifier) { + Text("Article Detail Screen article: ${vm.articleId}") + Button(onClick = onNavigateBack) { + Text("Close") + } +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailViewModel.kt b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailViewModel.kt new file mode 100644 index 0000000..2af2427 --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/details/ArticleDetailViewModel.kt @@ -0,0 +1,10 @@ +package com.germanautolabs.acaraus.screens.articles.details + +import androidx.lifecycle.ViewModel +import org.koin.android.annotation.KoinViewModel +import org.koin.core.annotation.InjectedParam + +@KoinViewModel +class ArticleDetailViewModel( + @InjectedParam val articleId: String, +) : ViewModel() diff --git a/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListNav.kt b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListNav.kt new file mode 100644 index 0000000..4fc6d8c --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListNav.kt @@ -0,0 +1,23 @@ +package com.germanautolabs.acaraus.screens.articles.list + +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.germanautolabs.acaraus.models.Article +import org.koin.androidx.compose.koinViewModel + +const val ARTICLE_LIST_ROUTE = "article-list" + +fun NavGraphBuilder.articleListScreen( + modifier: Modifier = Modifier, + onNavigateToDetails: (article: Article) -> Unit, +) { + composable(route = ARTICLE_LIST_ROUTE) { + val vm = koinViewModel() + ArticleListScreen( + modifier = modifier, + vm = vm, + onNavigateToDetails = onNavigateToDetails, + ) + } +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListScreen.kt b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListScreen.kt new file mode 100644 index 0000000..7abafcd --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListScreen.kt @@ -0,0 +1,45 @@ +package com.germanautolabs.acaraus.screens.articles.list + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.germanautolabs.acaraus.models.Article + +@Composable +fun ArticleListScreen( + modifier: Modifier = Modifier, + vm: ArticleListViewModel, + onNavigateToDetails: (article: Article) -> Unit, +) = Column(modifier = modifier) { + Text("Article List Screen") + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + items(vm.list) { article -> + ArticleListItem( + article = article, + onNavigateToDetails = onNavigateToDetails, + ) + } + } +} + +@Composable +fun ArticleListItem( + modifier: Modifier = Modifier, + article: Article, + onNavigateToDetails: (article: Article) -> Unit, +) = Card(onClick = { onNavigateToDetails(article) }) { + Column(modifier = modifier.padding(8.dp)) { + Text("Title: ${article.title}") + Text("Description: ${article.description}") + Text("Source: ${article.source}") + } +} diff --git a/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListViewModel.kt b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListViewModel.kt new file mode 100644 index 0000000..3d65e3c --- /dev/null +++ b/app/src/main/java/com/germanautolabs/acaraus/screens/articles/list/ArticleListViewModel.kt @@ -0,0 +1,25 @@ +package com.germanautolabs.acaraus.screens.articles.list + +import androidx.lifecycle.ViewModel +import com.germanautolabs.acaraus.models.Article +import org.koin.android.annotation.KoinViewModel + +@KoinViewModel +class ArticleListViewModel : ViewModel() { + + private val defaultArticle = Article( + id = "0", + title = "Old news", + description = "Everything new is a well-forgotten old", + source = "Internet", + imageURL = null, + ) + + val list = listOf( + defaultArticle, + defaultArticle.copy(id = "1", title = "New news 1"), + defaultArticle.copy(id = "2", title = "New news 2"), + defaultArticle.copy(id = "3", title = "New news 3"), + defaultArticle.copy(id = "4", title = "New news 4"), + ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70d1268..c2d95f1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,6 @@ +################################# [versions] +################################# # Android appPackageId = "com.germanautolabs.acaraus" @@ -12,17 +14,25 @@ coreKtx = "1.13.1" lifecycleRuntimeKtx = "2.8.4" activityCompose = "1.9.1" composeBom = "2024.06.00" +compose = "1.7.0-beta06" +compose-nav = "2.8.0-beta06" + +koin-bom = "4.0.0-RC1" +koin-ksp = "1.4.0-RC3" espressoCore = "3.6.1" junit = "4.13.2" junitVersion = "1.2.1" # Plugins -ktlint = "12.1.1" agp = "8.6.0-beta02" +ksp = "2.0.0-1.0.24" +ktlint = "12.1.1" test-coverage-plugin = "1.8.0" +################################### [libraries] +################################### androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } @@ -34,18 +44,39 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } + +androidx-compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "compose-nav" } +androidx-compose-navigation-common = { module = "androidx.navigation:navigation-common", version.ref = "compose-nav" } +androidx-compose-navigation-common-ktx = { module = "androidx.navigation:navigation-common-ktx", version.ref = "compose-nav" } + +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } +koin-core = { module = "io.insert-koin:koin-core" } +koin-android = { module = "io.insert-koin:koin-android" } +koin-android-compose = { module = "io.insert-koin:koin-androidx-compose" } +koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-ksp" } +koin-ksp = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-ksp" } + + # Instrumented tests androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } + # Unit tests junit = { group = "junit", name = "junit", version.ref = "junit" } +####################### [plugins] +###################### + android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } + ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } test-coverage = { id = "nl.neotech.plugin.rootcoverage", version.ref = "test-coverage-plugin" } +