diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8c21014..d30549d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -48,6 +48,13 @@
+
+
+
+
+
+
+
(FragmentMyBinding::inflat
}
private fun setupView() {
- binding.ivSettings.setOnClickListener {}
+ binding.ivSettings.setOnClickListener {
+ startActivity(
+ Intent(
+ requireActivity(),
+ SettingsActivity::class.java
+ )
+ )
+ }
binding.ivEdit.setOnClickListener {}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/SettingsActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/SettingsActivity.kt
new file mode 100644
index 0000000..ee08763
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/SettingsActivity.kt
@@ -0,0 +1,130 @@
+package kr.co.vividnext.sodalive.settings
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.graphics.Paint
+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.base.BaseActivity
+import kr.co.vividnext.sodalive.base.SodaDialog
+import kr.co.vividnext.sodalive.common.LoadingDialog
+import kr.co.vividnext.sodalive.common.SharedPreferenceManager
+import kr.co.vividnext.sodalive.databinding.ActivitySettingsBinding
+import kr.co.vividnext.sodalive.settings.event.EventActivity
+import kr.co.vividnext.sodalive.settings.notice.NoticeActivity
+import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsActivity
+import kr.co.vividnext.sodalive.settings.signout.SignOutActivity
+import kr.co.vividnext.sodalive.settings.terms.TermsActivity
+import kr.co.vividnext.sodalive.splash.SplashActivity
+import org.koin.android.ext.android.inject
+
+class SettingsActivity : BaseActivity(ActivitySettingsBinding::inflate) {
+ private val logoutDialog: SodaDialog by lazy {
+ SodaDialog(
+ activity = this,
+ layoutInflater = layoutInflater,
+ title = "알림",
+ desc = "로그아웃 하시겠어요?",
+ confirmButtonTitle = "확인",
+ confirmButtonClick = { logout() },
+ cancelButtonTitle = "취소",
+ cancelButtonClick = {}
+ )
+ }
+
+ private val viewModel: SettingsViewModel by inject()
+
+ private lateinit var loadingDialog: LoadingDialog
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ bindData()
+ }
+
+ @SuppressLint("SetTextI18n")
+ override fun setupView() {
+ binding.toolbar.tvBack.text = "설정"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ loadingDialog = LoadingDialog(this, layoutInflater)
+
+ binding.rlNotice.setOnClickListener {
+ startActivity(
+ Intent(
+ applicationContext,
+ NoticeActivity::class.java
+ )
+ )
+ }
+
+ binding.rlEvent.setOnClickListener {
+ startActivity(
+ Intent(
+ applicationContext,
+ EventActivity::class.java
+ )
+ )
+ }
+
+ binding.rlNotificationSettings.setOnClickListener {
+ startActivity(
+ Intent(
+ applicationContext,
+ NotificationSettingsActivity::class.java
+ )
+ )
+ }
+
+ binding.rlTerms.setOnClickListener {
+ val intent = Intent(applicationContext, TermsActivity::class.java)
+ intent.putExtra("terms", "terms")
+ startActivity(intent)
+ }
+
+ binding.rlPrivacyPolicy.setOnClickListener {
+ val intent = Intent(applicationContext, TermsActivity::class.java)
+ intent.putExtra("terms", "privacy")
+ startActivity(intent)
+ }
+
+ binding.rlOssLicense.setOnClickListener {
+ startActivity(Intent(applicationContext, OssLicensesMenuActivity::class.java))
+ OssLicensesMenuActivity.setActivityTitle("오픈소스 라이선스")
+ }
+
+ binding.tvVersion.text = "ver ${BuildConfig.VERSION_NAME}"
+
+ binding.tvLogOut.setOnClickListener {
+ logoutDialog.show(screenWidth)
+ }
+
+ binding.tvSignOut.paintFlags = binding.tvSignOut.paintFlags.or(Paint.UNDERLINE_TEXT_FLAG)
+ binding.tvSignOut.setOnClickListener {
+ startActivity(Intent(applicationContext, SignOutActivity::class.java))
+ }
+ }
+
+ private fun bindData() {
+ viewModel.isLoading.observe(this) {
+ if (it) {
+ loadingDialog.show(screenWidth)
+ } else {
+ loadingDialog.dismiss()
+ }
+ }
+
+ viewModel.toastLiveData.observe(this) {
+ it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
+ }
+ }
+
+ private fun logout() {
+ viewModel.logout {
+ SharedPreferenceManager.clear()
+ finishAffinity()
+ startActivity(Intent(applicationContext, SplashActivity::class.java))
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/SettingsViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/SettingsViewModel.kt
new file mode 100644
index 0000000..b05ed9b
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/SettingsViewModel.kt
@@ -0,0 +1,47 @@
+package kr.co.vividnext.sodalive.settings
+
+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.user.UserRepository
+
+class SettingsViewModel(private val userRepository: UserRepository) : BaseViewModel() {
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ fun logout(onSuccess: () -> Unit) {
+ compositeDisposable.add(
+ userRepository.logout(token = "Bearer ${SharedPreferenceManager.token}")
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ if (it.success) {
+ onSuccess()
+ } else {
+ if (it.message != null) {
+ _toastLiveData.postValue(it.message)
+ } else {
+ _toastLiveData.postValue(
+ "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
+ )
+ }
+ }
+ },
+ {
+ it.message?.let { message -> Logger.e(message) }
+ _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventActivity.kt
new file mode 100644
index 0000000..6bcbe1f
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventActivity.kt
@@ -0,0 +1,102 @@
+package kr.co.vividnext.sodalive.settings.event
+
+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.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.ActivityEventBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+import org.koin.android.ext.android.inject
+
+class EventActivity : BaseActivity(ActivityEventBinding::inflate) {
+
+ private val viewModel: EventViewModel by inject()
+
+ private lateinit var loadingDialog: LoadingDialog
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel.isLoading.observe(this) {
+ if (it) {
+ loadingDialog.show(screenWidth, "이벤트를 불러오고 있습니다.")
+ } else {
+ loadingDialog.dismiss()
+ }
+ }
+
+ viewModel.toastLiveData.observe(this) {
+ it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
+ }
+
+ viewModel.getEvents()
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ override fun setupView() {
+ binding.toolbar.tvBack.text = "이벤트"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ loadingDialog = LoadingDialog(this, layoutInflater)
+
+ val adapter = EventAdapter {
+ if (it.detailImageUrl != null) {
+ val intent = Intent(applicationContext, EventDetailActivity::class.java)
+ intent.putExtra(Constants.EXTRA_EVENT, it)
+ startActivity(intent)
+ } else if (!it.link.isNullOrBlank()) {
+ startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(it.link)
+ )
+ )
+ }
+ }
+ binding.rvEvent.layoutManager = LinearLayoutManager(this)
+ binding.rvEvent.addItemDecoration(object : RecyclerView.ItemDecoration() {
+ override fun getItemOffsets(
+ outRect: Rect,
+ view: View,
+ parent: RecyclerView,
+ state: RecyclerView.State
+ ) {
+ super.getItemOffsets(outRect, view, parent, state)
+
+ when (parent.getChildAdapterPosition(view)) {
+ 0 -> {
+ outRect.top = 13.3f.dpToPx().toInt()
+ outRect.bottom = 6.7f.dpToPx().toInt()
+ }
+
+ adapter.itemCount - 1 -> {
+ outRect.top = 6.7f.dpToPx().toInt()
+ outRect.bottom = 13.3f.dpToPx().toInt()
+ }
+
+ else -> {
+ outRect.top = 6.7f.dpToPx().toInt()
+ outRect.bottom = 6.7f.dpToPx().toInt()
+ }
+ }
+
+ outRect.left = 13.3f.dpToPx().toInt()
+ outRect.right = 13.3f.dpToPx().toInt()
+ }
+ })
+ binding.rvEvent.adapter = adapter
+
+ viewModel.eventLiveData.observe(this) {
+ adapter.items.addAll(it)
+ adapter.notifyDataSetChanged()
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventAdapter.kt
new file mode 100644
index 0000000..2b5b54d
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventAdapter.kt
@@ -0,0 +1,41 @@
+package kr.co.vividnext.sodalive.settings.event
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import kr.co.vividnext.sodalive.databinding.ItemEventBinding
+
+class EventAdapter(
+ private val onClick: (EventItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ val items = mutableListOf()
+
+ inner class ViewHolder(
+ private val binding: ItemEventBinding,
+ private val onClick: (EventItem) -> Unit
+ ) : RecyclerView.ViewHolder(binding.root) {
+ fun bind(event: EventItem) {
+ binding.ivEvent.load(event.thumbnailImageUrl)
+ binding.root.setOnClickListener { onClick(event) }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ ItemEventBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ),
+ onClick
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+
+ override fun getItemCount() = items.count()
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventDetailActivity.kt
new file mode 100644
index 0000000..13a8309
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventDetailActivity.kt
@@ -0,0 +1,55 @@
+package kr.co.vividnext.sodalive.settings.event
+
+import android.content.Intent
+import android.net.Uri
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RelativeLayout
+import androidx.core.content.IntentCompat
+import coil.load
+import kr.co.vividnext.sodalive.base.BaseActivity
+import kr.co.vividnext.sodalive.common.Constants
+import kr.co.vividnext.sodalive.databinding.ActivityEventDetailBinding
+
+class EventDetailActivity : BaseActivity(
+ ActivityEventDetailBinding::inflate
+) {
+ private lateinit var event: EventItem
+
+ override fun setupView() {
+ event = IntentCompat.getParcelableExtra(
+ intent,
+ Constants.EXTRA_EVENT,
+ EventItem::class.java
+ )!!
+
+ binding.toolbar.tvBack.text = "이벤트 상세"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ if (!event.link.isNullOrBlank()) {
+ binding.flParticipate.visibility = View.VISIBLE
+
+ val flParticipateLp =
+ binding.flParticipate.layoutParams as RelativeLayout.LayoutParams
+
+ val ivEventLp = binding.ivEvent.layoutParams as FrameLayout.LayoutParams
+ ivEventLp.bottomMargin = flParticipateLp.height
+ binding.ivEvent.layoutParams = ivEventLp
+ } else {
+ binding.flParticipate.visibility = View.GONE
+ }
+
+ binding.tvParticipate.setOnClickListener {
+ if (!event.link.isNullOrBlank()) {
+ startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(event.link)
+ )
+ )
+ }
+ }
+
+ binding.ivEvent.load(event.detailImageUrl)
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventViewModel.kt
new file mode 100644
index 0000000..fd91fc2
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventViewModel.kt
@@ -0,0 +1,66 @@
+package kr.co.vividnext.sodalive.settings.event
+
+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 EventViewModel(private val repository: EventRepository) : BaseViewModel() {
+ private val _eventLiveData = MutableLiveData>()
+ val eventLiveData: LiveData>
+ get() = _eventLiveData
+
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ private var totalCount = -1
+ private var page = 1
+ private val size = 10
+
+ fun getEvents() {
+ if (totalCount < 0 || page * size < totalCount) {
+ _isLoading.value = true
+ compositeDisposable.add(
+ repository.getEvents(
+ page = page - 1,
+ size = size,
+ token = "Bearer ${SharedPreferenceManager.token}"
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ if (it.success && it.data != null) {
+ val data = it.data
+ this.totalCount = data.totalCount
+ _eventLiveData.postValue(data.eventList)
+ page += 1
+ } else {
+ 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/GetNoticeResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/GetNoticeResponse.kt
new file mode 100644
index 0000000..4eafae9
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/GetNoticeResponse.kt
@@ -0,0 +1,17 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+import android.os.Parcelable
+import com.google.gson.annotations.SerializedName
+import kotlinx.parcelize.Parcelize
+
+data class GetNoticeResponse(
+ @SerializedName("totalCount") val totalCount: Int,
+ @SerializedName("noticeList") val noticeList: List
+)
+
+@Parcelize
+data class NoticeItem(
+ @SerializedName("title") val title: String,
+ @SerializedName("content") val content: String,
+ @SerializedName("date") val date: String
+) : Parcelable
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeActivity.kt
new file mode 100644
index 0000000..0e08066
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeActivity.kt
@@ -0,0 +1,107 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.View
+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.ActivityNoticeBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+import org.koin.android.ext.android.inject
+
+class NoticeActivity : BaseActivity(ActivityNoticeBinding::inflate) {
+
+ private val viewModel: NoticeViewModel by inject()
+
+ private lateinit var loadingDialog: LoadingDialog
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel.isLoading.observe(this) {
+ if (it) {
+ loadingDialog.show(screenWidth, "공지사항을 불러오고 있습니다.")
+ } else {
+ loadingDialog.dismiss()
+ }
+ }
+
+ viewModel.toastLiveData.observe(this) {
+ it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
+ }
+
+ viewModel.getNotices()
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ override fun setupView() {
+ binding.toolbar.tvBack.text = "공지사항"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ loadingDialog = LoadingDialog(this, layoutInflater)
+
+ val adapter = NoticeAdapter {
+ val intent = Intent(applicationContext, NoticeDetailActivity::class.java)
+ intent.putExtra(Constants.EXTRA_NOTICE, it)
+ startActivity(intent)
+ }
+ binding.rvNotice.layoutManager = LinearLayoutManager(this)
+ binding.rvNotice.addItemDecoration(object : RecyclerView.ItemDecoration() {
+ override fun getItemOffsets(
+ outRect: Rect,
+ view: View,
+ parent: RecyclerView,
+ state: RecyclerView.State
+ ) {
+ super.getItemOffsets(outRect, view, parent, state)
+
+ when (parent.getChildAdapterPosition(view)) {
+ 0 -> {
+ outRect.top = 13.3f.dpToPx().toInt()
+ outRect.bottom = 0
+ }
+
+ adapter.itemCount - 1 -> {
+ outRect.top = 0
+ outRect.bottom = 13.3f.dpToPx().toInt()
+ }
+
+ else -> {
+ outRect.top = 0
+ outRect.bottom = 0
+ }
+ }
+
+ outRect.left = 13.3f.dpToPx().toInt()
+ outRect.right = 13.3f.dpToPx().toInt()
+ }
+ })
+
+ binding.rvNotice.adapter = adapter
+
+ binding.rvNotice.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 = adapter.itemCount - 1
+
+ if (lastVisiblePosition == itemTotalCount) {
+ viewModel.getNotices()
+ }
+ }
+ })
+
+ viewModel.noticeLiveData.observe(this) {
+ adapter.items.addAll(it)
+ adapter.notifyDataSetChanged()
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeAdapter.kt
new file mode 100644
index 0000000..08ffc12
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeAdapter.kt
@@ -0,0 +1,41 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import kr.co.vividnext.sodalive.databinding.ItemNoticeBinding
+
+class NoticeAdapter(
+ private val onClick: (NoticeItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ val items = mutableListOf()
+
+ inner class ViewHolder(
+ private val binding: ItemNoticeBinding,
+ private val onClick: (NoticeItem) -> Unit
+ ) : RecyclerView.ViewHolder(binding.root) {
+ fun bind(notice: NoticeItem) {
+ binding.tvTitle.text = notice.title
+ binding.tvDate.text = notice.date
+ binding.root.setOnClickListener { onClick(notice) }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ ItemNoticeBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ),
+ onClick
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+
+ override fun getItemCount() = items.count()
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeApi.kt
new file mode 100644
index 0000000..cbf1fb8
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeApi.kt
@@ -0,0 +1,17 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+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 NoticeApi {
+ @GET("/notice")
+ fun getNotices(
+ @Query("timezone") timezone: String,
+ @Query("page") page: Int,
+ @Query("size") size: Int,
+ @Header("Authorization") authHeader: String
+ ): Single>
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeDetailActivity.kt
new file mode 100644
index 0000000..de5620c
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeDetailActivity.kt
@@ -0,0 +1,55 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+import android.annotation.SuppressLint
+import androidx.core.content.IntentCompat
+import androidx.webkit.WebSettingsCompat
+import androidx.webkit.WebViewFeature
+import kr.co.vividnext.sodalive.base.BaseActivity
+import kr.co.vividnext.sodalive.common.Constants
+import kr.co.vividnext.sodalive.databinding.ActivityNoticeDetailBinding
+
+class NoticeDetailActivity : BaseActivity(
+ ActivityNoticeDetailBinding::inflate
+) {
+ private lateinit var notice: NoticeItem
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun setupView() {
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+ WebSettingsCompat.setForceDark(
+ binding.webView.settings,
+ WebSettingsCompat.FORCE_DARK_ON
+ )
+ }
+
+ notice = IntentCompat.getParcelableExtra(
+ intent,
+ Constants.EXTRA_NOTICE,
+ NoticeItem::class.java
+ )!!
+
+ binding.toolbar.tvBack.text = "공지사항 상세"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ binding.tvTitle.text = notice.title
+ binding.tvDate.text = notice.date
+
+ val viewPort = ""
+ val data = viewPort + notice.content
+ binding.webView.settings.apply {
+ javaScriptEnabled = true // 자바스크립트 실행 허용
+ javaScriptCanOpenWindowsAutomatically = false // 자바스크립트에서 새창 실 행 허용
+ setSupportMultipleWindows(false) // 새 창 실행 허용
+ loadWithOverviewMode = true // 메타 태그 허용
+
+ useWideViewPort = true // 화면 사이즈 맞추기 허용
+ setSupportZoom(false) // 화면 줌 허용
+ builtInZoomControls = false // 화면 확대 축소 허용 여부
+ }
+ binding.webView.loadData(
+ data,
+ "text/html; charset=utf-8",
+ "utf-8"
+ )
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeRepository.kt
new file mode 100644
index 0000000..d73de8e
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeRepository.kt
@@ -0,0 +1,15 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+import io.reactivex.rxjava3.core.Single
+import kr.co.vividnext.sodalive.common.ApiResponse
+import java.util.TimeZone
+
+class NoticeRepository(private val api: NoticeApi) {
+ fun getNotices(
+ page: Int,
+ size: Int,
+ token: String
+ ): Single> {
+ return api.getNotices(TimeZone.getDefault().id, page, size, authHeader = token)
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeViewModel.kt
new file mode 100644
index 0000000..4c790f1
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notice/NoticeViewModel.kt
@@ -0,0 +1,66 @@
+package kr.co.vividnext.sodalive.settings.notice
+
+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 NoticeViewModel(private val repository: NoticeRepository) : BaseViewModel() {
+ private val _noticeLiveData = MutableLiveData>()
+ val noticeLiveData: LiveData>
+ get() = _noticeLiveData
+
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ private var totalCount = -1
+ private var page = 1
+ private val size = 10
+
+ fun getNotices() {
+ if (totalCount < 0 || page * size < totalCount) {
+ _isLoading.value = true
+ compositeDisposable.add(
+ repository.getNotices(
+ page = page - 1,
+ size = size,
+ token = "Bearer ${SharedPreferenceManager.token}"
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ if (it.success && it.data != null) {
+ val data = it.data
+ this.totalCount = data.totalCount
+ _noticeLiveData.postValue(data.noticeList)
+ page += 1
+ } else {
+ 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/NotificationSettingsActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/NotificationSettingsActivity.kt
new file mode 100644
index 0000000..46947d4
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/NotificationSettingsActivity.kt
@@ -0,0 +1,81 @@
+package kr.co.vividnext.sodalive.settings.notification
+
+import android.os.Bundle
+import android.widget.Toast
+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.databinding.ActivityNotificationSettingsBinding
+import org.koin.android.ext.android.inject
+
+class NotificationSettingsActivity : BaseActivity(
+ ActivityNotificationSettingsBinding::inflate
+) {
+
+ private val viewModel: NotificationSettingsViewModel by inject()
+
+ private lateinit var loadingDialog: LoadingDialog
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel.getNotificationSettings()
+ }
+
+ override fun setupView() {
+ binding.toolbar.tvBack.text = "알림 설정"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ loadingDialog = LoadingDialog(this, layoutInflater)
+
+ binding.ivNotifiedLive.setOnClickListener {
+ viewModel.toggleFollowingChannelLiveNotice()
+ }
+ binding.ivNotifiedUploadContent.setOnClickListener {
+ viewModel.toggleFollowingChannelUploadContentNotice()
+ }
+ binding.ivMessage.setOnClickListener { viewModel.toggleMessage() }
+
+ viewModel.followingChannelLiveNotice.observe(this) {
+ binding.ivNotifiedLive.setImageResource(
+ if (it) {
+ R.drawable.btn_toggle_on_big
+ } else {
+ R.drawable.btn_toggle_off_big
+ }
+ )
+ }
+
+ viewModel.followingChannelUploadContentNotice.observe(this) {
+ binding.ivNotifiedUploadContent.setImageResource(
+ if (it) {
+ R.drawable.btn_toggle_on_big
+ } else {
+ R.drawable.btn_toggle_off_big
+ }
+ )
+ }
+
+ viewModel.isMessage.observe(this) {
+ binding.ivMessage.setImageResource(
+ if (it) {
+ R.drawable.btn_toggle_on_big
+ } else {
+ R.drawable.btn_toggle_off_big
+ }
+ )
+ }
+
+ viewModel.isLoading.observe(this) {
+ if (it) {
+ loadingDialog.show(screenWidth, "")
+ } else {
+ loadingDialog.dismiss()
+ }
+ }
+
+ viewModel.toastLiveData.observe(this) {
+ it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/NotificationSettingsViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/NotificationSettingsViewModel.kt
new file mode 100644
index 0000000..8cec88c
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/notification/NotificationSettingsViewModel.kt
@@ -0,0 +1,110 @@
+package kr.co.vividnext.sodalive.settings.notification
+
+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.user.UserRepository
+
+class NotificationSettingsViewModel(
+ private val userRepository: UserRepository
+) : BaseViewModel() {
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ private var _followingChannelLiveNotice = MutableLiveData(false)
+ val followingChannelLiveNotice: LiveData
+ get() = _followingChannelLiveNotice
+
+ private var _isMessage = MutableLiveData(false)
+ val isMessage: LiveData
+ get() = _isMessage
+
+ private var _followingChannelUploadContentNotice = MutableLiveData(false)
+ val followingChannelUploadContentNotice: LiveData
+ get() = _followingChannelUploadContentNotice
+
+ fun getNotificationSettings() {
+ _isLoading.value = true
+
+ compositeDisposable.add(
+ userRepository.getMemberInfo(token = "Bearer ${SharedPreferenceManager.token}")
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ _isLoading.value = false
+
+ if (it.success && it.data != null) {
+ val data = it.data!!
+ _isMessage.value = data.messageNotice ?: false
+ _followingChannelUploadContentNotice.value =
+ data.followingChannelUploadContentNotice ?: false
+ _followingChannelLiveNotice.value =
+ data.followingChannelLiveNotice ?: false
+ } else {
+ if (it.message != null) {
+ _toastLiveData.postValue(it.message)
+ } else {
+ _toastLiveData.postValue(
+ "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
+ )
+ }
+ }
+ },
+ {
+ _isLoading.value = false
+ it.message?.let { message -> Logger.e(message) }
+ _toastLiveData.postValue(
+ "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
+ )
+ }
+ )
+ )
+ }
+
+ fun toggleFollowingChannelLiveNotice() {
+ _followingChannelLiveNotice.value = !followingChannelLiveNotice.value!!
+ updateNotificationSettings(live = followingChannelLiveNotice.value!!)
+ }
+
+ fun toggleFollowingChannelUploadContentNotice() {
+ _followingChannelUploadContentNotice.value = !followingChannelUploadContentNotice.value!!
+ updateNotificationSettings(uploadContent = followingChannelUploadContentNotice.value!!)
+ }
+
+ fun toggleMessage() {
+ _isMessage.value = !isMessage.value!!
+ updateNotificationSettings(message = isMessage.value!!)
+ }
+
+ private fun updateNotificationSettings(
+ live: Boolean? = null,
+ uploadContent: Boolean? = null,
+ message: Boolean? = null
+ ) {
+ if (live != null || uploadContent != null || message != null) {
+ compositeDisposable.add(
+ userRepository.updateNotificationSettings(
+ request = UpdateNotificationSettingRequest(
+ live,
+ uploadContent,
+ message
+ ),
+ token = "Bearer ${SharedPreferenceManager.token}"
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({}, {})
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutActivity.kt
new file mode 100644
index 0000000..9139078
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutActivity.kt
@@ -0,0 +1,106 @@
+package kr.co.vividnext.sodalive.settings.signout
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.TextView
+import android.widget.Toast
+import com.jakewharton.rxbinding4.widget.textChanges
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.schedulers.Schedulers
+import kr.co.vividnext.sodalive.base.BaseActivity
+import kr.co.vividnext.sodalive.common.LoadingDialog
+import kr.co.vividnext.sodalive.databinding.ActivitySignOutBinding
+import kr.co.vividnext.sodalive.splash.SplashActivity
+import org.koin.android.ext.android.inject
+import java.util.concurrent.TimeUnit
+
+class SignOutActivity : BaseActivity(ActivitySignOutBinding::inflate) {
+ private val viewModel: SignOutViewModel by inject()
+
+ private val reasonButtons = mutableListOf()
+ private lateinit var loadingDialog: LoadingDialog
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ bindData()
+ }
+
+ 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()
+ }
+ }
+
+ compositeDisposable.add(
+ binding.etPassword.textChanges().skip(1)
+ .debounce(500, TimeUnit.MILLISECONDS)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ viewModel.password = it.toString()
+ }
+ )
+ compositeDisposable.add(
+ binding.etReasonEtc.textChanges().skip(1)
+ .debounce(500, TimeUnit.MILLISECONDS)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ if (binding.tvReason10.isSelected) {
+ viewModel.reason = it.toString()
+ }
+ }
+ )
+ }
+
+ override fun setupView() {
+ reasonButtons.add(binding.tvReason1)
+ reasonButtons.add(binding.tvReason2)
+ reasonButtons.add(binding.tvReason3)
+ reasonButtons.add(binding.tvReason4)
+ reasonButtons.add(binding.tvReason5)
+ reasonButtons.add(binding.tvReason6)
+ reasonButtons.add(binding.tvReason7)
+ reasonButtons.add(binding.tvReason8)
+ reasonButtons.add(binding.tvReason9)
+ reasonButtons.add(binding.tvReason10)
+
+ loadingDialog = LoadingDialog(this, layoutInflater)
+ binding.toolbar.tvBack.text = "회원탈퇴"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ for (reasonButton in reasonButtons) {
+ val reason = reasonButton.text.toString()
+ reasonButton.setOnClickListener {
+ checkboxSelectedFalse()
+ reasonButton.isSelected = true
+ viewModel.reason = if (reason == "기타") {
+ binding.etReasonEtc.text.toString()
+ } else {
+ reasonButton.text.toString()
+ }
+ }
+ }
+
+ binding.tvSignOut.setOnClickListener {
+ viewModel.signOut {
+ finishAffinity()
+ startActivity(Intent(applicationContext, SplashActivity::class.java))
+ }
+ }
+ }
+
+ private fun checkboxSelectedFalse() {
+ for (reasonButton in reasonButtons) {
+ reasonButton.isSelected = false
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutRequest.kt
new file mode 100644
index 0000000..6d61121
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutRequest.kt
@@ -0,0 +1,8 @@
+package kr.co.vividnext.sodalive.settings.signout
+
+import com.google.gson.annotations.SerializedName
+
+data class SignOutRequest(
+ @SerializedName("reason") val reason: String,
+ @SerializedName("password") val password: String
+)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutViewModel.kt
new file mode 100644
index 0000000..fbf1c24
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/signout/SignOutViewModel.kt
@@ -0,0 +1,70 @@
+package kr.co.vividnext.sodalive.settings.signout
+
+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.user.UserRepository
+
+class SignOutViewModel(private val repository: UserRepository) : BaseViewModel() {
+
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ var reason: String = ""
+ var password = ""
+
+ fun signOut(onSuccess: () -> Unit) {
+ if (!_isLoading.value!!) {
+ if (reason.isBlank()) {
+ _toastLiveData.postValue("계정을 삭제하려는 이유를 선택해 주세요.")
+ return
+ }
+
+ if (password.isBlank()) {
+ _toastLiveData.postValue("비밀번호를 입력해 주세요.")
+ return
+ }
+
+ _isLoading.value = true
+ val request = SignOutRequest(reason.trim(), password)
+
+ compositeDisposable.add(
+ repository.signOut(request, "Bearer ${SharedPreferenceManager.token}")
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ _isLoading.value = false
+
+ if (it.message != null) {
+ _toastLiveData.postValue(it.message)
+ } else {
+ _toastLiveData.postValue(
+ "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
+ )
+ }
+
+ if (it.success) {
+ SharedPreferenceManager.clear()
+ onSuccess()
+ }
+ },
+ {
+ _isLoading.value = false
+ it.message?.let { message -> Logger.e(message) }
+ _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt
index d3350ff..f87d5e6 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt
@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest
import kr.co.vividnext.sodalive.mypage.MyPageResponse
import kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponse
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
+import kr.co.vividnext.sodalive.settings.signout.SignOutRequest
import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest
import kr.co.vividnext.sodalive.user.login.LoginRequest
import kr.co.vividnext.sodalive.user.login.LoginResponse
@@ -88,4 +89,13 @@ interface UserApi {
@Query("nickname") nickname: String,
@Header("Authorization") authHeader: String
): Single>>
+
+ @POST("/member/sign_out")
+ fun signOut(
+ @Body request: SignOutRequest,
+ @Header("Authorization") authHeader: String
+ ): Single>
+
+ @POST("/member/logout")
+ fun logout(@Header("Authorization") authHeader: String): Single>
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt
index f6e7cea..7506e26 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt
@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.mypage.MyPageResponse
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest
import kr.co.vividnext.sodalive.user.login.LoginRequest
+import kr.co.vividnext.sodalive.settings.signout.SignOutRequest
import okhttp3.MultipartBody
import okhttp3.RequestBody
@@ -70,4 +71,11 @@ class UserRepository(private val userApi: UserApi) {
): Single>> {
return userApi.searchUser(nickname, authHeader = token)
}
+
+ fun signOut(
+ request: SignOutRequest,
+ token: String
+ ) = userApi.signOut(request, authHeader = token)
+
+ fun logout(token: String) = userApi.logout(authHeader = token)
}
diff --git a/app/src/main/res/drawable/bg_round_corner_10_3e737c.xml b/app/src/main/res/drawable/bg_round_corner_10_3e737c.xml
new file mode 100644
index 0000000..7ad47c9
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_10_3e737c.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_event.xml b/app/src/main/res/layout/activity_event.xml
new file mode 100644
index 0000000..5d1a549
--- /dev/null
+++ b/app/src/main/res/layout/activity_event.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_event_detail.xml b/app/src/main/res/layout/activity_event_detail.xml
new file mode 100644
index 0000000..e7476c3
--- /dev/null
+++ b/app/src/main/res/layout/activity_event_detail.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_notice.xml b/app/src/main/res/layout/activity_notice.xml
new file mode 100644
index 0000000..1cc7f1f
--- /dev/null
+++ b/app/src/main/res/layout/activity_notice.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_notice_detail.xml b/app/src/main/res/layout/activity_notice_detail.xml
new file mode 100644
index 0000000..fa4cc17
--- /dev/null
+++ b/app/src/main/res/layout/activity_notice_detail.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_notification_settings.xml b/app/src/main/res/layout/activity_notification_settings.xml
new file mode 100644
index 0000000..980ba3b
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification_settings.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..03ff3f5
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_sign_out.xml b/app/src/main/res/layout/activity_sign_out.xml
new file mode 100644
index 0000000..1c207dd
--- /dev/null
+++ b/app/src/main/res/layout/activity_sign_out.xml
@@ -0,0 +1,288 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_event.xml b/app/src/main/res/layout/item_event.xml
new file mode 100644
index 0000000..2426f55
--- /dev/null
+++ b/app/src/main/res/layout/item_event.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_notice.xml b/app/src/main/res/layout/item_notice.xml
new file mode 100644
index 0000000..0ffadd7
--- /dev/null
+++ b/app/src/main/res/layout/item_notice.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 1d8a4a0..f23a871 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -69,4 +69,5 @@
#99000000
#4C9970FF
#4DD8D8D8
+ #3E737C