Skip to content

Commit

Permalink
Merge pull request #6 from azrael8576/chore/data
Browse files Browse the repository at this point in the history
Implement functionality of the data module
  • Loading branch information
azrael8576 authored Dec 1, 2023
2 parents e26a734 + 2cb844d commit 58b63a2
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.wei.picquest.core.data.di

import com.wei.picquest.core.data.repository.DefaultSearchImagesRepository
import com.wei.picquest.core.data.repository.SearchImagesRepository
import com.wei.picquest.core.data.utils.ConnectivityManagerNetworkMonitor
import com.wei.picquest.core.data.utils.NetworkMonitor
import dagger.Binds
Expand All @@ -11,6 +13,11 @@ import dagger.hilt.components.SingletonComponent
@InstallIn(SingletonComponent::class)
interface DataModule {

@Binds
fun bindsSearchImagesRepository(
searchImagesRepository: DefaultSearchImagesRepository,
): SearchImagesRepository

@Binds
fun bindsNetworkMonitor(
networkMonitor: ConnectivityManagerNetworkMonitor,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.wei.picquest.core.data.model

import com.wei.picquest.core.network.model.NetworkSearchImages

data class SearchImages(
val total: Int,
val totalHits: Int,
val images: List<ImageDetail>,
)

data class ImageDetail(
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: Long,
val views: Int,
val downloads: Int,
val likes: Int,
val comments: Int,
val userId: Int,
val user: String,
val userImageURL: String,
)

fun NetworkSearchImages.asExternalModel() = SearchImages(
total = this.total,
totalHits = this.totalHits,
images = this.hits.map { networkImageDetail ->
ImageDetail(
id = networkImageDetail.id,
pageURL = networkImageDetail.pageURL,
type = networkImageDetail.type,
tags = networkImageDetail.tags,
previewURL = networkImageDetail.previewURL,
previewWidth = networkImageDetail.previewWidth,
previewHeight = networkImageDetail.previewHeight,
webformatURL = networkImageDetail.webformatURL,
webformatWidth = networkImageDetail.webformatWidth,
webformatHeight = networkImageDetail.webformatHeight,
largeImageURL = networkImageDetail.largeImageURL,
imageWidth = networkImageDetail.imageWidth,
imageHeight = networkImageDetail.imageHeight,
imageSize = networkImageDetail.imageSize,
views = networkImageDetail.views,
downloads = networkImageDetail.downloads,
likes = networkImageDetail.likes,
comments = networkImageDetail.comments,
userId = networkImageDetail.userId,
user = networkImageDetail.user,
userImageURL = networkImageDetail.userImageURL,
)
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.wei.picquest.core.data.repository

import com.wei.picquest.core.network.Dispatcher
import com.wei.picquest.core.network.PqDispatchers
import com.wei.picquest.core.network.PqNetworkDataSource
import com.wei.picquest.core.network.model.NetworkSearchImages
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import javax.inject.Inject

/**
* Implementation of the [SearchImagesRepository].
* @param ioDispatcher 用於執行 IO 相關操作的 CoroutineDispatcher。
* @param network 數據源的網路接口。
*/
class DefaultSearchImagesRepository @Inject constructor(
@Dispatcher(PqDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
private val network: PqNetworkDataSource,
) : SearchImagesRepository {

/**
* @param query。A URL encoded search term. If omitted, all images are returned. This value may not exceed 100 characters.
* Example: "yellow+flower"
* @return 一個 Flow,內容為 Search Images 的數據。
*/
override suspend fun getSearchImages(
query: String,
): Flow<NetworkSearchImages> = withContext(ioDispatcher) {
flow {
emit(network.searchImages(query))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wei.picquest.core.data.repository

import com.wei.picquest.core.network.model.NetworkSearchImages
import kotlinx.coroutines.flow.Flow

interface SearchImagesRepository {

suspend fun getSearchImages(query: String): Flow<NetworkSearchImages>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wei.picquest.core.network

import com.wei.picquest.core.network.model.NetworkSearchImages

/**
* Interface representing network calls to the PicQuest backend
*/
interface PqNetworkDataSource {

suspend fun searchImages(query: String): NetworkSearchImages
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.wei.picquest.core.network.di

import com.wei.picquest.core.network.PqNetworkDataSource
import com.wei.picquest.core.network.retrofit.RetrofitPqNetwork
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

/**
* TODO Wei 移動至 build variants 下產出資料夾
*/
@Module
@InstallIn(SingletonComponent::class)
interface FlavoredNetworkModule {

@Binds
fun binds(implementation: RetrofitPqNetwork): PqNetworkDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.wei.picquest.core.network.di

import com.wei.core.network.BuildConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton

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

@Provides
@Singleton
fun providesNetworkJson(): Json = Json {
ignoreUnknownKeys = true
}

@Provides
@Singleton
fun okHttpCallFactory(): Call.Factory = OkHttpClient.Builder()
.addInterceptor(
HttpLoggingInterceptor()
.apply {
if (BuildConfig.DEBUG) {
setLevel(HttpLoggingInterceptor.Level.BODY)
}
},
)
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.wei.picquest.core.network.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* Network representation of [SearchImages] when fetched from /
*/
@Serializable
data class NetworkSearchImages(
@SerialName("total")
val total: Int,
@SerialName("totalHits")
val totalHits: Int,
@SerialName("hits")
val hits: List<NetworkImageDetail>,
)

@Serializable
data class NetworkImageDetail(
@SerialName("id")
val id: Int,
@SerialName("pageURL")
val pageURL: String,
@SerialName("type")
val type: String,
@SerialName("tags")
val tags: String,
@SerialName("previewURL")
val previewURL: String,
@SerialName("previewWidth")
val previewWidth: Int,
@SerialName("previewHeight")
val previewHeight: Int,
@SerialName("webformatURL")
val webformatURL: String,
@SerialName("webformatWidth")
val webformatWidth: Int,
@SerialName("webformatHeight")
val webformatHeight: Int,
@SerialName("largeImageURL")
val largeImageURL: String,
@SerialName("imageWidth")
val imageWidth: Int,
@SerialName("imageHeight")
val imageHeight: Int,
@SerialName("imageSize")
val imageSize: Long,
@SerialName("views")
val views: Int,
@SerialName("downloads")
val downloads: Int,
@SerialName("likes")
val likes: Int,
@SerialName("comments")
val comments: Int,
@SerialName("user_id")
val userId: Int,
@SerialName("user")
val user: String,
@SerialName("userImageURL")
val userImageURL: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.wei.picquest.core.network.retrofit

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.wei.core.network.BuildConfig
import com.wei.picquest.core.network.PqNetworkDataSource
import com.wei.picquest.core.network.model.NetworkSearchImages
import kotlinx.serialization.json.Json
import okhttp3.Call
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.http.GET
import retrofit2.http.Query
import javax.inject.Inject
import javax.inject.Singleton

private const val PIXABAY_BASE_URL = BuildConfig.BACKEND_URL
private const val API_KEY = BuildConfig.API_KEY

/**
* Retrofit API declaration for Pixabay Network API
*/
interface RetrofitPixabayApi {
/**
* https://pixabay.com/api/?key=${api key}&q=yellow+flowers&image_type=photo
*/
@GET(".")
suspend fun searchImages(
@Query("key") apiKey: String = API_KEY,
@Query("q") query: String,
@Query("image_type") imageType: String = "photo",
// Add more parameters as needed
): NetworkSearchImages
}

/**
* [Retrofit] backed [PqNetworkDataSource]
*/
@Singleton
class RetrofitPqNetwork @Inject constructor(
networkJson: Json,
okhttpCallFactory: Call.Factory,
) : PqNetworkDataSource {

private val pixabayApi = Retrofit.Builder()
.baseUrl(PIXABAY_BASE_URL)
.callFactory(okhttpCallFactory)
.addConverterFactory(
networkJson.asConverterFactory("application/json".toMediaType()),
)
.build()
.create(RetrofitPixabayApi::class.java)

override suspend fun searchImages(query: String): NetworkSearchImages {
return pixabayApi.searchImages(query = query)
}
}
3 changes: 2 additions & 1 deletion secrets.defaults.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## This file provides default values to modules using the secrets-gradle-plugin. It is necessary
# because the secrets properties file is not under source control so CI builds will fail without
# default values.
BACKEND_URL="https://pixabay.com/api/"
BACKEND_URL="https://pixabay.com/api/"
API_KEY="fake_api_key"

0 comments on commit 58b63a2

Please sign in to comment.