Initial commit
This commit is contained in:
1
data/.gitignore
vendored
Normal file
1
data/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
34
data/build.gradle.kts
Normal file
34
data/build.gradle.kts
Normal file
@ -0,0 +1,34 @@
|
||||
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
||||
plugins {
|
||||
alias(libs.plugins.convention.android.library)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "dev.adriankuta.pixabay.data"
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
buildConfigField("String", "PIXABAY_API_KEY", "\"<REPLACE_WITH_PIXABAY_API_KEY>\"")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.retrofit)
|
||||
|
||||
implementation(platform(libs.okhttp.bom))
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.logging.interceptor)
|
||||
|
||||
implementation(libs.androidx.paging.compose)
|
||||
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.retrofit2.kotlinx.serialization.converter)
|
||||
|
||||
implementation(libs.androidx.room.runtime)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.ktx)
|
||||
implementation(libs.androidx.room.paging)
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package dev.adriankuta.pixabay.data.di
|
||||
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.adriankuta.pixabay.data.BuildConfig
|
||||
import dev.adriankuta.pixabay.data.network.PixabayService
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
internal class NetworkModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
val logging = HttpLoggingInterceptor()
|
||||
logging.level =
|
||||
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
|
||||
return logging
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideOkHttpClient(
|
||||
httpLoggingInterceptor: HttpLoggingInterceptor
|
||||
): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providePixabayApi(
|
||||
okHttpClient: OkHttpClient
|
||||
): PixabayService {
|
||||
val networkJson = Json { ignoreUnknownKeys = true }
|
||||
return Retrofit.Builder()
|
||||
.client(okHttpClient)
|
||||
.baseUrl("https://pixabay.com/")
|
||||
.addConverterFactory(networkJson.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
.create(PixabayService::class.java)
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package dev.adriankuta.pixabay.data.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.adriankuta.pixabay.data.repository.ImageRepository
|
||||
import dev.adriankuta.pixabay.data.repository.PixabayImageRepository
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
internal abstract class NetworkRepositoresModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindPhotoRepository(pixabayImageRepository: PixabayImageRepository): ImageRepository
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package dev.adriankuta.pixabay.data.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.adriankuta.pixabay.data.room.AppDatabase
|
||||
import dev.adriankuta.pixabay.data.room.dao.PixabayImagesDao
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
internal class PersistanceModule {
|
||||
@Provides
|
||||
fun provideRoomDb(@ApplicationContext context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"pixabay.db"
|
||||
).build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providePixabayImagesDao(appDatabase: AppDatabase) = appDatabase.imagesDao()
|
||||
|
||||
@Provides
|
||||
fun provideRemoteKeysDao(appDatabase: AppDatabase) = appDatabase.remoteKeysDao()
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package dev.adriankuta.pixabay.data.dto.response
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PixabayImageResponse(
|
||||
val id: Int,
|
||||
val pageURL: String,
|
||||
val type: String,
|
||||
val tags: String,
|
||||
val previewURL: String,
|
||||
val previewWidth: Int,
|
||||
val previewHeight: Int,
|
||||
val webformatURL: String,
|
||||
val webformatWidth: Int,
|
||||
val webformatHeight: Int,
|
||||
val largeImageURL: String,
|
||||
val imageWidth: Int,
|
||||
val imageHeight: Int,
|
||||
val imageSize: Int,
|
||||
val views: Int,
|
||||
val downloads: Int,
|
||||
val likes: Int,
|
||||
val comments: Int,
|
||||
val user_id: Int,
|
||||
val user: String,
|
||||
val userImageURL: String
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package dev.adriankuta.pixabay.data.dto.response
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class SearchPixabayImagesResponse(
|
||||
val hits: List<PixabayImageResponse>
|
||||
)
|
@ -0,0 +1,78 @@
|
||||
package dev.adriankuta.pixabay.data.model
|
||||
|
||||
import dev.adriankuta.pixabay.data.dto.response.PixabayImageResponse
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity
|
||||
|
||||
data class PixabayImage(
|
||||
val id: Int,
|
||||
val pageURL: String,
|
||||
val type: String,
|
||||
val tags: String,
|
||||
val previewURL: String,
|
||||
val previewWidth: Int,
|
||||
val previewHeight: Int,
|
||||
val webformatURL: String,
|
||||
val webformatWidth: Int,
|
||||
val webformatHeight: Int,
|
||||
val largeImageURL: String,
|
||||
val imageWidth: Int,
|
||||
val imageHeight: Int,
|
||||
val imageSize: Int,
|
||||
val views: Int,
|
||||
val downloads: Int,
|
||||
val likes: Int,
|
||||
val comments: Int,
|
||||
val user_id: Int,
|
||||
val user: String,
|
||||
val userImageURL: String
|
||||
) {
|
||||
internal constructor(entity: PixabayImageEntity) :
|
||||
this(
|
||||
id = entity.id,
|
||||
pageURL = entity.pageURL,
|
||||
type = entity.type,
|
||||
tags = entity.tags,
|
||||
previewURL = entity.previewURL,
|
||||
previewWidth = entity.previewWidth,
|
||||
previewHeight = entity.previewHeight,
|
||||
webformatURL = entity.webformatURL,
|
||||
webformatWidth = entity.webformatWidth,
|
||||
webformatHeight = entity.webformatHeight,
|
||||
largeImageURL = entity.largeImageURL,
|
||||
imageWidth = entity.imageWidth,
|
||||
imageHeight = entity.imageHeight,
|
||||
imageSize = entity.imageSize,
|
||||
views = entity.views,
|
||||
downloads = entity.downloads,
|
||||
likes = entity.likes,
|
||||
comments = entity.comments,
|
||||
user_id = entity.userId,
|
||||
user = entity.user,
|
||||
userImageURL = entity.userImageURL
|
||||
)
|
||||
|
||||
internal constructor(entity: PixabayImageResponse) :
|
||||
this(
|
||||
id = entity.id,
|
||||
pageURL = entity.pageURL,
|
||||
type = entity.type,
|
||||
tags = entity.tags,
|
||||
previewURL = entity.previewURL,
|
||||
previewWidth = entity.previewWidth,
|
||||
previewHeight = entity.previewHeight,
|
||||
webformatURL = entity.webformatURL,
|
||||
webformatWidth = entity.webformatWidth,
|
||||
webformatHeight = entity.webformatHeight,
|
||||
largeImageURL = entity.largeImageURL,
|
||||
imageWidth = entity.imageWidth,
|
||||
imageHeight = entity.imageHeight,
|
||||
imageSize = entity.imageSize,
|
||||
views = entity.views,
|
||||
downloads = entity.downloads,
|
||||
likes = entity.likes,
|
||||
comments = entity.comments,
|
||||
user_id = entity.user_id,
|
||||
user = entity.user,
|
||||
userImageURL = entity.userImageURL
|
||||
)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package dev.adriankuta.pixabay.data.network
|
||||
|
||||
import dev.adriankuta.pixabay.data.BuildConfig
|
||||
import dev.adriankuta.pixabay.data.dto.response.SearchPixabayImagesResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
internal interface PixabayService {
|
||||
|
||||
@GET("api")
|
||||
suspend fun searchImages(
|
||||
@Query("q") query: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("per_page") pageSize: Int,
|
||||
@Query("key") key: String = BuildConfig.PIXABAY_API_KEY
|
||||
): SearchPixabayImagesResponse
|
||||
|
||||
@GET("api")
|
||||
suspend fun searchImageById(
|
||||
@Query("id") query: String,
|
||||
@Query("key") key: String = BuildConfig.PIXABAY_API_KEY
|
||||
): SearchPixabayImagesResponse
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package dev.adriankuta.pixabay.data.paging
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import dev.adriankuta.pixabay.data.model.PixabayImage
|
||||
import dev.adriankuta.pixabay.data.network.PixabayService
|
||||
import dev.adriankuta.pixabay.data.repository.PixabayImageRepository.Companion.NETWORK_PAGE_SIZE
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
private const val STARTING_PAGE_INDEX = 1
|
||||
|
||||
internal class PixabayPagingSource(
|
||||
private val pixabayService: PixabayService,
|
||||
private val query: String
|
||||
) : PagingSource<Int, PixabayImage>() {
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PixabayImage> {
|
||||
val position = params.key ?: STARTING_PAGE_INDEX
|
||||
return try {
|
||||
val response = pixabayService.searchImages(query, position, params.loadSize)
|
||||
val results = response.hits.map { PixabayImage(it) }
|
||||
val nextKey = if (results.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
// initial load size = 3 * NETWORK_PAGE_SIZE
|
||||
// ensure we're not requesting duplicating items, at the 2nd request
|
||||
position + (params.loadSize / NETWORK_PAGE_SIZE)
|
||||
}
|
||||
LoadResult.Page(
|
||||
data = results,
|
||||
prevKey = if (position == STARTING_PAGE_INDEX) null else position - 1,
|
||||
nextKey = nextKey
|
||||
)
|
||||
} catch (exception: IOException) {
|
||||
return LoadResult.Error(exception)
|
||||
} catch (exception: HttpException) {
|
||||
return LoadResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
// The refresh key is used for subsequent refresh calls to PagingSource.load after the initial load
|
||||
override fun getRefreshKey(state: PagingState<Int, PixabayImage>): Int? {
|
||||
// We need to get the previous key (or next key if previous is null) of the page
|
||||
// that was closest to the most recently accessed index.
|
||||
// Anchor position is the most recently accessed index
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
|
||||
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
@file:OptIn(ExperimentalPagingApi::class)
|
||||
|
||||
package dev.adriankuta.pixabay.data.paging
|
||||
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.paging.LoadType
|
||||
import androidx.paging.PagingState
|
||||
import androidx.paging.RemoteMediator
|
||||
import androidx.room.withTransaction
|
||||
import dev.adriankuta.pixabay.data.model.PixabayImage
|
||||
import dev.adriankuta.pixabay.data.network.PixabayService
|
||||
import dev.adriankuta.pixabay.data.room.AppDatabase
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity
|
||||
import dev.adriankuta.pixabay.data.room.entity.RemoteKeys
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
private const val STARTING_PAGE_INDEX = 1
|
||||
|
||||
internal class PixabayRemoteMediator(
|
||||
private val query: String,
|
||||
private val database: AppDatabase,
|
||||
private val pixabayService: PixabayService
|
||||
) : RemoteMediator<Int, PixabayImageEntity>() {
|
||||
|
||||
private val imagesDao = database.imagesDao()
|
||||
private val remoteKeysDao = database.remoteKeysDao()
|
||||
|
||||
|
||||
override suspend fun load(
|
||||
loadType: LoadType,
|
||||
state: PagingState<Int, PixabayImageEntity>
|
||||
): MediatorResult {
|
||||
val page = when (loadType) {
|
||||
LoadType.REFRESH -> {
|
||||
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
|
||||
remoteKeys?.nextKey?.minus(1) ?: STARTING_PAGE_INDEX
|
||||
}
|
||||
|
||||
LoadType.PREPEND -> {
|
||||
val remoteKeys = getRemoteKeyForFirstItem(state)
|
||||
// If remoteKeys is null, that means the refresh result is not in the database yet.
|
||||
val prevKey = remoteKeys?.prevKey
|
||||
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||
prevKey
|
||||
}
|
||||
|
||||
LoadType.APPEND -> {
|
||||
val remoteKeys = getRemoteKeyForLastItem(state)
|
||||
// If remoteKeys is null, that means the refresh result is not in the database yet.
|
||||
// We can return Success with endOfPaginationReached = false because Paging
|
||||
// will call this method again if RemoteKeys becomes non-null.
|
||||
// If remoteKeys is NOT NULL but its nextKey is null, that means we've reached
|
||||
// the end of pagination for append.
|
||||
val nextKey = remoteKeys?.nextKey
|
||||
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
||||
nextKey
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val response = pixabayService.searchImages(query, page, state.config.pageSize)
|
||||
val imageEntities = response.hits.map { PixabayImageEntity(it, query) }
|
||||
val endOfPaginationReached = response.hits.isEmpty()
|
||||
database.withTransaction {
|
||||
if (loadType == LoadType.REFRESH) {
|
||||
imagesDao.clearAll()
|
||||
remoteKeysDao.clearRemoteKeys()
|
||||
}
|
||||
val prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1
|
||||
val nextKey = if (endOfPaginationReached) null else page + 1
|
||||
val keys = imageEntities.map {
|
||||
RemoteKeys(id = it.id, prevKey = prevKey, nextKey = nextKey)
|
||||
}
|
||||
remoteKeysDao.insertAll(keys)
|
||||
imagesDao.insertAll(imageEntities)
|
||||
}
|
||||
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
||||
} catch (exception: IOException) {
|
||||
return MediatorResult.Error(exception)
|
||||
} catch (exception: HttpException) {
|
||||
return MediatorResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, PixabayImageEntity>): RemoteKeys? {
|
||||
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
|
||||
?.let { pixabayImage ->
|
||||
remoteKeysDao.remoteKeysImageId(pixabayImage.id)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, PixabayImageEntity>): RemoteKeys? {
|
||||
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
|
||||
?.let { pixabayImage ->
|
||||
remoteKeysDao.remoteKeysImageId(pixabayImage.id)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRemoteKeyClosestToCurrentPosition(
|
||||
state: PagingState<Int, PixabayImageEntity>
|
||||
): RemoteKeys? {
|
||||
// The paging library is trying to load data after the anchor position
|
||||
// Get the item closest to the anchor position
|
||||
return state.anchorPosition?.let { position ->
|
||||
state.closestItemToPosition(position)?.id?.let { imageId ->
|
||||
remoteKeysDao.remoteKeysImageId(imageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dev.adriankuta.pixabay.data.repository
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import dev.adriankuta.pixabay.data.model.PixabayImage
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ImageRepository {
|
||||
|
||||
suspend fun searchImageById(id: Int): PixabayImage
|
||||
|
||||
suspend fun searchImages(query: String, page: Int, pageSize: Int): List<PixabayImage>
|
||||
|
||||
fun getSearchResultStream(query: String): Flow<PagingData<PixabayImage>>
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
@file:OptIn(ExperimentalPagingApi::class)
|
||||
|
||||
package dev.adriankuta.pixabay.data.repository
|
||||
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.map
|
||||
import dev.adriankuta.pixabay.data.model.PixabayImage
|
||||
import dev.adriankuta.pixabay.data.network.PixabayService
|
||||
import dev.adriankuta.pixabay.data.paging.PixabayPagingSource
|
||||
import dev.adriankuta.pixabay.data.paging.PixabayRemoteMediator
|
||||
import dev.adriankuta.pixabay.data.room.AppDatabase
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class PixabayImageRepository @Inject constructor(
|
||||
private val database: AppDatabase,
|
||||
private val pixabayService: PixabayService
|
||||
) : ImageRepository {
|
||||
|
||||
override suspend fun searchImageById(id: Int): PixabayImage {
|
||||
return runCatching {
|
||||
pixabayService.searchImageById(id.toString())
|
||||
}.mapCatching { response ->
|
||||
PixabayImage(response.hits.first())
|
||||
}.onFailure { e ->
|
||||
Timber.e(e)
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun searchImages(query: String, page: Int, pageSize: Int): List<PixabayImage> {
|
||||
return runCatching {
|
||||
pixabayService.searchImages(query, page, pageSize)
|
||||
}.mapCatching { response ->
|
||||
response.hits.map { PixabayImage(it) }
|
||||
}.onFailure { e ->
|
||||
Timber.e(e)
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
override fun getSearchResultStream(query: String): Flow<PagingData<PixabayImage>> {
|
||||
return if (USE_CACHE_PAGER)
|
||||
getOnlinePagerWithCache(query)
|
||||
else
|
||||
getOnlinePager(query)
|
||||
}
|
||||
|
||||
private fun getOnlinePager(query: String) = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = NETWORK_PAGE_SIZE,
|
||||
enablePlaceholders = false
|
||||
),
|
||||
pagingSourceFactory = { PixabayPagingSource(pixabayService, query) }
|
||||
).flow
|
||||
|
||||
private fun getOnlinePagerWithCache(query: String) = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = NETWORK_PAGE_SIZE,
|
||||
enablePlaceholders = false
|
||||
),
|
||||
remoteMediator = PixabayRemoteMediator(
|
||||
query,
|
||||
database,
|
||||
pixabayService
|
||||
),
|
||||
pagingSourceFactory = { database.imagesDao().imagesByQuery(query) }
|
||||
).flow
|
||||
.map { pagingSource ->
|
||||
pagingSource.map { PixabayImage(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NETWORK_PAGE_SIZE = 30
|
||||
const val USE_CACHE_PAGER = false
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package dev.adriankuta.pixabay.data.room
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import dev.adriankuta.pixabay.data.room.dao.PixabayImagesDao
|
||||
import dev.adriankuta.pixabay.data.room.dao.RemoteKeysDao
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity
|
||||
import dev.adriankuta.pixabay.data.room.entity.RemoteKeys
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
PixabayImageEntity::class,
|
||||
RemoteKeys::class
|
||||
], version = 1
|
||||
)
|
||||
internal abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun imagesDao(): PixabayImagesDao
|
||||
abstract fun remoteKeysDao(): RemoteKeysDao
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package dev.adriankuta.pixabay.data.room.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity.Companion.COLUMN_QUERY_USED
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity.Companion.TABLE_NAME
|
||||
|
||||
@Dao
|
||||
internal interface PixabayImagesDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(pixabayImages: List<PixabayImageEntity>)
|
||||
|
||||
|
||||
@Query("SELECT * FROM $TABLE_NAME WHERE $COLUMN_QUERY_USED LIKE :query ORDER BY entry_id ASC")
|
||||
fun imagesByQuery(query: String): PagingSource<Int, PixabayImageEntity>
|
||||
|
||||
|
||||
@Query("DELETE FROM $TABLE_NAME")
|
||||
suspend fun clearAll()
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package dev.adriankuta.pixabay.data.room.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import dev.adriankuta.pixabay.data.room.entity.RemoteKeys
|
||||
import dev.adriankuta.pixabay.data.room.entity.RemoteKeys.Companion.COLUMN_ID
|
||||
import dev.adriankuta.pixabay.data.room.entity.RemoteKeys.Companion.TABLE_NAME
|
||||
|
||||
@Dao
|
||||
internal interface RemoteKeysDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(remoteKey: List<RemoteKeys>)
|
||||
|
||||
@Query("SELECT * FROM $TABLE_NAME WHERE $COLUMN_ID = :id")
|
||||
suspend fun remoteKeysImageId(id: Int): RemoteKeys?
|
||||
|
||||
@Query("DELETE FROM $TABLE_NAME")
|
||||
suspend fun clearRemoteKeys()
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package dev.adriankuta.pixabay.data.room.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import dev.adriankuta.pixabay.data.dto.response.PixabayImageResponse
|
||||
import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity.Companion.TABLE_NAME
|
||||
|
||||
@Entity(tableName = TABLE_NAME)
|
||||
internal data class PixabayImageEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo("entry_id") val entryId: Int = 0,
|
||||
@ColumnInfo(COLUMN_ID) val id: Int,
|
||||
@ColumnInfo(COLUMN_QUERY_USED) val queryUsed: String,
|
||||
@ColumnInfo(COLUMN_PAGE_URL) val pageURL: String,
|
||||
@ColumnInfo(COLUMN_TYPE) val type: String,
|
||||
@ColumnInfo(COLUMN_TAGS) val tags: String,
|
||||
@ColumnInfo(COLUMN_PREVIEW_URL) val previewURL: String,
|
||||
@ColumnInfo(COLUMN_PREVIEW_WIDTH) val previewWidth: Int,
|
||||
@ColumnInfo(COLUMN_PREVIEW_HEIGHT) val previewHeight: Int,
|
||||
@ColumnInfo(COLUMN_WEB_FORMAT_URL) val webformatURL: String,
|
||||
@ColumnInfo(COLUMN_WEB_FORMAT_WIDTH) val webformatWidth: Int,
|
||||
@ColumnInfo(COLUMN_WEB_FORMAT_HEIGHT) val webformatHeight: Int,
|
||||
@ColumnInfo(COLUMN_LARGE_IMAGE_URL) val largeImageURL: String,
|
||||
@ColumnInfo(COLUMN_IMAGE_WIDTH) val imageWidth: Int,
|
||||
@ColumnInfo(COLUMN_IMAGE_HEIGHT) val imageHeight: Int,
|
||||
@ColumnInfo(COLUMN_IMAGE_SIZE) val imageSize: Int,
|
||||
@ColumnInfo(COLUMN_VIEWS) val views: Int,
|
||||
@ColumnInfo(COLUMN_DOWNLOADS) val downloads: Int,
|
||||
@ColumnInfo(COLUMN_LIKES) val likes: Int,
|
||||
@ColumnInfo(COLUMN_COMMENTS) val comments: Int,
|
||||
@ColumnInfo(COLUMN_USER_ID) val userId: Int,
|
||||
@ColumnInfo(COLUMN_USER) val user: String,
|
||||
@ColumnInfo(COLUMN_USER_IMAGE_URL) val userImageURL: String,
|
||||
|
||||
) {
|
||||
constructor(pixabayImageResponse: PixabayImageResponse, queryUsed: String) :
|
||||
this(
|
||||
id = pixabayImageResponse.id,
|
||||
queryUsed = queryUsed,
|
||||
pageURL = pixabayImageResponse.pageURL,
|
||||
type = pixabayImageResponse.type,
|
||||
tags = pixabayImageResponse.tags,
|
||||
previewURL = pixabayImageResponse.previewURL,
|
||||
previewWidth = pixabayImageResponse.previewWidth,
|
||||
previewHeight = pixabayImageResponse.previewHeight,
|
||||
webformatURL = pixabayImageResponse.webformatURL,
|
||||
webformatWidth = pixabayImageResponse.webformatWidth,
|
||||
webformatHeight = pixabayImageResponse.webformatHeight,
|
||||
largeImageURL = pixabayImageResponse.largeImageURL,
|
||||
imageWidth = pixabayImageResponse.imageWidth,
|
||||
imageHeight = pixabayImageResponse.imageHeight,
|
||||
imageSize = pixabayImageResponse.imageSize,
|
||||
views = pixabayImageResponse.views,
|
||||
downloads = pixabayImageResponse.downloads,
|
||||
likes = pixabayImageResponse.likes,
|
||||
comments = pixabayImageResponse.comments,
|
||||
userId = pixabayImageResponse.user_id,
|
||||
user = pixabayImageResponse.user,
|
||||
userImageURL = pixabayImageResponse.userImageURL
|
||||
)
|
||||
|
||||
|
||||
companion object {
|
||||
const val TABLE_NAME = "pixabay_images"
|
||||
const val COLUMN_ID = "id"
|
||||
const val COLUMN_QUERY_USED = "query_used"
|
||||
const val COLUMN_PAGE_URL = "page_url"
|
||||
const val COLUMN_TYPE = "type"
|
||||
const val COLUMN_TAGS = "tags"
|
||||
const val COLUMN_PREVIEW_URL = "preview_url"
|
||||
const val COLUMN_PREVIEW_WIDTH = "preview_width"
|
||||
const val COLUMN_PREVIEW_HEIGHT = "preview_height"
|
||||
const val COLUMN_WEB_FORMAT_URL = "web_format_url"
|
||||
const val COLUMN_WEB_FORMAT_WIDTH = "web_format_width"
|
||||
const val COLUMN_WEB_FORMAT_HEIGHT = "web_format_height"
|
||||
const val COLUMN_LARGE_IMAGE_URL = "large_image_url"
|
||||
const val COLUMN_IMAGE_WIDTH = "image_width"
|
||||
const val COLUMN_IMAGE_HEIGHT = "image_height"
|
||||
const val COLUMN_IMAGE_SIZE = "image_size"
|
||||
const val COLUMN_VIEWS = "views"
|
||||
const val COLUMN_DOWNLOADS = "downloads"
|
||||
const val COLUMN_LIKES = "likes"
|
||||
const val COLUMN_COMMENTS = "comments"
|
||||
const val COLUMN_USER_ID = "user_id"
|
||||
const val COLUMN_USER = "user"
|
||||
const val COLUMN_USER_IMAGE_URL = "user_image_url"
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package dev.adriankuta.pixabay.data.room.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import dev.adriankuta.pixabay.data.room.entity.RemoteKeys.Companion.TABLE_NAME
|
||||
|
||||
@Entity(tableName = TABLE_NAME)
|
||||
internal data class RemoteKeys(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = COLUMN_ID)
|
||||
val id: Int,
|
||||
@ColumnInfo(name = COLUMN_PREV_KEY)
|
||||
val prevKey: Int?,
|
||||
@ColumnInfo(name = COLUMN_NEXT_KEY)
|
||||
val nextKey: Int?
|
||||
) {
|
||||
companion object {
|
||||
const val TABLE_NAME = "remote_keys"
|
||||
const val COLUMN_ID = "id"
|
||||
const val COLUMN_PREV_KEY = "prev_key"
|
||||
const val COLUMN_NEXT_KEY = "next_key"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user