Skip to content

Commit

Permalink
Merge pull request #256 from mash-up-kr/feat/image_upload
Browse files Browse the repository at this point in the history
[Feat] 밈 등록하기 기능
  • Loading branch information
EvergreenTree97 authored Oct 1, 2024
2 parents e395e81 + e73d8b2 commit 93a161b
Show file tree
Hide file tree
Showing 27 changed files with 635 additions and 143 deletions.
3 changes: 2 additions & 1 deletion app/src/main/java/team/ppac/navigation/FarmemeNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ fun FarmemeNavHost(
exitTransition = { ExitTransition.None },
) {
recommendationScreen(
analyticsHelper = analyticsHelper
analyticsHelper = analyticsHelper,
navigateToRegister = navigateToRegister,
)
searchScreen(
analyticsHelper = analyticsHelper,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ internal class MemeRepositoryImpl @Inject constructor(
override val savedMemeEventFlow: Flow<SavedMemeEvent>
get() = _savedMemeEventFlow

override suspend fun uploadMeme(
keywordIds: List<String>,
memeImageUri: String,
memeTitle: String,
memeSource: String
): Boolean {
return memeDataSource.uploadMeme(
keywordIds = keywordIds,
memeTitle = memeTitle,
memeImageUri = memeImageUri,
memeSource = memeSource
)
}

override suspend fun emitRefreshEvent() {
_savedMemeEventFlow.emit(SavedMemeEvent.Refresh)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import team.ppac.domain.usecase.SaveMemeUseCase
import team.ppac.domain.usecase.SaveMemeUseCaseImpl
import team.ppac.domain.usecase.SetLevelUseCase
import team.ppac.domain.usecase.SetLevelUseCaseImpl
import team.ppac.domain.usecase.UploadMemeUseCase
import team.ppac.domain.usecase.UploadMemeUseCaseImpl
import team.ppac.domain.usecase.WatchMemeUseCase
import team.ppac.domain.usecase.WatchMemeUseCaseImpl

Expand Down Expand Up @@ -116,4 +118,8 @@ internal abstract class UseCaseModule {
@Binds
@ViewModelScoped
abstract fun bindEmitRefreshEventUseCase(impl: EmitRefreshEventUseCaseImpl): EmitRefreshEventUseCase

@Binds
@ViewModelScoped
abstract fun bindUploadMemeUseCase(impl: UploadMemeUseCaseImpl): UploadMemeUseCase
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package team.ppac.domain.repository

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import team.ppac.domain.model.Meme
import team.ppac.domain.model.MemeWatchType
Expand All @@ -15,6 +14,7 @@ interface MemeRepository {
keyword: String,
getCurrentPage: (Int) -> Unit
): MemeWithPagination

suspend fun reactMeme(memeId: String): Boolean
suspend fun watchMeme(
memeId: String,
Expand All @@ -23,6 +23,12 @@ interface MemeRepository {

suspend fun emitRefreshEvent()
val savedMemeEventFlow: Flow<SavedMemeEvent>
suspend fun uploadMeme(
keywordIds: List<String>,
memeImageUri: String,
memeTitle: String,
memeSource: String
): Boolean
}

sealed class SavedMemeEvent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package team.ppac.domain.usecase

import team.ppac.domain.repository.MemeRepository
import javax.inject.Inject

interface UploadMemeUseCase {
suspend operator fun invoke(
keywordIds: List<String>,
memeImageUri: String,
memeTitle: String,
memeSource: String
): Boolean
}

internal class UploadMemeUseCaseImpl @Inject constructor(
private val memeRepository: MemeRepository
) : UploadMemeUseCase {
override suspend fun invoke(
keywordIds: List<String>,
memeImageUri: String,
memeTitle: String,
memeSource: String
): Boolean {
return memeRepository.uploadMeme(
keywordIds = keywordIds,
memeTitle = memeTitle,
memeImageUri = memeImageUri,
memeSource = memeSource
)
}
}
14 changes: 14 additions & 0 deletions core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package team.ppac.remote.api

import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import team.ppac.remote.model.response.meme.MemeResponse
import team.ppac.remote.model.response.meme.UploadMemeResponse
import team.ppac.remote.model.response.user.SavedMemesResponse

internal interface MemeApi {
Expand Down Expand Up @@ -39,4 +44,13 @@ internal interface MemeApi {
@Path("memeId") memeId: String,
@Path("type") type: String,
): Boolean

@Multipart
@POST("/api/meme")
suspend fun postMeme(
@Part image: MultipartBody.Part,
@Part("title") title: RequestBody,
@Part("source") source: RequestBody,
@Part("keywordIds[]") keywordIds: ArrayList<RequestBody>,
): UploadMemeResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ interface MemeDataSource {
page: Int,
size: Int,
): SavedMemesResponse

suspend fun reactMeme(memeId: String): Boolean
suspend fun watchMeme(
memeId: String,
type: String,
): Boolean

suspend fun uploadMeme(
keywordIds: List<String>,
memeImageUri: String,
memeTitle: String,
memeSource: String
): Boolean
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
package team.ppac.remote.datasource.impl

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import team.ppac.remote.api.MemeApi
import team.ppac.remote.datasource.MemeDataSource
import team.ppac.remote.model.response.meme.MemeResponse
import team.ppac.remote.model.response.user.SavedMemesResponse
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject


internal class MemeDataSourceImpl @Inject constructor(
private val memeApi: MemeApi,
@ApplicationContext private val context: Context,
) : MemeDataSource {
override suspend fun getMemeById(memeId: String): MemeResponse {
return memeApi.getMemeById(memeId = memeId)
Expand Down Expand Up @@ -36,4 +51,59 @@ internal class MemeDataSourceImpl @Inject constructor(
override suspend fun watchMeme(memeId: String, type: String): Boolean {
return memeApi.watchMeme(memeId, type)
}
}

override suspend fun uploadMeme(
keywordIds: List<String>,
memeImageUri: String,
memeTitle: String,
memeSource: String
): Boolean {
val file =
getFileFromUri(memeImageUri.toUri()) ?: throw IllegalStateException("파일을 찾을 수 없습니다.")
val imageBody = file.asRequestBody("image/*".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData("image", file.name, imageBody)

val titleRequest = memeTitle.toRequestBody("text/plain".toMediaTypeOrNull())
val sourceRequest = memeSource.toRequestBody("text/plain".toMediaTypeOrNull())
val keywordIdRequests =
keywordIds.map { it.toRequestBody("text/plain".toMediaTypeOrNull()) }
memeApi.postMeme(
image = imagePart,
title = titleRequest,
source = sourceRequest,
keywordIds = ArrayList(keywordIdRequests)
)
return true
}

private fun getFileFromUri(uri: Uri): File? {
val contentResolver: ContentResolver = context.contentResolver
val fileName = getFileName(contentResolver, uri) ?: return null // 파일 이름 얻기
val tempFile = File(context.cacheDir, fileName)

try {
val inputStream: InputStream? = contentResolver.openInputStream(uri)
val outputStream: OutputStream = FileOutputStream(tempFile)

inputStream?.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
}
return tempFile
} catch (e: Exception) {
e.printStackTrace()
}
return null
}

private fun getFileName(contentResolver: ContentResolver, uri: Uri): String? {
val cursor = contentResolver.query(uri, null, null, null, null)
return cursor?.use {
if (it.moveToFirst()) {
val index = it.getColumnIndex("_display_name")
it.getString(index)
} else null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package team.ppac.remote.model.response.meme

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class UploadMemeResponse(
@field:Json(name = "deviceId")
val deviceId: String,
@field:Json(name = "title")
val title: String,
@field:Json(name = "keywordIds")
val keywords: List<String>?,
@field:Json(name = "image")
val image: String,
@field:Json(name = "reaction")
val reaction: Int,
@field:Json(name = "source")
val source: String,
@field:Json(name = "isTodayMeme")
val isTodayMeme: Boolean,
@field:Json(name = "isDeleted")
val isDeleted: Boolean,
@field:Json(name = "_id")
val id: String,
@field:Json(name = "createdAt")
val createdAt: String,
@field:Json(name = "updatedAt")
val updatedAt: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import team.ppac.recommendation.mvi.RecommendationSideEffect
@Composable
internal fun RecommendationRoute(
analyticsHelper: AnalyticsHelper,
navigateToRegister: () -> Unit,
viewModel: RecommendationViewModel = hiltViewModel(),
) {
val context = LocalContext.current
Expand Down Expand Up @@ -138,6 +139,10 @@ internal fun RecommendationRoute(
screen = ScreenType.RECOMMENDATION,
)
}

RecommendationSideEffect.NavigateToRegister -> {
navigateToRegister()
}
}
}
}
Expand Down Expand Up @@ -169,6 +174,9 @@ internal fun RecommendationRoute(
memeBitmap[index] = bitmap
},
onActionButtonsIntentClick = viewModel::intent,
onUpload = {
viewModel.intent(RecommendationIntent.ClickUpload)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ internal fun RecommendationScreen(
onRetryClick: () -> Unit,
onLoadMeme: (Int, Bitmap) -> Unit,
onScrollPager: (Int, Meme) -> Unit,
onUpload: () -> Unit,
onActionButtonsIntentClick: (RecommendationIntent.ClickButton) -> Unit,
) {
val heroModulePagerState = rememberPagerState { state.thisWeekMemes.size }
Expand Down Expand Up @@ -151,7 +152,7 @@ internal fun RecommendationScreen(
} else {
UploadButton(
onClick = {

onUpload()
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ class RecommendationViewModel @Inject constructor(
copy(isError = false)
}
}

RecommendationIntent.ClickUpload -> {
postSideEffect(RecommendationSideEffect.NavigateToRegister)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import team.ppac.domain.model.Meme
sealed interface RecommendationIntent : UiIntent {
data object Init : RecommendationIntent
data object PullRefresh : RecommendationIntent
data object ClickUpload : RecommendationIntent

data class MovePage(
val meme: Meme,
val currentPage: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ sealed interface RecommendationSideEffect : UiSideEffect {
data class CopyClipBoard(val selectedMemeIndex: Int) : RecommendationSideEffect
data class ShareLink(val memeId: String) : RecommendationSideEffect
data object LogHashTagsClicked : RecommendationSideEffect
data object NavigateToRegister : RecommendationSideEffect

data class LogSaveMeme(val meme: Meme) : RecommendationSideEffect
data class LogSaveMemeCancel(val meme: Meme) : RecommendationSideEffect
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ fun NavController.navigateToRecommendation(navOptions: NavOptions) = navigate(RE

fun NavGraphBuilder.recommendationScreen(
analyticsHelper: AnalyticsHelper,
navigateToRegister: () -> Unit
) {
composable(
route = RECOMMENDATION_ROUTE
) {
RecommendationRoute(
analyticsHelper = analyticsHelper
analyticsHelper = analyticsHelper,
navigateToRegister = navigateToRegister,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal fun RegisterRoute(
RegisterScreen(
uiState = uiState,
navigateToBack = navigateToBack,
onIntent = viewModel::intent,
)
}
}
Loading

0 comments on commit 93a161b

Please sign in to comment.