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 4b26bc2..37c62c0 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 @@ -23,6 +23,7 @@ import kr.co.vividnext.sodalive.explorer.ExplorerRepository import kr.co.vividnext.sodalive.explorer.ExplorerViewModel import kr.co.vividnext.sodalive.explorer.profile.UserProfileViewModel 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 import kr.co.vividnext.sodalive.following.FollowingCreatorRepository import kr.co.vividnext.sodalive.following.FollowingCreatorViewModel @@ -211,6 +212,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { factory { FollowingCreatorRepository(get(), get()) } factory { FaqRepository(get()) } factory { MemberTagRepository(get()) } + factory { UserProfileFantalkAllViewModel(get(), get()) } } private val moduleList = listOf( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt index 409a840..6fa0b87 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt @@ -1,7 +1,9 @@ package kr.co.vividnext.sodalive.explorer +import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse import kr.co.vividnext.sodalive.explorer.profile.GetCreatorProfileResponse import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest import kr.co.vividnext.sodalive.explorer.profile.cheers.PostWriteCheersRequest @@ -44,6 +46,15 @@ interface ExplorerApi { @Header("Authorization") authHeader: String ): Single> + @GET("/explorer/profile/{id}/cheers") + fun getCreatorProfileCheers( + @Path("id") creatorId: Long, + @Query("page") page: Int, + @Query("size") size: Int, + @Query("timezone") timezone: String, + @Header("Authorization") authHeader: String + ): Flowable> + @POST("/explorer/profile/cheers") fun writeCheers( @Body request: PostWriteCheersRequest, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt index f0bece8..e06501c 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt @@ -1,7 +1,9 @@ package kr.co.vividnext.sodalive.explorer +import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest import kr.co.vividnext.sodalive.explorer.profile.cheers.PostWriteCheersRequest import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest @@ -24,6 +26,21 @@ class ExplorerRepository( authHeader = token ) + fun getCreatorProfileCheers( + creatorId: Long, + page: Int, + size: Int, + token: String + ): Flowable> { + return api.getCreatorProfileCheers( + creatorId = creatorId, + page = page - 1, + size = size, + timezone = TimeZone.getDefault().id, + authHeader = token + ) + } + fun writeCheers( parentCheersId: Long?, creatorId: Long, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewActivity.kt index ac9685c..533e7ab 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewActivity.kt @@ -1,10 +1,203 @@ package kr.co.vividnext.sodalive.explorer.profile.fantalk +import android.annotation.SuppressLint +import android.app.Service +import android.graphics.Rect +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.View +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.databinding.ActivityUserProfileFantalkAllBinding +import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.report.CheersReportDialog +import org.koin.android.ext.android.inject class UserProfileFantalkAllViewActivity : BaseActivity( ActivityUserProfileFantalkAllBinding::inflate ) { - override fun setupView() {} + private val viewModel: UserProfileFantalkAllViewModel by inject() + + private lateinit var imm: InputMethodManager + private lateinit var loadingDialog: LoadingDialog + private lateinit var cheersAdapter: UserProfileCheersAdapter + + private val handler = Handler(Looper.getMainLooper()) + private var userId: Long = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + userId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0) + super.onCreate(savedInstanceState) + + imm = getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager + + if (userId > 0) { + bindData() + + viewModel.getCheersList(creatorId = userId) + } else { + Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show() + finish() + } + } + + @SuppressLint("SetTextI18n") + override fun setupView() { + loadingDialog = LoadingDialog(this, layoutInflater) + binding.toolbar.tvBack.text = "팬 Talk 전체보기" + binding.toolbar.tvBack.setOnClickListener { finish() } + + setupCheersView() + } + + private fun setupCheersView() { + binding.ivSend.setOnClickListener { + hideKeyboard { + viewModel.writeCheers( + creatorId = userId, + cheersContent = binding.etCheer.text.toString() + ) + } + } + + val rvCheers = binding.rvCheers + cheersAdapter = UserProfileCheersAdapter( + userId = userId, + enterReply = { cheersId, content -> + hideKeyboard { + viewModel.writeCheers( + parentCheersId = cheersId, + creatorId = userId, + cheersContent = content + ) + } + }, + modifyReply = { cheersId, content -> + hideKeyboard { + viewModel.modifyCheers( + cheersId = cheersId, + creatorId = userId, + cheersContent = content + ) + } + }, + onClickReport = { showCheersReportPopup(it) } + ) + + rvCheers.layoutManager = LinearLayoutManager( + applicationContext, + LinearLayoutManager.VERTICAL, + false + ) + + rvCheers.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + outRect.bottom = 0 + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.top = 0 + } + + cheersAdapter.itemCount - 1 -> { + outRect.top = 10.dpToPx().toInt() + outRect.bottom = 10.dpToPx().toInt() + } + + else -> { + outRect.top = 10.dpToPx().toInt() + } + } + } + }) + + rvCheers.adapter = cheersAdapter + rvCheers.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val lastVisiblePosition = (recyclerView.layoutManager as LinearLayoutManager) + .findLastVisibleItemPosition() + val itemTotalCount = cheersAdapter.itemCount - 1 + + if (itemTotalCount > 0 && lastVisiblePosition == itemTotalCount) { + viewModel.getCheersList(userId) + } + } + }) + } + + private fun hideKeyboard(onAfterExecute: () -> Unit) { + handler.postDelayed({ + imm.hideSoftInputFromWindow( + window.decorView.applicationWindowToken, + InputMethodManager.HIDE_NOT_ALWAYS + ) + onAfterExecute() + }, 100) + } + + private fun showCheersReportPopup(cheersId: Long) { + val dialog = CheersReportDialog(this, layoutInflater) { + if (it.isBlank()) { + Toast.makeText( + applicationContext, + "신고 이유를 선택해 주세요.", + Toast.LENGTH_LONG + ).show() + } else { + viewModel.cheersReport(cheersId, reason = it) + } + } + + dialog.show(screenWidth) + } + + @SuppressLint("NotifyDataSetChanged") + private fun bindData() { + 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.cheersLiveData.observe(this) { + binding.etCheer.setText("") + + binding.tvCheersCount.text = it.totalCount.toString() + binding.tvCheersCount.requestLayout() + cheersAdapter.items.addAll(it.cheers) + cheersAdapter.notifyDataSetChanged() + + if (cheersAdapter.itemCount <= 0) { + binding.rvCheers.visibility = View.GONE + binding.tvNoCheers.visibility = View.VISIBLE + } else { + binding.rvCheers.visibility = View.VISIBLE + binding.tvNoCheers.visibility = View.GONE + } + + binding.rvCheers.requestLayout() + } + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewModel.kt new file mode 100644 index 0000000..4477293 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewModel.kt @@ -0,0 +1,186 @@ +package kr.co.vividnext.sodalive.explorer.profile.fantalk + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +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.ExplorerRepository +import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse +import kr.co.vividnext.sodalive.report.ReportRepository +import kr.co.vividnext.sodalive.report.ReportRequest +import kr.co.vividnext.sodalive.report.ReportType + +class UserProfileFantalkAllViewModel( + private val repository: ExplorerRepository, + private val reportRepository: ReportRepository +) : BaseViewModel() { + + var cheersPage = 1 + val pageSize = 10 + private var isCheersLast = false + + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private var _cheersLiveData = MutableLiveData() + val cheersLiveData: LiveData + get() = _cheersLiveData + + fun getCheersList(creatorId: Long) { + if (!isCheersLast && !_isLoading.value!!) { + _isLoading.value = true + + compositeDisposable.add( + repository.getCreatorProfileCheers( + creatorId = creatorId, + page = cheersPage, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ).subscribe( + { + if (it.success && it.data != null) { + _cheersLiveData.postValue(it.data!!) + if (it.data.cheers.isNotEmpty()) { + cheersPage += 1 + } else { + isCheersLast = true + } + } 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + } + + fun cheersReport(cheersId: Long, reason: String) { + _isLoading.value = true + + val request = ReportRequest(ReportType.CHEERS, reason, cheersId = cheersId) + compositeDisposable.add( + reportRepository.report( + request = request, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "신고가 접수되었습니다." + ) + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("신고가 접수되었습니다.") + } + ) + ) + } + + fun writeCheers(parentCheersId: Long? = null, creatorId: Long, cheersContent: String) { + if (cheersContent.isBlank()) { + _toastLiveData.postValue("내용을 입력하세요") + return + } + + _isLoading.value = true + + compositeDisposable.add( + repository.writeCheers( + parentCheersId = parentCheersId, + creatorId = creatorId, + content = cheersContent, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + if (it.success) { + isCheersLast = false + cheersPage = 1 + getCheersList(creatorId) + } else { + val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + _toastLiveData.postValue(message) + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun modifyCheers(cheersId: Long, creatorId: Long, cheersContent: String) { + if (cheersContent.isBlank()) { + _toastLiveData.postValue("내용을 입력하세요") + return + } + + _isLoading.value = true + + compositeDisposable.add( + repository.modifyCheers( + cheersId = cheersId, + content = cheersContent, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + if (it.success) { + isCheersLast = false + cheersPage = 1 + getCheersList(creatorId) + } else { + val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + _toastLiveData.postValue(message) + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } +}