diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 042874c..2942fa8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -97,6 +97,7 @@ + diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt index cd468ac..1ab76d6 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt @@ -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 { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt index 959b29e..3faddb8 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt @@ -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( } 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( binding.layoutCreatorCommunityPost.ivWrite.visibility = View.GONE } + binding.layoutCreatorCommunityPost.llContainer.removeAllViews() communityPostList.forEachIndexed { index, item -> val layout = ItemCreatorCommunityBinding.inflate( LayoutInflater.from(this@UserProfileActivity), diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt index 4c0043a..40ea926 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt @@ -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> + @GET("/creator-community") fun getCommunityPostList( @Query("creatorId") creatorId: Long, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt index 27db775..e192173 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt @@ -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 + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreateCommunityPostRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreateCommunityPostRequest.kt new file mode 100644 index 0000000..0ad6361 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreateCommunityPostRequest.kt @@ -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 +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt new file mode 100644 index 0000000..5748420 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt @@ -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::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?) { + 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 + ) + ) + } + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteViewModel.kt new file mode 100644 index 0000000..df1deaf --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteViewModel.kt @@ -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() + val toastLiveData: LiveData + get() = _toastLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private val _isAdultLiveData = MutableLiveData(false) + val isAdultLiveData: LiveData + get() = _isAdultLiveData + + private val _isAvailableCommentLiveData = MutableLiveData(true) + val isAvailableCommentLiveData: LiveData + 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 + } +} diff --git a/app/src/main/res/drawable-xxhdpi/ic_logo2.png b/app/src/main/res/drawable-xxhdpi/ic_logo2.png new file mode 100644 index 0000000..c13cfe6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_logo2.png differ diff --git a/app/src/main/res/drawable/bg_round_corner_33_3_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_33_3_3bb9f1.xml new file mode 100644 index 0000000..6533c04 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_33_3_3bb9f1.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_13181b.xml b/app/src/main/res/drawable/bg_round_corner_6_7_13181b.xml new file mode 100644 index 0000000..12bdf0f --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_6_7_13181b.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_13181b_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_6_7_13181b_3bb9f1.xml new file mode 100644 index 0000000..282e552 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_6_7_13181b_3bb9f1.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_6_7_3bb9f1.xml new file mode 100644 index 0000000..fb1d9cf --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_6_7_3bb9f1.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_creator_community_write.xml b/app/src/main/res/layout/activity_creator_community_write.xml new file mode 100644 index 0000000..9353ca4 --- /dev/null +++ b/app/src/main/res/layout/activity_creator_community_write.xml @@ -0,0 +1,346 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +