diff --git a/app/src/main/java/sopt/motivoo/data/repository/HomeRepositoryImpl.kt b/app/src/main/java/sopt/motivoo/data/repository/HomeRepositoryImpl.kt index ad36fa32..2533d3a1 100644 --- a/app/src/main/java/sopt/motivoo/data/repository/HomeRepositoryImpl.kt +++ b/app/src/main/java/sopt/motivoo/data/repository/HomeRepositoryImpl.kt @@ -1,7 +1,6 @@ package sopt.motivoo.data.repository import android.graphics.Bitmap -import okhttp3.RequestBody.Companion.toRequestBody import sopt.motivoo.data.datasource.remote.HomeDataSource import sopt.motivoo.data.model.request.home.RequestMissionTodayDto import sopt.motivoo.domain.entity.error.ResponseHandler @@ -10,7 +9,7 @@ import sopt.motivoo.domain.entity.home.MissionChoiceData import sopt.motivoo.domain.entity.home.MissionImageData import sopt.motivoo.domain.error.UserErrorHandler import sopt.motivoo.domain.repository.HomeRepository -import java.io.ByteArrayOutputStream +import sopt.motivoo.util.BitmapRequestBody import javax.inject.Inject class HomeRepositoryImpl @Inject constructor( @@ -53,9 +52,7 @@ class HomeRepositoryImpl @Inject constructor( } override suspend fun uploadPhoto(url: String, bitmap: Bitmap): Unit? = try { - val outputStream = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 80, outputStream) - val requestBody = outputStream.toByteArray().toRequestBody() + val requestBody = BitmapRequestBody(bitmap).create(50) homeDataSource.uploadPhoto(url, requestBody) } catch (e: Exception) { null diff --git a/app/src/main/java/sopt/motivoo/presentation/home/bottomsheet/HomeBottomSheetFragment.kt b/app/src/main/java/sopt/motivoo/presentation/home/bottomsheet/HomeBottomSheetFragment.kt index 20e24969..ff6c4026 100644 --- a/app/src/main/java/sopt/motivoo/presentation/home/bottomsheet/HomeBottomSheetFragment.kt +++ b/app/src/main/java/sopt/motivoo/presentation/home/bottomsheet/HomeBottomSheetFragment.kt @@ -27,9 +27,9 @@ import sopt.motivoo.R import sopt.motivoo.databinding.BottomSheetHomeBinding import sopt.motivoo.presentation.home.HomePictureState import sopt.motivoo.presentation.home.viewmodel.HomeViewModel +import sopt.motivoo.util.BitmapUtil import sopt.motivoo.util.Constants.S3_BUCKET_NAME import sopt.motivoo.util.UriManager -import sopt.motivoo.util.extension.createUriToBitmap @AndroidEntryPoint class HomeBottomSheetFragment : BottomSheetDialogFragment() { @@ -39,6 +39,7 @@ class HomeBottomSheetFragment : BottomSheetDialogFragment() { private val viewModel: HomeViewModel by viewModels() var pictureUri: Uri? = null + private lateinit var bitmapUtil: BitmapUtil private val isCameraPermissionResult = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> @@ -60,8 +61,10 @@ class HomeBottomSheetFragment : BottomSheetDialogFragment() { registerForActivityResult(ActivityResultContracts.TakePicture()) { isSuccess -> if (isSuccess) { binding.pvLoading.visibility = View.VISIBLE - pictureUri?.let { - viewModel.getMissionImage(S3_BUCKET_NAME, requireContext().createUriToBitmap(it)) + pictureUri?.let { uri -> + bitmapUtil.createUriToBitmap(uri, size = 2)?.let { bitmap -> + viewModel.getMissionImage(S3_BUCKET_NAME, bitmap) + } } } } @@ -92,6 +95,7 @@ class HomeBottomSheetFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + bitmapUtil = BitmapUtil(requireContext()) setLayoutSize() collectHomePictureState() onClickTakePicture() diff --git a/app/src/main/java/sopt/motivoo/presentation/home/dialog/HomePhotoDialogFragment.kt b/app/src/main/java/sopt/motivoo/presentation/home/dialog/HomePhotoDialogFragment.kt index 07cefae1..5c190457 100644 --- a/app/src/main/java/sopt/motivoo/presentation/home/dialog/HomePhotoDialogFragment.kt +++ b/app/src/main/java/sopt/motivoo/presentation/home/dialog/HomePhotoDialogFragment.kt @@ -17,9 +17,10 @@ import sopt.motivoo.R import sopt.motivoo.databinding.DialogHomePhotoBinding import sopt.motivoo.presentation.home.HomePictureState import sopt.motivoo.presentation.home.viewmodel.HomeViewModel +import sopt.motivoo.util.BitmapUtil import sopt.motivoo.util.Constants.S3_BUCKET_NAME import sopt.motivoo.util.binding.BindingDialogFragment -import sopt.motivoo.util.extension.createUriToBitmap +import sopt.motivoo.util.extension.showToast @AndroidEntryPoint class HomePhotoDialogFragment : @@ -27,10 +28,12 @@ class HomePhotoDialogFragment : private val viewModel: HomeViewModel by viewModels() private lateinit var photoUri: Uri + private lateinit var bitmapUtil: BitmapUtil override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setLayoutSizeRatio(widthPercent = 1f, heightPercent = 1f) + bitmapUtil = BitmapUtil(requireContext()) val safeArgs: HomePhotoDialogFragmentArgs by navArgs() photoUri = safeArgs.photoUri @@ -38,11 +41,12 @@ class HomePhotoDialogFragment : binding.ivPhoto.load(photoUri) binding.tvConfirm.setOnClickListener { - binding.pvLoading.visibility = View.VISIBLE - viewModel.getMissionImage( - S3_BUCKET_NAME, - requireContext().createUriToBitmap(safeArgs.photoUri) - ) + bitmapUtil.createUriToBitmap(photoUri, size = 2)?.let { bitmap -> + binding.pvLoading.visibility = View.VISIBLE + viewModel.getMissionImage( + S3_BUCKET_NAME, bitmap + ) + } ?: requireContext().showToast("createUriToBitmap is null") } viewLifecycleOwner.lifecycleScope.launch { diff --git a/app/src/main/java/sopt/motivoo/util/BitmapRequestBody.kt b/app/src/main/java/sopt/motivoo/util/BitmapRequestBody.kt new file mode 100644 index 00000000..b3b6bed9 --- /dev/null +++ b/app/src/main/java/sopt/motivoo/util/BitmapRequestBody.kt @@ -0,0 +1,24 @@ +package sopt.motivoo.util + +import android.graphics.Bitmap +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.ByteArrayOutputStream + +class BitmapRequestBody(private val bitmap: Bitmap) { + + /** + * byte size = output.toByteArray().size + */ + fun create(quality: Int = 100): RequestBody { + val output = ByteArrayOutputStream() + try { + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, output) + } catch (e: Exception) { + e.message + } finally { + output.close() + } + return output.toByteArray().toRequestBody() + } +} diff --git a/app/src/main/java/sopt/motivoo/util/BitmapUtil.kt b/app/src/main/java/sopt/motivoo/util/BitmapUtil.kt new file mode 100644 index 00000000..472adb33 --- /dev/null +++ b/app/src/main/java/sopt/motivoo/util/BitmapUtil.kt @@ -0,0 +1,92 @@ +package sopt.motivoo.util + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.graphics.Matrix +import android.media.ExifInterface +import android.net.Uri +import android.os.Build +import android.provider.MediaStore + +class BitmapUtil(private val context: Context) { + private fun loadOrientation(uri: Uri): Int { + var orientation = 0 + val stream = context.contentResolver.openInputStream(uri) ?: return 0 + try { + val exifInterface = ExifInterface(stream) + orientation = exifInterface.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL + ) + } catch (e: Exception) { + e.message + } finally { + stream.close() + } + return orientation + } + + /** + * @param bounds : if ture, assign bitmap in memory. if false, no assign bitmap in memory + * @param size : return image ratio + */ + private fun decodeUriToBitmap(uri: Uri, bounds: Boolean = false, size: Int = 1): Bitmap? { + var bitmap: Bitmap? = null + val options = BitmapFactory.Options().apply { + inJustDecodeBounds = bounds + inSampleSize = size + } + + val stream = context.contentResolver.openInputStream(uri) + try { + bitmap = BitmapFactory.decodeStream(stream, null, options) + } catch (e: Exception) { + e.message + } finally { + stream?.close() + } + + return bitmap + } + + private fun rotateBitmap(orientation: Int, bitmap: Bitmap?): Bitmap? = when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(bitmap, 90f) + ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(bitmap, 180f) + ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(bitmap, 270f) + else -> bitmap + } + + private fun rotateImage(bitmap: Bitmap?, angle: Float): Bitmap? { + val matrix = Matrix().apply { postRotate(angle) } + return bitmap?.let { + Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + } + } + + /** + * Use BitmapFactory Bitmap Resize + */ + fun createUriToBitmap(uri: Uri, bounds: Boolean = false, size: Int = 1): Bitmap? { + val orientation = loadOrientation(uri) + val bitmap = decodeUriToBitmap(uri, bounds, size) + + return rotateBitmap(orientation, bitmap) + } + + /** + * Use ImageDecoder Bitmap Resize, easy convert uri to bitmap + */ + fun createUriToBitmap(uri: Uri): Bitmap = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val source = + ImageDecoder.createSource(context.contentResolver, uri) + ImageDecoder.decodeBitmap(source) + } else { + MediaStore.Images.Media.getBitmap( + context.contentResolver, + uri + ) + } +} diff --git a/app/src/main/java/sopt/motivoo/util/extension/ContextExt.kt b/app/src/main/java/sopt/motivoo/util/extension/ContextExt.kt index be9becfc..e096ed9c 100644 --- a/app/src/main/java/sopt/motivoo/util/extension/ContextExt.kt +++ b/app/src/main/java/sopt/motivoo/util/extension/ContextExt.kt @@ -5,13 +5,9 @@ import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.graphics.Bitmap -import android.graphics.ImageDecoder import android.net.ConnectivityManager import android.net.NetworkCapabilities -import android.net.Uri import android.os.Build -import android.provider.MediaStore import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.ImageView @@ -92,18 +88,6 @@ fun Context.sendNotification( }.build() } -fun Context.createUriToBitmap(photoUri: Uri): Bitmap = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - val source = - ImageDecoder.createSource(contentResolver, photoUri) - ImageDecoder.decodeBitmap(source) - } else { - MediaStore.Images.Media.getBitmap( - contentResolver, - photoUri - ) - } - fun Context.checkNetworkState(): Boolean { val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager