fix(이미지 선택): 이미지 선택 및 크롭 로직 수정

This commit is contained in:
2025-09-18 00:17:20 +09:00
parent 02155065f7
commit d22907c7d5
16 changed files with 397 additions and 571 deletions

View File

@@ -148,7 +148,6 @@ dependencies {
// permission // permission
implementation "io.github.ParkSangGwon:tedpermission-normal:3.3.0" 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.yalantis:ucrop:2.2.11'
implementation 'com.github.zhpanvip:bannerviewpager:3.5.7' implementation 'com.github.zhpanvip:bannerviewpager:3.5.7'

View File

@@ -2,16 +2,18 @@ package kr.co.vividnext.sodalive.audio_content.modify
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.setPadding import androidx.core.view.setPadding
import coil.load import coil.load
import coil.transform.RoundedCornersTransformation 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.PermissionListener
import com.gun0912.tedpermission.normal.TedPermission import com.gun0912.tedpermission.normal.TedPermission
import com.jakewharton.rxbinding4.widget.textChanges 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.R
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants 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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentModifyBinding import kr.co.vividnext.sodalive.databinding.ActivityAudioContentModifyBinding
@@ -33,36 +36,7 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
private val viewModel: AudioContentModifyViewModel by inject() private val viewModel: AudioContentModifyViewModel by inject()
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var cropper: ImagePickerCropper
private val imageResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
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()
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -82,24 +56,50 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() } viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
} }
override fun onDestroy() {
cropper.cleanup()
super.onDestroy()
}
override fun setupView() { override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater) loadingDialog = LoadingDialog(this, layoutInflater)
cropper = ImagePickerCropper(
caller = this,
context = this,
excludeGif = true,
isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90
),
onSuccess = { file, uri ->
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.text = "콘텐츠 수정"
binding.toolbar.tvBack.setOnClickListener { finish() } binding.toolbar.tvBack.setOnClickListener { finish() }
binding.ivPhotoPicker.setOnClickListener { binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
ImagePicker.with(this)
.crop()
.galleryOnly()
.galleryMimeTypes( // Exclude gif images
mimeTypes = arrayOf(
"image/png",
"image/jpg",
"image/jpeg"
)
)
.createIntent { imageResult.launch(it) }
}
binding.llAvailablePoint.setOnClickListener { viewModel.setAvailablePoint(true) } binding.llAvailablePoint.setOnClickListener { viewModel.setAvailablePoint(true) }
binding.llNotAvailablePoint.setOnClickListener { viewModel.setAvailablePoint(false) } binding.llNotAvailablePoint.setOnClickListener { viewModel.setAvailablePoint(false) }
@@ -239,8 +239,8 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
viewModel.setAdult(true) viewModel.setAdult(true)
} }
viewModel.isAdultLiveData.observe(this) { viewModel.isAdultLiveData.observe(this) { isAdult ->
if (it) { if (isAdult) {
binding.ivAgeAll.visibility = View.GONE binding.ivAgeAll.visibility = View.GONE
binding.llAgeAll.setBackgroundResource( binding.llAgeAll.setBackgroundResource(
R.drawable.bg_round_corner_6_7_13181b R.drawable.bg_round_corner_6_7_13181b

View File

@@ -68,7 +68,7 @@ class AudioContentModifyViewModel(
var title: String? = null var title: String? = null
var detail: String? = null var detail: String? = null
var tags: String? = null var tags: String? = null
var coverImageUri: Uri? = null var coverImageFile: File? = null
var isPointAvailable: Boolean? = null var isPointAvailable: Boolean? = null
fun setAdult(isAdult: Boolean) { fun setAdult(isAdult: Boolean) {
@@ -154,8 +154,8 @@ class AudioContentModifyViewModel(
val requestJson = Gson().toJson(request) val requestJson = Gson().toJson(request)
val coverImage = if (coverImageUri != null) { val coverImage = if (coverImageFile != null) {
val file = File(getRealPathFromURI(coverImageUri!!)) val file = coverImageFile!!
MultipartBody.Part.createFormData( MultipartBody.Part.createFormData(
"coverImage", "coverImage",
file.name, file.name,

View File

@@ -5,6 +5,7 @@ import android.annotation.SuppressLint
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.app.TimePickerDialog import android.app.TimePickerDialog
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -15,8 +16,9 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import coil.transform.RoundedCornersTransformation import com.bumptech.glide.Glide
import com.github.dhaval2404.imagepicker.ImagePicker import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.normal.TedPermission import com.gun0912.tedpermission.normal.TedPermission
import com.jakewharton.rxbinding4.widget.textChanges 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.PurchaseOption
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeFragment import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeFragment
import kr.co.vividnext.sodalive.base.BaseActivity 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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
@@ -48,6 +51,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
private val viewModel: AudioContentUploadViewModel by inject() private val viewModel: AudioContentUploadViewModel by inject()
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var cropper: ImagePickerCropper
private val themeFragment: AudioContentThemeFragment by lazy { private val themeFragment: AudioContentThemeFragment by lazy {
AudioContentThemeFragment( AudioContentThemeFragment(
@@ -66,35 +70,6 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
) )
} }
private val imageResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
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( private val selectAudioActivityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
@@ -113,18 +88,29 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
} else if (resultCode == ImagePicker.RESULT_ERROR) { } else {
binding.tvSelectContent.text = "파일 선택" binding.tvSelectContent.text = "파일 선택"
viewModel.contentUri = null viewModel.contentUri = null
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() Toast.makeText(
this,
"파일 선택을 실패했습니다.\n다시 시도해 주세요.",
Toast.LENGTH_SHORT
).show()
} }
} }
private val datePickerDialogListener = private val datePickerDialogListener =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
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( viewModel.setReservationDate(
String.format( String.format(
Locale.getDefault(),
"%d.%02d.%02d", "%d.%02d.%02d",
year, year,
monthOfYear + 1, monthOfYear + 1,
@@ -135,7 +121,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
private val timePickerDialogListener = private val timePickerDialogListener =
TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute -> TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->
val timeString = String.format("%02d:%02d", hourOfDay, minute) val timeString = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute)
viewModel.releaseTime = timeString viewModel.releaseTime = timeString
viewModel.setReservationTime(timeString.convertDateFormat("HH:mm", "a hh:mm")) viewModel.setReservationTime(timeString.convertDateFormat("HH:mm", "a hh:mm"))
} }
@@ -151,9 +137,45 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
bindData() bindData()
} }
override fun onDestroy() {
cropper.cleanup()
super.onDestroy()
}
override fun setupView() { override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater) loadingDialog = LoadingDialog(this, layoutInflater)
cropper = ImagePickerCropper(
caller = this,
context = this,
excludeGif = true,
isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90
),
onSuccess = { file, uri ->
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) { binding.tvServiceDate.text = if (SharedPreferenceManager.userId == 17958L) {
"※ 이용기간 : 대여(5일) | 소장(이용 기간 1년)" "※ 이용기간 : 대여(5일) | 소장(이용 기간 1년)"
} else { } else {
@@ -167,19 +189,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
themeFragment.show(supportFragmentManager, themeFragment.tag) themeFragment.show(supportFragmentManager, themeFragment.tag)
} }
binding.ivPhotoPicker.setOnClickListener { binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
ImagePicker.with(this)
.crop()
.galleryOnly()
.galleryMimeTypes( // Exclude gif images
mimeTypes = arrayOf(
"image/png",
"image/jpg",
"image/jpeg"
)
)
.createIntent { imageResult.launch(it) }
}
binding.tvSelectContent.setOnClickListener { binding.tvSelectContent.setOnClickListener {
val intent = Intent().apply { val intent = Intent().apply {
@@ -359,7 +369,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
.subscribe { .subscribe {
val price = it.toString().toIntOrNull() val price = it.toString().toIntOrNull()
if (price != null) { if (price != null) {
viewModel.price = price.toInt() viewModel.price = price
} else { } else {
viewModel.price = 0 viewModel.price = 0
if (it.isNotBlank()) { if (it.isNotBlank()) {
@@ -377,7 +387,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
.subscribe { .subscribe {
val limited = it.toString().toIntOrNull() val limited = it.toString().toIntOrNull()
if (limited != null) { if (limited != null) {
viewModel.limited = limited.toInt() viewModel.limited = limited
} else { } else {
viewModel.limited = 0 viewModel.limited = 0
if (it.isNotBlank()) { if (it.isNotBlank()) {

View File

@@ -89,7 +89,7 @@ class AudioContentUploadViewModel(
var releaseDate = "" var releaseDate = ""
var releaseTime = "" var releaseTime = ""
var theme: GetAudioContentThemeResponse? = null var theme: GetAudioContentThemeResponse? = null
var coverImageUri: Uri? = null var coverImageFile: File? = null
var contentUri: Uri? = null var contentUri: Uri? = null
var previewStartTime: String? = null var previewStartTime: String? = null
var previewEndTime: String? = null var previewEndTime: String? = null
@@ -203,8 +203,8 @@ class AudioContentUploadViewModel(
val requestJson = Gson().toJson(request) val requestJson = Gson().toJson(request)
val coverImage = if (coverImageUri != null) { val coverImage = if (coverImageFile != null) {
val file = File(getRealPathFromURI(coverImageUri!!)) val file = coverImageFile!!
MultipartBody.Part.createFormData( MultipartBody.Part.createFormData(
"coverImage", "coverImage",
file.name, file.name,
@@ -323,7 +323,7 @@ class AudioContentUploadViewModel(
return false return false
} }
if (coverImageUri == null) { if (coverImageFile == null) {
_toastLiveData.postValue("커버이미지를 선택해 주세요.") _toastLiveData.postValue("커버이미지를 선택해 주세요.")
return false return false
} }
@@ -413,7 +413,7 @@ class AudioContentUploadViewModel(
// Check if the time difference is greater than 30 seconds (30000 milliseconds) // Check if the time difference is greater than 30 seconds (30000 milliseconds)
return date2.time - date1.time return date2.time - date1.time
} catch (e: Exception) { } catch (_: Exception) {
// Handle invalid time formats or parsing errors // Handle invalid time formats or parsing errors
return 0 return 0
} }

View File

@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns
import androidx.activity.result.ActivityResultCaller import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
@@ -13,6 +14,7 @@ import com.yalantis.ucrop.UCrop
import com.yalantis.ucrop.UCropActivity import com.yalantis.ucrop.UCropActivity
import kr.co.vividnext.sodalive.BuildConfig import kr.co.vividnext.sodalive.BuildConfig
import java.io.File import java.io.File
import java.io.FileOutputStream
/** /**
* 단일 이미지 선택(13+ Photo Picker / 12- GetContent) → uCrop(기본 9:20) → [File, Uri] 반환 * 단일 이미지 선택(13+ Photo Picker / 12- GetContent) → uCrop(기본 9:20) → [File, Uri] 반환
@@ -20,8 +22,10 @@ import java.io.File
* - 결과 파일은 cacheDir에 임시 생성 → 업로드 후 cleanup() 호출로 삭제 * - 결과 파일은 cacheDir에 임시 생성 → 업로드 후 cleanup() 호출로 삭제
*/ */
class ImagePickerCropper( class ImagePickerCropper(
private val caller: ActivityResultCaller, caller: ActivityResultCaller,
private val context: Context, private val context: Context,
private val excludeGif: Boolean = false,
private val isEnabledFreeStyleCrop: Boolean = false,
private val config: Config = Config(), private val config: Config = Config(),
private val onSuccess: (file: File, uri: Uri) -> Unit, private val onSuccess: (file: File, uri: Uri) -> Unit,
private val onError: (Throwable) -> Unit = { it.printStackTrace() } private val onError: (Throwable) -> Unit = { it.printStackTrace() }
@@ -41,15 +45,21 @@ class ImagePickerCropper(
// 13+ : 시스템 Photo Picker // 13+ : 시스템 Photo Picker
private val pickPhoto: ActivityResultLauncher<PickVisualMediaRequest> = private val pickPhoto: ActivityResultLauncher<PickVisualMediaRequest> =
caller.registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> caller.registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri == null) onError(CancellationException("User cancelled picking.")) if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다."))
else startCrop(uri) else handlePickedUri(uri)
} }
// 12- : SAF GetContent // 12- : SAF GetContent
private val pickContent: ActivityResultLauncher<String> = private val pickContent: ActivityResultLauncher<String> =
caller.registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> caller.registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri == null) onError(CancellationException("User cancelled picking.")) if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다."))
else startCrop(uri) else handlePickedUri(uri)
}
private val openDocument =
caller.registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다."))
else handlePickedUri(uri)
} }
// uCrop 결과 수신 // uCrop 결과 수신
@@ -62,20 +72,24 @@ class ImagePickerCropper(
if (out != null && file != null && file.exists()) { if (out != null && file != null && file.exists()) {
onSuccess(file, out) onSuccess(file, out)
} else { } else {
onError(IllegalStateException("Crop finished but no output file/uri")) onError(IllegalStateException("이미지 크롭을 실패했습니다.\n다시 시도해 주세요"))
} }
} }
UCrop.RESULT_ERROR -> onError( 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() { fun launch() {
if (excludeGif) {
openDocument.launch(arrayOf("image/png", "image/jpg", "image/jpeg"))
} else {
if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(context)) { if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(context)) {
pickPhoto.launch( pickPhoto.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
@@ -84,9 +98,7 @@ class ImagePickerCropper(
pickContent.launch("image/*") pickContent.launch("image/*")
} }
} }
}
/** 마지막 크롭 결과 파일 (있으면) */
fun getCroppedFile(): File? = lastCroppedFile?.takeIf { it.exists() }
/** 임시 파일 삭제 */ /** 임시 파일 삭제 */
fun cleanup() { fun cleanup() {
@@ -94,6 +106,64 @@ class ImagePickerCropper(
lastCroppedFile = null 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) { private fun startCrop(source: Uri) {
val destFile = createTempCropFile() val destFile = createTempCropFile()
lastCroppedFile = destFile lastCroppedFile = destFile
@@ -110,8 +180,8 @@ class ImagePickerCropper(
setCompressionQuality(config.compressQuality) setCompressionQuality(config.compressQuality)
// 제스처/컨트롤 (필요시 조절) // 제스처/컨트롤 (필요시 조절)
setFreeStyleCropEnabled(false) setFreeStyleCropEnabled(isEnabledFreeStyleCrop)
setHideBottomControls(false) setHideBottomControls(true)
setAllowedGestures( setAllowedGestures(
UCropActivity.SCALE, // Aspect 줄이진 못하지만 확대/축소 UCropActivity.SCALE, // Aspect 줄이진 못하지만 확대/축소
UCropActivity.ROTATE, // 회전 UCropActivity.ROTATE, // 회전

View File

@@ -2,35 +2,29 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.graphics.Bitmap
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.github.dhaval2404.imagepicker.ImagePicker
import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.normal.TedPermission import com.gun0912.tedpermission.normal.TedPermission
import com.jakewharton.rxbinding4.widget.textChanges 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.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants 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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityModifyBinding import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityModifyBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.io.File
class CreatorCommunityModifyActivity : BaseActivity<ActivityCreatorCommunityModifyBinding>( class CreatorCommunityModifyActivity : BaseActivity<ActivityCreatorCommunityModifyBinding>(
ActivityCreatorCommunityModifyBinding::inflate ActivityCreatorCommunityModifyBinding::inflate
@@ -39,142 +33,11 @@ class CreatorCommunityModifyActivity : BaseActivity<ActivityCreatorCommunityModi
private val viewModel: CreatorCommunityModifyViewModel by inject() private val viewModel: CreatorCommunityModifyViewModel by inject()
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var croppedTempFile: File private lateinit var cropper: ImagePickerCropper
private val imageCropResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
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()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
checkPermissions() checkPermissions()
viewModel.getRealPathFromURI = {
RealPathUtil.getRealPath(applicationContext, it)
}
bindData() bindData()
val postId = intent.getLongExtra(Constants.EXTRA_COMMUNITY_POST_ID, 0) val postId = intent.getLongExtra(Constants.EXTRA_COMMUNITY_POST_ID, 0)
@@ -193,31 +56,47 @@ class CreatorCommunityModifyActivity : BaseActivity<ActivityCreatorCommunityModi
} }
override fun onDestroy() { override fun onDestroy() {
deleteCroppedTempFile() cropper.cleanup()
super.onDestroy() super.onDestroy()
} }
private fun deleteCroppedTempFile() {
try {
if (::croppedTempFile.isInitialized && croppedTempFile.exists()) {
val deleted = croppedTempFile.delete()
if (!deleted) {
Logger.w("임시 파일 삭제 실패: ${croppedTempFile.absolutePath}")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun setupView() { override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater) loadingDialog = LoadingDialog(this, layoutInflater)
cropper = ImagePickerCropper(
caller = this,
context = this,
isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90
),
onSuccess = { file, uri ->
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.text = "게시글 등록"
binding.toolbar.tvBack.setOnClickListener { finish() } binding.toolbar.tvBack.setOnClickListener { finish() }
binding.ivPhotoPicker.setOnClickListener { launchImagePicker() } binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
if (SharedPreferenceManager.isAuth) { if (SharedPreferenceManager.isAuth) {
binding.llSetAdult.visibility = View.VISIBLE binding.llSetAdult.visibility = View.VISIBLE

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify
import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson import com.google.gson.Gson
@@ -46,11 +45,9 @@ class CreatorCommunityModifyViewModel(
val isAvailableCommentLiveData: LiveData<Boolean> val isAvailableCommentLiveData: LiveData<Boolean>
get() = _isAvailableCommentLiveData get() = _isAvailableCommentLiveData
lateinit var getRealPathFromURI: (Uri) -> String?
var postId = 0L var postId = 0L
var content = "" var content = ""
var imageUri: Uri? = null var imageFile: File? = null
private var communityPost: GetCommunityPostListResponse? = null private var communityPost: GetCommunityPostListResponse? = null
fun setAdult(isAdult: Boolean) { fun setAdult(isAdult: Boolean) {
@@ -134,8 +131,8 @@ class CreatorCommunityModifyViewModel(
val requestJson = Gson().toJson(request) val requestJson = Gson().toJson(request)
val postImage = if (imageUri != null) { val postImage = if (imageFile != null) {
val file = File(getRealPathFromURI(imageUri!!)) val file = imageFile!!
MultipartBody.Part.createFormData( MultipartBody.Part.createFormData(
"postImage", "postImage",
file.name, file.name,

View File

@@ -2,29 +2,24 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.graphics.Bitmap
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.github.dhaval2404.imagepicker.ImagePicker
import com.gun0912.tedpermission.PermissionListener import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.normal.TedPermission import com.gun0912.tedpermission.normal.TedPermission
import com.jakewharton.rxbinding4.widget.textChanges 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.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity 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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityWriteBinding import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityWriteBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
@@ -38,154 +33,18 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
private val viewModel: CreatorCommunityWriteViewModel by inject() private val viewModel: CreatorCommunityWriteViewModel by inject()
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var croppedTempFile: File
private val imageCropResult = registerForActivityResult( private lateinit var cropper: ImagePickerCropper
ActivityResultContracts.StartActivityForResult()
) { result ->
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()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
checkPermissions() checkPermissions()
viewModel.getRealPathFromURI = {
RealPathUtil.getRealPath(applicationContext, it)
}
bindData() bindData()
} }
override fun onDestroy() { override fun onDestroy() {
cropper.cleanup()
deleteAudioFile() deleteAudioFile()
deleteCroppedTempFile()
super.onDestroy() super.onDestroy()
} }
@@ -195,22 +54,39 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
} }
} }
private fun deleteCroppedTempFile() {
try {
if (::croppedTempFile.isInitialized && croppedTempFile.exists()) {
val deleted = croppedTempFile.delete()
if (!deleted) {
Logger.w("임시 파일 삭제 실패: ${croppedTempFile.absolutePath}")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun setupView() { override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater) loadingDialog = LoadingDialog(this, layoutInflater)
cropper = ImagePickerCropper(
caller = this,
context = this,
isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90
),
onSuccess = { file, uri ->
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.text = "게시글 등록"
binding.toolbar.tvBack.setOnClickListener { finish() } binding.toolbar.tvBack.setOnClickListener { finish() }
@@ -220,7 +96,7 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
fragment.show(supportFragmentManager, fragment.tag) fragment.show(supportFragmentManager, fragment.tag)
} }
binding.ivPhotoPicker.setOnClickListener { launchImagePicker() } binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
if (SharedPreferenceManager.isAuth) { if (SharedPreferenceManager.isAuth) {
binding.llSetAdult.visibility = View.VISIBLE binding.llSetAdult.visibility = View.VISIBLE
@@ -278,7 +154,7 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
.subscribe { .subscribe {
val price = it.toString().toIntOrNull() val price = it.toString().toIntOrNull()
if (price != null) { if (price != null) {
viewModel.price = price.toInt() viewModel.price = price
} else { } else {
viewModel.price = 0 viewModel.price = 0
if (it.isNotBlank()) { if (it.isNotBlank()) {

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.explorer.profile.creator_community.write package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson import com.google.gson.Gson
@@ -45,12 +44,10 @@ class CreatorCommunityWriteViewModel(
val isShowPriceUiLiveData: LiveData<Boolean> val isShowPriceUiLiveData: LiveData<Boolean>
get() = _isShowPriceUiLiveData get() = _isShowPriceUiLiveData
lateinit var getRealPathFromURI: (Uri) -> String?
var price = 0 var price = 0
var content = "" var content = ""
var audioFile: File? = null var audioFile: File? = null
private var imageUri: Uri? = null private var imageFile: File? = null
fun setAdult(isAdult: Boolean) { fun setAdult(isAdult: Boolean) {
_isAdultLiveData.postValue(isAdult) _isAdultLiveData.postValue(isAdult)
@@ -64,8 +61,8 @@ class CreatorCommunityWriteViewModel(
_isPriceFreeLiveData.postValue(isPriceFree) _isPriceFreeLiveData.postValue(isPriceFree)
} }
fun setImageUri(uri: Uri) { fun setImageFile(file: File) {
this.imageUri = uri this.imageFile = file
_isShowPriceUiLiveData.postValue(true) _isShowPriceUiLiveData.postValue(true)
} }
@@ -82,8 +79,8 @@ class CreatorCommunityWriteViewModel(
val requestJson = Gson().toJson(request) val requestJson = Gson().toJson(request)
val postImage = if (imageUri != null) { val postImage = if (imageFile != null) {
val file = File(getRealPathFromURI(imageUri!!)) val file = imageFile!!
MultipartBody.Part.createFormData( MultipartBody.Part.createFormData(
"postImage", "postImage",
file.name, file.name,
@@ -188,12 +185,12 @@ class CreatorCommunityWriteViewModel(
return false return false
} }
if (imageUri == null) { if (imageFile == null) {
_toastLiveData.postValue("유료 게시글 등록을 위해서는 이미지가 필요합니다.") _toastLiveData.postValue("유료 게시글 등록을 위해서는 이미지가 필요합니다.")
return false return false
} }
} }
} catch (e: Exception) { } catch (_: Exception) {
_toastLiveData.postValue("가격은 숫자만 입력 가능 합니다.") _toastLiveData.postValue("가격은 숫자만 입력 가능 합니다.")
return false return false
} }

View File

@@ -3,15 +3,14 @@ package kr.co.vividnext.sodalive.live.room
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Service
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Rect import android.graphics.Rect
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
@@ -37,6 +36,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView 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.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.github.dhaval2404.imagepicker.ImagePicker
import com.google.gson.Gson import com.google.gson.Gson
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.agora.rtc2.ClientRoleOptions 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.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants 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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.SodaLiveService import kr.co.vividnext.sodalive.common.SodaLiveService
import kr.co.vividnext.sodalive.databinding.ActivityLiveRoomBinding import kr.co.vividnext.sodalive.databinding.ActivityLiveRoomBinding
@@ -117,6 +116,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private lateinit var speakerListAdapter: LiveRoomProfileListAdapter private lateinit var speakerListAdapter: LiveRoomProfileListAdapter
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var cropper: ImagePickerCropper
private lateinit var imm: InputMethodManager private lateinit var imm: InputMethodManager
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
@@ -235,20 +236,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(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( private val rouletteConfigResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
@@ -280,9 +267,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback) onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
viewModel.getRealPathFromURI = {
RealPathUtil.getRealPath(applicationContext, it)
}
this.roomId = intent.getLongExtra(Constants.EXTRA_ROOM_ID, 0) this.roomId = intent.getLongExtra(Constants.EXTRA_ROOM_ID, 0)
if (roomId <= 0) { if (roomId <= 0) {
@@ -321,27 +305,34 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
bindData() bindData()
loadingDialog = LoadingDialog(this, layoutInflater) 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( imm = getSystemService(
Service.INPUT_METHOD_SERVICE INPUT_METHOD_SERVICE
) as InputMethodManager ) as InputMethodManager
roomDialog = LiveRoomDialog(this, layoutInflater) roomDialog = LiveRoomDialog(this, layoutInflater)
roomInfoEditDialog = LiveRoomInfoEditDialog( roomInfoEditDialog = LiveRoomInfoEditDialog(
activity = this, activity = this,
layoutInflater = layoutInflater, layoutInflater = layoutInflater,
onClickImagePicker = { onClickImagePicker = { cropper.launch() }
ImagePicker.with(this)
.crop()
.galleryOnly()
.galleryMimeTypes( // Exclude gif images
mimeTypes = arrayOf(
"image/png",
"image/jpg",
"image/jpeg"
)
)
.createIntent { imageResult.launch(it) }
}
) )
roomProfileDialog = LiveRoomProfileDialog( roomProfileDialog = LiveRoomProfileDialog(
layoutInflater = layoutInflater, layoutInflater = layoutInflater,
@@ -559,6 +550,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
override fun onDestroy() { override fun onDestroy() {
cropper.cleanup()
hideKeyboard { hideKeyboard {
viewModel.quitRoom(roomId) { viewModel.quitRoom(roomId) {
SodaLiveService.stopService(this) SodaLiveService.stopService(this)
@@ -960,7 +952,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl) roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl)
roomInfoEditDialog.setMenuPreset(it) 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) { if (isEntryMessageEnabled != null) {
this.isEntryMessageEnabled = isEntryMessageEnabled this.isEntryMessageEnabled = isEntryMessageEnabled
} }
@@ -969,12 +961,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
response.roomId, response.roomId,
newTitle, newTitle,
newContent, newContent,
newCoverImageUri, newCoverImageFile,
isActivateMenu, isActivateMenu,
menuId, menuId,
menu, menu,
isAdult, isAdult,
onSuccess = { onSuccess = {
cropper.cleanup()
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
"라이브 정보가 수정되었습니다.", "라이브 정보가 수정되었습니다.",
@@ -1280,7 +1273,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val clickableSpan = object : ClickableSpan() { val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) { override fun onClick(widget: View) {
val url = spannable.subSequence(start, end).toString() 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) spannable.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.live.room package kr.co.vividnext.sodalive.live.room
import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson import com.google.gson.Gson
@@ -99,8 +98,6 @@ class LiveRoomViewModel(
val isSignatureOn: LiveData<Boolean> val isSignatureOn: LiveData<Boolean>
get() = _isSignatureOn get() = _isSignatureOn
lateinit var getRealPathFromURI: (Uri) -> String?
private val blockedMemberIdList: MutableList<Long> = mutableListOf() private val blockedMemberIdList: MutableList<Long> = mutableListOf()
val mutex = Mutex() val mutex = Mutex()
@@ -430,7 +427,7 @@ class LiveRoomViewModel(
roomId: Long, roomId: Long,
newTitle: String, newTitle: String,
newContent: String, newContent: String,
newCoverImageUri: Uri? = null, newCoverImageFile: File? = null,
isActivateMenu: Boolean?, isActivateMenu: Boolean?,
menuId: Long, menuId: Long,
menu: String, menu: String,
@@ -462,7 +459,7 @@ class LiveRoomViewModel(
request.notice == null && request.notice == null &&
menu == roomInfoResponse.menuPan && menu == roomInfoResponse.menuPan &&
request.isAdult == null && request.isAdult == null &&
newCoverImageUri == null && newCoverImageFile == null &&
request.isActiveMenuPan == null request.isActiveMenuPan == null
) { ) {
return return
@@ -480,8 +477,8 @@ class LiveRoomViewModel(
null null
} }
val coverImage = if (newCoverImageUri != null) { val coverImage = if (newCoverImageFile != null) {
val file = File(getRealPathFromURI(newCoverImageUri)) val file = newCoverImageFile
MultipartBody.Part.createFormData( MultipartBody.Part.createFormData(
"coverImage", "coverImage",
file.name, file.name,

View File

@@ -131,6 +131,7 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
cropper = ImagePickerCropper( cropper = ImagePickerCropper(
caller = this, caller = this,
context = this, context = this,
excludeGif = true,
config = ImagePickerCropper.Config( config = ImagePickerCropper.Config(
aspectX = 2f, aspectY = 3.8f, aspectX = 2f, aspectY = 3.8f,
maxWidth = 1080, maxHeight = 2052, maxWidth = 1080, maxHeight = 2052,

View File

@@ -2,8 +2,6 @@ package kr.co.vividnext.sodalive.live.room.update
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
@@ -15,15 +13,18 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import coil.load import com.bumptech.glide.Glide
import coil.transform.RoundedCornersTransformation 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.R
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.DialogLiveRoomInfoUpdateBinding import kr.co.vividnext.sodalive.databinding.DialogLiveRoomInfoUpdateBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateViewModel import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateViewModel
import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse
import java.io.File
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
class LiveRoomInfoEditDialog( class LiveRoomInfoEditDialog(
@@ -35,7 +36,7 @@ class LiveRoomInfoEditDialog(
private val dialogView = DialogLiveRoomInfoUpdateBinding.inflate(layoutInflater) private val dialogView = DialogLiveRoomInfoUpdateBinding.inflate(layoutInflater)
private var coverImageUrl: String? = null private var coverImageUrl: String? = null
private var coverImageUri: Uri? = null private var coverImageFile: File? = null
private var menuId = 0L private var menuId = 0L
private val menuList = mutableListOf<GetMenuPresetResponse>() private val menuList = mutableListOf<GetMenuPresetResponse>()
@@ -58,7 +59,7 @@ class LiveRoomInfoEditDialog(
alertDialog = dialogBuilder.create() alertDialog = dialogBuilder.create()
alertDialog.setCancelable(false) alertDialog.setCancelable(false)
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
dialogView.ivPhotoPicker.setOnClickListener { onClickImagePicker() } dialogView.ivPhotoPicker.setOnClickListener { onClickImagePicker() }
dialogView.ivClose.setOnClickListener { alertDialog.dismiss() } dialogView.ivClose.setOnClickListener { alertDialog.dismiss() }
@@ -185,22 +186,34 @@ class LiveRoomInfoEditDialog(
isEntryMessageEnabledLiveData.value = isEntryMessageEnabled isEntryMessageEnabledLiveData.value = isEntryMessageEnabled
} }
fun setCoverImageUri(coverImageUri: Uri) { fun setCoverImageUri(file: File) {
this.coverImageUri = coverImageUri this.coverImageFile = file
dialogView.ivCover.load(coverImageUri) { Glide.with(activity)
crossfade(true) .load(file)
placeholder(R.drawable.ic_place_holder) .placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) .apply(
} RequestOptions().transform(
RoundedCorners(
13.3f.dpToPx().toInt()
)
)
)
.into(dialogView.ivCover)
} }
fun setCoverImageUrl(coverImageUrl: String) { fun setCoverImageUrl(coverImageUrl: String) {
this.coverImageUrl = coverImageUrl this.coverImageUrl = coverImageUrl
dialogView.ivCover.load(coverImageUrl) { Glide.with(activity)
crossfade(true) .load(coverImageUrl)
placeholder(R.drawable.ic_place_holder) .placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) .apply(
} RequestOptions().transform(
RoundedCorners(
13.3f.dpToPx().toInt()
)
)
)
.into(dialogView.ivCover)
} }
fun setMenuPreset(menuList: List<GetMenuPresetResponse>) { fun setMenuPreset(menuList: List<GetMenuPresetResponse>) {
@@ -223,7 +236,7 @@ class LiveRoomInfoEditDialog(
} }
fun setConfirmAction( 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 { dialogView.tvConfirm.setOnClickListener {
alertDialog.dismiss() alertDialog.dismiss()
@@ -235,7 +248,7 @@ class LiveRoomInfoEditDialog(
confirmAction( confirmAction(
newTitle, newTitle,
newContent, newContent,
coverImageUri, coverImageFile,
if (isActivateMenu != null) { if (isActivateMenu != null) {
isActivateMenu isActivateMenu
} else if ( } else if (
@@ -262,7 +275,7 @@ class LiveRoomInfoEditDialog(
null null
} }
) )
coverImageUri = null coverImageFile = null
coverImageUrl = 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.llSelectMenu2.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
dialogView.tvSelectMenu2.setTextColor( dialogView.tvSelectMenu2.setTextColor(
ContextCompat.getColor( ContextCompat.getColor(

View File

@@ -1,21 +1,23 @@
package kr.co.vividnext.sodalive.mypage.profile package kr.co.vividnext.sodalive.mypage.profile
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import coil.load import coil.load
import coil.transform.CircleCropTransformation 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 com.jakewharton.rxbinding4.widget.textChanges
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity 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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityProfileUpdateBinding import kr.co.vividnext.sodalive.databinding.ActivityProfileUpdateBinding
import kr.co.vividnext.sodalive.databinding.ItemLiveTagSelectedBinding import kr.co.vividnext.sodalive.databinding.ItemLiveTagSelectedBinding
@@ -33,27 +35,6 @@ class ProfileUpdateActivity : BaseActivity<ActivityProfileUpdateBinding>(
private val viewModel: ProfileUpdateViewModel by inject() 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 { private val tagFragment: MemberTagFragment by lazy {
MemberTagFragment(viewModel.tags) { tag, isChecked -> MemberTagFragment(viewModel.tags) { tag, isChecked ->
when { when {
@@ -76,22 +57,23 @@ class ProfileUpdateActivity : BaseActivity<ActivityProfileUpdateBinding>(
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var cropper: ImagePickerCropper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel.getRealPathFromURI = {
RealPathUtil.getRealPath(applicationContext, it)
}
bindData() bindData()
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
viewModel.getUserInfo() viewModel.getUserInfo()
} }
override fun onDestroy() {
cropper.cleanup()
super.onDestroy()
}
private fun bindData() { private fun bindData() {
compositeDisposable.add( compositeDisposable.add(
binding.etBlog.textChanges().skip(1) binding.etBlog.textChanges().skip(1)
@@ -220,6 +202,34 @@ class ProfileUpdateActivity : BaseActivity<ActivityProfileUpdateBinding>(
loadingDialog = LoadingDialog(this, layoutInflater) 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( compositeDisposable.add(
binding.etIntroduce.textChanges() binding.etIntroduce.textChanges()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
@@ -241,19 +251,7 @@ class ProfileUpdateActivity : BaseActivity<ActivityProfileUpdateBinding>(
viewModel.changeGender(Gender.NONE) viewModel.changeGender(Gender.NONE)
} }
binding.ivPhotoPicker.setOnClickListener { binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
ImagePicker.with(this)
.crop()
.galleryOnly()
.galleryMimeTypes( // Exclude gif images
mimeTypes = arrayOf(
"image/png",
"image/jpg",
"image/jpeg"
)
)
.createIntent { imageResult.launch(it) }
}
binding.tvSelectTag.setOnClickListener { binding.tvSelectTag.setOnClickListener {
if (tagFragment.isAdded) return@setOnClickListener if (tagFragment.isAdded) return@setOnClickListener

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.mypage.profile package kr.co.vividnext.sodalive.mypage.profile
import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
@@ -53,8 +52,6 @@ class ProfileUpdateViewModel(private val repository: UserRepository) : BaseViewM
val isLoading: LiveData<Boolean> val isLoading: LiveData<Boolean>
get() = _isLoading get() = _isLoading
lateinit var getRealPathFromURI: (Uri) -> String?
fun getUserInfo() { fun getUserInfo() {
compositeDisposable.add( compositeDisposable.add(
repository.getProfile("Bearer ${SharedPreferenceManager.token}") repository.getProfile("Bearer ${SharedPreferenceManager.token}")
@@ -90,8 +87,7 @@ class ProfileUpdateViewModel(private val repository: UserRepository) : BaseViewM
_genderLiveData.postValue(gender) _genderLiveData.postValue(gender)
} }
fun updateProfileImage(uri: Uri, onSuccess: (String) -> Unit) { fun updateProfileImage(file: File, onSuccess: (String) -> Unit) {
val file = File(getRealPathFromURI(uri))
val image = MultipartBody.Part.createFormData( val image = MultipartBody.Part.createFormData(
"image", "image",
file.name, file.name,