fix(이미지 선택): 이미지 선택 및 크롭 로직 수정
This commit is contained in:
		@@ -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'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ActivityAudioContentModifyBindin
 | 
			
		||||
    private val viewModel: AudioContentModifyViewModel by inject()
 | 
			
		||||
 | 
			
		||||
    private lateinit var loadingDialog: LoadingDialog
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private lateinit var cropper: ImagePickerCropper
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
@@ -82,24 +56,50 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
 | 
			
		||||
        viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        cropper.cleanup()
 | 
			
		||||
        super.onDestroy()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setupView() {
 | 
			
		||||
        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.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<ActivityAudioContentModifyBindin
 | 
			
		||||
                    viewModel.setAdult(true)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                viewModel.isAdultLiveData.observe(this) {
 | 
			
		||||
                    if (it) {
 | 
			
		||||
                viewModel.isAdultLiveData.observe(this) { isAdult ->
 | 
			
		||||
                    if (isAdult) {
 | 
			
		||||
                        binding.ivAgeAll.visibility = View.GONE
 | 
			
		||||
                        binding.llAgeAll.setBackgroundResource(
 | 
			
		||||
                            R.drawable.bg_round_corner_6_7_13181b
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ActivityAudioContentUploadBindin
 | 
			
		||||
    private val viewModel: AudioContentUploadViewModel by inject()
 | 
			
		||||
 | 
			
		||||
    private lateinit var loadingDialog: LoadingDialog
 | 
			
		||||
    private lateinit var cropper: ImagePickerCropper
 | 
			
		||||
 | 
			
		||||
    private val themeFragment: AudioContentThemeFragment by lazy {
 | 
			
		||||
        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(
 | 
			
		||||
        ActivityResultContracts.StartActivityForResult()
 | 
			
		||||
    ) { result ->
 | 
			
		||||
@@ -113,18 +88,29 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
 | 
			
		||||
                    Toast.LENGTH_SHORT
 | 
			
		||||
                ).show()
 | 
			
		||||
            }
 | 
			
		||||
        } else if (resultCode == ImagePicker.RESULT_ERROR) {
 | 
			
		||||
        } else {
 | 
			
		||||
            binding.tvSelectContent.text = "파일 선택"
 | 
			
		||||
            viewModel.contentUri = null
 | 
			
		||||
            Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
 | 
			
		||||
            Toast.makeText(
 | 
			
		||||
                this,
 | 
			
		||||
                "파일 선택을 실패했습니다.\n다시 시도해 주세요.",
 | 
			
		||||
                Toast.LENGTH_SHORT
 | 
			
		||||
            ).show()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val datePickerDialogListener =
 | 
			
		||||
        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(
 | 
			
		||||
                String.format(
 | 
			
		||||
                    Locale.getDefault(),
 | 
			
		||||
                    "%d.%02d.%02d",
 | 
			
		||||
                    year,
 | 
			
		||||
                    monthOfYear + 1,
 | 
			
		||||
@@ -135,7 +121,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
 | 
			
		||||
 | 
			
		||||
    private val timePickerDialogListener =
 | 
			
		||||
        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.setReservationTime(timeString.convertDateFormat("HH:mm", "a hh:mm"))
 | 
			
		||||
        }
 | 
			
		||||
@@ -151,9 +137,45 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
 | 
			
		||||
        bindData()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        cropper.cleanup()
 | 
			
		||||
        super.onDestroy()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setupView() {
 | 
			
		||||
        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) {
 | 
			
		||||
            "※ 이용기간 : 대여(5일) | 소장(이용 기간 1년)"
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -167,19 +189,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
 | 
			
		||||
            themeFragment.show(supportFragmentManager, themeFragment.tag)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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.tvSelectContent.setOnClickListener {
 | 
			
		||||
            val intent = Intent().apply {
 | 
			
		||||
@@ -359,7 +369,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
 | 
			
		||||
                .subscribe {
 | 
			
		||||
                    val price = it.toString().toIntOrNull()
 | 
			
		||||
                    if (price != null) {
 | 
			
		||||
                        viewModel.price = price.toInt()
 | 
			
		||||
                        viewModel.price = price
 | 
			
		||||
                    } else {
 | 
			
		||||
                        viewModel.price = 0
 | 
			
		||||
                        if (it.isNotBlank()) {
 | 
			
		||||
@@ -377,7 +387,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
 | 
			
		||||
                .subscribe {
 | 
			
		||||
                    val limited = it.toString().toIntOrNull()
 | 
			
		||||
                    if (limited != null) {
 | 
			
		||||
                        viewModel.limited = limited.toInt()
 | 
			
		||||
                        viewModel.limited = limited
 | 
			
		||||
                    } else {
 | 
			
		||||
                        viewModel.limited = 0
 | 
			
		||||
                        if (it.isNotBlank()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ class AudioContentUploadViewModel(
 | 
			
		||||
    var releaseDate = ""
 | 
			
		||||
    var releaseTime = ""
 | 
			
		||||
    var theme: GetAudioContentThemeResponse? = null
 | 
			
		||||
    var coverImageUri: Uri? = null
 | 
			
		||||
    var coverImageFile: File? = null
 | 
			
		||||
    var contentUri: Uri? = null
 | 
			
		||||
    var previewStartTime: String? = null
 | 
			
		||||
    var previewEndTime: String? = null
 | 
			
		||||
@@ -203,8 +203,8 @@ class AudioContentUploadViewModel(
 | 
			
		||||
 | 
			
		||||
            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,
 | 
			
		||||
@@ -323,7 +323,7 @@ class AudioContentUploadViewModel(
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (coverImageUri == null) {
 | 
			
		||||
        if (coverImageFile == null) {
 | 
			
		||||
            _toastLiveData.postValue("커버이미지를 선택해 주세요.")
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
@@ -413,7 +413,7 @@ class AudioContentUploadViewModel(
 | 
			
		||||
 | 
			
		||||
            // Check if the time difference is greater than 30 seconds (30000 milliseconds)
 | 
			
		||||
            return date2.time - date1.time
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
        } catch (_: Exception) {
 | 
			
		||||
            // Handle invalid time formats or parsing errors
 | 
			
		||||
            return 0
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import android.app.Activity
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.graphics.Bitmap
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.provider.OpenableColumns
 | 
			
		||||
import androidx.activity.result.ActivityResultCaller
 | 
			
		||||
import androidx.activity.result.ActivityResultLauncher
 | 
			
		||||
import androidx.activity.result.PickVisualMediaRequest
 | 
			
		||||
@@ -13,6 +14,7 @@ import com.yalantis.ucrop.UCrop
 | 
			
		||||
import com.yalantis.ucrop.UCropActivity
 | 
			
		||||
import kr.co.vividnext.sodalive.BuildConfig
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 단일 이미지 선택(13+ Photo Picker / 12- GetContent) → uCrop(기본 9:20) → [File, Uri] 반환
 | 
			
		||||
@@ -20,8 +22,10 @@ import java.io.File
 | 
			
		||||
 * - 결과 파일은 cacheDir에 임시 생성 → 업로드 후 cleanup() 호출로 삭제
 | 
			
		||||
 */
 | 
			
		||||
class ImagePickerCropper(
 | 
			
		||||
    private val caller: ActivityResultCaller,
 | 
			
		||||
    caller: ActivityResultCaller,
 | 
			
		||||
    private val context: Context,
 | 
			
		||||
    private val excludeGif: Boolean = false,
 | 
			
		||||
    private val isEnabledFreeStyleCrop: Boolean = false,
 | 
			
		||||
    private val config: Config = Config(),
 | 
			
		||||
    private val onSuccess: (file: File, uri: Uri) -> Unit,
 | 
			
		||||
    private val onError: (Throwable) -> Unit = { it.printStackTrace() }
 | 
			
		||||
@@ -41,15 +45,21 @@ class ImagePickerCropper(
 | 
			
		||||
    // 13+ : 시스템 Photo Picker
 | 
			
		||||
    private val pickPhoto: ActivityResultLauncher<PickVisualMediaRequest> =
 | 
			
		||||
        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<String> =
 | 
			
		||||
        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,20 +72,24 @@ 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 (excludeGif) {
 | 
			
		||||
            openDocument.launch(arrayOf("image/png", "image/jpg", "image/jpeg"))
 | 
			
		||||
        } else {
 | 
			
		||||
            if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(context)) {
 | 
			
		||||
                pickPhoto.launch(
 | 
			
		||||
                    PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
 | 
			
		||||
@@ -84,9 +98,7 @@ class ImagePickerCropper(
 | 
			
		||||
                pickContent.launch("image/*")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /** 마지막 크롭 결과 파일 (있으면) */
 | 
			
		||||
    fun getCroppedFile(): File? = lastCroppedFile?.takeIf { it.exists() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** 임시 파일 삭제 */
 | 
			
		||||
    fun cleanup() {
 | 
			
		||||
@@ -94,6 +106,64 @@ class ImagePickerCropper(
 | 
			
		||||
        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, // 회전
 | 
			
		||||
 
 | 
			
		||||
@@ -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>(
 | 
			
		||||
    ActivityCreatorCommunityModifyBinding::inflate
 | 
			
		||||
@@ -39,142 +33,11 @@ class CreatorCommunityModifyActivity : BaseActivity<ActivityCreatorCommunityModi
 | 
			
		||||
    private val viewModel: CreatorCommunityModifyViewModel by inject()
 | 
			
		||||
 | 
			
		||||
    private lateinit var loadingDialog: LoadingDialog
 | 
			
		||||
    private lateinit var croppedTempFile: File
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
    }
 | 
			
		||||
    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<ActivityCreatorCommunityModi
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        deleteCroppedTempFile()
 | 
			
		||||
 | 
			
		||||
        cropper.cleanup()
 | 
			
		||||
        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() {
 | 
			
		||||
        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.setOnClickListener { finish() }
 | 
			
		||||
 | 
			
		||||
        binding.ivPhotoPicker.setOnClickListener { launchImagePicker() }
 | 
			
		||||
        binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
 | 
			
		||||
 | 
			
		||||
        if (SharedPreferenceManager.isAuth) {
 | 
			
		||||
            binding.llSetAdult.visibility = View.VISIBLE
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Boolean>
 | 
			
		||||
        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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ActivityCreatorCommunityWrite
 | 
			
		||||
    private val viewModel: CreatorCommunityWriteViewModel by inject()
 | 
			
		||||
 | 
			
		||||
    private lateinit var loadingDialog: LoadingDialog
 | 
			
		||||
    private lateinit var croppedTempFile: File
 | 
			
		||||
 | 
			
		||||
    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.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<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() {
 | 
			
		||||
        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.setOnClickListener { finish() }
 | 
			
		||||
 | 
			
		||||
@@ -220,7 +96,7 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
 | 
			
		||||
            fragment.show(supportFragmentManager, fragment.tag)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.ivPhotoPicker.setOnClickListener { launchImagePicker() }
 | 
			
		||||
        binding.ivPhotoPicker.setOnClickListener { cropper.launch() }
 | 
			
		||||
 | 
			
		||||
        if (SharedPreferenceManager.isAuth) {
 | 
			
		||||
            binding.llSetAdult.visibility = View.VISIBLE
 | 
			
		||||
@@ -278,7 +154,7 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
 | 
			
		||||
                .subscribe {
 | 
			
		||||
                    val price = it.toString().toIntOrNull()
 | 
			
		||||
                    if (price != null) {
 | 
			
		||||
                        viewModel.price = price.toInt()
 | 
			
		||||
                        viewModel.price = price
 | 
			
		||||
                    } else {
 | 
			
		||||
                        viewModel.price = 0
 | 
			
		||||
                        if (it.isNotBlank()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import com.google.gson.Gson
 | 
			
		||||
@@ -45,12 +44,10 @@ class CreatorCommunityWriteViewModel(
 | 
			
		||||
    val isShowPriceUiLiveData: LiveData<Boolean>
 | 
			
		||||
        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
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ActivityLiveRoomBinding>(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<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(
 | 
			
		||||
        ActivityResultContracts.StartActivityForResult()
 | 
			
		||||
    ) { result ->
 | 
			
		||||
@@ -280,9 +267,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(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<ActivityLiveRoomBinding>(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<ActivityLiveRoomBinding>(ActivityLiveRoomB
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        cropper.cleanup()
 | 
			
		||||
        hideKeyboard {
 | 
			
		||||
            viewModel.quitRoom(roomId) {
 | 
			
		||||
                SodaLiveService.stopService(this)
 | 
			
		||||
@@ -960,7 +952,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(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<ActivityLiveRoomBinding>(ActivityLiveRoomB
 | 
			
		||||
                                response.roomId,
 | 
			
		||||
                                newTitle,
 | 
			
		||||
                                newContent,
 | 
			
		||||
                                newCoverImageUri,
 | 
			
		||||
                                newCoverImageFile,
 | 
			
		||||
                                isActivateMenu,
 | 
			
		||||
                                menuId,
 | 
			
		||||
                                menu,
 | 
			
		||||
                                isAdult,
 | 
			
		||||
                                onSuccess = {
 | 
			
		||||
                                    cropper.cleanup()
 | 
			
		||||
                                    Toast.makeText(
 | 
			
		||||
                                        applicationContext,
 | 
			
		||||
                                        "라이브 정보가 수정되었습니다.",
 | 
			
		||||
@@ -1280,7 +1273,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Boolean>
 | 
			
		||||
        get() = _isSignatureOn
 | 
			
		||||
 | 
			
		||||
    lateinit var getRealPathFromURI: (Uri) -> String?
 | 
			
		||||
 | 
			
		||||
    private val blockedMemberIdList: MutableList<Long> = 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,
 | 
			
		||||
 
 | 
			
		||||
@@ -131,6 +131,7 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
 | 
			
		||||
        cropper = ImagePickerCropper(
 | 
			
		||||
            caller = this,
 | 
			
		||||
            context = this,
 | 
			
		||||
            excludeGif = true,
 | 
			
		||||
            config = ImagePickerCropper.Config(
 | 
			
		||||
                aspectX = 2f, aspectY = 3.8f,
 | 
			
		||||
                maxWidth = 1080, maxHeight = 2052,
 | 
			
		||||
 
 | 
			
		||||
@@ -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<GetMenuPresetResponse>()
 | 
			
		||||
@@ -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<GetMenuPresetResponse>) {
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ActivityProfileUpdateBinding>(
 | 
			
		||||
 | 
			
		||||
    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<ActivityProfileUpdateBinding>(
 | 
			
		||||
 | 
			
		||||
    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<ActivityProfileUpdateBinding>(
 | 
			
		||||
 | 
			
		||||
        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<ActivityProfileUpdateBinding>(
 | 
			
		||||
            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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Boolean>
 | 
			
		||||
        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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user