고객센터 페이지 추가

This commit is contained in:
2023-08-09 08:39:24 +09:00
parent e8b4134956
commit a75217ee09
20 changed files with 625 additions and 1 deletions

View File

@@ -55,6 +55,9 @@ import kr.co.vividnext.sodalive.mypage.can.CanRepository
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeViewModel
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentViewModel
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusViewModel
import kr.co.vividnext.sodalive.mypage.service_center.FaqApi
import kr.co.vividnext.sodalive.mypage.service_center.FaqRepository
import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel
import kr.co.vividnext.sodalive.network.TokenAuthenticator
import kr.co.vividnext.sodalive.report.ReportApi
import kr.co.vividnext.sodalive.report.ReportRepository
@@ -130,6 +133,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
single { ApiBuilder().build(get(), MessageApi::class.java) }
single { ApiBuilder().build(get(), NoticeApi::class.java) }
single { ApiBuilder().build(get(), AudioContentApi::class.java) }
single { ApiBuilder().build(get(), FaqApi::class.java) }
}
private val viewModelModule = module {
@@ -174,6 +178,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { AudioContentCommentListViewModel(get()) }
viewModel { AudioContentCommentReplyViewModel(get()) }
viewModel { FollowingCreatorViewModel(get()) }
viewModel { ServiceCenterViewModel(get()) }
}
private val repositoryModule = module {
@@ -193,6 +198,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { AudioContentCommentRepository(get()) }
factory { PlaybackTrackingRepository(get()) }
factory { FollowingCreatorRepository(get(), get()) }
factory { FaqRepository(get()) }
}
private val moduleList = listOf(

View File

@@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusActivity
import kr.co.vividnext.sodalive.live.reservation_status.LiveReservationStatusActivity
import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterActivity
import kr.co.vividnext.sodalive.settings.SettingsActivity
import kr.co.vividnext.sodalive.settings.notification.MemberRole
import org.koin.android.ext.android.inject
@@ -86,7 +87,14 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
)
}
binding.rlServiceCenter.setOnClickListener {}
binding.rlServiceCenter.setOnClickListener {
startActivity(
Intent(
requireActivity(),
ServiceCenterActivity::class.java
)
)
}
binding.tvAuth.setOnClickListener {
Auth.auth(requireActivity(), requireContext()) {

View File

@@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.mypage.service_center
import com.google.gson.annotations.SerializedName
data class Faq(
@SerializedName("question") val question: String,
@SerializedName("answer") val answer: String
)

View File

@@ -0,0 +1,93 @@
package kr.co.vividnext.sodalive.mypage.service_center
import android.annotation.SuppressLint
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebSettings
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemFaqBinding
class FaqAdapter : RecyclerView.Adapter<FaqAdapter.ViewHolder>() {
val items = mutableListOf<Faq>()
@SuppressLint("SetJavaScriptEnabled")
inner class ViewHolder(
private val binding: ItemFaqBinding
) : RecyclerView.ViewHolder(binding.root) {
init {
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
WebSettingsCompat.setForceDark(
binding.wvAnswer.settings,
WebSettingsCompat.FORCE_DARK_ON
)
}
binding.wvAnswer.apply {
setLayerType(View.LAYER_TYPE_HARDWARE, null)
clearCache(true)
settings.apply {
javaScriptEnabled = true
javaScriptCanOpenWindowsAutomatically = true
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
val cookieManager = CookieManager.getInstance()
cookieManager.setAcceptCookie(true)
cookieManager.setAcceptThirdPartyCookies(binding.wvAnswer, true)
cacheMode = WebSettings.LOAD_NO_CACHE
blockNetworkImage = false
loadsImagesAutomatically = true
useWideViewPort = false
loadWithOverviewMode = true
javaScriptCanOpenWindowsAutomatically = true
domStorageEnabled = true
loadWithOverviewMode = true
allowContentAccess = true
setSupportZoom(false)
displayZoomControls = false
}
}
}
fun bind(item: Faq) {
binding.tvQuestion.text = item.question
binding.wvAnswer.loadData(item.answer, "text/html", "UTF-8")
binding.wvAnswer.setBackgroundColor(Color.BLACK)
binding.root.setOnClickListener {
binding.llAnswer.isVisible = !binding.llAnswer.isVisible
binding.ivDropdown.setImageResource(
if (binding.llAnswer.isVisible) {
R.drawable.btn_dropdown_up
} else {
R.drawable.btn_dropdown_down
}
)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemFaqBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.count()
}

View File

@@ -0,0 +1,20 @@
package kr.co.vividnext.sodalive.mypage.service_center
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
interface FaqApi {
@GET("/faq")
fun getFaqs(
@Query("category") category: String,
@Header("Authorization") authHeader: String
): Single<ApiResponse<List<Faq>>>
@GET("/faq/category")
fun getFaqCategories(
@Header("Authorization") authHeader: String
): Single<ApiResponse<List<String>>>
}

View File

@@ -0,0 +1,67 @@
package kr.co.vividnext.sodalive.mypage.service_center
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import kr.co.vividnext.sodalive.R
class FaqCategoryAdapter(
private val context: Context
) : BaseAdapter() {
val items = mutableListOf<String>()
private var selectedCategory = ""
override fun getCount(): Int {
return items.count()
}
override fun getItem(position: Int): Any {
return items[position]
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getView(position: Int, view: View?, parent: ViewGroup?): View {
var convertView = view
val viewHolder: ViewHolder
if (convertView == null) {
convertView =
LayoutInflater.from(context).inflate(R.layout.item_faq_category, parent, false)
viewHolder = ViewHolder()
viewHolder.tvCategory = convertView!!.findViewById(R.id.tv_category)
convertView.tag = viewHolder
} else {
viewHolder = convertView.tag as ViewHolder
}
val item = items[position]
viewHolder.tvCategory.text = item
viewHolder.tvCategory.setBackgroundResource(
if (item == selectedCategory) {
R.drawable.bg_round_corner_4_7_9970ff
} else {
R.drawable.bg_round_corner_4_7_222222
}
)
return convertView
}
fun selectCategory(position: Int): String {
selectedCategory = items[position]
notifyDataSetChanged()
return selectedCategory
}
inner class ViewHolder {
lateinit var tvCategory: TextView
}
}

View File

@@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.mypage.service_center
class FaqRepository(private val api: FaqApi) {
fun getCategories(token: String) = api.getFaqCategories(authHeader = token)
fun getFaqs(category: String, token: String) = api.getFaqs(
category = category,
authHeader = token
)
}

View File

@@ -0,0 +1,107 @@
package kr.co.vividnext.sodalive.mypage.service_center
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
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.databinding.ActivityServiceCenterBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject
import kotlin.math.ceil
class ServiceCenterActivity : BaseActivity<ActivityServiceCenterBinding>(
ActivityServiceCenterBinding::inflate
) {
private val viewModel: ServiceCenterViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getFaqCategories()
}
@SuppressLint("NotifyDataSetChanged")
override fun setupView() {
binding.toolbar.tvBack.text = "고객센터"
binding.toolbar.tvBack.setOnClickListener { finish() }
val categoryAdapter = FaqCategoryAdapter(this)
binding.gvFaqCategory.adapter = categoryAdapter
binding.gvFaqCategory.setOnItemClickListener { _, _, position, _ ->
viewModel.getFaqs(categoryAdapter.selectCategory(position))
}
binding.llQuestionKakao.setOnClickListener {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse("http://pf.kakao.com/_sZaeb")
)
)
}
val recyclerView = binding.rvFaq
val faqAdapter = FaqAdapter()
recyclerView.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.left = 13.3f.dpToPx().toInt()
outRect.right = 13.3f.dpToPx().toInt()
when (parent.getChildAdapterPosition(view)) {
faqAdapter.itemCount - 1 -> {
outRect.top = 0.dpToPx().toInt()
outRect.bottom = 40.dpToPx().toInt()
}
else -> {
outRect.top = 0
outRect.bottom = 0
}
}
}
})
recyclerView.adapter = faqAdapter
viewModel.toastLiveData.observe(this) {
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
}
viewModel.categoriesLiveData.observe(this) {
categoryAdapter.items.addAll(it)
val lp = binding.gvFaqCategory.layoutParams as LinearLayout.LayoutParams
lp.height = (ceil(it.count().toFloat() / 4f) * 46.7f + 10f).dpToPx().toInt()
viewModel.getFaqs(categoryAdapter.selectCategory(0))
}
viewModel.faqLiveData.observe(this) {
faqAdapter.items.clear()
faqAdapter.items.addAll(it)
faqAdapter.notifyDataSetChanged()
}
}
}

View File

@@ -0,0 +1,81 @@
package kr.co.vividnext.sodalive.mypage.service_center
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
class ServiceCenterViewModel(val repository: FaqRepository) : BaseViewModel() {
private val _categoriesLiveData = MutableLiveData<List<String>>()
val categoriesLiveData: LiveData<List<String>>
get() = _categoriesLiveData
private val _faqLiveData = MutableLiveData<List<Faq>>()
val faqLiveData: LiveData<List<Faq>>
get() = _faqLiveData
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
fun getFaqCategories() {
compositeDisposable.add(
repository.getCategories(token = "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_categoriesLiveData.postValue(it.data!!)
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
fun getFaqs(category: String) {
compositeDisposable.add(
repository.getFaqs(
category = category,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_faqLiveData.postValue(it.data!!)
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@@ -7,6 +7,7 @@ import android.os.Bundle
import android.widget.Toast
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.LoadingDialog
@@ -121,6 +122,12 @@ class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsB
}
private fun logout() {
startService(
Intent(applicationContext, AudioContentPlayService::class.java).apply {
action = AudioContentPlayService.MusicAction.STOP.name
}
)
viewModel.logout {
SharedPreferenceManager.clear()
finishAffinity()