From 7f753aa943d48a54e86c43651704b7b644591e20 Mon Sep 17 00:00:00 2001 From: JonibekXolmonov Date: Mon, 1 Apr 2024 16:19:07 +0500 Subject: [PATCH] unsplash api integration to show university images --- .idea/kotlinc.xml | 2 +- app/build.gradle | 31 ++-- .../com/bera/josaahelpertool/di/AppModule.kt | 16 ++ .../josaahelpertool/models/UniversityImage.kt | 28 ++++ .../josaahelpertool/models/ui/TopHalfItem.kt | 6 + .../josaahelpertool/network/UnsplashApi.kt | 19 +++ .../repository/QuotesRepository.kt | 1 + .../repository/UniversityImageRepository.kt | 14 ++ .../screens/home/HomeScreen.kt | 141 ++++++++++-------- .../screens/home/HomeViewModel.kt | 34 +++-- .../use_cases/GetUniversityImagesUseCase.kt | 35 +++++ .../bera/josaahelpertool/utils/Constants.kt | 3 + build.gradle | 5 +- 13 files changed, 249 insertions(+), 86 deletions(-) create mode 100644 app/src/main/java/com/bera/josaahelpertool/models/UniversityImage.kt create mode 100644 app/src/main/java/com/bera/josaahelpertool/models/ui/TopHalfItem.kt create mode 100644 app/src/main/java/com/bera/josaahelpertool/network/UnsplashApi.kt create mode 100644 app/src/main/java/com/bera/josaahelpertool/repository/UniversityImageRepository.kt create mode 100644 app/src/main/java/com/bera/josaahelpertool/use_cases/GetUniversityImagesUseCase.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 217e5c5..fe63bb6 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ff17fc7..f0cb1fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,8 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' id 'com.google.dagger.hilt.android' + id 'com.google.devtools.ksp' } android { @@ -55,7 +55,7 @@ android { buildConfig true } composeOptions { - kotlinCompilerExtensionVersion '1.4.7' + kotlinCompilerExtensionVersion '1.5.11' } packagingOptions { resources { @@ -73,7 +73,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') - implementation 'androidx.activity:activity-compose:1.8.1' + implementation 'androidx.activity:activity-compose:1.8.2' implementation platform('androidx.compose:compose-bom:2023.06.01') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' @@ -90,12 +90,12 @@ dependencies { //Dagger-Hilt def hilt_version = "2.47" implementation "com.google.dagger:hilt-android:$hilt_version" - kapt "com.google.dagger:hilt-compiler:$hilt_version" - kapt "androidx.hilt:hilt-compiler:1.1.0" - implementation "androidx.hilt:hilt-navigation-compose:1.1.0" + ksp "com.google.dagger:hilt-compiler:$hilt_version" + ksp "androidx.hilt:hilt-compiler:1.2.0" + implementation "androidx.hilt:hilt-navigation-compose:1.2.0" // Coroutines - def coroutine_version = "1.5.2" + def coroutine_version = "1.8.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutine_version" @@ -109,18 +109,27 @@ dependencies { //Retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.squareup.okhttp3:okhttp:4.11.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' //Coil - implementation "io.coil-kt:coil-compose:2.4.0" + implementation "io.coil-kt:coil-compose:2.6.0" + + //Glide + implementation("com.github.bumptech.glide:compose:1.0.0-beta01") //Extended Icons - implementation "androidx.compose.material:material-icons-extended:1.5.0" + implementation "androidx.compose.material:material-icons-extended:1.6.4" //Browser View - implementation 'androidx.browser:browser:1.7.0' + implementation 'androidx.browser:browser:1.8.0' //Permissions implementation "com.google.accompanist:accompanist-permissions:0.23.1" +// Room + def roomVersion = "2.6.1" + implementation "androidx.room:room-runtime:$roomVersion" + implementation "androidx.room:room-ktx:$roomVersion" + ksp "androidx.room:room-compiler:$roomVersion" + } \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/di/AppModule.kt b/app/src/main/java/com/bera/josaahelpertool/di/AppModule.kt index 9e67346..1531e9b 100644 --- a/app/src/main/java/com/bera/josaahelpertool/di/AppModule.kt +++ b/app/src/main/java/com/bera/josaahelpertool/di/AppModule.kt @@ -3,10 +3,12 @@ package com.bera.josaahelpertool.di import android.content.Context import com.bera.josaahelpertool.network.CutoffApi import com.bera.josaahelpertool.network.QuotesApi +import com.bera.josaahelpertool.network.UnsplashApi import com.bera.josaahelpertool.network.connectivity.ConnectivityObserver import com.bera.josaahelpertool.network.okhttp.CacheInterceptor import com.bera.josaahelpertool.network.okhttp.ForceCacheInterceptor import com.bera.josaahelpertool.repository.CutoffRepository +import com.bera.josaahelpertool.repository.UniversityImageRepository import com.bera.josaahelpertool.utils.Constants import dagger.Module import dagger.Provides @@ -28,6 +30,10 @@ object AppModule { @Provides fun provideCutoffRepository(api: CutoffApi) = CutoffRepository(api) + @Singleton + @Provides + fun provideUniversityImageRepository(api: UnsplashApi) = UniversityImageRepository(api) + @Singleton @Provides fun provideCutoffApi(@ApplicationContext appContext: Context, connectivityObserver: ConnectivityObserver): CutoffApi { @@ -56,6 +62,16 @@ object AppModule { .create(QuotesApi::class.java) } + @Singleton + @Provides + fun provideUnsplashApi(): UnsplashApi { + return Retrofit.Builder() + .baseUrl(Constants.BASE_URL_UNSPLASH) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(UnsplashApi::class.java) + } + @Singleton @Provides fun provideConnectivityObserver(@ApplicationContext appContext: Context): ConnectivityObserver = diff --git a/app/src/main/java/com/bera/josaahelpertool/models/UniversityImage.kt b/app/src/main/java/com/bera/josaahelpertool/models/UniversityImage.kt new file mode 100644 index 0000000..f5a2261 --- /dev/null +++ b/app/src/main/java/com/bera/josaahelpertool/models/UniversityImage.kt @@ -0,0 +1,28 @@ +package com.bera.josaahelpertool.models + +import com.bera.josaahelpertool.models.ui.TopHalfItem +import com.google.gson.annotations.SerializedName + +data class UniversityImageResponse( + val results: List +) + +data class UniversityImage( + val description: String?, + @SerializedName("alt_description") + val altDescription: String, + val urls: Urls, +) + +data class Urls( + val raw: String, + val full: String, + val regular: String, + val small: String, + val thumb: String, + @SerializedName("small_s3") + val smallS3: String +) + +fun UniversityImage.toModel() = + TopHalfItem(imageUrl = urls.regular, name = description ?: altDescription) \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/models/ui/TopHalfItem.kt b/app/src/main/java/com/bera/josaahelpertool/models/ui/TopHalfItem.kt new file mode 100644 index 0000000..8b0ae7b --- /dev/null +++ b/app/src/main/java/com/bera/josaahelpertool/models/ui/TopHalfItem.kt @@ -0,0 +1,6 @@ +package com.bera.josaahelpertool.models.ui + +data class TopHalfItem( + val imageUrl: String, + val name: String +) \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/network/UnsplashApi.kt b/app/src/main/java/com/bera/josaahelpertool/network/UnsplashApi.kt new file mode 100644 index 0000000..d991704 --- /dev/null +++ b/app/src/main/java/com/bera/josaahelpertool/network/UnsplashApi.kt @@ -0,0 +1,19 @@ +package com.bera.josaahelpertool.network + +import com.bera.josaahelpertool.models.UniversityImageResponse +import com.bera.josaahelpertool.utils.Constants.API_KEY_UNSPLASH +import com.bera.josaahelpertool.utils.Constants.QUERY +import retrofit2.http.GET +import retrofit2.http.Query +import javax.inject.Singleton + +@Singleton +interface UnsplashApi { + @GET("search/photos") + suspend fun getUniversityImages( + @Query("page") page: Int = 1, + @Query("client_id") clientId: String = API_KEY_UNSPLASH, + @Query("query") query: String = QUERY, + @Query("per_page") perPage: Int = 30 + ): UniversityImageResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/repository/QuotesRepository.kt b/app/src/main/java/com/bera/josaahelpertool/repository/QuotesRepository.kt index 0ad9864..1bc935c 100644 --- a/app/src/main/java/com/bera/josaahelpertool/repository/QuotesRepository.kt +++ b/app/src/main/java/com/bera/josaahelpertool/repository/QuotesRepository.kt @@ -1,6 +1,7 @@ package com.bera.josaahelpertool.repository import com.bera.josaahelpertool.models.Quotes +import com.bera.josaahelpertool.models.UniversityImageResponse import com.bera.josaahelpertool.network.QuotesApi import javax.inject.Inject diff --git a/app/src/main/java/com/bera/josaahelpertool/repository/UniversityImageRepository.kt b/app/src/main/java/com/bera/josaahelpertool/repository/UniversityImageRepository.kt new file mode 100644 index 0000000..b555694 --- /dev/null +++ b/app/src/main/java/com/bera/josaahelpertool/repository/UniversityImageRepository.kt @@ -0,0 +1,14 @@ +package com.bera.josaahelpertool.repository + +import android.util.Log +import com.bera.josaahelpertool.models.UniversityImageResponse +import com.bera.josaahelpertool.network.UnsplashApi +import javax.inject.Inject +import kotlin.random.Random + +class UniversityImageRepository @Inject constructor(private val unsplashApi: UnsplashApi) { + suspend fun getUniversityImages(): UniversityImageResponse { + val page = Random.nextInt(1, 4) + return unsplashApi.getUniversityImages(page) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeScreen.kt b/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeScreen.kt index 0ced892..8f17aab 100644 --- a/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeScreen.kt +++ b/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeScreen.kt @@ -1,6 +1,11 @@ +@file:OptIn(ExperimentalGlideComposeApi::class, ExperimentalGlideComposeApi::class, + ExperimentalGlideComposeApi::class +) + package com.bera.josaahelpertool.screens.home import android.net.Uri +import android.util.Log import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image @@ -42,6 +47,8 @@ import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -57,10 +64,14 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import coil.compose.AsyncImage +import com.bera.josaahelpertool.models.ui.TopHalfItem import com.bera.josaahelpertool.navigation.Routes import com.bera.josaahelpertool.ui.theme.rubikFamily import com.bera.josaahelpertool.utils.CustomDivider import com.bera.josaahelpertool.utils.ShimmerListItem +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideImage import kotlinx.coroutines.launch import kotlin.reflect.KSuspendFunction2 @@ -70,6 +81,10 @@ fun HomeScreen( navController: NavController, viewModel: HomeViewModel ) { + + + val slideImages by viewModel.slideImages.collectAsState() + Surface(modifier = Modifier.fillMaxSize()) { LazyColumn( @@ -153,8 +168,7 @@ fun HomeScreen( .height(380.dp) .padding(6.dp), navController, - viewModel.slideImage, - viewModel.imageTexts, + slideImages, viewModel::changeImagePage, ) } @@ -316,8 +330,7 @@ fun QuoteBox(modifier: Modifier, quote: String, author: String, isLoading: Boole fun TopHalf( modifier: Modifier = Modifier, navController: NavController, - slideImage: Array, - imageText: Array, + topHalfItems: List, changeImage: KSuspendFunction2 ) { @@ -326,79 +339,81 @@ fun TopHalf( initialPage = 0, initialPageOffsetFraction = 0f ) { - 4 + topHalfItems.size } Box( modifier = modifier .clip(RoundedCornerShape(16.dp)) ) { - HorizontalPager(state = pagerState, key = { slideImage[it] }) { index -> - Image( - painter = painterResource(id = slideImage[index]), - contentDescription = imageText[index], - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } + if (topHalfItems.isNotEmpty()) { + HorizontalPager(state = pagerState, key = { topHalfItems[it].imageUrl }) { index -> + GlideImage( + model = topHalfItems[index].imageUrl, + contentDescription = topHalfItems[index].toString(), + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } - FilledTonalIconButton( - onClick = { scope.launch { changeImage(pagerState, false) } }, - modifier = Modifier - .align(Alignment.CenterStart) - .alpha(0.6f) - ) { - Icon( - imageVector = Icons.Default.KeyboardArrowLeft, - contentDescription = "Previous Image", - modifier = modifier.size(24.dp), - ) - } + FilledTonalIconButton( + onClick = { scope.launch { changeImage(pagerState, false) } }, + modifier = Modifier + .align(Alignment.CenterStart) + .alpha(0.6f) + ) { + Icon( + imageVector = Icons.Default.KeyboardArrowLeft, + contentDescription = "Previous Image", + modifier = modifier.size(24.dp), + ) + } - FilledTonalIconButton( - onClick = { scope.launch { changeImage(pagerState, true) } }, - modifier = Modifier - .align(Alignment.CenterEnd) - .alpha(0.6f), - ) { - Icon( - imageVector = Icons.Default.KeyboardArrowRight, - contentDescription = "Next Image", - modifier = modifier.size(24.dp), - ) - } + FilledTonalIconButton( + onClick = { scope.launch { changeImage(pagerState, true) } }, + modifier = Modifier + .align(Alignment.CenterEnd) + .alpha(0.6f), + ) { + Icon( + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = "Next Image", + modifier = modifier.size(24.dp), + ) + } + + OutlinedButton( + colors = ButtonDefaults.outlinedButtonColors( + containerColor = Color.Black.copy(0.6f) + ), + onClick = { navController.navigate(Routes.CBRScreen.route) }, + modifier = Modifier + .align(Alignment.BottomCenter) + .offset(y = (-60).dp), + ) { + Text( + modifier = Modifier + .padding(10.dp), + text = "NEW COLLEGE PREDICTOR", + fontFamily = rubikFamily, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + color = Color(0xFF81D5FC) + ) + } - OutlinedButton( - colors = ButtonDefaults.outlinedButtonColors( - containerColor = Color.Black.copy(0.6f) - ), - onClick = { navController.navigate(Routes.CBRScreen.route) }, - modifier = Modifier - .align(Alignment.BottomCenter) - .offset(y = (-60).dp), - ) { Text( modifier = Modifier - .padding(10.dp), - text = "NEW COLLEGE PREDICTOR", + .padding(10.dp) + .align(Alignment.BottomEnd), + text = topHalfItems[pagerState.currentPage].name, + fontSize = 10.sp, fontFamily = rubikFamily, - fontSize = 15.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - color = Color(0xFF81D5FC) + fontWeight = FontWeight.Thin, + color = Color.White.copy(alpha = 0.8f) ) } - - Text( - modifier = Modifier - .padding(10.dp) - .align(Alignment.BottomEnd), - text = imageText[pagerState.currentPage], - fontSize = 10.sp, - fontFamily = rubikFamily, - fontWeight = FontWeight.Thin, - color = Color.White.copy(alpha = 0.8f) - ) } } diff --git a/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeViewModel.kt b/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeViewModel.kt index 96b1c8e..d9aa05c 100644 --- a/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/bera/josaahelpertool/screens/home/HomeViewModel.kt @@ -9,17 +9,25 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bera.josaahelpertool.R +import com.bera.josaahelpertool.models.ui.TopHalfItem import com.bera.josaahelpertool.use_cases.GetQuotesUseCase +import com.bera.josaahelpertool.use_cases.GetUniversityImagesUseCase import com.bera.josaahelpertool.utils.Resource import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @OptIn(ExperimentalFoundationApi::class) @HiltViewModel class HomeViewModel @Inject constructor( - private val getQuotesUseCase: GetQuotesUseCase + private val getQuotesUseCase: GetQuotesUseCase, + private val getUniversityImagesUseCase: GetUniversityImagesUseCase ) : ViewModel() { val drawableIds = @@ -29,14 +37,8 @@ class HomeViewModel @Inject constructor( R.drawable.img_6, R.drawable.img_1 ) - - val slideImage = - arrayOf( - R.drawable.ogc, - R.drawable.iit, - R.drawable.nit, - R.drawable.iitbombay - ) + private val _slideImages = MutableStateFlow>(emptyList()) + val slideImages get() = _slideImages.asStateFlow() data class Link( val link: String, @@ -74,6 +76,16 @@ class HomeViewModel @Inject constructor( "IIT Bombay" ) + private fun fetchUniversityCampusImages() { + viewModelScope.launch { + withContext(Dispatchers.IO) { + val allImages = getUniversityImagesUseCase.getAllImages() + + _slideImages.value = allImages + } + } + } + suspend fun changeImagePage(pagerState: PagerState, next: Boolean) { pagerState .animateScrollToPage( @@ -117,4 +129,8 @@ class HomeViewModel @Inject constructor( } }.launchIn(viewModelScope) } + + init { + fetchUniversityCampusImages() + } } \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/use_cases/GetUniversityImagesUseCase.kt b/app/src/main/java/com/bera/josaahelpertool/use_cases/GetUniversityImagesUseCase.kt new file mode 100644 index 0000000..45064f4 --- /dev/null +++ b/app/src/main/java/com/bera/josaahelpertool/use_cases/GetUniversityImagesUseCase.kt @@ -0,0 +1,35 @@ +package com.bera.josaahelpertool.use_cases + +import com.bera.josaahelpertool.models.UniversityImage +import com.bera.josaahelpertool.models.toModel +import com.bera.josaahelpertool.models.ui.TopHalfItem +import com.bera.josaahelpertool.repository.UniversityImageRepository +import javax.inject.Inject + +class GetUniversityImagesUseCase @Inject constructor(private val repository: UniversityImageRepository) { + suspend fun getAllImages(): List { + try { + val response = repository.getUniversityImages() + val universities = response.results + val keywords = listOf("student", "university", "college") + val filtered = filterImagesByKeywords(universities, keywords) + + return filtered.map { it.toModel() } + + } catch (e: Exception) { + + } + return emptyList() + } +} + +fun filterImagesByKeywords( + images: List, + keywords: List +): List { + return images.filter { image -> + val description = image.description ?: image.altDescription + val hasKeyword = keywords.any { keyword -> description.lowercase().contains(keyword) } + description.length < 100 && hasKeyword + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bera/josaahelpertool/utils/Constants.kt b/app/src/main/java/com/bera/josaahelpertool/utils/Constants.kt index 8d2dd71..9dc2a91 100644 --- a/app/src/main/java/com/bera/josaahelpertool/utils/Constants.kt +++ b/app/src/main/java/com/bera/josaahelpertool/utils/Constants.kt @@ -6,13 +6,16 @@ import com.bera.josaahelpertool.models.CutoffItem object Constants { const val BASE_URL_CUTOFF = "https://api.npoint.io/" const val BASE_URL_QUOTES = "https://api.api-ninjas.com/" + const val BASE_URL_UNSPLASH = "https://api.unsplash.com/" const val API_KEY_CUTOFF = BuildConfig.API_KEY_CUTOFF const val API_KEY_QUOTES = BuildConfig.API_KEY_QUOTES + const val API_KEY_UNSPLASH = "6nlmANUApAAm_Kqer-xedtHQ61JRnzuZD3AmBaHhjoQ" const val IIT_STRING = "Indian Institute of Technology" const val IIT_STRING_1 = "Indian Institute of Technology" const val NIT_STRING = "National Institute of Technology" const val IIIT_STRING = "Indian Institute of Information Technology" + const val QUERY = "university campus" val FakeCutoffItem = CutoffItem( "", "", diff --git a/build.gradle b/build.gradle index 76eb646..409e1d6 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' version '8.2.0' apply false id 'com.android.library' version '8.2.0' apply false - id 'org.jetbrains.kotlin.android' version '1.8.21' apply false - id 'com.google.dagger.hilt.android' version '2.44' apply false + id 'com.google.dagger.hilt.android' version '2.47' apply false + id("org.jetbrains.kotlin.android") version "1.9.23" apply false + id("com.google.devtools.ksp") version "1.9.23-1.0.19" apply false } \ No newline at end of file