diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9c16adc4..cbd58854 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + + + ( + R.layout.fragment_date_picker_bottom_sheet, + FragmentDatePickerBottomSheetBinding::inflate, +) { + var onDateSelected: ((year: Int, month: Int, day: Int) -> Unit)? = null + + companion object { + private val calendarInstance: Calendar by lazy { + Calendar.getInstance() + } + } + + private var selectedYear: Int = calendarInstance.get(Calendar.YEAR) + private var selectedMonth: Int = calendarInstance.get(Calendar.MONTH) + private var selectedDay: Int = calendarInstance.get(Calendar.DAY_OF_MONTH) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.dpDatePicker.init(selectedYear, selectedMonth, selectedDay) { _, year, month, day -> + selectedYear = year + selectedMonth = month + selectedDay = day + } + } + + override fun onDestroyView() { + super.onDestroyView() + onDateSelected?.invoke(selectedYear, selectedMonth, selectedDay) + } +} diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ImageUploadDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ImageUploadDialog.kt new file mode 100644 index 00000000..be897456 --- /dev/null +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ImageUploadDialog.kt @@ -0,0 +1,99 @@ +package com.depromeet.presentation.seatReview + +import android.app.Activity.RESULT_OK +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import com.depromeet.core.base.BindingBottomSheetDialog +import com.depromeet.presentation.R +import com.depromeet.presentation.databinding.FragmentUploadBottomSheetBinding +import com.depromeet.presentation.extension.setOnSingleClickListener +import dagger.hilt.android.AndroidEntryPoint +import java.io.ByteArrayOutputStream + +@AndroidEntryPoint +class ImageUploadDialog : BindingBottomSheetDialog( + R.layout.fragment_upload_bottom_sheet, + FragmentUploadBottomSheetBinding::inflate, +) { + + companion object { + private const val REQUEST_KEY = "requestKey" + private const val SELECTED_IMAGES = "selected_images" + private const val IMAGE_TITLE = "image" + } + + private lateinit var pickMultipleMediaLauncher: ActivityResultLauncher + private lateinit var takePhotoLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NORMAL, R.style.TransparentBottomSheetDialogFragment) + setupPickMultipleMediaLauncher() + setupTakePhotoLauncher() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUploadMethod() + } + + private fun setupPickMultipleMediaLauncher() { + pickMultipleMediaLauncher = + registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(3)) { uris -> + val uriList = uris.map { it.toString() } + val bundle = bundleOf(SELECTED_IMAGES to uriList) + setFragmentResult(REQUEST_KEY, bundle) + dismiss() + } + } + + private fun setupTakePhotoLauncher() { + takePhotoLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + val data: Intent? = result.data + val bitmap = data?.extras?.get("data") as Bitmap? + bitmap?.let { + val uri = getImageUri(requireContext(), it) + val bundle = Bundle().apply { + putStringArrayList(SELECTED_IMAGES, arrayListOf(uri.toString())) + } + setFragmentResult(REQUEST_KEY, bundle) + dismiss() + } + } + } + } + + private fun setupUploadMethod() { + binding.layoutGallery.setOnSingleClickListener { + pickMultipleMediaLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } + binding.layoutTakePhoto.setOnSingleClickListener { + val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + takePhotoLauncher.launch(takePictureIntent) + } + } + + private fun getImageUri(context: Context, bitmap: Bitmap): Uri { + val bytes = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes) + val path = MediaStore.Images.Media.insertImage( + context.contentResolver, + bitmap, + IMAGE_TITLE, + null, + ) + return Uri.parse(path) + } +} diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewMainActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewMainActivity.kt new file mode 100644 index 00000000..e82d4016 --- /dev/null +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewMainActivity.kt @@ -0,0 +1,106 @@ +package com.depromeet.presentation.seatReview + +import android.app.DatePickerDialog +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.widget.ImageView +import androidx.core.view.isVisible +import coil.load +import coil.transform.RoundedCornersTransformation +import com.depromeet.core.base.BaseActivity +import com.depromeet.presentation.databinding.ActivityMainReviewBinding +import com.depromeet.presentation.extension.setOnSingleClickListener +import dagger.hilt.android.AndroidEntryPoint +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +@AndroidEntryPoint +class ReviewMainActivity : BaseActivity({ + ActivityMainReviewBinding.inflate(it) +}) { + + private val imageViews: List by lazy { + listOf( + binding.ivFirstImage, + binding.ivSecondImage, + binding.ivThirdImage, + ) + } + private var selectedImageUris: MutableList = mutableListOf() + + companion object { + private const val DATE_FORMAT = "yy.MM.dd" + private const val FRAGMENT_RESULT_KEY = "requestKey" + private const val SELECTED_IMAGES = "selected_images" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initUploadDialog() + initDatePickerDialog() + setupFragmentResultListener() + } + + private fun initUploadDialog() { + binding.btnAddImage.setOnClickListener { + val uploadDialogFragment = ImageUploadDialog() + uploadDialogFragment.show(supportFragmentManager, uploadDialogFragment.tag) + } + } + + private fun initDatePickerDialog() { + val today = Calendar.getInstance() + val dateFormat = SimpleDateFormat(DATE_FORMAT, Locale.getDefault()) + with(binding) { + tvDate.text = dateFormat.format(today.time) + layoutDatePicker.setOnSingleClickListener { + val datePickerDialogFragment = DatePickerDialog().apply { + onDateSelected = { year, month, day -> + val selectedDate = Calendar.getInstance() + selectedDate.set(year, month, day) + tvDate.text = dateFormat.format(selectedDate.time) + } + } + datePickerDialogFragment.show(supportFragmentManager, datePickerDialogFragment.tag) + } + } + } + + private fun setupFragmentResultListener() { + supportFragmentManager.setFragmentResultListener(FRAGMENT_RESULT_KEY, this) { _, bundle -> + val newSelectedImages = bundle.getStringArrayList(SELECTED_IMAGES) + newSelectedImages?.let { addSelectedImages(it) } + } + } + + private fun addSelectedImages(newImageUris: List) { + selectedImageUris.addAll(newImageUris.filterNot { selectedImageUris.contains(it) }) + updateImageViews() + } + + private fun updateImageViews() { + with(binding) { + layoutAddDefaultImage.isVisible = selectedImageUris.isEmpty() + selectedImageUris.forEachIndexed { index, uri -> + if (index < imageViews.size) { + val image = imageViews[index] as ImageView + image.isVisible = true + image.setImageURI(Uri.parse(uri)) + image.load(Uri.parse(uri)) { + transformations(RoundedCornersTransformation(26f)) + } + } + } + for (index in selectedImageUris.size until imageViews.size) { + val image = imageViews[index] as ImageView + image.isVisible = false + } + if (selectedImageUris.size == 3) { + svAddImage.post { svAddImage.fullScroll(View.FOCUS_RIGHT) } + } + btnAddImage.isVisible = selectedImageUris.size < imageViews.size + } + } +} diff --git a/presentation/src/main/res/drawable/ic_camera.xml b/presentation/src/main/res/drawable/ic_camera.xml new file mode 100644 index 00000000..d9aed6fb --- /dev/null +++ b/presentation/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,10 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_chevron_down.xml b/presentation/src/main/res/drawable/ic_chevron_down.xml new file mode 100644 index 00000000..20b83c5a --- /dev/null +++ b/presentation/src/main/res/drawable/ic_chevron_down.xml @@ -0,0 +1,10 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_chevron_left.xml b/presentation/src/main/res/drawable/ic_chevron_left.xml new file mode 100644 index 00000000..84f3770c --- /dev/null +++ b/presentation/src/main/res/drawable/ic_chevron_left.xml @@ -0,0 +1,10 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_delete.xml b/presentation/src/main/res/drawable/ic_delete.xml new file mode 100644 index 00000000..ececea62 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,13 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_image.xml b/presentation/src/main/res/drawable/ic_image.xml new file mode 100644 index 00000000..6acec9cd --- /dev/null +++ b/presentation/src/main/res/drawable/ic_image.xml @@ -0,0 +1,14 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_pencil.xml b/presentation/src/main/res/drawable/ic_pencil.xml new file mode 100644 index 00000000..fdf54958 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_pencil.xml @@ -0,0 +1,12 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_plus.xml b/presentation/src/main/res/drawable/ic_plus.xml new file mode 100644 index 00000000..feb7b21d --- /dev/null +++ b/presentation/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/drawable/rect_gray200_fill_16.xml b/presentation/src/main/res/drawable/rect_gray200_fill_16.xml new file mode 100644 index 00000000..fd967f3e --- /dev/null +++ b/presentation/src/main/res/drawable/rect_gray200_fill_16.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/presentation/src/main/res/drawable/rect_gray200_fill_6.xml b/presentation/src/main/res/drawable/rect_gray200_fill_6.xml new file mode 100644 index 00000000..ed20478d --- /dev/null +++ b/presentation/src/main/res/drawable/rect_gray200_fill_6.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/presentation/src/main/res/drawable/rect_gray50_fill_30.xml b/presentation/src/main/res/drawable/rect_gray50_fill_30.xml new file mode 100644 index 00000000..6e1bb096 --- /dev/null +++ b/presentation/src/main/res/drawable/rect_gray50_fill_30.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/presentation/src/main/res/drawable/rect_gray50_fill_gray200_line_12.xml b/presentation/src/main/res/drawable/rect_gray50_fill_gray200_line_12.xml new file mode 100644 index 00000000..276033b8 --- /dev/null +++ b/presentation/src/main/res/drawable/rect_gray50_fill_gray200_line_12.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/presentation/src/main/res/drawable/rect_gray600_fill_12.xml b/presentation/src/main/res/drawable/rect_gray600_fill_12.xml new file mode 100644 index 00000000..d6836f10 --- /dev/null +++ b/presentation/src/main/res/drawable/rect_gray600_fill_12.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/presentation/src/main/res/drawable/rect_white_fill_32.xml b/presentation/src/main/res/drawable/rect_white_fill_32.xml new file mode 100644 index 00000000..00d7a872 --- /dev/null +++ b/presentation/src/main/res/drawable/rect_white_fill_32.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/presentation/src/main/res/drawable/rect_white_fill_gray200_dash_12.xml b/presentation/src/main/res/drawable/rect_white_fill_gray200_dash_12.xml new file mode 100644 index 00000000..75f14191 --- /dev/null +++ b/presentation/src/main/res/drawable/rect_white_fill_gray200_dash_12.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/activity_main_review.xml b/presentation/src/main/res/layout/activity_main_review.xml new file mode 100644 index 00000000..d60fc30a --- /dev/null +++ b/presentation/src/main/res/layout/activity_main_review.xml @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/fragment_date_picker_bottom_sheet.xml b/presentation/src/main/res/layout/fragment_date_picker_bottom_sheet.xml new file mode 100644 index 00000000..4cba601a --- /dev/null +++ b/presentation/src/main/res/layout/fragment_date_picker_bottom_sheet.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_upload_bottom_sheet.xml b/presentation/src/main/res/layout/fragment_upload_bottom_sheet.xml new file mode 100644 index 00000000..2d07de80 --- /dev/null +++ b/presentation/src/main/res/layout/fragment_upload_bottom_sheet.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/themes.xml b/presentation/src/main/res/values/themes.xml index cb50f84c..d561782c 100644 --- a/presentation/src/main/res/values/themes.xml +++ b/presentation/src/main/res/values/themes.xml @@ -1,4 +1,4 @@ - + + + + + \ No newline at end of file diff --git "a/\353\254\264\354\240\234" "b/\353\254\264\354\240\234" index 0d0bcf8e..76155b69 160000 --- "a/\353\254\264\354\240\234" +++ "b/\353\254\264\354\240\234" @@ -1 +1 @@ -Subproject commit 0d0bcf8ec527adce77f4150b3ea55af12156b5ab +Subproject commit 76155b69b52cff2222f597363cd568ae896cccd8