Skip to content

Commit

Permalink
Merge pull request #16 from Nexters/feature/#14-리뷰생성
Browse files Browse the repository at this point in the history
[feat] 리뷰 생성 API, 리뷰 조회 API 연결
  • Loading branch information
sxunea authored Feb 5, 2025
2 parents 218a041 + a80cdae commit 78833c0
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 24 deletions.
7 changes: 7 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ android {
}

dependencies {
implementation(projects.network)
implementation(libs.timber)
implementation(libs.retrofit.core)
implementation(libs.okhttp.logging)
implementation(projects.domain)
implementation(platform(libs.okhttp.bom))
implementation(libs.retrofit.kotlinx.serialization.converter)
implementation(libs.kotlinx.serialization.json)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.nexters.misik.data.datasource

import com.nexters.misik.data.mapper.ReviewMapper.toModel
import com.nexters.misik.data.model.Review
import com.nexters.misik.network.dto.GenerateReviewRequestDto
import com.nexters.misik.network.service.ReviewService
import javax.inject.Inject

class RemoteDataSource @Inject constructor(
private val reviewService: ReviewService,
) {
suspend fun generateReview(request: GenerateReviewRequestDto): Long =
reviewService.generateReview(request)

suspend fun getReview(id: Long): Review =
reviewService.getReview(id).toModel()
}
17 changes: 17 additions & 0 deletions data/src/main/java/com/nexters/misik/data/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.nexters.misik.data.di

import com.nexters.misik.data.repository.ReviewRepositoryImpl
import com.nexters.misik.domain.ReviewRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindReviewRepository(reviewRepositoryImpl: ReviewRepositoryImpl): ReviewRepository
}
21 changes: 21 additions & 0 deletions data/src/main/java/com/nexters/misik/data/di/ServiceModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.nexters.misik.data.di

import com.nexters.misik.network.service.ReviewService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {
@Singleton
@Provides
internal fun provideReviewService(
retrofit: Retrofit,
): ReviewService {
return retrofit.create(ReviewService::class.java)
}
}
23 changes: 23 additions & 0 deletions data/src/main/java/com/nexters/misik/data/mapper/ReviewMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.nexters.misik.data.mapper

import com.nexters.misik.data.model.Review
import com.nexters.misik.domain.ReviewEntity
import com.nexters.misik.network.dto.GetReviewResponseDto

object ReviewMapper {
fun GetReviewResponseDto.toModel(): Review {
return Review(
id = id,
isSuccess = isSuccess,
review = review,
)
}

fun Review.toDomain(): ReviewEntity {
return ReviewEntity(
id = this.id,
review = this.review,
isSuccess = this.isSuccess,
)
}
}
7 changes: 7 additions & 0 deletions data/src/main/java/com/nexters/misik/data/model/Review.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.nexters.misik.data.model

data class Review(
val isSuccess: Boolean?,
val id: String?,
val review: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nexters.misik.data.repository

import com.nexters.misik.data.datasource.RemoteDataSource
import com.nexters.misik.data.mapper.ReviewMapper.toDomain
import com.nexters.misik.domain.ReviewEntity
import com.nexters.misik.domain.ReviewRepository
import com.nexters.misik.network.dto.GenerateReviewRequestDto
import javax.inject.Inject

class ReviewRepositoryImpl @Inject constructor(
private val remoteDataSource: RemoteDataSource,
) : ReviewRepository {

override suspend fun generateReview(
ocrText: String,
hashTags: List<String>,
reviewStyle: String,
): Result<Long> = runCatching {
val requestDto = GenerateReviewRequestDto(
ocrText = ocrText,
hashTag = hashTags,
reviewStyle = reviewStyle,
)
remoteDataSource.generateReview(requestDto)
}

override suspend fun getReview(id: Long): Result<ReviewEntity?> = runCatching {
remoteDataSource.getReview(id).toDomain()
}
}
7 changes: 7 additions & 0 deletions domain/src/main/java/com/nexters/misik/domain/ReviewEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.nexters.misik.domain

data class ReviewEntity(
val isSuccess: Boolean?,
val id: String?,
val review: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nexters.misik.domain

interface ReviewRepository {
// 리뷰 생성
suspend fun generateReview(ocrText: String, hashTags: List<String>, reviewStyle: String): Result<Long?>

// 리뷰 조회
suspend fun getReview(id: Long): Result<ReviewEntity?>
}
4 changes: 4 additions & 0 deletions feature/webview/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
plugins {
alias(libs.plugins.misik.android.library)
alias(libs.plugins.misik.feature)
alias(libs.plugins.misik.android.hilt)
}

android {
namespace = "com.nexters.misik.feature.webview"
}

dependencies {
implementation(projects.data)
implementation(projects.domain)
implementation(projects.network)

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import android.webkit.WebViewClient
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
Expand All @@ -27,34 +25,26 @@ fun WebViewScreen(
modifier: Modifier = Modifier,
viewModel: WebViewViewModel = hiltViewModel(),
) {
val state by viewModel.state.collectAsStateWithLifecycle()
val uiState by viewModel.state.collectAsStateWithLifecycle()

// 페이지 로딩 이벤트 발생 (WebView 로드 시작 시)
LaunchedEffect(Unit) {
viewModel.onEvent(WebViewEvent.LoadPage)
viewModel.generateReview()
viewModel.getReview()
}

Box(modifier = modifier.fillMaxSize()) {
// 로딩 상태 UI
if (state.isLoading) {
if (uiState.isLoading) {
Timber.d("WebViewScreen_UiState", "Loading")
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
)
}

// 에러 상태 UI
state.error?.let {
Timber.d("WebViewScreen_UiState", "Error: $it")
Text(
text = "Error: $it",
color = Color.Red,
modifier = Modifier.align(Alignment.Center),
)
}

// 콘텐츠가 있을 경우, WebView를 보여줌
if (!state.isLoading) {
if (!uiState.isLoading) {
Timber.d("WebViewScreen_UiState", "Loaded")
AndroidView(
modifier = Modifier.fillMaxSize(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.nexters.misik.webview

import com.nexters.misik.domain.ReviewEntity

data class WebViewState(
val isLoading: Boolean = false,
val content: String? = null,
val error: String? = null,
val reviewId: Long = 0,
val review: ReviewEntity? = null,
)
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.nexters.misik.webview

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nexters.misik.domain.ReviewRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

@HiltViewModel
class WebViewViewModel @Inject constructor() : ViewModel() {
class WebViewViewModel @Inject constructor(
private val reviewRepository: ReviewRepository,
) : ViewModel() {
private val _state = MutableStateFlow(WebViewState())
val state: StateFlow<WebViewState> get() = _state

Expand All @@ -16,15 +23,59 @@ class WebViewViewModel @Inject constructor() : ViewModel() {
WebViewEvent.LoadPage -> {
_state.value = _state.value.copy(isLoading = true, error = null)
}

WebViewEvent.PageLoaded -> {
_state.value = _state.value.copy(isLoading = false, error = null)
}

is WebViewEvent.JsResponse -> {
_state.value = _state.value.copy(isLoading = false, content = event.response, error = null)
_state.value =
_state.value.copy(isLoading = false, content = event.response, error = null)
}

is WebViewEvent.JsError -> {
_state.value = _state.value.copy(isLoading = false, error = event.error)
}
}
}

fun generateReview() {
viewModelScope.launch {
reviewRepository.generateReview(
ocrText = "청담커피•앤•토스트 전화번호: 02-554-2458•주소: 서울특별시 강남구 • 테헤란로 313•지하 1층• 2024-07-29 NO: 2-267 •품명 • 단가 수량 카야토스트 음료세트 3,000 admin 금액 6.5 100% • 리얼 토마토 생과일주스 (3500) 소계 1품목 1건 6,500",
hashTags = listOf("특별한 메뉴가 있어요"),
reviewStyle = "CUTE",
)
.onSuccess { data ->
if (data != null) {
_state.update {
it.copy(reviewId = data)
}
Timber.d("generateReview_Success", data.toString())
}
}
.onFailure { exception ->
Timber.d("generateReview_Failure", exception.message)
}
}
}

fun getReview() {
viewModelScope.launch {
reviewRepository.getReview(
id = 674907886775732982,
)
.onSuccess { data ->
if (data != null) {
_state.update {
it.copy(review = data)
}
Timber.d("getReview_Success", " ${data.isSuccess} ${data.review} ${data.id}")
}
}
.onFailure { exception ->
Timber.d("getReview_Failure", exception.message)
}
}
}
}
28 changes: 21 additions & 7 deletions network/src/main/java/com/nexters/misik/network/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,39 @@ object NetworkModule {
return json.asConverterFactory(CONTENT_TYPE.toMediaType())
}

@Logging
@Logger
@Singleton
@Provides
fun provideLoggingInterceptor(): Interceptor =
HttpLoggingInterceptor().setLevel(
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
},

HttpLoggingInterceptor.Level.BODY,

)

@Auth
@Singleton
@Provides
fun provideAuthInterceptor(): Interceptor = Interceptor { chain ->
val originalRequest = chain.request()

// 일단은 모든 요청 100 으로 고정
val newRequest = originalRequest.newBuilder()
.addHeader("device-id", "100")
.build()

chain.proceed(newRequest)
}

@Logging
@Singleton
@Provides
fun provideOkHttpClient(
@Logging loggingInterceptor: Interceptor,
@Logger loggingInterceptor: Interceptor,
@Auth authInterceptor: Interceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build()

@Singleton
Expand Down
11 changes: 11 additions & 0 deletions network/src/main/java/com/nexters/misik/network/Qualifier.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.nexters.misik.network

import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Logger

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.nexters.misik.network.dto

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

@Serializable
data class GenerateReviewRequestDto(
@SerialName("ocrText")
val ocrText: String?,
@SerialName("hashTag")
val hashTag: List<String>?,
@SerialName("reviewStyle")
val reviewStyle: String?,
)
Loading

0 comments on commit 78833c0

Please sign in to comment.