커뮤니티 게시물 업로드 페이지 추가
This commit is contained in:
@@ -34,6 +34,7 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommun
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreatorCommunityCommentListViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel
|
||||
@@ -219,6 +220,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
||||
viewModel { RouletteSettingsViewModel(get()) }
|
||||
viewModel { CreatorCommunityAllViewModel(get()) }
|
||||
viewModel { CreatorCommunityCommentListViewModel(get()) }
|
||||
viewModel { CreatorCommunityWriteViewModel(get()) }
|
||||
}
|
||||
|
||||
private val repositoryModule = module {
|
||||
|
@@ -37,6 +37,7 @@ import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter
|
||||
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity
|
||||
@@ -474,7 +475,9 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
|
||||
}
|
||||
|
||||
private fun setupCreatorCommunityView() {
|
||||
binding.layoutCreatorCommunityPost.ivWrite.setOnClickListener { }
|
||||
binding.layoutCreatorCommunityPost.ivWrite.setOnClickListener {
|
||||
startActivity(Intent(applicationContext, CreatorCommunityWriteActivity::class.java))
|
||||
}
|
||||
binding.layoutCreatorCommunityPost.llAll.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
|
||||
@@ -706,6 +709,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
|
||||
binding.layoutCreatorCommunityPost.ivWrite.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.layoutCreatorCommunityPost.llContainer.removeAllViews()
|
||||
communityPostList.forEachIndexed { index, item ->
|
||||
val layout = ItemCreatorCommunityBinding.inflate(
|
||||
LayoutInflater.from(this@UserProfileActivity),
|
||||
|
@@ -5,15 +5,27 @@ import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PostCommunityPostLikeRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreateCommunityPostCommentRequest
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface CreatorCommunityApi {
|
||||
@POST("/creator-community")
|
||||
@Multipart
|
||||
fun createCommunityPost(
|
||||
@Part postImage: MultipartBody.Part?,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/creator-community")
|
||||
fun getCommunityPostList(
|
||||
@Query("creatorId") creatorId: Long,
|
||||
|
@@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PostCommunityPostLikeRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreateCommunityPostCommentRequest
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.util.TimeZone
|
||||
|
||||
class CreatorCommunityRepository(private val api: CreatorCommunityApi) {
|
||||
@@ -70,4 +72,14 @@ class CreatorCommunityRepository(private val api: CreatorCommunityApi) {
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun createCommunityPost(
|
||||
postImage: MultipartBody.Part?,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
) = api.createCommunityPost(
|
||||
postImage = postImage,
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateCommunityPostRequest(
|
||||
@SerializedName("content") val content: String,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean
|
||||
)
|
@@ -0,0 +1,248 @@
|
||||
package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
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 coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
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.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
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWriteBinding>(
|
||||
ActivityCreatorCommunityWriteBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: CreatorCommunityWriteViewModel 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.ivContent.background = null
|
||||
binding.ivContent.load(fileUri) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_place_holder)
|
||||
transformations(RoundedCornersTransformation(8f.dpToPx()))
|
||||
}
|
||||
viewModel.imageUri = 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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
checkPermissions()
|
||||
|
||||
viewModel.getRealPathFromURI = {
|
||||
RealPathUtil.getRealPath(applicationContext, it)
|
||||
}
|
||||
|
||||
bindData()
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
binding.llSetAdult.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.llSetAdult.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.llCommentNo.setOnClickListener { viewModel.setAvailableComment(false) }
|
||||
binding.llCommentYes.setOnClickListener { viewModel.setAvailableComment(true) }
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
binding.tvUpload.setOnClickListener {
|
||||
viewModel.createCommunityPost { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
listOf(Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage(R.string.read_storage_permission_denied_message)
|
||||
.setPermissions(*permissions.toTypedArray())
|
||||
.check()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etContent.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
binding.tvNumberOfCharacters.text = "${it.length}자"
|
||||
viewModel.content = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAvailableCommentLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivCommentYes.visibility = View.VISIBLE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.white
|
||||
)
|
||||
)
|
||||
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
|
||||
|
||||
binding.ivCommentNo.visibility = View.GONE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_80d8ff
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_13181b
|
||||
)
|
||||
} else {
|
||||
binding.ivCommentNo.visibility = View.VISIBLE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.white
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
|
||||
|
||||
binding.ivCommentYes.visibility = View.GONE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_80d8ff
|
||||
)
|
||||
)
|
||||
binding.llCommentYes
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
|
||||
}
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
binding.llAgeAll.setOnClickListener {
|
||||
viewModel.setAdult(false)
|
||||
}
|
||||
|
||||
binding.llAge19.setOnClickListener {
|
||||
viewModel.setAdult(true)
|
||||
}
|
||||
|
||||
viewModel.isAdultLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivAgeAll.visibility = View.GONE
|
||||
binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_80d8ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAge19.visibility = View.VISIBLE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.white
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.ivAge19.visibility = View.GONE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_80d8ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAgeAll.visibility = View.VISIBLE
|
||||
binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.white
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,136 @@
|
||||
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
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.BufferedSink
|
||||
import java.io.File
|
||||
|
||||
class CreatorCommunityWriteViewModel(private val repository: CreatorCommunityRepository
|
||||
): BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private val _isAdultLiveData = MutableLiveData(false)
|
||||
val isAdultLiveData: LiveData<Boolean>
|
||||
get() = _isAdultLiveData
|
||||
|
||||
private val _isAvailableCommentLiveData = MutableLiveData(true)
|
||||
val isAvailableCommentLiveData: LiveData<Boolean>
|
||||
get() = _isAvailableCommentLiveData
|
||||
|
||||
lateinit var getRealPathFromURI: (Uri) -> String?
|
||||
|
||||
var content = ""
|
||||
var imageUri: Uri? = null
|
||||
|
||||
fun setAdult(isAdult: Boolean) {
|
||||
_isAdultLiveData.postValue(isAdult)
|
||||
}
|
||||
|
||||
fun setAvailableComment(isAvailableComment: Boolean) {
|
||||
_isAvailableCommentLiveData.postValue(isAvailableComment)
|
||||
}
|
||||
|
||||
fun createCommunityPost(onSuccess: () -> Unit) {
|
||||
if (!_isLoading.value!! && validateData()) {
|
||||
_isLoading.postValue(true)
|
||||
|
||||
val request = CreateCommunityPostRequest(
|
||||
content = content,
|
||||
isAdult = _isAdultLiveData.value!!,
|
||||
isCommentAvailable = _isAvailableCommentLiveData.value!!
|
||||
)
|
||||
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val postImage = if (imageUri != null) {
|
||||
val file = File(getRealPathFromURI(imageUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"postImage",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "image/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.createCommunityPost(
|
||||
postImage = postImage,
|
||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
_isLoading.postValue(false)
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateData(): Boolean {
|
||||
if (content.isBlank() || content.length < 5) {
|
||||
_toastLiveData.postValue("내용을 5자 이상 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user