diff --git a/README.md b/README.md new file mode 100644 index 0000000..94dcca2 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# JetPackCompose_Basic +It is a basic functionality of jetpack compose UI element and how does it works. diff --git a/app/build.gradle b/app/build.gradle index 75677b5..3e8e664 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-parcelize' } android { @@ -27,17 +28,18 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } buildFeatures { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.1.1' + + kotlinCompilerExtensionVersion '1.4.0' } packagingOptions { resources { @@ -48,16 +50,31 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - implementation 'androidx.activity:activity-compose:1.6.1' + implementation 'androidx.core:core-ktx:1.10.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.activity:activity-compose:1.7.2' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.compose.material3:material3:1.1.0-alpha03' + implementation 'androidx.compose.material3:material3:1.1.1' + + implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" + //retrofit + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation "com.squareup.retrofit2:converter-gson:2.9.0" + //coil + implementation("io.coil-kt:coil-compose:2.2.2") + testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + + implementation "androidx.navigation:navigation-compose:2.6.0" + implementation 'androidx.browser:browser:1.5.0' + + testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.0' + testImplementation 'org.mockito:mockito-core:2.25.0' + testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ebfd06..f74e623 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/News.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/News.kt new file mode 100644 index 0000000..60aa2e8 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/News.kt @@ -0,0 +1,12 @@ +package com.lahsuak.apps.jetpackcomposebasic.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class News( + val description: String, + val title: String, + val url: String, + val urlToImage: String, +) : Parcelable diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/NewsParentModel.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/NewsParentModel.kt new file mode 100644 index 0000000..d829b71 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/NewsParentModel.kt @@ -0,0 +1,7 @@ +package com.lahsuak.apps.jetpackcomposebasic.model + +data class NewsParentModel( + val status: String, + val totalResults: Int, + val articles: List +) \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/repo/NewsRepository.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/repo/NewsRepository.kt new file mode 100644 index 0000000..0fb21e6 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/repo/NewsRepository.kt @@ -0,0 +1,14 @@ +package com.lahsuak.apps.jetpackcomposebasic.repo + +import com.lahsuak.apps.jetpackcomposebasic.api.ApiClient +import com.lahsuak.apps.jetpackcomposebasic.api.NewApi +import com.lahsuak.apps.jetpackcomposebasic.model.News +import com.lahsuak.apps.jetpackcomposebasic.model.NewsParentModel + +class NewsRepository(private val apiInterface: NewApi= ApiClient.apiInterface) { + suspend fun getNews(category: String): NewsParentModel { + return apiInterface.getNews( + category + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/ChipGroup.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/ChipGroup.kt new file mode 100644 index 0000000..2ba0699 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/ChipGroup.kt @@ -0,0 +1,54 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.List +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.lahsuak.apps.jetpackcomposebasic.R +import com.lahsuak.apps.jetpackcomposebasic.ui.components.model.Category + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChipGroup( + categories: List, + selectedCategory: Category, + onSelectedChanged: (Category) -> Unit = {}, +) { + Column(modifier = Modifier.padding(8.dp)) { + LazyRow { + item { + IconButton( + onClick = { /* no-op */ } + ) { + Icon( + imageVector = Icons.Default.List, + contentDescription = stringResource(R.string.label_filters) + ) + } + } + items(categories) { + FilterChip( + label = { + Text(it.name.uppercase()) + }, + modifier = Modifier.padding(horizontal = 4.dp), + selected = selectedCategory == it, + onClick = { + onSelectedChanged(it) + }, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/NewsItem.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/NewsItem.kt new file mode 100644 index 0000000..6a64714 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/NewsItem.kt @@ -0,0 +1,92 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.rememberAsyncImagePainter +import coil.request.ImageRequest +import com.lahsuak.apps.jetpackcomposebasic.R +import com.lahsuak.apps.jetpackcomposebasic.model.News +import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme + +@Composable +fun NewsItem(news: News, onClick: () -> Unit) { + // coil image painter + val painter = rememberAsyncImagePainter( + model = ImageRequest.Builder(LocalContext.current) + .data(news.urlToImage) + .placeholder(R.drawable.ic_launcher_foreground) + .crossfade(true) + .build(), + contentScale = ContentScale.FillWidth + ) + Card(modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clickable { + onClick() + }) { + Column() { + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + contentScale = ContentScale.Crop + ) + Text( + text = news.title, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + ) + Text( + text = news.description, + style = MaterialTheme.typography.bodySmall, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + color = Color.Gray, + modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) + ) + } + } +} + + +@Preview +@Composable +fun NewsItemPreview() { + JetPackComposeBasicTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + NewsItem( + news = News( + "Kaushal", + "Ness", + "https://pixabay.com/get/gc5bde75f8a92a997aa842681d52d37e41990f5e639497bad55c1cf9921835528340e1269788a027624b201d17734fb431c5269478e54aece4e65f7581452fe98_640.jpg", + "https://pixabay.com/get/gc5bde75f8a92a997aa842681d52d37e41990f5e639497bad55c1cf9921835528340e1269788a027624b201d17734fb431c5269478e54aece4e65f7581452fe98_640.jpg", + ) + ) {} + } + } +} diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/model/Category.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/model/Category.kt new file mode 100644 index 0000000..9abad44 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/model/Category.kt @@ -0,0 +1,10 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.components.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Category( + val pos: Int, + val name: String, +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/model/CategoryType.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/model/CategoryType.kt new file mode 100644 index 0000000..637eac4 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/components/model/CategoryType.kt @@ -0,0 +1,7 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.components.model + +enum class CategoryType { + GENERAL, + ENTERTAINMENT, + SPORTS +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/NavHost.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/NavHost.kt new file mode 100644 index 0000000..cf7d4e2 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/NavHost.kt @@ -0,0 +1,39 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.lahsuak.apps.jetpackcomposebasic.model.News +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.detailscreen.DetailScreen +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.home.HomeScreen + +private const val NEWS_ARG = "news" +@Composable +fun MyAppNavHost( + modifier: Modifier = Modifier, + navController: NavHostController = rememberNavController(), + startDestination: String = NavigationItem.Home.route +) { + NavHost( + modifier = modifier, + navController = navController, + startDestination = startDestination + ) { + composable(NavigationItem.Home.route) { + HomeScreen { + navController.currentBackStackEntry?.savedStateHandle?.set(NEWS_ARG, it) + navController.navigate(State.DETAILS.name) + } + } + composable(NavigationItem.Details.route) { + val news = + navController.previousBackStackEntry?.savedStateHandle?.get(NEWS_ARG) + news?.let { + DetailScreen(it,navController) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/NavigationItem.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/NavigationItem.kt new file mode 100644 index 0000000..f1862c6 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/NavigationItem.kt @@ -0,0 +1,10 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.navigation + +enum class State{ + HOME, + DETAILS +} +sealed class NavigationItem(val route: String) { + object Home : NavigationItem(State.HOME.name) + object Details : NavigationItem(State.DETAILS.name) +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/detailscreen/Detailscreen.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/detailscreen/Detailscreen.kt new file mode 100644 index 0000000..51bf285 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/detailscreen/Detailscreen.kt @@ -0,0 +1,94 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen.detailscreen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import coil.compose.rememberAsyncImagePainter +import coil.request.ImageRequest +import com.lahsuak.apps.jetpackcomposebasic.R +import com.lahsuak.apps.jetpackcomposebasic.model.News +import com.lahsuak.apps.jetpackcomposebasic.util.AppUtil.openTab + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DetailScreen(news: News, navController: NavHostController) { + val context = LocalContext.current + val scrollState = rememberScrollState() + + Box( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + ) { + Column { + TopAppBar(scrollBehavior = + TopAppBarDefaults.pinnedScrollBehavior(), + title = { + Text(text = "Details screen") + }, + navigationIcon = { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "back", + modifier = Modifier.clickable { + navController.popBackStack() + } + ) + }) + + val painter = rememberAsyncImagePainter( + model = ImageRequest.Builder(LocalContext.current) + .data(news.urlToImage) + .placeholder(R.drawable.ic_launcher_foreground) + .crossfade(true) + .build(), + contentScale = ContentScale.FillBounds + ) + Image( + painter = painter, + contentDescription = news.title, + modifier = Modifier + .fillMaxWidth(), + contentScale = ContentScale.Crop + ) + Text(text = news.title, modifier = Modifier.padding(8.dp), fontSize = 20.sp) + Text( + text = news.description, + fontSize = 16.sp, + modifier = Modifier.padding(8.dp) + ) + Text( + text = context.getString(R.string.open_for_more), + style = TextStyle(color = Color.Blue), + modifier = Modifier + .padding(horizontal = 8.dp) + .clickable { + openTab(context, news.url) + } + ) + } + } +} + diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/home/HomeScreen.kt new file mode 100644 index 0000000..37ac527 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/home/HomeScreen.kt @@ -0,0 +1,60 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen.home + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.lahsuak.apps.jetpackcomposebasic.model.News +import com.lahsuak.apps.jetpackcomposebasic.ui.components.ChipGroup +import com.lahsuak.apps.jetpackcomposebasic.ui.components.NewsItem +import com.lahsuak.apps.jetpackcomposebasic.ui.components.model.Category +import com.lahsuak.apps.jetpackcomposebasic.ui.components.model.CategoryType +import com.lahsuak.apps.jetpackcomposebasic.ui.viewmodel.NewsViewModel + +@Composable +fun HomeScreen(navController: (News) -> Unit) { + val newsViewModel: NewsViewModel = viewModel() + val newsList by newsViewModel.newsFlow.collectAsState() + val categories = listOf( + Category(0, CategoryType.GENERAL.name.lowercase()), + Category(1, CategoryType.ENTERTAINMENT.name.lowercase()), + Category(2, CategoryType.SPORTS.name.lowercase()) + ) + var selectedItem by rememberSaveable { + mutableStateOf(categories[0]) // initially, first item is selected + } + Box( + Modifier + .fillMaxSize() + ) { + Column { + ChipGroup( + categories, + selectedItem + ) { + selectedItem = it + newsViewModel.getNews(it.name) + } + LazyColumn(modifier = Modifier.padding(horizontal = 8.dp)) { + items(items = newsList) { news -> + NewsItem( + news = news, + ) { + navController(news) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt index e0a4930..c45f5f8 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt @@ -8,4 +8,5 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) +val Grey = Color(0xFF534F50) \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/viewmodel/NewsViewModel.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/viewmodel/NewsViewModel.kt new file mode 100644 index 0000000..cb1f208 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/viewmodel/NewsViewModel.kt @@ -0,0 +1,31 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lahsuak.apps.jetpackcomposebasic.model.News +import com.lahsuak.apps.jetpackcomposebasic.repo.NewsRepository +import com.lahsuak.apps.jetpackcomposebasic.ui.components.model.Category +import com.lahsuak.apps.jetpackcomposebasic.ui.components.model.CategoryType +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class NewsViewModel : ViewModel() { + private val _newsFlow: MutableStateFlow> = MutableStateFlow(emptyList()) + val newsFlow: StateFlow> + get() = _newsFlow + + private val newsRepo: NewsRepository by lazy { + NewsRepository() + } + + init { + getNews(CategoryType.GENERAL.name.lowercase()) + } + fun getNews(category: String) { + viewModelScope.launch { + val newsParentModel = newsRepo.getNews(category) + _newsFlow.value = newsParentModel.articles + } + } +} diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/util/AppUtil.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/util/AppUtil.kt new file mode 100644 index 0000000..7688c33 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/util/AppUtil.kt @@ -0,0 +1,29 @@ +package com.lahsuak.apps.jetpackcomposebasic.util + +import android.content.Context +import android.net.Uri +import androidx.browser.customtabs.CustomTabsIntent +import java.io.InputStreamReader + +object AppUtil { + fun readFileResource(fileName: String): String { + val inputStream = AppUtil::class.java.getResourceAsStream(fileName) + val builder = StringBuilder() + val reader = InputStreamReader(inputStream, Charsets.UTF_8) + reader.readLines().forEach { + builder.append(it) + } + return builder.toString() + } + + fun openTab(context: Context, url: String) { + val packageName = "com.android.chrome" + val builder = CustomTabsIntent.Builder() + builder.setShowTitle(true) + builder.setInstantAppsEnabled(true) + val customBuilder = builder.build() + customBuilder.intent.setPackage(packageName) + customBuilder.launchUrl(context, Uri.parse(url)) + } +} + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17b9141..e373964 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ JetPackCompose Basic + Click here to see news in detail + Filter \ No newline at end of file diff --git a/app/src/test/java/com/lahsuak/apps/jetpackcomposebasic/NewsApiTest.kt b/app/src/test/java/com/lahsuak/apps/jetpackcomposebasic/NewsApiTest.kt new file mode 100644 index 0000000..3f08df2 --- /dev/null +++ b/app/src/test/java/com/lahsuak/apps/jetpackcomposebasic/NewsApiTest.kt @@ -0,0 +1,72 @@ +package com.lahsuak.apps.jetpackcomposebasic + +import com.lahsuak.apps.jetpackcomposebasic.api.NewApi +import com.lahsuak.apps.jetpackcomposebasic.util.AppUtil +import kotlinx.coroutines.runBlocking +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +class NewsApiTest { + + lateinit var mockWebServer: MockWebServer + lateinit var newApi: NewApi + + @Before + fun setUp() { + mockWebServer = MockWebServer() + newApi = Retrofit.Builder() + .baseUrl(mockWebServer.url("/")) + .addConverterFactory(GsonConverterFactory.create()) + .build().create(NewApi::class.java) + } + + @Test + fun testGetNews_ReturnEmptyResponse() = runBlocking { + val mockResponse = MockResponse() + mockResponse.setBody("{}") + mockWebServer.enqueue(mockResponse) + + val response = newApi.getNews("general") + mockWebServer.takeRequest() + Assert.assertEquals(null, response.articles) + } + + @Test + fun testGetNews_returnNews() = runBlocking { + val mockResponse = MockResponse() + val content = AppUtil.readFileResource("/test.json") + mockResponse.setResponseCode(200) + mockResponse.setBody(content) + mockWebServer.enqueue(mockResponse) + + val response = newApi.getNews("general") + mockWebServer.takeRequest() + Assert.assertEquals(true, response.articles.isNotEmpty()) + Assert.assertEquals(2, response.articles.size) + } + + @Test + fun testGetNews_returnError() = runBlocking { + val mockResponse = MockResponse() + mockResponse.setResponseCode(404) + mockResponse.setBody("Something went wrong!") + mockWebServer.enqueue(mockResponse) + + val response = newApi.getNews2() + mockWebServer.takeRequest() + Assert.assertEquals(true, response.isSuccessful) + Assert.assertEquals(404, response.code()) + } + + + @After + fun tearDown() { + mockWebServer.shutdown() + } +} diff --git a/app/src/test/resources/test.json b/app/src/test/resources/test.json new file mode 100644 index 0000000..4698474 --- /dev/null +++ b/app/src/test/resources/test.json @@ -0,0 +1,32 @@ +{ + "status": "ok", + "totalResults": 35, + "articles": [ + { + "source": { + "id": null, + "name": "CNBCTV18" + }, + "author": "CNBCTV18.com", + "title": "Stock Market Live News Updates: Sensex live today Nifty50 live today Nestle HCL Tech Larsen Toubro Tech Russia - CNBCTV18", + "description": "Stock Market Highlights: Indian equity benchmarks BSE Sensex and NSE Nifty50 continued to rise for a second straight day on Thursday led by gains across IT stocks and heavyweights Reliance Industries, Infosys and the HDFC twins. Broader markets also strengthe\u2026", + "url": "https://www.cnbctv18.com/market/stocks/stock-market-news-live-updates-nifty50-bse-sensex-hcl-tech-nestle-lt-tech-tata-elxsi-brent-crude-bitcoin-gold-price-13222162.htm", + "urlToImage": "https://images.cnbctv18.com/wp-content/uploads/2019/07/BSE-Sensex.jpg", + "publishedAt": "2022-04-21T12:17:49Z", + "content": "Why Saurabh\u00a0Mukherjea likes HDFC Bank \r\nSaurabh Mukherjea, Founder at Marcellus Investment Managers, says in an interview to CNBC-TV18 that HDFC Bank's delivery over the past three, five, 10 or even \u2026 [+1086 chars]" + }, + { + "source": { + "id": null, + "name": "NDTV News" + }, + "author": null, + "title": "Sachin Pilot Meets Sonia Gandhi Over His Future Role In Congress: Sources - NDTV", + "description": "Rajasthan Congress leader Sachin Pilot met with Sonia Gandhi today amid reports that he has expressed his fervent wish to become Chief Minister.", + "url": "https://www.ndtv.com/india-news/sachin-pilot-meets-sonia-gandhi-over-his-future-role-in-congress-sources-2907424", + "urlToImage": "https://c.ndtvimg.com/2021-10/an9r19h4_sachin-pilot-pti-photo_625x300_06_October_21.jpg", + "publishedAt": "2022-04-21T12:03:22Z", + "content": "The political call on Sonia Gandhi's role will be taken by Sonia Gandhi. (FILE)\r\nNew Delhi: Rajasthan Congress leader Sachin Pilot met with Sonia Gandhi today amid reports that he has expressed his f\u2026 [+1845 chars]" + } + ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index e868fc8..13d8630 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ buildscript { ext { - compose_version = '1.3.2' + compose_version = '1.4.3' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.3.0' apply false - id 'com.android.library' version '7.3.0' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'com.android.application' version '8.0.2' apply false + id 'com.android.library' version '8.0.2' apply false + id 'org.jetbrains.kotlin.android' version '1.8.0' apply false } \ No newline at end of file