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
Changes from 1 commit
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
Next Next commit
[Feat] 이미지 갤러리에서 불러오기 구현
evergreentree97 committed Sep 30, 2024
commit e6c5692b7167f61f309d0baac7abd42922bd131f
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
@@ -37,7 +37,8 @@ fun FarmemeNavHost(
exitTransition = { ExitTransition.None },
) {
recommendationScreen(
analyticsHelper = analyticsHelper
analyticsHelper = analyticsHelper,
navigateToRegister = navigateToRegister,
)
searchScreen(
analyticsHelper = analyticsHelper,
Original file line number Diff line number Diff line change
@@ -71,6 +71,15 @@ internal class MemeRepositoryImpl @Inject constructor(
override val savedMemeEventFlow: Flow<SavedMemeEvent>
get() = _savedMemeEventFlow

override suspend fun uploadMeme(
memeId: String,
memeImageUri: String,
memeTitle: String,
memeSource: String,
): Boolean {
return false // TODO
}

override suspend fun emitRefreshEvent() {
_savedMemeEventFlow.emit(SavedMemeEvent.Refresh)
}
Original file line number Diff line number Diff line change
@@ -23,6 +23,12 @@ interface MemeRepository {

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

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

interface UpLoadImageUseCase {
suspend operator fun invoke(uri: String)
}

internal class UpLoadImageUseCaseImpl : UpLoadImageUseCase {
override suspend fun invoke(uri: String) {

}
}
Original file line number Diff line number Diff line change
@@ -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
@@ -138,6 +139,10 @@ internal fun RecommendationRoute(
screen = ScreenType.RECOMMENDATION,
)
}

RecommendationSideEffect.NavigateToRegister -> {
navigateToRegister()
}
}
}
}
@@ -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
@@ -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 }
@@ -151,7 +152,7 @@ internal fun RecommendationScreen(
} else {
UploadButton(
onClick = {

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

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

Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -22,6 +22,7 @@ internal fun RegisterRoute(
RegisterScreen(
uiState = uiState,
navigateToBack = navigateToBack,
onIntent = viewModel::intent,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package team.ppac.register

import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -22,13 +25,21 @@ import team.ppac.register.component.RegisterCategoryContent
import team.ppac.register.component.RegisterImageArea
import team.ppac.register.component.RegisterInputArea
import team.ppac.register.component.RegisterKeywordHeader
import team.ppac.register.mvi.RegisterIntent
import team.ppac.register.mvi.RegisterUiState

@Composable
internal fun RegisterScreen(
uiState: RegisterUiState,
navigateToBack: () -> Unit,
onIntent: (RegisterIntent) -> Unit,
) {
val imagePicker =
rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
onIntent(RegisterIntent.SetImageFromGallery(uri.toString()))
}
}
Comment on lines +45 to +50
Copy link
Member

Choose a reason for hiding this comment

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

각 API Level에 해당하는 권한체크는 없어도 되려나?

<!-- Devices running Android 12L (API level 32) or lower  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- To handle the reselection within the app on Android 14 (API level 34) -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
`

Copy link
Collaborator

Choose a reason for hiding this comment

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

https://mundroid.tistory.com/2, https://developer.android.com/training/data-storage/shared/photopicker
따로 권한을 명시하지 않아도 되고, 31버전이하의 경우 문서 선택기로 열린다고 하더라구
커스텀 갤러리가 아니라면 휘뚜루마뚜루 써도 될듯?

FarmemeScaffold(
modifier = Modifier.navigationBarsPadding(),
topBar = {
@@ -48,7 +59,7 @@ internal fun RegisterScreen(
modifier = Modifier.padding(bottom = 36.dp),
text = "등록하기",
enabled = true,
onClick = {},
onClick = { },
)
}
) {
@@ -58,7 +69,8 @@ internal fun RegisterScreen(
) {
item {
RegisterImageArea(
hasImage = false,
loadImage = { imagePicker.launch(PickVisualMediaRequest(mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly)) },
imageUri = uiState.imageUri,
)
}
item { RegisterInputArea() }
@@ -86,5 +98,6 @@ private fun RegisterScreenPreview() {
RegisterScreen(
uiState = RegisterUiState.INITIAL_STATE,
navigateToBack = {},
onIntent = {},
)
}
Original file line number Diff line number Diff line change
@@ -27,8 +27,12 @@ class RegisterViewModel @Inject constructor(
}

override suspend fun handleIntent(intent: RegisterIntent) {
// when (intent) {
//
// }
when (intent) {
is RegisterIntent.SetImageFromGallery -> {
reduce {
copy(imageUri = intent.uri)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -13,22 +13,27 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import team.ppac.designsystem.FarmemeTheme
import team.ppac.designsystem.component.button.FarmemeCircleButton
import team.ppac.designsystem.component.button.FarmemeWeakButton
import team.ppac.designsystem.foundation.FarmemeIcon
import team.ppac.designsystem.foundation.FarmemeRadius
import team.ppac.designsystem.util.extension.noRippleClickable

@Composable
internal fun RegisterImageArea(
modifier: Modifier = Modifier,
hasImage: Boolean,
loadImage: () -> Unit = {},
imageUri: String,
loadImage: () -> Unit,
) {
val borderColor =
if (hasImage) FarmemeTheme.borderColor.primary else FarmemeTheme.borderColor.tertiary
if (imageUri.isNotEmpty()) FarmemeTheme.borderColor.primary else FarmemeTheme.borderColor.tertiary

Box(
modifier = modifier
@@ -41,10 +46,11 @@ internal fun RegisterImageArea(
color = borderColor,
shape = FarmemeRadius.Radius20.shape,
)
.background(FarmemeTheme.backgroundColor.assistive),
.background(FarmemeTheme.backgroundColor.assistive)
.noRippleClickable(onClick = loadImage),
contentAlignment = Alignment.Center,
) {
if (!hasImage) {
if (imageUri.isEmpty()) {
FarmemeWeakButton(
text = "이미지 등록",
withStar = true,
@@ -56,12 +62,20 @@ internal fun RegisterImageArea(
}
)
} else {
// AsyncImage
Box(
modifier = Modifier
.fillMaxSize()
.background(FarmemeTheme.backgroundColor.brandAssistive),
)
AsyncImage(
modifier = Modifier.matchParentSize(),
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
)
FarmemeCircleButton(
modifier = Modifier
.align(Alignment.BottomEnd)
@@ -79,10 +93,12 @@ internal fun RegisterImageArea(
private fun RegisterImageAreaPreview() {
Column {
RegisterImageArea(
hasImage = true,
loadImage = {},
imageUri = "",
)
RegisterImageArea(
hasImage = false,
loadImage = {},
imageUri = "asdf",
)
}
}
Original file line number Diff line number Diff line change
@@ -3,5 +3,5 @@ package team.ppac.register.mvi
import team.ppac.common.android.base.UiIntent

sealed interface RegisterIntent : UiIntent {

data class SetImageFromGallery(val uri: String) : RegisterIntent
}
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@ import team.ppac.register.model.RegisterCategoryUiModel
data class RegisterUiState(
val isLoading: Boolean,
val isError: Boolean,
val registerCategories: ImmutableList<RegisterCategoryUiModel>
val registerCategories: ImmutableList<RegisterCategoryUiModel>,
val imageUri: String,
) : UiState {

companion object {
@@ -59,6 +60,7 @@ data class RegisterUiState(
),
)
),
imageUri = ""
)
}
}