Skip to content

Commit

Permalink
Merge pull request #97 from snuhcs-course/feat/android-reaction-API
Browse files Browse the repository at this point in the history
Android - Reaction API Connection
  • Loading branch information
2018JunyoungLim authored Dec 4, 2023
2 parents 6324afa + f45d5c5 commit cfff903
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.goliath.emojihub.data_sources
import com.goliath.emojihub.EmojiHubApplication
import com.goliath.emojihub.data_sources.api.EmojiApi
import com.goliath.emojihub.data_sources.api.PostApi
import com.goliath.emojihub.data_sources.api.ReactionApi
import com.goliath.emojihub.data_sources.api.UserApi
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -59,6 +60,11 @@ object NetworkModule {
fun providesPostRestApi(retrofit: Retrofit): PostApi =
retrofit.create(PostApi::class.java)

@Provides
@Singleton
fun providesReactionRestApi(retrofit: Retrofit): ReactionApi =
retrofit.create(ReactionApi::class.java)

// empty responses should be handled `success`
private val nullOnEmptyConverterFactory = object : Converter.Factory() {
fun converterFactory() = this
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.goliath.emojihub.data_sources.api

import retrofit2.Response
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query

interface ReactionApi {
@POST("reaction")
suspend fun uploadReaction(
@Query("postId") postId: String,
@Query("emojiId") emojiId: String
): Response<Unit>

@GET("reactions")
suspend fun fetchReactionList(
): Response<Unit>

@GET("reaction")
suspend fun getReactionWithId(
@Path("id") id: String
): Response<Unit>

@DELETE("reaction")
suspend fun deleteReaction(
@Query("reactionId") reactionId: String
): Response<Unit>
}
21 changes: 19 additions & 2 deletions android/app/src/main/java/com/goliath/emojihub/models/Reaction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ package com.goliath.emojihub.models

import com.google.gson.annotations.SerializedName

class Reaction(
dto: ReactionDto
) {
val id: String = dto.id
val createdAt: String = dto.createdAt
val createdBy: String = dto.createdBy
val emojiId: String = dto.emojiId
val postId: String = dto.postId
}
data class ReactionMetaDataDto(
@SerializedName("emoji_unicode_list")
val unicodeList: List<String>
)

data class ReactionDto(
@SerializedName("emoji_list")
val emojiList: List<EmojiMetaDataDto>
val id: String,
@SerializedName("created_at") val createdAt: String,
@SerializedName("created_by") val createdBy: String,
@SerializedName("emoji_id") val emojiId: String,
@SerializedName("post_id") val postId: String
)

data class UploadReactionDto(
@SerializedName("postId") val postId: String,
@SerializedName("emojiId") val emojiId: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.goliath.emojihub.repositories.remote.EmojiRepository
import com.goliath.emojihub.repositories.remote.EmojiRepositoryImpl
import com.goliath.emojihub.repositories.remote.PostRepository
import com.goliath.emojihub.repositories.remote.PostRepositoryImpl
import com.goliath.emojihub.repositories.remote.ReactionRepository
import com.goliath.emojihub.repositories.remote.ReactionRepositoryImpl
import com.goliath.emojihub.repositories.remote.UserRepository
import com.goliath.emojihub.repositories.remote.UserRepositoryImpl
import dagger.Binds
Expand All @@ -27,4 +29,7 @@ abstract class RepositoryModule {

@Binds
abstract fun bindsX3dRepository(impl: X3dRepositoryImpl): X3dRepository

@Binds
abstract fun bindsReactionRepository(impl: ReactionRepositoryImpl): ReactionRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.goliath.emojihub.repositories.remote

import androidx.paging.PagingData
import com.goliath.emojihub.data_sources.api.ReactionApi
import com.goliath.emojihub.models.ReactionDto
import kotlinx.coroutines.flow.Flow
import retrofit2.Response
import javax.inject.Inject
import javax.inject.Singleton

interface ReactionRepository {
suspend fun fetchReactionList(): Flow<PagingData<ReactionDto>>
suspend fun uploadReaction(postId: String, emojiId: String): Response<Unit>
suspend fun getReactionWithId(id: String)
suspend fun deleteReaction(reactionId: String)
}

@Singleton
class ReactionRepositoryImpl @Inject constructor(
private val reactionApi: ReactionApi
): ReactionRepository {
override suspend fun fetchReactionList(): Flow<PagingData<ReactionDto>> {
TODO()
}

override suspend fun uploadReaction(postId: String, emojiId: String): Response<Unit> {
return reactionApi.uploadReaction(postId, emojiId)
}

override suspend fun getReactionWithId(id: String) {
TODO()
}

override suspend fun deleteReaction(reactionId: String) {
reactionApi.deleteReaction(reactionId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.goliath.emojihub.usecases

import androidx.paging.PagingData
import androidx.paging.map
import com.goliath.emojihub.data_sources.ApiErrorController
import com.goliath.emojihub.models.Reaction
import com.goliath.emojihub.repositories.remote.ReactionRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

interface ReactionUseCase {

val reactionList: StateFlow<PagingData<Reaction>>
suspend fun fetchReactionList(): Flow<PagingData<Reaction>>
suspend fun updateReactionList(data: PagingData<Reaction>)
suspend fun uploadReaction(postId: String, emojiId: String): Boolean
suspend fun getReactionWithId(id: String)
suspend fun deleteReaction(reactionId: String)
}

@Singleton
class ReactionUseCaseImpl @Inject constructor(
private val repository: ReactionRepository,
private val errorController: ApiErrorController
): ReactionUseCase {

private val _reactionList = MutableStateFlow<PagingData<Reaction>>(PagingData.empty())
override val reactionList: StateFlow<PagingData<Reaction>>
get() = _reactionList

override suspend fun updateReactionList(data: PagingData<Reaction>) {
_reactionList.emit(data)
}

override suspend fun fetchReactionList(): Flow<PagingData<Reaction>> {
return repository.fetchReactionList().map { it.map { dto -> Reaction(dto) } }
}

override suspend fun uploadReaction(postId: String, emojiId: String): Boolean {
val response = repository.uploadReaction(postId, emojiId)
return if (response.isSuccessful) {
true
} else {
errorController.setErrorState(response.code())
false
}
}

override suspend fun getReactionWithId(id: String) {
repository.getReactionWithId(id)
}

override suspend fun deleteReaction(reactionId: String) {
repository.deleteReaction(reactionId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ abstract class UseCaseModule {

@Binds
abstract fun bindsPostUseCase(impl: PostUseCaseImpl): PostUseCase

@Binds
abstract fun bindsReactionUseCase(impl: ReactionUseCaseImpl): ReactionUseCase
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.goliath.emojihub.viewmodels

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
Expand All @@ -15,6 +18,7 @@ class PostViewModel @Inject constructor(

val postList = postUseCase.postList
val myPostList = postUseCase.myPostList
var currentPostId by mutableStateOf("")

suspend fun fetchPostList() {
viewModelScope.launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.goliath.emojihub.viewmodels

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
import com.goliath.emojihub.usecases.ReactionUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class ReactionViewModel @Inject constructor(
private val reactionUseCase: ReactionUseCase
): ViewModel() {

val reactionList = reactionUseCase.reactionList

suspend fun fetchReactionList() {
viewModelScope.launch {
reactionUseCase.fetchReactionList()
.cachedIn(viewModelScope)
.collect {
reactionUseCase.updateReactionList(it)
}
}
}

suspend fun uploadReaction(postId: String, emojiId: String): Boolean {
return reactionUseCase.uploadReaction(postId, emojiId)
}

suspend fun getReactionWithId(id: String) {
reactionUseCase.getReactionWithId(id)
}

suspend fun deleteReaction(reactionId: String) {
reactionUseCase.deleteReaction(reactionId)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.goliath.emojihub.views.components

import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -34,14 +35,15 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.compose.collectAsLazyPagingItems
import com.goliath.emojihub.LocalBottomSheetController
import com.goliath.emojihub.LocalNavController
import com.goliath.emojihub.NavigationDestination
import com.goliath.emojihub.ui.theme.Color.EmojiHubDividerColor
import com.goliath.emojihub.extensions.toEmoji
import com.goliath.emojihub.models.Emoji
import com.goliath.emojihub.ui.theme.Color.LightGray
import kotlinx.coroutines.launch
import com.goliath.emojihub.ui.theme.Color.White
import com.goliath.emojihub.viewmodels.EmojiViewModel
import com.goliath.emojihub.viewmodels.PostViewModel
import com.goliath.emojihub.viewmodels.ReactionViewModel

enum class BottomSheetContent {
VIEW_REACTION, ADD_REACTION, EMPTY
Expand All @@ -58,6 +60,10 @@ fun CustomBottomSheet (
val coroutineScope = rememberCoroutineScope()
val viewModel = hiltViewModel<EmojiViewModel>()

val reactionViewModel = hiltViewModel<ReactionViewModel>()
val postViewModel = hiltViewModel<PostViewModel>()
val navController = LocalNavController.current

var selectedEmojiClass by remember { mutableStateOf<String?>("전체") }
val emojisByClass = emojiList.groupBy { it.unicode }
val emojiClassFilters = listOf("전체") + emojisByClass.keys.toList()
Expand Down Expand Up @@ -186,15 +192,31 @@ fun CustomBottomSheet (
items(myCreatedEmojiList.itemCount) { index ->
myCreatedEmojiList[index]?.let {
EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) {
//TODO: add reaction to post
coroutineScope.launch {
val success = reactionViewModel.uploadReaction(postId = postViewModel.currentPostId, emojiId = it.id)
if (success) {
Log.d("addReaction", "postId = ${postViewModel.currentPostId}, emojiId = ${it.id}")
coroutineScope.launch {
bottomSheetState.hide()
}
}
}
}
}
}
} else {
items(mySavedEmojiList.itemCount) { index ->
mySavedEmojiList[index]?.let {
EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) {
//TODO: add emoji reaction to post
coroutineScope.launch {
val success = reactionViewModel.uploadReaction(postId = postViewModel.currentPostId, emojiId = it.id)
if (success) {
Log.d("addReaction", "postId = ${postViewModel.currentPostId}, emojiId = ${it.id}")
coroutineScope.launch {
bottomSheetState.hide()
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.goliath.emojihub.extensions.reactionsToString
import com.goliath.emojihub.models.Post
import com.goliath.emojihub.ui.theme.Color.EmojiHubDetailLabel
import com.goliath.emojihub.viewmodels.EmojiViewModel
import com.goliath.emojihub.viewmodels.PostViewModel
import kotlinx.coroutines.launch

@Composable
Expand All @@ -36,6 +37,7 @@ fun PostCell(
val bottomSheetState = LocalBottomSheetController.current
val coroutineScope = rememberCoroutineScope()
val emojiViewModel = hiltViewModel<EmojiViewModel>()
val postViewModel = hiltViewModel<PostViewModel>()

Box(
modifier = Modifier
Expand Down Expand Up @@ -101,6 +103,7 @@ fun PostCell(
IconButton(onClick = {
coroutineScope.launch {
emojiViewModel.bottomSheetContent = BottomSheetContent.ADD_REACTION
postViewModel.currentPostId = post.id
bottomSheetState.show()
}
}) {
Expand Down

0 comments on commit cfff903

Please sign in to comment.