고객센터 페이지 추가

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

View File

@ -88,6 +88,7 @@
<activity android:name=".following.FollowingCreatorActivity" />
<activity android:name=".live.now.all.LiveNowAllActivity" />
<activity android:name=".live.reservation.all.LiveReservationAllActivity" />
<activity android:name=".mypage.service_center.ServiceCenterActivity" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"

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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_222222" />
<corners android:radius="4.7dp" />
<stroke
android:width="1dp"
android:color="@color/color_222222" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_ffe368" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="@color/color_ffe368" />
</shape>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/detail_toolbar" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_logo" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="소다라이브 고객센터"
android:textColor="@color/color_eeeeee"
android:textSize="20sp" />
<LinearLayout
android:id="@+id/ll_question_kakao"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="13.3dp"
android:layout_marginTop="20dp"
android:background="@drawable/bg_round_corner_8_ffe368"
android:gravity="center"
android:orientation="horizontal"
android:paddingVertical="14dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="TALK 문의"
android:textColor="@color/black"
android:textSize="13.3sp"
app:drawableStartCompat="@drawable/ic_service_center_kakao" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="6.7dp"
android:layout_marginVertical="20dp"
android:background="@color/color_232323" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="자주 묻는 질문"
android:textColor="@color/color_eeeeee"
android:textSize="18.3sp" />
<GridView
android:id="@+id/gv_faq_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="13.3dp"
android:layout_marginVertical="20dp"
android:horizontalSpacing="10dp"
android:numColumns="4"
android:verticalSpacing="10dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_faq"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="20dp">
<TextView
android:id="@+id/tv_q"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginHorizontal="6.7dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="Q"
android:textColor="@color/color_9970ff"
android:textSize="13.3sp" />
<TextView
android:id="@+id/tv_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/iv_dropdown"
android:layout_toEndOf="@+id/tv_q"
android:fontFamily="@font/gmarket_sans_medium"
android:textColor="@color/color_eeeeee"
android:textSize="14.7sp"
tools:text="댓보이스는 어떻게 사용하는 건가요?" />
<ImageView
android:id="@+id/iv_dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="6.7dp"
android:contentDescription="@null"
android:src="@drawable/btn_dropdown_down" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/color_88909090" />
<LinearLayout
android:id="@+id/ll_answer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:paddingVertical="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="7.5dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="A"
android:textColor="@color/color_9970ff"
android:textSize="13.3sp" />
<WebView
android:id="@+id/wv_answer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="7.5dp"
android:layout_marginEnd="13.7dp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/color_88909090" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="46.7dp">
<TextView
android:id="@+id/tv_category"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:lineSpacingExtra="6.7sp"
android:textColor="@color/white"
android:textSize="13.3sp"
tools:text="전체" />
</FrameLayout>

View File

@ -96,4 +96,5 @@
<color name="color_660fd4">#660FD4</color>
<color name="color_2f90b7">#2F90B7</color>
<color name="color_a94400">#A94400</color>
<color name="color_ffe368">#FFE368</color>
</resources>