Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Gradle JDK: JetBrains Runtime version 17.0.6/...**
### Library References
1. Android Components Navigation [Read here](https://developer.android.com/jetpack/docs/guide)
0. Kotlin [Read here](https://developer.android.com/kotlin/ktx)
0. Retrofit [Read here](https://square.github.io/retrofit/)
0. okhttp3 [Read here](https://square.github.io/okhttp/)
0. Gson [Read here](https://github.com/google/gson#readme)
0. Dependency Injections [Read here](https://developer.android.com/training/dependency-injection/hilt-android)
0. Jetpack [Read here](https://developer.android.com/jetpack/getting-started)
0. Hilt & Jetpack [Read here](https://developer.android.com/jetpack/androidx/releases/hilt)
Expand All @@ -44,4 +47,4 @@ Gradle JDK: JetBrains Runtime version 17.0.6/...**
0. View Models [Read here](https://developer.android.com/topic/libraries/architecture/viewmodel)
0. DataModel [Read here](https://developer.android.com/topic/libraries/architecture/viewmodel)
0. DataModel [Read here](https://developer.android.com/topic/libraries/architecture/viewmodel)
0. Coil Compose [Read here](https://developer.android.com/jetpack/compose/graphics/images/loading)
11 changes: 10 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ dependencies {
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'

/*Image*/
implementation "io.coil-kt:coil-compose:$coil_version"

/*Dependency injection library*/
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
Expand All @@ -73,11 +76,17 @@ dependencies {
/*ViewModel libraries*/
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-compose:$uistate_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.compose.runtime:runtime-livedata:$livedata_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-compose:$uistate_version"

/*Network*/
implementation "com.google.code.gson:gson:$gson_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".FlickrApp"
android:allowBackup="true"
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/example/flickrapp/core/ApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.flickrapp.core

import com.example.flickrapp.data.PicturesResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query


interface ApiService {
@GET("?method=$METHOD_RECENT&api_key=$API_KEY&per_page=$PAGING_LIMIT&format=$JSON$CALLBACK")
suspend fun getAllPictures(
): Response<PicturesResponse?>

@GET("?method=$METHOD_SEARCH&api_key=$API_KEY&per_page=$PAGING_LIMIT_SEARCH&format=$JSON$CALLBACK")
suspend fun getPictureByTag(
@Query("text") tag: String
): Response<PicturesResponse?>

}
11 changes: 11 additions & 0 deletions app/src/main/java/com/example/flickrapp/core/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.flickrapp.core

const val API_KEY = "1508443e49213ff84d566777dc211f2a"
const val METHOD_SEARCH = "flickr.photos.search"
const val METHOD_RECENT = "flickr.photos.getRecent"
const val JSON = "json"
const val PAGING_LIMIT = "50"
const val PAGING_LIMIT_SEARCH = "25"
const val CALLBACK = "&nojsoncallback=1"
const val WEB_SERVICE_URL = "https://api.flickr.com/services/rest/"
const val CONNECTION_TIMEOUT: Long = 10
30 changes: 30 additions & 0 deletions app/src/main/java/com/example/flickrapp/data/ManagerService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.flickrapp.data

import com.example.flickrapp.core.ApiService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject

class ManagerService @Inject constructor(private val retrofit: ApiService) {
suspend fun getAllPictures() : PicturesResponse? {
return withContext(Dispatchers.IO) {
val response = retrofit.getAllPictures()
if(response.isSuccessful) {
response.body()
} else {
null
}
}
}

suspend fun getPictureSearchBy(query: String) : PicturesResponse? {
return withContext(Dispatchers.IO) {
val response = retrofit.getPictureByTag(query)
if(response.isSuccessful) {
response.body()
} else {
null
}
}
}
}
16 changes: 16 additions & 0 deletions app/src/main/java/com/example/flickrapp/data/Repository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.flickrapp.data

import retrofit2.http.Query
import javax.inject.Inject

/*Local save data*/
class Repository @Inject constructor(private val api: ManagerService) {

suspend fun getAllPictures(): PicturesResponse? {
return api.getAllPictures()
}

suspend fun getPictureSearchBy(query: String): PicturesResponse? {
return api.getPictureSearchBy(query)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.flickrapp.data

import com.example.flickrapp.data.model.Picture

class RepositoryProvider {
companion object {
var pictures: List<Picture> = emptyList()
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/com/example/flickrapp/data/model/Picture.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.flickrapp.data.model

data class Picture(
val id: String?,
val url: String?,
val title: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.flickrapp.data

data class PicturesResponse(
val photos: PhotosMetaData?
)

data class PhotosMetaData(
val page: Int?,
val photo: List<PhotoResponse?>
)

data class PhotoResponse(
val id: String?,
val owner: String?,
val secret: String?,
val server: String?,
val farm: Int?,
val title: String?
)
65 changes: 65 additions & 0 deletions app/src/main/java/com/example/flickrapp/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.example.flickrapp.di

import com.example.flickrapp.core.ApiService
import com.example.flickrapp.core.CONNECTION_TIMEOUT
import com.example.flickrapp.core.WEB_SERVICE_URL
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

@Singleton
@Provides
fun provideLoggingInterceptor() : HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
}
}

@Provides
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor
) : OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.build()
}

@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient) : Retrofit {
return Retrofit.Builder()
.baseUrl(WEB_SERVICE_URL)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
)
)
.client(okHttpClient)
.build()
}


@Singleton
@Provides
fun provideRepository(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}

private val TAG = NetworkModule::class.simpleName
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.flickrapp.domain

import com.example.flickrapp.data.model.Picture
import com.example.flickrapp.data.PicturesResponse
import com.example.flickrapp.data.Repository
import com.example.flickrapp.data.RepositoryProvider
import javax.inject.Inject

class PicturesFlickrUseCase @Inject constructor(private val repository: Repository) {

suspend operator fun invoke() = getPictures(repository.getAllPictures())

private fun getPictures(response: PicturesResponse?): List<Picture> {
return response?.let {
val picturesList = it?.photos?.photo?.map { picture ->
Picture(
id = picture?.id,
url = "https://farm${picture?.farm}.staticflickr.com/${picture?.server}/${picture?.id}_${picture?.secret}.jpg",
title = picture?.title
)
}
if (picturesList != null) {
RepositoryProvider.pictures = picturesList
}
picturesList
} ?: emptyList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.flickrapp.domain

import com.example.flickrapp.data.PicturesResponse
import com.example.flickrapp.data.Repository
import com.example.flickrapp.data.RepositoryProvider
import com.example.flickrapp.data.model.Picture
import javax.inject.Inject

class SearchPicturesUseCase @Inject constructor(private val repository: Repository) {

suspend operator fun invoke(search: String) = getPictures(repository.getPictureSearchBy(search))

private fun getPictures(response: PicturesResponse?): List<Picture> {
return response?.let {
val picturesList = it?.photos?.photo?.map { picture ->
Picture(
id = picture?.id,
url = "https://farm${picture?.farm}.staticflickr.com/${picture?.server}/${picture?.id}_${picture?.secret}.jpg",
title = picture?.title
)
}
if (picturesList != null) {
RepositoryProvider.pictures = picturesList
}
picturesList
} ?: emptyList()
}
}
Loading