Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 밈 등록하기 기능 #256

Merged
merged 7 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -37,7 +37,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
}
}
Comment on lines +79 to +108
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 했을 때 실제 fileName이랑 photopicker uri에서 fileName 얻어왔을 때랑 동일해??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정상 업로드 된것을 봐서는 동일해

}
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