From d22907c7d5f7da87c433fa72fa1c0eb98e608e80 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 18 Sep 2025 00:17:20 +0900 Subject: [PATCH] =?UTF-8?q?fix(=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D):=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=20=EB=B0=8F=20=ED=81=AC=EB=A1=AD=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 1 - .../modify/AudioContentModifyActivity.kt | 94 ++++----- .../modify/AudioContentModifyViewModel.kt | 6 +- .../upload/AudioContentUploadActivity.kt | 110 +++++----- .../upload/AudioContentUploadViewModel.kt | 10 +- .../sodalive/common/ImagePickerCropper.kt | 106 ++++++++-- .../modify/CreatorCommunityModifyActivity.kt | 191 ++++------------- .../modify/CreatorCommunityModifyViewModel.kt | 9 +- .../write/CreatorCommunityWriteActivity.kt | 196 ++++-------------- .../write/CreatorCommunityWriteViewModel.kt | 17 +- .../sodalive/live/room/LiveRoomActivity.kt | 69 +++--- .../sodalive/live/room/LiveRoomViewModel.kt | 11 +- .../room/create/LiveRoomCreateActivity.kt | 1 + .../room/update/LiveRoomInfoEditDialog.kt | 57 +++-- .../mypage/profile/ProfileUpdateActivity.kt | 84 ++++---- .../mypage/profile/ProfileUpdateViewModel.kt | 6 +- 16 files changed, 397 insertions(+), 571 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 65ba05e5..e3c5eb1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -148,7 +148,6 @@ dependencies { // permission implementation "io.github.ParkSangGwon:tedpermission-normal:3.3.0" - implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.github.yalantis:ucrop:2.2.11' implementation 'com.github.zhpanvip:bannerviewpager:3.5.7' diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyActivity.kt index 91c56745..02087d47 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyActivity.kt @@ -2,16 +2,18 @@ package kr.co.vividnext.sodalive.audio_content.modify import android.Manifest import android.annotation.SuppressLint +import android.graphics.Bitmap import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.core.view.setPadding import coil.load import coil.transform.RoundedCornersTransformation -import com.github.dhaval2404.imagepicker.ImagePicker +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.normal.TedPermission import com.jakewharton.rxbinding4.widget.textChanges @@ -20,6 +22,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.ImagePickerCropper import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.databinding.ActivityAudioContentModifyBinding @@ -33,36 +36,7 @@ class AudioContentModifyActivity : BaseActivity - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - val fileUri = data?.data - - if (fileUri != null) { - binding.ivCover.setPadding(0) - binding.ivCover.background = null - binding.ivCover.load(fileUri) { - crossfade(true) - placeholder(R.drawable.bg_placeholder) - transformations(RoundedCornersTransformation(13.3f.dpToPx())) - } - viewModel.coverImageUri = fileUri - } else { - Toast.makeText( - this, - "잘못된 파일입니다.\n다시 선택해 주세요.", - Toast.LENGTH_SHORT - ).show() - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() - } - } + private lateinit var cropper: ImagePickerCropper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -82,24 +56,50 @@ class AudioContentModifyActivity : BaseActivity + binding.ivCover.setPadding(0) + binding.ivCover.background = null + Glide.with(this) + .load(uri) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions().transform( + RoundedCorners( + 13.3f.dpToPx().toInt() + ) + ) + ) + .into(binding.ivCover) + + viewModel.coverImageFile = file + }, + onError = { e -> + Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show() + } + ) + binding.toolbar.tvBack.text = "콘텐츠 수정" binding.toolbar.tvBack.setOnClickListener { finish() } - binding.ivPhotoPicker.setOnClickListener { - ImagePicker.with(this) - .crop() - .galleryOnly() - .galleryMimeTypes( // Exclude gif images - mimeTypes = arrayOf( - "image/png", - "image/jpg", - "image/jpeg" - ) - ) - .createIntent { imageResult.launch(it) } - } + binding.ivPhotoPicker.setOnClickListener { cropper.launch() } binding.llAvailablePoint.setOnClickListener { viewModel.setAvailablePoint(true) } binding.llNotAvailablePoint.setOnClickListener { viewModel.setAvailablePoint(false) } @@ -239,8 +239,8 @@ class AudioContentModifyActivity : BaseActivity + if (isAdult) { binding.ivAgeAll.visibility = View.GONE binding.llAgeAll.setBackgroundResource( R.drawable.bg_round_corner_6_7_13181b diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyViewModel.kt index 6cd14902..5eac4c50 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/modify/AudioContentModifyViewModel.kt @@ -68,7 +68,7 @@ class AudioContentModifyViewModel( var title: String? = null var detail: String? = null var tags: String? = null - var coverImageUri: Uri? = null + var coverImageFile: File? = null var isPointAvailable: Boolean? = null fun setAdult(isAdult: Boolean) { @@ -154,8 +154,8 @@ class AudioContentModifyViewModel( val requestJson = Gson().toJson(request) - val coverImage = if (coverImageUri != null) { - val file = File(getRealPathFromURI(coverImageUri!!)) + val coverImage = if (coverImageFile != null) { + val file = coverImageFile!! MultipartBody.Part.createFormData( "coverImage", file.name, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/upload/AudioContentUploadActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/upload/AudioContentUploadActivity.kt index c102e5e2..4bac230e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/upload/AudioContentUploadActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/upload/AudioContentUploadActivity.kt @@ -5,6 +5,7 @@ import android.annotation.SuppressLint import android.app.DatePickerDialog import android.app.TimePickerDialog import android.content.Intent +import android.graphics.Bitmap import android.net.Uri import android.os.Build import android.os.Bundle @@ -15,8 +16,9 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import coil.load import coil.transform.CircleCropTransformation -import coil.transform.RoundedCornersTransformation -import com.github.dhaval2404.imagepicker.ImagePicker +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.normal.TedPermission import com.jakewharton.rxbinding4.widget.textChanges @@ -26,6 +28,7 @@ import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.audio_content.PurchaseOption import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeFragment import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.ImagePickerCropper import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.SharedPreferenceManager @@ -48,6 +51,7 @@ class AudioContentUploadActivity : BaseActivity - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - val fileUri = data?.data - - if (fileUri != null) { - binding.ivCover.background = null - binding.ivCover.load(fileUri) { - crossfade(true) - placeholder(R.drawable.ic_place_holder) - transformations(RoundedCornersTransformation(13.3f.dpToPx())) - } - viewModel.coverImageUri = fileUri - } else { - Toast.makeText( - this, - "잘못된 파일입니다.\n다시 선택해 주세요.", - Toast.LENGTH_SHORT - ).show() - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() - } - } - private val selectAudioActivityResultLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> @@ -113,18 +88,29 @@ class AudioContentUploadActivity : BaseActivity - viewModel.releaseDate = String.format("%d-%02d-%02d", year, monthOfYear + 1, dayOfMonth) + viewModel.releaseDate = String.format( + Locale.getDefault(), + "%d-%02d-%02d", + year, + monthOfYear + 1, + dayOfMonth + ) viewModel.setReservationDate( String.format( + Locale.getDefault(), "%d.%02d.%02d", year, monthOfYear + 1, @@ -135,7 +121,7 @@ class AudioContentUploadActivity : BaseActivity - val timeString = String.format("%02d:%02d", hourOfDay, minute) + val timeString = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute) viewModel.releaseTime = timeString viewModel.setReservationTime(timeString.convertDateFormat("HH:mm", "a hh:mm")) } @@ -151,9 +137,45 @@ class AudioContentUploadActivity : BaseActivity + binding.ivCover.background = null + Glide.with(this) + .load(uri) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions().transform( + RoundedCorners( + 13.3f.dpToPx().toInt() + ) + ) + ) + .into(binding.ivCover) + + viewModel.coverImageFile = file + }, + onError = { e -> + Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show() + } + ) + binding.tvServiceDate.text = if (SharedPreferenceManager.userId == 17958L) { "※ 이용기간 : 대여(5일) | 소장(이용 기간 1년)" } else { @@ -167,19 +189,7 @@ class AudioContentUploadActivity : BaseActivity Unit, private val onError: (Throwable) -> Unit = { it.printStackTrace() } @@ -41,15 +45,21 @@ class ImagePickerCropper( // 13+ : 시스템 Photo Picker private val pickPhoto: ActivityResultLauncher = caller.registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - if (uri == null) onError(CancellationException("User cancelled picking.")) - else startCrop(uri) + if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다.")) + else handlePickedUri(uri) } // 12- : SAF GetContent private val pickContent: ActivityResultLauncher = caller.registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> - if (uri == null) onError(CancellationException("User cancelled picking.")) - else startCrop(uri) + if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다.")) + else handlePickedUri(uri) + } + + private val openDocument = + caller.registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다.")) + else handlePickedUri(uri) } // uCrop 결과 수신 @@ -62,38 +72,98 @@ class ImagePickerCropper( if (out != null && file != null && file.exists()) { onSuccess(file, out) } else { - onError(IllegalStateException("Crop finished but no output file/uri")) + onError(IllegalStateException("이미지 크롭을 실패했습니다.\n다시 시도해 주세요")) } } UCrop.RESULT_ERROR -> onError( - UCrop.getError(result.data!!) ?: RuntimeException("Crop error") + UCrop.getError(result.data!!) + ?: RuntimeException("이미지 크롭을 실패했습니다.\n다시 시도해 주세요") ) - else -> onError(CancellationException("User cancelled cropping.")) + else -> onError(CancellationException("이미지 크롭을 취소했습니다.")) } } /** 외부에서 호출: 선택 → 크롭 시작 */ fun launch() { - if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(context)) { - pickPhoto.launch( - PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) - ) + if (excludeGif) { + openDocument.launch(arrayOf("image/png", "image/jpg", "image/jpeg")) } else { - pickContent.launch("image/*") + if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(context)) { + pickPhoto.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } else { + pickContent.launch("image/*") + } } } - /** 마지막 크롭 결과 파일 (있으면) */ - fun getCroppedFile(): File? = lastCroppedFile?.takeIf { it.exists() } - /** 임시 파일 삭제 */ fun cleanup() { lastCroppedFile?.let { if (it.exists()) it.delete() } lastCroppedFile = null } + private fun handlePickedUri(source: Uri) { + if (isGifUri(source)) { + // 1) 캐시에 gif 그대로 복사 + val gifFile = copyUriToCacheAsGif(source) + lastCroppedFile = gifFile + val fileUri = FileProvider.getUriForFile( + context, "${BuildConfig.APPLICATION_ID}.fileprovider", gifFile + ) + // 2) 바로 반환 (크롭 생략) + onSuccess(gifFile, fileUri) + } else { + // 기존 그대로: uCrop 9:20 실행 + startCrop(source) + } + } + + private fun isGifUri(uri: Uri): Boolean { + // 1순위: MIME type + val mime = context.contentResolver.getType(uri) + if (mime?.equals("image/gif", ignoreCase = true) == true) return true + + // 2순위: 파일명 확장자 + val name = getDisplayName(uri)?.lowercase() + return name?.endsWith(".gif") == true + } + + private fun getDisplayName(uri: Uri): String? { + return context.contentResolver.query( + uri, + arrayOf(OpenableColumns.DISPLAY_NAME), + null, + null, + null + ) + ?.use { c -> + val idx = c.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (idx >= 0 && c.moveToFirst()) c.getString(idx) else null + } + } + + private fun copyUriToCacheAsGif(uri: Uri): File { + val base = if (config.useExternalCache) context.externalCacheDir ?: context.cacheDir + else context.cacheDir + + // 원본 이름 유지 시도, 실패하면 타임스탬프 + val name = getDisplayName(uri)?.takeIf { it.endsWith(".gif", true) } + ?: "picked_${System.currentTimeMillis()}.gif" + + val outFile = File(base, name) + context.contentResolver.openInputStream(uri).use { input -> + requireNotNull(input) { "Cannot open input stream for $uri" } + FileOutputStream(outFile).use { output -> + input.copyTo(output) + } + } + return outFile + } + private fun startCrop(source: Uri) { val destFile = createTempCropFile() lastCroppedFile = destFile @@ -110,8 +180,8 @@ class ImagePickerCropper( setCompressionQuality(config.compressQuality) // 제스처/컨트롤 (필요시 조절) - setFreeStyleCropEnabled(false) - setHideBottomControls(false) + setFreeStyleCropEnabled(isEnabledFreeStyleCrop) + setHideBottomControls(true) setAllowedGestures( UCropActivity.SCALE, // Aspect 줄이진 못하지만 확대/축소 UCropActivity.ROTATE, // 회전 diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyActivity.kt index 3f4cd636..983b0aa9 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyActivity.kt @@ -2,35 +2,29 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify import android.Manifest import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri +import android.graphics.Bitmap import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions -import com.github.dhaval2404.imagepicker.ImagePicker import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.normal.TedPermission import com.jakewharton.rxbinding4.widget.textChanges -import com.orhanobut.logger.Logger -import com.yalantis.ucrop.UCrop import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.ImagePickerCropper import kr.co.vividnext.sodalive.common.LoadingDialog -import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityModifyBinding import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject -import java.io.File class CreatorCommunityModifyActivity : BaseActivity( ActivityCreatorCommunityModifyBinding::inflate @@ -39,142 +33,11 @@ class CreatorCommunityModifyActivity : BaseActivity - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - val fileUri = UCrop.getOutput(data!!) - - if (fileUri != null) { - handleCroppedImage(fileUri) - } else { - showWrongImageFile() - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - val cropError = UCrop.getError(data!!) - cropError?.printStackTrace() - Toast.makeText( - this, - cropError?.message, - Toast.LENGTH_SHORT - ).show() - } - } - - private val imageSelectResult = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - val fileUri = data?.data - - if (fileUri != null) { - val mime = contentResolver.getType(fileUri) - - if (mime.equals("image/gif", ignoreCase = true)) { - handleGifImage(fileUri) - } else { - launchCrop(fileUri) - } - } else { - showWrongImageFile() - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - showImagePickerError(data = data) - } - } - - private fun launchImagePicker() { - ImagePicker.with(this) - .galleryOnly() - .createIntent { imageSelectResult.launch(it) } - } - - private fun launchCrop(fileUri: Uri) { - croppedTempFile = File.createTempFile("cropped_", ".jpg", cacheDir) - - val destinationUri = Uri.fromFile(croppedTempFile) - - val options = UCrop.Options().apply { - setFreeStyleCropEnabled(true) - setToolbarTitle("이미지 자르기") - } - - val uCrop = UCrop.of(fileUri, destinationUri) - .withOptions(options) - .withMaxResultSize(1080, 1080) - - imageCropResult.launch(uCrop.getIntent(this)) - } - - private fun handleGifImage(fileUri: Uri) { - binding.ivContent.background = null - - Glide.with(this) - .asGif() - .load(fileUri) - .placeholder(R.drawable.ic_place_holder) - .apply( - RequestOptions().transform( - RoundedCorners( - 8f.dpToPx().toInt() - ) - ) - ) - .into(binding.ivContent) - - viewModel.imageUri = fileUri - } - - private fun handleCroppedImage(fileUri: Uri) { - binding.ivContent.background = null - - Glide.with(this) - .load(fileUri) - .placeholder(R.drawable.ic_place_holder) - .apply( - RequestOptions().transform( - RoundedCorners( - 8f.dpToPx().toInt() - ) - ) - ) - .into(binding.ivContent) - - viewModel.imageUri = fileUri - } - - private fun showWrongImageFile() { - Toast.makeText( - this, - "잘못된 파일입니다.\n다시 선택해 주세요.", - Toast.LENGTH_SHORT - ).show() - } - - private fun showImagePickerError(data: Intent?) { - Toast.makeText( - this, - ImagePicker.getError(data), - Toast.LENGTH_SHORT - ).show() - } + private lateinit var cropper: ImagePickerCropper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) checkPermissions() - - viewModel.getRealPathFromURI = { - RealPathUtil.getRealPath(applicationContext, it) - } - bindData() val postId = intent.getLongExtra(Constants.EXTRA_COMMUNITY_POST_ID, 0) @@ -193,31 +56,47 @@ class CreatorCommunityModifyActivity : BaseActivity + binding.ivContent.background = null + Glide.with(this) + .load(uri) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions().transform( + RoundedCorners( + 16f.dpToPx().toInt() + ) + ) + ) + .into(binding.ivContent) + + viewModel.imageFile = file + }, + onError = { e -> + Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show() + } + ) + binding.toolbar.tvBack.text = "게시글 등록" binding.toolbar.tvBack.setOnClickListener { finish() } - binding.ivPhotoPicker.setOnClickListener { launchImagePicker() } + binding.ivPhotoPicker.setOnClickListener { cropper.launch() } if (SharedPreferenceManager.isAuth) { binding.llSetAdult.visibility = View.VISIBLE diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyViewModel.kt index 964315b9..cc5ab4f5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/modify/CreatorCommunityModifyViewModel.kt @@ -1,6 +1,5 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify -import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.google.gson.Gson @@ -46,11 +45,9 @@ class CreatorCommunityModifyViewModel( val isAvailableCommentLiveData: LiveData get() = _isAvailableCommentLiveData - lateinit var getRealPathFromURI: (Uri) -> String? - var postId = 0L var content = "" - var imageUri: Uri? = null + var imageFile: File? = null private var communityPost: GetCommunityPostListResponse? = null fun setAdult(isAdult: Boolean) { @@ -134,8 +131,8 @@ class CreatorCommunityModifyViewModel( val requestJson = Gson().toJson(request) - val postImage = if (imageUri != null) { - val file = File(getRealPathFromURI(imageUri!!)) + val postImage = if (imageFile != null) { + val file = imageFile!! MultipartBody.Part.createFormData( "postImage", file.name, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt index 2b0d62b4..9394b9bc 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt @@ -2,29 +2,24 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community.write import android.Manifest import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri +import android.graphics.Bitmap import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions -import com.github.dhaval2404.imagepicker.ImagePicker import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.normal.TedPermission import com.jakewharton.rxbinding4.widget.textChanges -import com.orhanobut.logger.Logger -import com.yalantis.ucrop.UCrop import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.ImagePickerCropper import kr.co.vividnext.sodalive.common.LoadingDialog -import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityWriteBinding import kr.co.vividnext.sodalive.extensions.dpToPx @@ -38,154 +33,18 @@ class CreatorCommunityWriteActivity : BaseActivity - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - val fileUri = UCrop.getOutput(data!!) - - if (fileUri != null) { - handleCroppedImage(fileUri) - } else { - showWrongImageFile() - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - val cropError = UCrop.getError(data!!) - cropError?.printStackTrace() - Toast.makeText( - this, - cropError?.message, - Toast.LENGTH_SHORT - ).show() - } - } - - private val imageSelectResult = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - val fileUri = data?.data - - if (fileUri != null) { - val mime = contentResolver.getType(fileUri) - - if (mime.equals("image/gif", ignoreCase = true)) { - handleGifImage(fileUri) - } else { - launchCrop(fileUri) - } - } else { - showWrongImageFile() - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - showImagePickerError(data = data) - } - } - - private fun launchImagePicker() { - ImagePicker.with(this) - .galleryOnly() - .createIntent { imageSelectResult.launch(it) } - } - - private fun launchCrop(fileUri: Uri) { - croppedTempFile = File.createTempFile("cropped_", ".jpg", cacheDir) - - val destinationUri = Uri.fromFile(croppedTempFile) - - val options = UCrop.Options().apply { - setFreeStyleCropEnabled(true) - setToolbarTitle("이미지 자르기") - } - - val uCrop = UCrop.of(fileUri, destinationUri) - .withOptions(options) - .withMaxResultSize(1080, 1080) - - imageCropResult.launch(uCrop.getIntent(this)) - } - - private fun handleGifImage(fileUri: Uri) { - binding.llRecordAudio.visibility = View.VISIBLE - binding.ivContent.background = null - - Glide.with(this) - .asGif() - .load(fileUri) - .placeholder(R.drawable.ic_place_holder) - .apply( - RequestOptions().transform( - RoundedCorners( - 8f.dpToPx().toInt() - ) - ) - ) - .into(binding.ivContent) - - viewModel.setImageUri(fileUri) - } - - private fun handleCroppedImage(fileUri: Uri) { - binding.llRecordAudio.visibility = View.VISIBLE - binding.ivContent.background = null - - Glide.with(this) - .load(fileUri) - .placeholder(R.drawable.ic_place_holder) - .apply( - RequestOptions().transform( - RoundedCorners( - 8f.dpToPx().toInt() - ) - ) - ) - .into(binding.ivContent) - - viewModel.setImageUri(fileUri) - } - - private fun showWrongImageFile() { - binding.llRecordAudio.visibility = View.GONE - Toast.makeText( - this, - "잘못된 파일입니다.\n다시 선택해 주세요.", - Toast.LENGTH_SHORT - ).show() - } - - private fun showImagePickerError(data: Intent?) { - binding.llRecordAudio.visibility = View.GONE - Toast.makeText( - this, - ImagePicker.getError(data), - Toast.LENGTH_SHORT - ).show() - } + private lateinit var cropper: ImagePickerCropper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) checkPermissions() - - viewModel.getRealPathFromURI = { - RealPathUtil.getRealPath(applicationContext, it) - } - bindData() } override fun onDestroy() { + cropper.cleanup() deleteAudioFile() - - deleteCroppedTempFile() - super.onDestroy() } @@ -195,22 +54,39 @@ class CreatorCommunityWriteActivity : BaseActivity + binding.ivContent.background = null + Glide.with(this) + .load(uri) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions().transform( + RoundedCorners( + 16f.dpToPx().toInt() + ) + ) + ) + .into(binding.ivContent) + + viewModel.setImageFile(file) + }, + onError = { e -> + Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show() + } + ) + binding.toolbar.tvBack.text = "게시글 등록" binding.toolbar.tvBack.setOnClickListener { finish() } @@ -220,7 +96,7 @@ class CreatorCommunityWriteActivity : BaseActivity get() = _isShowPriceUiLiveData - lateinit var getRealPathFromURI: (Uri) -> String? - var price = 0 var content = "" var audioFile: File? = null - private var imageUri: Uri? = null + private var imageFile: File? = null fun setAdult(isAdult: Boolean) { _isAdultLiveData.postValue(isAdult) @@ -64,8 +61,8 @@ class CreatorCommunityWriteViewModel( _isPriceFreeLiveData.postValue(isPriceFree) } - fun setImageUri(uri: Uri) { - this.imageUri = uri + fun setImageFile(file: File) { + this.imageFile = file _isShowPriceUiLiveData.postValue(true) } @@ -82,8 +79,8 @@ class CreatorCommunityWriteViewModel( val requestJson = Gson().toJson(request) - val postImage = if (imageUri != null) { - val file = File(getRealPathFromURI(imageUri!!)) + val postImage = if (imageFile != null) { + val file = imageFile!! MultipartBody.Part.createFormData( "postImage", file.name, @@ -188,12 +185,12 @@ class CreatorCommunityWriteViewModel( return false } - if (imageUri == null) { + if (imageFile == null) { _toastLiveData.postValue("유료 게시글 등록을 위해서는 이미지가 필요합니다.") return false } } - } catch (e: Exception) { + } catch (_: Exception) { _toastLiveData.postValue("가격은 숫자만 입력 가능 합니다.") return false } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt index 258cedf9..a0fb02c3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt @@ -3,15 +3,14 @@ package kr.co.vividnext.sodalive.live.room import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.app.AlertDialog -import android.app.Service import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.graphics.Bitmap import android.graphics.Rect import android.graphics.Typeface import android.graphics.drawable.Drawable -import android.net.Uri import android.os.Build import android.os.Bundle import android.os.CountDownTimer @@ -37,6 +36,7 @@ import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.PopupMenu import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -46,7 +46,6 @@ import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target -import com.github.dhaval2404.imagepicker.ImagePicker import com.google.gson.Gson import com.orhanobut.logger.Logger import io.agora.rtc2.ClientRoleOptions @@ -67,8 +66,8 @@ import kr.co.vividnext.sodalive.agora.Agora import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.ImagePickerCropper import kr.co.vividnext.sodalive.common.LoadingDialog -import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SodaLiveService import kr.co.vividnext.sodalive.databinding.ActivityLiveRoomBinding @@ -117,6 +116,8 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB private lateinit var speakerListAdapter: LiveRoomProfileListAdapter private lateinit var loadingDialog: LoadingDialog + private lateinit var cropper: ImagePickerCropper + private lateinit var imm: InputMethodManager private val handler = Handler(Looper.getMainLooper()) @@ -235,20 +236,6 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB } } - private val imageResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - // Image Uri will not be null for RESULT_OK - val fileUri = data?.data!! - roomInfoEditDialog.setCoverImageUri(fileUri) - } else if (resultCode == ImagePicker.RESULT_ERROR) { - Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() - } - } - private val rouletteConfigResult = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> @@ -280,9 +267,6 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB super.onCreate(savedInstanceState) onBackPressedDispatcher.addCallback(this, onBackPressedCallback) - viewModel.getRealPathFromURI = { - RealPathUtil.getRealPath(applicationContext, it) - } this.roomId = intent.getLongExtra(Constants.EXTRA_ROOM_ID, 0) if (roomId <= 0) { @@ -321,27 +305,34 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB bindData() loadingDialog = LoadingDialog(this, layoutInflater) + + cropper = ImagePickerCropper( + caller = this, + context = this, + excludeGif = true, + config = ImagePickerCropper.Config( + aspectX = 2f, aspectY = 3.8f, + maxWidth = 1080, maxHeight = 2052, + compressFormat = Bitmap.CompressFormat.JPEG, + compressQuality = 90 + ), + onSuccess = { file, uri -> + roomInfoEditDialog.setCoverImageUri(file) + }, + onError = { e -> + Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show() + } + ) + imm = getSystemService( - Service.INPUT_METHOD_SERVICE + INPUT_METHOD_SERVICE ) as InputMethodManager roomDialog = LiveRoomDialog(this, layoutInflater) roomInfoEditDialog = LiveRoomInfoEditDialog( activity = this, layoutInflater = layoutInflater, - onClickImagePicker = { - ImagePicker.with(this) - .crop() - .galleryOnly() - .galleryMimeTypes( // Exclude gif images - mimeTypes = arrayOf( - "image/png", - "image/jpg", - "image/jpeg" - ) - ) - .createIntent { imageResult.launch(it) } - } + onClickImagePicker = { cropper.launch() } ) roomProfileDialog = LiveRoomProfileDialog( layoutInflater = layoutInflater, @@ -559,6 +550,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB } override fun onDestroy() { + cropper.cleanup() hideKeyboard { viewModel.quitRoom(roomId) { SodaLiveService.stopService(this) @@ -960,7 +952,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB ) roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl) roomInfoEditDialog.setMenuPreset(it) - roomInfoEditDialog.setConfirmAction { newTitle, newContent, newCoverImageUri, isActivateMenu, menuId, menu, isAdult, isEntryMessageEnabled -> + roomInfoEditDialog.setConfirmAction { newTitle, newContent, newCoverImageFile, isActivateMenu, menuId, menu, isAdult, isEntryMessageEnabled -> if (isEntryMessageEnabled != null) { this.isEntryMessageEnabled = isEntryMessageEnabled } @@ -969,12 +961,13 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB response.roomId, newTitle, newContent, - newCoverImageUri, + newCoverImageFile, isActivateMenu, menuId, menu, isAdult, onSuccess = { + cropper.cleanup() Toast.makeText( applicationContext, "라이브 정보가 수정되었습니다.", @@ -1280,7 +1273,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB val clickableSpan = object : ClickableSpan() { override fun onClick(widget: View) { val url = spannable.subSequence(start, end).toString() - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + startActivity(Intent(Intent.ACTION_VIEW, url.toUri())) } } spannable.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt index 905804a6..a8a8cdfe 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt @@ -1,6 +1,5 @@ package kr.co.vividnext.sodalive.live.room -import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.google.gson.Gson @@ -99,8 +98,6 @@ class LiveRoomViewModel( val isSignatureOn: LiveData get() = _isSignatureOn - lateinit var getRealPathFromURI: (Uri) -> String? - private val blockedMemberIdList: MutableList = mutableListOf() val mutex = Mutex() @@ -430,7 +427,7 @@ class LiveRoomViewModel( roomId: Long, newTitle: String, newContent: String, - newCoverImageUri: Uri? = null, + newCoverImageFile: File? = null, isActivateMenu: Boolean?, menuId: Long, menu: String, @@ -462,7 +459,7 @@ class LiveRoomViewModel( request.notice == null && menu == roomInfoResponse.menuPan && request.isAdult == null && - newCoverImageUri == null && + newCoverImageFile == null && request.isActiveMenuPan == null ) { return @@ -480,8 +477,8 @@ class LiveRoomViewModel( null } - val coverImage = if (newCoverImageUri != null) { - val file = File(getRealPathFromURI(newCoverImageUri)) + val coverImage = if (newCoverImageFile != null) { + val file = newCoverImageFile MultipartBody.Part.createFormData( "coverImage", file.name, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/create/LiveRoomCreateActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/create/LiveRoomCreateActivity.kt index 5b861186..82860ad5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/create/LiveRoomCreateActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/create/LiveRoomCreateActivity.kt @@ -131,6 +131,7 @@ class LiveRoomCreateActivity : BaseActivity( cropper = ImagePickerCropper( caller = this, context = this, + excludeGif = true, config = ImagePickerCropper.Config( aspectX = 2f, aspectY = 3.8f, maxWidth = 1080, maxHeight = 2052, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/update/LiveRoomInfoEditDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/update/LiveRoomInfoEditDialog.kt index a32ca4c5..2be2046c 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/update/LiveRoomInfoEditDialog.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/update/LiveRoomInfoEditDialog.kt @@ -2,8 +2,6 @@ package kr.co.vividnext.sodalive.live.room.update import android.annotation.SuppressLint import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.net.Uri import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -15,15 +13,18 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toDrawable import androidx.lifecycle.MutableLiveData -import coil.load -import coil.transform.RoundedCornersTransformation +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.DialogLiveRoomInfoUpdateBinding import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateViewModel import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse +import java.io.File @SuppressLint("ClickableViewAccessibility") class LiveRoomInfoEditDialog( @@ -35,7 +36,7 @@ class LiveRoomInfoEditDialog( private val dialogView = DialogLiveRoomInfoUpdateBinding.inflate(layoutInflater) private var coverImageUrl: String? = null - private var coverImageUri: Uri? = null + private var coverImageFile: File? = null private var menuId = 0L private val menuList = mutableListOf() @@ -58,7 +59,7 @@ class LiveRoomInfoEditDialog( alertDialog = dialogBuilder.create() alertDialog.setCancelable(false) - alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) dialogView.ivPhotoPicker.setOnClickListener { onClickImagePicker() } dialogView.ivClose.setOnClickListener { alertDialog.dismiss() } @@ -185,22 +186,34 @@ class LiveRoomInfoEditDialog( isEntryMessageEnabledLiveData.value = isEntryMessageEnabled } - fun setCoverImageUri(coverImageUri: Uri) { - this.coverImageUri = coverImageUri - dialogView.ivCover.load(coverImageUri) { - crossfade(true) - placeholder(R.drawable.ic_place_holder) - transformations(RoundedCornersTransformation(13.3f.dpToPx())) - } + fun setCoverImageUri(file: File) { + this.coverImageFile = file + Glide.with(activity) + .load(file) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions().transform( + RoundedCorners( + 13.3f.dpToPx().toInt() + ) + ) + ) + .into(dialogView.ivCover) } fun setCoverImageUrl(coverImageUrl: String) { this.coverImageUrl = coverImageUrl - dialogView.ivCover.load(coverImageUrl) { - crossfade(true) - placeholder(R.drawable.ic_place_holder) - transformations(RoundedCornersTransformation(13.3f.dpToPx())) - } + Glide.with(activity) + .load(coverImageUrl) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions().transform( + RoundedCorners( + 13.3f.dpToPx().toInt() + ) + ) + ) + .into(dialogView.ivCover) } fun setMenuPreset(menuList: List) { @@ -223,7 +236,7 @@ class LiveRoomInfoEditDialog( } fun setConfirmAction( - confirmAction: (String, String, Uri?, Boolean?, Long, String, Boolean?, Boolean?) -> Unit + confirmAction: (String, String, File?, Boolean?, Long, String, Boolean?, Boolean?) -> Unit ) { dialogView.tvConfirm.setOnClickListener { alertDialog.dismiss() @@ -235,7 +248,7 @@ class LiveRoomInfoEditDialog( confirmAction( newTitle, newContent, - coverImageUri, + coverImageFile, if (isActivateMenu != null) { isActivateMenu } else if ( @@ -262,7 +275,7 @@ class LiveRoomInfoEditDialog( null } ) - coverImageUri = null + coverImageFile = null coverImageUrl = null } } @@ -291,7 +304,7 @@ class LiveRoomInfoEditDialog( ) ) - if (menuList.size > 0) { + if (menuList.isNotEmpty()) { dialogView.llSelectMenu2.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b) dialogView.tvSelectMenu2.setTextColor( ContextCompat.getColor( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt index 932d2e85..babb2282 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt @@ -1,21 +1,23 @@ package kr.co.vividnext.sodalive.mypage.profile import android.content.Intent +import android.graphics.Bitmap import android.os.Bundle import android.view.View import android.widget.LinearLayout import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import coil.load import coil.transform.CircleCropTransformation -import com.github.dhaval2404.imagepicker.ImagePicker +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CircleCrop +import com.bumptech.glide.request.RequestOptions import com.jakewharton.rxbinding4.widget.textChanges import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.ImagePickerCropper import kr.co.vividnext.sodalive.common.LoadingDialog -import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityProfileUpdateBinding import kr.co.vividnext.sodalive.databinding.ItemLiveTagSelectedBinding @@ -33,27 +35,6 @@ class ProfileUpdateActivity : BaseActivity( private val viewModel: ProfileUpdateViewModel by inject() - private val imageResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - // Image Uri will not be null for RESULT_OK - val fileUri = data?.data!! - binding.ivProfile.background = null - viewModel.updateProfileImage(fileUri) { - SharedPreferenceManager.profileImage = it - binding.ivProfile.load(it) { - crossfade(true) - transformations(CircleCropTransformation()) - } - } - } else if (resultCode == ImagePicker.RESULT_ERROR) { - Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() - } - } - private val tagFragment: MemberTagFragment by lazy { MemberTagFragment(viewModel.tags) { tag, isChecked -> when { @@ -76,22 +57,23 @@ class ProfileUpdateActivity : BaseActivity( private lateinit var loadingDialog: LoadingDialog + private lateinit var cropper: ImagePickerCropper + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - viewModel.getRealPathFromURI = { - RealPathUtil.getRealPath(applicationContext, it) - } - bindData() } override fun onStart() { super.onStart() - viewModel.getUserInfo() } + override fun onDestroy() { + cropper.cleanup() + super.onDestroy() + } + private fun bindData() { compositeDisposable.add( binding.etBlog.textChanges().skip(1) @@ -220,6 +202,34 @@ class ProfileUpdateActivity : BaseActivity( loadingDialog = LoadingDialog(this, layoutInflater) + cropper = ImagePickerCropper( + caller = this, + context = this, + isEnabledFreeStyleCrop = false, + config = ImagePickerCropper.Config( + aspectX = 1f, aspectY = 1f, + compressFormat = Bitmap.CompressFormat.JPEG, + compressQuality = 90 + ), + onSuccess = { file, _ -> + binding.ivProfile.background = null + viewModel.updateProfileImage(file) { + SharedPreferenceManager.profileImage = it + Glide.with(this) + .load(it) + .placeholder(R.drawable.ic_place_holder) + .apply( + RequestOptions() + .transform(CircleCrop()) + ) + .into(binding.ivProfile) + } + }, + onError = { e -> + Toast.makeText(this, "${e.message}", Toast.LENGTH_SHORT).show() + } + ) + compositeDisposable.add( binding.etIntroduce.textChanges() .debounce(500, TimeUnit.MILLISECONDS) @@ -241,19 +251,7 @@ class ProfileUpdateActivity : BaseActivity( viewModel.changeGender(Gender.NONE) } - binding.ivPhotoPicker.setOnClickListener { - ImagePicker.with(this) - .crop() - .galleryOnly() - .galleryMimeTypes( // Exclude gif images - mimeTypes = arrayOf( - "image/png", - "image/jpg", - "image/jpeg" - ) - ) - .createIntent { imageResult.launch(it) } - } + binding.ivPhotoPicker.setOnClickListener { cropper.launch() } binding.tvSelectTag.setOnClickListener { if (tagFragment.isAdded) return@setOnClickListener diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt index 8565934a..193871e7 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt @@ -1,6 +1,5 @@ package kr.co.vividnext.sodalive.mypage.profile -import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.orhanobut.logger.Logger @@ -53,8 +52,6 @@ class ProfileUpdateViewModel(private val repository: UserRepository) : BaseViewM val isLoading: LiveData get() = _isLoading - lateinit var getRealPathFromURI: (Uri) -> String? - fun getUserInfo() { compositeDisposable.add( repository.getProfile("Bearer ${SharedPreferenceManager.token}") @@ -90,8 +87,7 @@ class ProfileUpdateViewModel(private val repository: UserRepository) : BaseViewM _genderLiveData.postValue(gender) } - fun updateProfileImage(uri: Uri, onSuccess: (String) -> Unit) { - val file = File(getRealPathFromURI(uri)) + fun updateProfileImage(file: File, onSuccess: (String) -> Unit) { val image = MultipartBody.Part.createFormData( "image", file.name,