diff --git a/app/src/main/java/com/flint/data/dto/collection/request/CollectionCreateRequestDto.kt b/app/src/main/java/com/flint/data/dto/collection/request/CollectionCreateRequestDto.kt index 63c64bce..a0791eec 100644 --- a/app/src/main/java/com/flint/data/dto/collection/request/CollectionCreateRequestDto.kt +++ b/app/src/main/java/com/flint/data/dto/collection/request/CollectionCreateRequestDto.kt @@ -24,5 +24,7 @@ data class CollectionCreateRequestDto( val isSpoiler: Boolean, @SerialName("reason") val reason: String, + @SerialName("imageUrls") + val imageUrls: List = emptyList(), ) } diff --git a/app/src/main/java/com/flint/domain/mapper/collection/CollectionCreateMapper.kt b/app/src/main/java/com/flint/domain/mapper/collection/CollectionCreateMapper.kt index 4080489c..0c60de21 100644 --- a/app/src/main/java/com/flint/domain/mapper/collection/CollectionCreateMapper.kt +++ b/app/src/main/java/com/flint/domain/mapper/collection/CollectionCreateMapper.kt @@ -20,6 +20,7 @@ private fun CollectionCreateContentModel.toDto(): CollectionCreateRequestDto.Con contentId = contentId, isSpoiler = isSpoiler, reason = reason, + imageUrls = imageUrls, ) fun CollectionCreateResponseDto.toModel(): CollectionCreateModel = diff --git a/app/src/main/java/com/flint/domain/model/collection/CollectionCreateModel.kt b/app/src/main/java/com/flint/domain/model/collection/CollectionCreateModel.kt index aa54cbee..41b38e29 100644 --- a/app/src/main/java/com/flint/domain/model/collection/CollectionCreateModel.kt +++ b/app/src/main/java/com/flint/domain/model/collection/CollectionCreateModel.kt @@ -12,6 +12,7 @@ data class CollectionCreateContentModel( val contentId: String, val isSpoiler: Boolean, val reason: String, + val imageUrls: List = emptyList(), ) data class CollectionCreateModel( diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt b/app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt index 33418f1e..d6bde25d 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt @@ -36,7 +36,7 @@ import com.flint.core.designsystem.theme.FlintTheme import com.flint.domain.model.search.SearchContentItemModel import com.flint.domain.model.search.SearchContentListModel import com.flint.presentation.collectioncreate.component.CollectionCreateContentDeleteModal -import com.flint.presentation.collectioncreate.component.CollectionCreateContentSelect +import com.flint.presentation.collectioncreate.component.AddContentSelectItem import kotlinx.collections.immutable.ImmutableList @Composable @@ -177,7 +177,7 @@ fun AddContentScreen( ) { content -> val isSelected = selectedContents.any { it.id == content.id } - CollectionCreateContentSelect( + AddContentSelectItem( onCheckClick = { if (isSelected){ if (uiState.isCancelModalVisible) { @@ -189,7 +189,7 @@ fun AddContentScreen( } else onToggleContent(content) }, isSelected = isSelected, - imageUrl = content.posterUrl, + posterImageUrl = content.posterUrl, title = content.title, director = content.author, createdYear = content.year, diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt b/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt index caebd23f..5055cd2f 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt @@ -1,6 +1,10 @@ package com.flint.presentation.collectioncreate +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -12,21 +16,25 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.input.ImeAction @@ -36,6 +44,8 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.flint.R import com.flint.core.common.util.UiState +import com.flint.core.designsystem.component.bottomsheet.MenuBottomSheet +import com.flint.core.designsystem.component.bottomsheet.MenuBottomSheetData import com.flint.core.designsystem.component.button.FlintButtonState import com.flint.core.designsystem.component.button.FlintIconButton import com.flint.core.designsystem.component.button.FlintLargeButton @@ -45,9 +55,12 @@ import com.flint.core.designsystem.theme.FlintTheme import com.flint.domain.model.search.SearchContentItemModel import com.flint.domain.model.search.SearchContentListModel import com.flint.presentation.collectioncreate.component.CollectionCreateContentDeleteModal -import com.flint.presentation.collectioncreate.component.CollectionCreateContentItemList +import com.flint.presentation.collectioncreate.component.CollectionCreateContentImage +import com.flint.presentation.collectioncreate.component.CollectionCreateContentReason +import com.flint.presentation.collectioncreate.component.CollectionCreateContentSection import com.flint.presentation.collectioncreate.component.CollectionCreateThumbnail import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList @Composable fun CollectionCreateRoute( @@ -71,43 +84,69 @@ fun CollectionCreateRoute( } } + val galleryLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent(), + ) { uri: Uri? -> + uri ?: return@rememberLauncherForActivityResult + viewModel.updateThumbnailImageUri(uri) + } + + var pendingContentId by rememberSaveable { mutableStateOf(null) } + val contentImageLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent(), + ) { uri: Uri? -> + uri ?: return@rememberLauncherForActivityResult + pendingContentId?.let { contentId -> viewModel.addContentImageUri(contentId, uri) } + pendingContentId = null + } + CollectionCreateScreen( uiState = uiState, onTitleChanged = viewModel::updateTitle, onDescriptionChanged = viewModel::updateDescription, onPublicChanged = viewModel::updateIsPublic, - selectedContents = uiState.selectedContents, - contentDetailsMap = uiState.contentDetailsMap, onRemoveContent = viewModel::removeContent, onBackClick = navigateUp, onSpoilerChanged = viewModel::updateSpoiler, onReasonChanged = viewModel::updateReason, onAddContentClick = navigateToAddContent, - onFinishClick = { - viewModel.onClickFinish() + onFinishClick = viewModel::onClickFinish, + onGalleryClick = { galleryLauncher.launch("image/*") }, + onThumbnailDelete = { viewModel.updateThumbnailImageUri(null) }, + onSelectContentImage = { contentId -> + val currentCount = uiState.contentDetailsMap[contentId]?.contentImageUris?.size ?: 0 + if (currentCount < 5) { + pendingContentId = contentId + contentImageLauncher.launch("image/*") + } }, + onRemoveContentImage = viewModel::removeContentImageUri, modifier = Modifier.padding(paddingValues), ) } +@OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class) @Composable fun CollectionCreateScreen( uiState: CollectionCreateUiState, onTitleChanged: (String) -> Unit = {}, onDescriptionChanged: (String) -> Unit = {}, onPublicChanged: (Boolean?) -> Unit = {}, - selectedContents: ImmutableList, - contentDetailsMap: Map, onRemoveContent: (SearchContentItemModel) -> Unit, onBackClick: () -> Unit, onSpoilerChanged: (String, Boolean) -> Unit = { _, _ -> }, onReasonChanged: (String, String) -> Unit = { _, _ -> }, onAddContentClick: () -> Unit, onFinishClick: () -> Unit, + onGalleryClick: () -> Unit = {}, + onThumbnailDelete: () -> Unit = {}, + onSelectContentImage: (contentId: String) -> Unit = {}, + onRemoveContentImage: (contentId: String, index: Int) -> Unit = { _, _ -> }, modifier: Modifier = Modifier, ) { var isModalVisible by remember { mutableStateOf(false) } var contentToDelete by remember { mutableStateOf(null) } + var isThumbnailBottomSheetVisible by remember { mutableStateOf(false) } Column( modifier = @@ -119,199 +158,76 @@ fun CollectionCreateScreen( LazyColumn( modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(28.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { // 썸네일 item { CollectionCreateThumbnail( - imageUrl = "", - onClick = { }, + imageUrl = uiState.thumbnailImageUri, + onClick = { isThumbnailBottomSheetVisible = true }, ) - Spacer(Modifier.height(8.dp)) + Spacer(Modifier.height(20.dp)) } // 컬렉션 제목 item { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Text( - text = "컬렉션 제목", - color = FlintTheme.colors.white, - style = FlintTheme.typography.head3M18, - ) - - Spacer(Modifier.height(16.dp)) - - CollectionInputTextField( - modifier = Modifier.fillMaxWidth(), - value = uiState.title, - placeholder = "컬렉션의 제목을 입력해주세요.", - onValueChanged = onTitleChanged, - maxLength = 20, - singleLine = true, - maxLines = 1, - isShowLengthTitle = true, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next - ) - ) - } + CollectionTitle( + title = uiState.title, + onTitleChanged = onTitleChanged, + modifier = Modifier.padding(horizontal = (16).dp), + ) } // 컬렉션 소개 item { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Text( - text = - buildAnnotatedString { - append("컬렉션 소개 ") - withStyle( - style = SpanStyle(color = FlintTheme.colors.gray300), - ) { - append("(선택)") - } - }, - color = FlintTheme.colors.white, - style = FlintTheme.typography.head3M18, - ) - - Spacer(Modifier.height(16.dp)) - - CollectionInputTextField( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 104.dp), - value = uiState.description, - placeholder = "컬렉션의 소개를 작성해주세요.", - onValueChanged = onDescriptionChanged, - maxLength = 200, - singleLine = false, - maxLines = Int.MAX_VALUE, - isShowLengthTitle = true, - keyboardActions = KeyboardActions( - onDone = {}, - ), - ) - } + CollectionDescription( + description = uiState.description, + onDescriptionChanged = onDescriptionChanged, + modifier = Modifier.padding(horizontal = (16).dp), + ) } // 컬렉션 공개 여부 item { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Text( - text = "컬렉션 공개 여부", - color = FlintTheme.colors.white, - style = FlintTheme.typography.head3M18, - ) - - Spacer(Modifier.height(16.dp)) - - Row { - FlintIconButton( - text = "공개", - iconRes = R.drawable.ic_share, - state = when(uiState.isPublic){ - true -> FlintButtonState.ColorOutline - false -> FlintButtonState.Disable - else -> FlintButtonState.Outline - }, - - onClick = { onPublicChanged(true) }, - modifier = Modifier.weight(1f), - ) + CollectionPublicSection( + isPublic = uiState.isPublic, + onPublicChanged = onPublicChanged, + modifier = Modifier.padding(horizontal = (16).dp), + ) - Spacer(Modifier.width(8.dp)) - - FlintIconButton( - text = "비공개", - iconRes = R.drawable.ic_lock, - state = when(uiState.isPublic){ - true -> FlintButtonState.Disable - false -> FlintButtonState.ColorOutline - else -> FlintButtonState.Outline - }, - onClick = { onPublicChanged(false) }, - modifier = Modifier.weight(1f), - contentPadding = PaddingValues(start = 8.dp, end = 12.dp, top= 10.dp, bottom = 10.dp) - ) - } - } + Spacer(Modifier.height(20.dp)) } - // 작품 추가 헤더 + // 작품 추가 섹션 item { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Text( - text = "작품 추가", - color = FlintTheme.colors.white, - style = FlintTheme.typography.head3M18, - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "작품을 2개 이상 추가해주세요.", - color = FlintTheme.colors.gray200, - style = FlintTheme.typography.body2R14, - ) - Text( - text = "${selectedContents.size}/10", - color = FlintTheme.colors.white, - style = FlintTheme.typography.body2R14, - ) - } - } - } + CollectionAddContentSection( + selectedContents = uiState.selectedContents, + contentDetailsMap = uiState.contentDetailsMap, + onDeleteRequest = { content -> + contentToDelete = content + isModalVisible = true + }, + onSpoilerChanged = onSpoilerChanged, + onReasonChanged = onReasonChanged, + onAddContentClick = onAddContentClick, + onSelectContentImage = onSelectContentImage, + onRemoveContentImage = onRemoveContentImage, + modifier = Modifier.padding(horizontal = (16).dp), + ) - // 작품 리스트 - items( - items = selectedContents, - key = { it.id }, - ) { content -> - val detail = contentDetailsMap[content.id] ?: ContentDetail() - - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - CollectionCreateContentItemList( - onCancelClick = { - contentToDelete = content - isModalVisible = true - }, - imageUrl = content.posterUrl, - title = content.title, - director = content.author, - createdYear = content.year, - isSpoiler = detail.isSpoiler, - onSpoilerChanged = { isSpoiler -> - onSpoilerChanged(content.id, isSpoiler) - }, - selectedReason = detail.reason, - onSelectedReasonChanged = { reason -> - onReasonChanged(content.id, reason) - }, - ) - } + Spacer(Modifier.height(20.dp)) } - // 작품 추가 버튼 item { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - FlintIconButton( - text = "작품 추가하기", - iconRes = R.drawable.ic_plus, - state = FlintButtonState.ColorOutline, - onClick = onAddContentClick, - modifier = - Modifier - .fillMaxWidth() - .defaultMinSize(minHeight = 80.dp), - contentPadding = PaddingValues(vertical = 28.dp) - ) + Text( + text = "Flint에서 제공하는 영화 · 드라마를 포함한 모든 콘텐츠의 저작권은 각 권리자에게 있으며, 관련 법령에 따라 보호됩니다. \n컬렉션 이용 시 저작권을 준수해 주세요.", + color = FlintTheme.colors.gray300, + style = FlintTheme.typography.caption1R12, + modifier = Modifier.padding(horizontal = 16.dp) + ) - Spacer(Modifier.height(36.dp)) - } + Spacer(Modifier.height(12.dp)) } } @@ -343,23 +259,263 @@ fun CollectionCreateScreen( }, ) } + + if (isThumbnailBottomSheetVisible) { + val menuBottomSheetDataList = + listOf( + MenuBottomSheetData( + label = "갤러리에서 선택", + clickAction = onGalleryClick, + ), + MenuBottomSheetData( + label = "커버 사진 삭제", + color = FlintTheme.colors.error500, + clickAction = onThumbnailDelete, + ), + ) + + val sheetState = rememberModalBottomSheetState() + + MenuBottomSheet( + menuBottomSheetDataList = menuBottomSheetDataList, + onDismiss = { isThumbnailBottomSheetVisible = false }, + sheetState = sheetState, + ) + } +} + +@Composable +private fun CollectionTitle( + title: String, + onTitleChanged: (String) -> Unit, + modifier: Modifier = Modifier, +){ + Column(modifier = modifier) { + Text( + text = "컬렉션 제목", + color = FlintTheme.colors.white, + style = FlintTheme.typography.head3M18, + ) + + Spacer(Modifier.height(16.dp)) + + CollectionInputTextField( + modifier = Modifier.fillMaxWidth(), + value = title, + placeholder = "컬렉션의 제목을 입력해주세요.", + onValueChanged = onTitleChanged, + maxLength = 20, + singleLine = true, + maxLines = 1, + isShowLengthTitle = true, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next + ) + ) + } } -@Preview @Composable -fun CollectionCreateScreenPreview() { +private fun CollectionDescription( + description: String, + onDescriptionChanged: (String) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Text( + text = + buildAnnotatedString { + append("컬렉션 소개 ") + withStyle( + style = SpanStyle(color = FlintTheme.colors.gray300), + ) { + append("(선택)") + } + }, + color = FlintTheme.colors.white, + style = FlintTheme.typography.head3M18, + ) + + Spacer(Modifier.height(16.dp)) + + CollectionInputTextField( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 104.dp), + value = description, + placeholder = "컬렉션의 소개를 작성해주세요.", + onValueChanged = onDescriptionChanged, + maxLength = 45, + singleLine = false, + maxLines = Int.MAX_VALUE, + isShowLengthTitle = true, + ) + } +} + +@Composable +private fun CollectionPublicSection( + isPublic: Boolean?, + onPublicChanged: (Boolean?) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Text( + text = "컬렉션 공개 여부", + color = FlintTheme.colors.white, + style = FlintTheme.typography.head3M18, + ) + + Spacer(Modifier.height(16.dp)) + + Row { + FlintIconButton( + text = "공개", + iconRes = R.drawable.ic_share, + state = when (isPublic) { + true -> FlintButtonState.ColorOutline + false -> FlintButtonState.Disable + else -> FlintButtonState.Outline + }, + onClick = { onPublicChanged(true) }, + modifier = Modifier.weight(1f), + ) + + Spacer(Modifier.width(8.dp)) + + FlintIconButton( + text = "비공개", + iconRes = R.drawable.ic_lock, + state = when (isPublic) { + true -> FlintButtonState.Disable + false -> FlintButtonState.ColorOutline + else -> FlintButtonState.Outline + }, + onClick = { onPublicChanged(false) }, + modifier = Modifier.weight(1f), + contentPadding = PaddingValues(start = 8.dp, end = 12.dp, top = 10.dp, bottom = 10.dp) + ) + } + } +} + +@Composable +private fun CollectionAddContentSection( + selectedContents: ImmutableList, + contentDetailsMap: Map, + onDeleteRequest: (SearchContentItemModel) -> Unit, + onSpoilerChanged: (String, Boolean) -> Unit, + onReasonChanged: (String, String) -> Unit, + onAddContentClick: () -> Unit, + onSelectContentImage: (contentId: String) -> Unit, + onRemoveContentImage: (contentId: String, index: Int) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Text( + text = "작품 추가", + color = FlintTheme.colors.white, + style = FlintTheme.typography.head3M18, + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "작품을 2개 이상 추가해주세요.", + color = FlintTheme.colors.gray200, + style = FlintTheme.typography.body2R14, + ) + Text( + text = "${selectedContents.size}/10", + color = FlintTheme.colors.white, + style = FlintTheme.typography.body2R14, + ) + } + + selectedContents.forEach { content -> + val detail = contentDetailsMap[content.id] ?: ContentDetail() + + Spacer(Modifier.height(28.dp)) + + Icon( + painter = painterResource(R.drawable.ic_deselect_large_pri), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .align(Alignment.End) + .clickable(onClick = { onDeleteRequest(content) }) + .padding(vertical = 10.dp) + .size(28.dp), + ) + + CollectionCreateContentSection( + posterImageUrl = content.posterUrl, + title = content.title, + director = content.author, + createdYear = content.year, + ) + + Spacer(Modifier.height(16.dp)) + + if (detail.contentImageUris.isNotEmpty()) { + CollectionCreateContentImage( + imageUris = detail.contentImageUris, + onDeleteClick = { index -> onRemoveContentImage(content.id, index) }, + ) + + Spacer(Modifier.height(16.dp)) + } + + CollectionCreateContentReason( + selectedReason = detail.reason, + onSelectedReasonChanged = { reason -> onReasonChanged(content.id, reason) }, + onSelectImageClick = { onSelectContentImage(content.id) }, + isSpoiler = detail.isSpoiler, + onSpoilerChanged = { isSpoiler -> onSpoilerChanged(content.id, isSpoiler) }, + ) + } + + Spacer(Modifier.height(28.dp)) + + FlintIconButton( + text = "작품 추가하기", + iconRes = R.drawable.ic_plus, + state = FlintButtonState.ColorOutline, + onClick = onAddContentClick, + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 80.dp), + contentPadding = PaddingValues(vertical = 28.dp) + ) + } +} + +@Preview() +@Composable +private fun CollectionCreateScreenPreview() { + val fakeContents = SearchContentListModel.FakeList.take(2).toImmutableList() + FlintTheme { CollectionCreateScreen( - uiState = CollectionCreateUiState(), - onTitleChanged = {}, - onDescriptionChanged = {}, - onPublicChanged = {}, - selectedContents = SearchContentListModel.FakeList, - contentDetailsMap = emptyMap(), + uiState = CollectionCreateUiState( + title = "내 컬렉션", + description = "컬렉션 소개입니다.", + isPublic = true, + selectedContents = fakeContents, + contentDetailsMap = fakeContents.associate { + it.id to ContentDetail( + contentImageUris = listOf( + Uri.parse("https://example.com/1"), + Uri.parse("https://example.com/2"), + ) + ) + }, + ), onRemoveContent = {}, onBackClick = {}, - onSpoilerChanged = { _, _ -> }, - onReasonChanged = { _, _ -> }, onAddContentClick = {}, onFinishClick = {}, ) diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateViewModel.kt b/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateViewModel.kt index ea922fa5..df4a9c5b 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateViewModel.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateViewModel.kt @@ -1,18 +1,21 @@ package com.flint.presentation.collectioncreate +import android.content.Context +import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.flint.core.common.util.UiState -import com.flint.data.dto.collection.request.CollectionCreateRequestDto import com.flint.domain.mapper.collection.toDto import com.flint.domain.model.collection.CollectionCreateContentModel import com.flint.domain.model.collection.CollectionCreateRequestModel import com.flint.domain.model.search.SearchContentItemModel -import com.flint.domain.model.search.SearchContentListModel import com.flint.domain.repository.CollectionRepository import com.flint.domain.repository.SearchRepository -import com.kakao.sdk.common.model.Description +import com.flint.domain.repository.StorageRepository +import com.flint.domain.type.FileExtension +import com.flint.domain.type.StoragePathType import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -20,19 +23,23 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject @HiltViewModel class CollectionCreateViewModel @Inject constructor( + @ApplicationContext private val context: Context, private val collectionRepository: CollectionRepository, - private val searchRepository: SearchRepository -) - : ViewModel() { + private val searchRepository: SearchRepository, + private val storageRepository: StorageRepository, +) : ViewModel() { private val _uiState = MutableStateFlow(CollectionCreateUiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -48,33 +55,50 @@ class CollectionCreateViewModel @Inject constructor( } fun onClickFinish() { + if (_uiState.value.isLoading) return postCollectionCreate() } private fun postCollectionCreate() { viewModelScope.launch { - val requestModel = CollectionCreateRequestModel( - imageUrl = "", - title = uiState.value.title, - description = uiState.value.description.ifBlank { "" }, - isPublic = uiState.value.isPublic ?: true, - contentList = uiState.value.selectedContents.map { content -> - val detail = uiState.value.contentDetailsMap[content.id] ?: ContentDetail() - CollectionCreateContentModel( - contentId = content.id, - isSpoiler = detail.isSpoiler, - reason = detail.reason.ifBlank { "" }, - ) - }, - ) + _uiState.update { it.copy(isLoading = true) } + try { + val thumbnailKey = uploadImageIfNeeded(_uiState.value.thumbnailImageUri, StoragePathType.COLLECTION_THUMBNAIL) + .getOrElse { + _createSuccess.emit(UiState.Failure) + return@launch + } + val contentImageKeysMap = uploadContentImagesIfNeeded() + .getOrElse { + _createSuccess.emit(UiState.Failure) + return@launch + } + val requestModel = CollectionCreateRequestModel( + imageUrl = thumbnailKey ?: "", + title = uiState.value.title, + description = uiState.value.description.ifBlank { "" }, + isPublic = uiState.value.isPublic ?: true, + contentList = uiState.value.selectedContents.map { content -> + val detail = uiState.value.contentDetailsMap[content.id] ?: ContentDetail() + CollectionCreateContentModel( + contentId = content.id, + isSpoiler = detail.isSpoiler, + reason = detail.reason.ifBlank { "" }, + imageUrls = contentImageKeysMap[content.id] ?: emptyList(), + ) + }, + ) - collectionRepository - .postCollectionCreate(requestModel.toDto()) - .onSuccess { - println("컬렉션 생성 성공") - _createSuccess.emit(UiState.Success(it.collectionId)) - } - .onFailure { e -> println("컬렉션 생성 실패: ${e.message}") } + collectionRepository + .postCollectionCreate(requestModel.toDto()) + .onSuccess { + println("컬렉션 생성 성공") + _createSuccess.emit(UiState.Success(it.collectionId)) + } + .onFailure { e -> println("컬렉션 생성 실패: ${e.message}") } + } finally { + _uiState.update { it.copy(isLoading = false) } + } } } @@ -198,4 +222,89 @@ class CollectionCreateViewModel @Inject constructor( } } } + + fun updateThumbnailImageUri(uri: Uri?) { + _uiState.update { it.copy(thumbnailImageUri = uri) } + } + + fun addContentImageUri(contentId: String, uri: Uri) { + _uiState.update { state -> + val current = state.contentDetailsMap[contentId] ?: ContentDetail() + if (current.contentImageUris.size >= 5) return@update state + val updated = current.copy(contentImageUris = current.contentImageUris + uri) + state.copy(contentDetailsMap = state.contentDetailsMap + (contentId to updated)) + } + } + + fun removeContentImageUri(contentId: String, index: Int) { + _uiState.update { state -> + val current = state.contentDetailsMap[contentId] ?: return@update state + if (index !in current.contentImageUris.indices) return@update state + val updated = current.copy( + contentImageUris = current.contentImageUris.toMutableList().also { it.removeAt(index) } + ) + state.copy(contentDetailsMap = state.contentDetailsMap + (contentId to updated)) + } + } + + private suspend fun uploadContentImagesIfNeeded(): Result>> { + val result = mutableMapOf>() + for ((contentId, detail) in _uiState.value.contentDetailsMap) { + val keys = mutableListOf() + for (uri in detail.contentImageUris) { + val key = uploadImageIfNeeded(uri, StoragePathType.COLLECTION_CONTENT) + .getOrElse { return Result.failure(it) } + if (key != null) keys.add(key) + } + if (keys.isNotEmpty()) result[contentId] = keys + } + return Result.success(result) + } + + private suspend fun uploadImageIfNeeded(uri: Uri?, pathType: StoragePathType): Result { + uri ?: return Result.success(null) + + val mimeType = runCatching { + withContext(Dispatchers.IO) { context.contentResolver.getType(uri) } + }.getOrElse { error -> + Timber.e(error, "Failed to resolve mimeType") + return Result.failure(error) + } ?: "image/jpeg" + val extension = mimeTypeToFileExtension(mimeType) + + val presignedUrl = storageRepository.getPresignedUrl( + pathType = pathType, + extension = extension, + ).getOrElse { error -> + Timber.e(error, "Failed to get presigned URL") + return Result.failure(error) + } + + val imageBytes = runCatching { + withContext(Dispatchers.IO) { + context.contentResolver.openInputStream(uri)?.use { it.readBytes() } + } + }.getOrElse { error -> + Timber.e(error, "Failed to read image bytes") + return Result.failure(error) + } ?: return Result.failure(IllegalStateException("Failed to open image stream: $uri")) + + storageRepository.uploadToS3( + uploadUrl = presignedUrl.uploadUrl, + imageBytes = imageBytes, + mimeType = mimeType, + ).getOrElse { error -> + Timber.e(error, "Failed to upload image to S3") + return Result.failure(error) + } + + return Result.success(presignedUrl.key) + } + + private fun mimeTypeToFileExtension(mimeType: String): FileExtension = when (mimeType) { + "image/png" -> FileExtension.PNG + "image/gif" -> FileExtension.GIF + "image/webp" -> FileExtension.WEBP + else -> FileExtension.JPEG + } } diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSelect.kt b/app/src/main/java/com/flint/presentation/collectioncreate/component/AddContentSelectItem.kt similarity index 86% rename from app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSelect.kt rename to app/src/main/java/com/flint/presentation/collectioncreate/component/AddContentSelectItem.kt index 44f97b5e..1ab791c3 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSelect.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/component/AddContentSelectItem.kt @@ -22,31 +22,30 @@ import com.flint.R import com.flint.core.designsystem.theme.FlintTheme @Composable -fun CollectionCreateContentSelect( +fun AddContentSelectItem( onCheckClick: () -> Unit, isSelected: Boolean, - imageUrl: String, + posterImageUrl: String, title: String, director: String, createdYear: Int, modifier: Modifier = Modifier, ) { Row( - modifier = - modifier + modifier = modifier .fillMaxWidth() .background(color = FlintTheme.colors.background), verticalAlignment = Alignment.CenterVertically, ) { CollectionCreateContentSection( - imageUrl = imageUrl, + posterImageUrl = posterImageUrl, title = title, director = director, createdYear = createdYear, modifier = Modifier.weight(1f), ) - CollectionCreateContentSelectTag( + AddContentSelectItemTag( isSelected = isSelected, onClick = onCheckClick, ) @@ -54,7 +53,7 @@ fun CollectionCreateContentSelect( } @Composable -fun CollectionCreateContentSelectTag( +fun AddContentSelectItemTag( isSelected: Boolean, onClick: () -> Unit, ) { @@ -71,13 +70,13 @@ fun CollectionCreateContentSelectTag( @Preview @Composable -private fun CollectionCreateContentSectionPreview() { +private fun AddContentSelectItemPreview() { FlintTheme { var isSelected by remember { mutableStateOf(false) } - CollectionCreateContentSelect( + AddContentSelectItem( onCheckClick = { isSelected = !isSelected }, isSelected = isSelected, - imageUrl = "https://buly.kr/DEaVFRZ", + posterImageUrl = "https://buly.kr/DEaVFRZ", title = "해리포터 불의 잔", director = "메롱", createdYear = 2005, diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentImage.kt b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentImage.kt new file mode 100644 index 00000000..127b0647 --- /dev/null +++ b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentImage.kt @@ -0,0 +1,148 @@ +package com.flint.presentation.collectioncreate.component + +import android.net.Uri +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.flint.R +import com.flint.core.designsystem.component.image.NetworkImage +import com.flint.core.designsystem.theme.FlintTheme + +@Composable +fun CollectionCreateContentImage( + imageUris: List, + onDeleteClick: (index: Int) -> Unit, + modifier: Modifier = Modifier, +) { + if (imageUris.isEmpty()) return + + val pageCount = Int.MAX_VALUE + val pagerState = rememberPagerState( + initialPage = pageCount / 2 - (pageCount / 2) % imageUris.size, + ) { pageCount } + val currentIndex = pagerState.currentPage % imageUris.size + + var prevSize by remember { mutableIntStateOf(-1) } + var deletedIndex by remember { mutableIntStateOf(-1) } + + LaunchedEffect(imageUris.size) { + if (prevSize == -1) { + prevSize = imageUris.size + return@LaunchedEffect + } + if (imageUris.isEmpty()) { + prevSize = 0 + return@LaunchedEffect + } + val isAdded = imageUris.size > prevSize + val targetIndex = if (isAdded) { + imageUris.size - 1 + } else { + minOf(deletedIndex, imageUris.size - 1) + } + prevSize = imageUris.size + // 새로운 size 기준으로 targetIndex에 해당하는 가장 가까운 절대 페이지 계산 + val base = (pagerState.currentPage / imageUris.size) * imageUris.size + val targetPage = base + targetIndex + if (isAdded) { + pagerState.animateScrollToPage(targetPage) + } else { + pagerState.scrollToPage(targetPage) + } + } + + Column(modifier = modifier) { + HorizontalPager( + state = pagerState, + userScrollEnabled = imageUris.size > 1, + modifier = Modifier.fillMaxWidth(), + ) { page -> + val index = page % imageUris.size + Box { + NetworkImage( + imageUrl = imageUris[index], + contentScale = ContentScale.FillBounds, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(4f / 3f), + ) + Icon( + painter = painterResource(R.drawable.ic_deselect_large_gray), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .align(Alignment.TopEnd) + .clickable { + deletedIndex = index + onDeleteClick(index) + } + .padding(all = 16.dp) + .size(24.dp), + ) + } + } + + if (imageUris.size > 1) { + Spacer(Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), + ) { + repeat(imageUris.size) { index -> + Box( + modifier = Modifier + .size(8.dp) + .clip(CircleShape) + .background( + if (index == currentIndex) FlintTheme.colors.secondary400 + else FlintTheme.colors.gray500 + ), + ) + } + } + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFF121212) +@Composable +private fun CollectionCreateContentImagePreview() { + FlintTheme { + CollectionCreateContentImage( + imageUris = listOf( + Uri.parse("https://example.com/1"), + Uri.parse("https://example.com/2"), + Uri.parse("https://example.com/3"), + ), + onDeleteClick = {}, + ) + } +} diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentItemList.kt b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentReason.kt similarity index 53% rename from app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentItemList.kt rename to app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentReason.kt index a2b275a6..5fc3a4d7 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentItemList.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentReason.kt @@ -1,27 +1,31 @@ package com.flint.presentation.collectioncreate.component import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text 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.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.flint.R @@ -30,58 +34,57 @@ import com.flint.core.designsystem.component.toggle.FlintBasicToggle import com.flint.core.designsystem.theme.FlintTheme @Composable -fun CollectionCreateContentItemList( - onCancelClick: () -> Unit, - imageUrl: String, - title: String, - director: String, - createdYear: Int, - isSpoiler: Boolean, - onSpoilerChanged: (Boolean) -> Unit = {}, +fun CollectionCreateContentReason( selectedReason: String, - onSelectedReasonChanged: (String) -> Unit = {}, + onSelectedReasonChanged: (String) -> Unit, + onSelectImageClick: () -> Unit, + isSpoiler: Boolean, + onSpoilerChanged: (Boolean) -> Unit, + modifier: Modifier = Modifier, ) { - Column( - modifier = Modifier.background(color = FlintTheme.colors.background), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_cancel), - contentDescription = null, - tint = Color.White, - modifier = - Modifier - .clickable(onClick = onCancelClick) - .padding(12.dp) - .size(24.dp), - ) - } + Column(modifier = modifier) { + Text( + text = "이 작품을 선택한 이유", + color = FlintTheme.colors.white, + style = FlintTheme.typography.head3M18, + ) Spacer(Modifier.height(16.dp)) - CollectionCreateContentSection( - imageUrl = imageUrl, - title = title, - director = director, - createdYear = createdYear, + CollectionInputTextField( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 104.dp), + value = selectedReason, + placeholder = "이 작품의 매력 포인트를 적어주세요.", + onValueChanged = onSelectedReasonChanged, + singleLine = false, + maxLength = Int.MAX_VALUE, + maxLines = Int.MAX_VALUE, + isShowLengthTitle = false ) - Spacer(Modifier.height(16.dp)) - Row( modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "이 작품을 선택한 이유", - color = FlintTheme.colors.white, - style = FlintTheme.typography.head3M18, - ) - + Box( + modifier = Modifier + .size(width = 48.dp, height = 28.dp) + .border(1.dp, Color(0xFF1ABFF2), RoundedCornerShape(60.dp)) + .clip(RoundedCornerShape(60.dp)) + .background(Color(0xFF21242C)) + .clickable(onClick = onSelectImageClick), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(R.drawable.ic_background_photo), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.size(14.dp), + ) + } Row( verticalAlignment = Alignment.CenterVertically, ) { @@ -99,38 +102,22 @@ fun CollectionCreateContentItemList( ) } } - - Spacer(Modifier.height(4.dp)) - - CollectionInputTextField( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 104.dp), - value = selectedReason, - placeholder = "이 작품의 매력 포인트를 적어주세요.", - onValueChanged = onSelectedReasonChanged, - singleLine = false, - maxLength = Int.MAX_VALUE, - maxLines = Int.MAX_VALUE, - isShowLengthTitle = false - ) } } @Preview(showBackground = true, backgroundColor = 0xFF121212) @Composable -private fun CollectionCreateContentItemListPreview() { +private fun CollectionCreateContentReasonPreview() { FlintTheme { - CollectionCreateContentItemList( - onCancelClick = {}, - imageUrl = "https://buly.kr/DEaVFRZ", - title = "해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔", - director = "메롱", - createdYear = 2005, - isSpoiler = false, - selectedReason = "더미 이유", - onSpoilerChanged = {}, - onSelectedReasonChanged = {}, + var reason by remember { mutableStateOf("") } + var isSpoiler by remember { mutableStateOf(false) } + CollectionCreateContentReason( + selectedReason = reason, + onSelectedReasonChanged = { reason = it }, + onSelectImageClick = {}, + isSpoiler = isSpoiler, + onSpoilerChanged = { isSpoiler = it }, ) } } + diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSection.kt b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSection.kt index 93c4b49f..f39ad61a 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSection.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSection.kt @@ -1,24 +1,31 @@ package com.flint.presentation.collectioncreate.component +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.flint.R import com.flint.core.designsystem.component.image.NetworkImage import com.flint.core.designsystem.theme.FlintTheme @Composable fun CollectionCreateContentSection( - imageUrl: String, + posterImageUrl: String, title: String, director: String, createdYear: Int, @@ -29,7 +36,7 @@ fun CollectionCreateContentSection( verticalAlignment = Alignment.CenterVertically, ) { CollectionCreateContentSectionImage( - imageUrl = imageUrl, + imageUrl = posterImageUrl, modifier = Modifier .height(150.dp) @@ -101,7 +108,7 @@ private fun CollectionCreateContentSectionImage( private fun CollectionCreateContentSectionPreview() { FlintTheme { CollectionCreateContentSection( - imageUrl = "https://buly.kr/DEaVFRZ", + posterImageUrl = "https://buly.kr/DEaVFRZ", title = "해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔 해리포터 불의 잔", director = "메롱", createdYear = 2005, diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt index 1991176c..0a6959c0 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateThumbnail.kt @@ -24,11 +24,11 @@ import com.flint.core.designsystem.theme.FlintTheme @Composable fun CollectionCreateThumbnail( - imageUrl: String?, + imageUrl: Any?, onClick: () -> Unit, modifier: Modifier = Modifier, ) { - if (imageUrl.isNullOrBlank()) { + if (imageUrl == null || (imageUrl is String && imageUrl.isBlank())) { CollectionCreateEmptyThumbnail( onClick = onClick, modifier = modifier, @@ -63,17 +63,17 @@ private fun CollectionCreateEmptyThumbnail( contentDescription = null, ) -// Icon( -// painter = painterResource(R.drawable.ic_background_photo), -// contentDescription = null, -// tint = Color.Unspecified, -// ) + Icon( + painter = painterResource(R.drawable.ic_background_photo), + contentDescription = null, + tint = Color.Unspecified, + ) } } @Composable private fun CollectionCreateFillThumbnail( - imageUrl: String, + imageUrl: Any, onClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -97,11 +97,11 @@ private fun CollectionCreateFillThumbnail( .background(FlintTheme.colors.imgBlur), ) -// Icon( -// painter = painterResource(R.drawable.ic_background_photo), -// contentDescription = null, -// tint = Color.Unspecified, -// ) + Icon( + painter = painterResource(R.drawable.ic_background_photo), + contentDescription = null, + tint = Color.Unspecified, + ) } } diff --git a/app/src/main/java/com/flint/presentation/collectioncreate/uistate/CollectionCreateUiState.kt b/app/src/main/java/com/flint/presentation/collectioncreate/uistate/CollectionCreateUiState.kt index ee09df82..de80c34b 100644 --- a/app/src/main/java/com/flint/presentation/collectioncreate/uistate/CollectionCreateUiState.kt +++ b/app/src/main/java/com/flint/presentation/collectioncreate/uistate/CollectionCreateUiState.kt @@ -1,5 +1,6 @@ package com.flint.presentation.collectioncreate +import android.net.Uri import androidx.compose.runtime.Immutable import com.flint.domain.model.search.SearchContentItemModel import kotlinx.collections.immutable.ImmutableList @@ -7,6 +8,7 @@ import kotlinx.collections.immutable.persistentListOf @Immutable data class CollectionCreateUiState( + val thumbnailImageUri: Uri? = null, val title: String = "", val description: String = "", val isPublic: Boolean? = null, @@ -14,9 +16,10 @@ data class CollectionCreateUiState( val contentDetailsMap: Map = emptyMap(), val contents: ImmutableList = persistentListOf(), val searchText: String = "", - + val isLoading: Boolean = false, ) { val isFinishButtonEnabled: Boolean = + !isLoading && title.isNotBlank() && isPublic != null && selectedContents.size >= 2 @@ -28,5 +31,6 @@ data class CollectionCreateUiState( @Immutable data class ContentDetail( val isSpoiler: Boolean = false, - val reason: String = "" + val reason: String = "", + val contentImageUris: List = emptyList(), ) \ No newline at end of file