diff --git a/app/build.gradle b/app/build.gradle index 0aeb36e..4052e67 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -146,4 +146,6 @@ dependencies { // Glide implementation 'com.github.bumptech.glide:glide:4.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' + + implementation "com.michalsvec:single-row-calednar:1.0.0" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4d4db9a..7071186 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -87,6 +87,7 @@ + (FragmentLiveBinding::infl binding.layoutLiveReservation.root.requestLayout() } - binding.layoutLiveReservation.tvAllView.setOnClickListener {} + binding.layoutLiveReservation.tvAllView.setOnClickListener { + startActivity( + Intent(requireContext(), LiveReservationAllActivity::class.java) + ) + } } @SuppressLint("NotifyDataSetChanged") diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt index df98de6..f2cf4e5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt @@ -490,4 +490,47 @@ class LiveViewModel( ) } } + + fun getLiveReservation(dateString: String? = null) { + if (!isLast && !_isLoading.value!!) { + _isLoading.postValue(true) + compositeDisposable.add( + repository.roomList( + dateString = dateString, + status = LiveRoomStatus.RESERVATION, + page = page, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.postValue(false) + if (it.success && it.data != null) { + _liveReservationLiveData.postValue(it.data!!) + if (it.data.isNotEmpty()) { + page += 1 + } else { + isLast = true + } + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.postValue(false) + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/reservation/all/LiveReservationAllActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/reservation/all/LiveReservationAllActivity.kt new file mode 100644 index 0000000..8f2944c --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/reservation/all/LiveReservationAllActivity.kt @@ -0,0 +1,383 @@ +package kr.co.vividnext.sodalive.live.reservation.all + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.graphics.Rect +import android.os.Bundle +import android.view.View +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.michalsvec.singlerowcalendar.calendar.CalendarChangesObserver +import com.michalsvec.singlerowcalendar.calendar.CalendarViewManager +import com.michalsvec.singlerowcalendar.calendar.SingleRowCalendarAdapter +import com.michalsvec.singlerowcalendar.selection.CalendarSelectionManager +import com.michalsvec.singlerowcalendar.utils.DateUtils +import kr.co.vividnext.sodalive.R +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.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ActivityLiveReservationAllBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.extensions.moneyFormat +import kr.co.vividnext.sodalive.live.LiveViewModel +import kr.co.vividnext.sodalive.live.reservation.LiveReservationAdapter +import kr.co.vividnext.sodalive.live.reservation.complete.LiveReservationCompleteActivity +import kr.co.vividnext.sodalive.live.room.LiveRoomActivity +import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateActivity +import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse +import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment +import kr.co.vividnext.sodalive.live.room.dialog.LiveCancelDialog +import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog +import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog +import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditActivity +import org.koin.android.ext.android.inject +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +class LiveReservationAllActivity : BaseActivity( + ActivityLiveReservationAllBinding::inflate +) { + private val viewModel: LiveViewModel by inject() + + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: LiveReservationAdapter + private lateinit var selectedDateString: String + private lateinit var activityResultLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + activityResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == Activity.RESULT_OK) { + refresh() + } + } + + setupCalendar() + } + + private fun setupCalendar() { + val myCalendarViewManager = object : CalendarViewManager { + override fun setCalendarViewResourceId( + position: Int, + date: Date, + isSelected: Boolean + ): Int { + return if (isSelected) + R.layout.item_calendar_selected + else + R.layout.item_calendar + } + + override fun bindDataToCalendarView( + holder: SingleRowCalendarAdapter.CalendarViewHolder, + date: Date, + position: Int, + isSelected: Boolean + ) { + holder.itemView.findViewById( + R.id.tv_date_calendar_item + ).text = DateUtils.getDayNumber(date) + + val tvDayCalendarItem = holder.itemView.findViewById( + R.id.tv_day_calendar_item + ) + tvDayCalendarItem.text = DateUtils.getDay3LettersName(date) + + val cal = Calendar.getInstance() + cal.time = date + when (cal[Calendar.DAY_OF_WEEK]) { + Calendar.SATURDAY -> tvDayCalendarItem.setTextColor( + ContextCompat.getColor( + applicationContext, + R.color.color_2f90b7 + ) + ) + + Calendar.SUNDAY -> tvDayCalendarItem.setTextColor( + ContextCompat.getColor( + applicationContext, + R.color.color_a94400 + ) + ) + + else -> tvDayCalendarItem.setTextColor( + ContextCompat.getColor( + applicationContext, + R.color.white + ) + ) + } + } + } + + // using calendar changes observer we can track changes in calendar + val myCalendarChangesObserver = object : + CalendarChangesObserver { + // you can override more methods, in this example we need only this one + override fun whenSelectionChanged(isSelected: Boolean, position: Int, date: Date) { + if (isSelected) { + val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + selectedDateString = sdf.format(date.time) + + adapter.clear() + viewModel.page = 1 + viewModel.isLast = false + viewModel.getLiveReservation(selectedDateString) + } + super.whenSelectionChanged(isSelected, position, date) + } + } + + // selection manager is responsible for managing selection + val mySelectionManager = object : CalendarSelectionManager { + override fun canBeItemSelected(position: Int, date: Date): Boolean { + return true + } + } + + binding.calendarView.apply { + calendarViewManager = myCalendarViewManager + calendarChangesObserver = myCalendarChangesObserver + calendarSelectionManager = mySelectionManager + setDates(getDates(mutableListOf())) + init() + select(0) + } + } + + private fun getDates(list: MutableList): List { + // load dates of whole month + val calendar = Calendar.getInstance() + val currentMonth = calendar[Calendar.MONTH] + calendar.set(Calendar.MONTH, currentMonth) + calendar.set(Calendar.DAY_OF_MONTH, calendar[Calendar.DAY_OF_MONTH]) + list.add(calendar.time) + for (index in 1..6) { + calendar.add(Calendar.DATE, +1) + list.add(calendar.time) + } + calendar.add(Calendar.DATE, -1) + return list + } + + @SuppressLint("NotifyDataSetChanged") + override fun setupView() { + binding.toolbar.tvBack.text = "라이브, 예약 캘린더" + binding.toolbar.tvBack.setOnClickListener { finish() } + + loadingDialog = LoadingDialog(this, layoutInflater) + + val recyclerView = binding.rvCounselor + + viewModel.toastLiveData.observe(this) { + it?.let { Toast.makeText(this, it, Toast.LENGTH_LONG).show() } + } + + viewModel.isLoading.observe(this) { + if (it) { + loadingDialog.show(screenWidth, "") + } else { + loadingDialog.dismiss() + } + } + + viewModel.liveReservationLiveData.observe(this) { + if (it.isEmpty()) { + if (adapter.items.isEmpty()) { + binding.swipeRefreshLayout.visibility = View.GONE + binding.llNoItems.visibility = View.VISIBLE + binding.llNoItems.setOnClickListener { + val intent = Intent(applicationContext, LiveRoomCreateActivity::class.java) + activityResultLauncher.launch(intent) + } + } + } else { + binding.swipeRefreshLayout.visibility = View.VISIBLE + binding.llNoItems.visibility = View.GONE + adapter.items.addAll(it) + adapter.notifyDataSetChanged() + } + } + + adapter = LiveReservationAdapter { + val detailFragment = LiveRoomDetailFragment( + it.roomId, + onClickParticipant = {}, + onClickReservation = { onClickReservation(it.roomId) }, + onClickModify = { roomDetailResponse -> onClickModify(roomDetailResponse) }, + onClickStart = { onClickStart(it.roomId) }, + onClickCancel = { onClickCancel(it.roomId) } + ) + + if (detailFragment.isAdded) return@LiveReservationAdapter + + detailFragment.show( + supportFragmentManager, + detailFragment.tag + ) + } + + recyclerView.layoutManager = LinearLayoutManager( + applicationContext, + 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) + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.top = 0.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + + adapter.itemCount - 1 -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 0.dpToPx().toInt() + } + + else -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + } + } + }) + + recyclerView.adapter = adapter + + recyclerView.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.getLiveReservation(selectedDateString) + } + } + }) + + binding.swipeRefreshLayout.setOnRefreshListener { refresh() } + } + + private fun refresh() { + adapter.clear() + viewModel.page = 1 + viewModel.isLast = false + viewModel.getLiveReservation(selectedDateString) + + binding.swipeRefreshLayout.isRefreshing = false + } + + private fun onClickReservation(roomId: Long) { + viewModel.getRoomDetail(roomId) { + if (it.manager.id == SharedPreferenceManager.userId) { + showToast("내가 만든 라이브는 예약할 수 없습니다.") + } else { + if (it.isPrivateRoom) { + LiveRoomPasswordDialog( + activity = this, + layoutInflater = layoutInflater, + can = if (it.isPaid) 0 else it.price, + confirmButtonClick = { password -> + processLiveReservation(roomId, password) + } + ).show(screenWidth) + } else { + if (it.price == 0 || it.isPaid) { + processLiveReservation(roomId) + } else { + LivePaymentDialog( + activity = this, + layoutInflater = layoutInflater, + title = "${it.price.moneyFormat()} 캔으로 예약", + desc = "'${it.title}' 라이브에 참여하기 위해 결제합니다.", + confirmButtonTitle = "예약하기", + confirmButtonClick = { processLiveReservation(roomId) }, + cancelButtonTitle = "취소", + cancelButtonClick = {} + ).show(screenWidth) + } + } + } + } + } + + private fun processLiveReservation(roomId: Long, password: String? = null) { + viewModel.reservationRoom(roomId, password) { + refresh() + val intent = Intent( + applicationContext, + LiveReservationCompleteActivity::class.java + ) + intent.putExtra(Constants.EXTRA_LIVE_RESERVATION_RESPONSE, it) + startActivity(intent) + } + } + + private fun onClickStart(roomId: Long) { + val onEnterRoomSuccess = { + viewModel.getSummary() + runOnUiThread { + val intent = Intent(applicationContext, LiveRoomActivity::class.java) + intent.putExtra(Constants.EXTRA_ROOM_ID, roomId) + startActivity(intent) + } + } + + viewModel.startLive(roomId, onEnterRoomSuccess) + } + + private fun onClickCancel(roomId: Long) { + LiveCancelDialog( + activity = this, + layoutInflater = layoutInflater, + title = "예약취소", + hint = "취소사유를 입력하세요.", + confirmButtonTitle = "예약취소", + confirmButtonClick = { + viewModel.cancelLive(roomId, it) { + Toast.makeText( + applicationContext, + "예약이 취소되었습니다.", + Toast.LENGTH_LONG + ).show() + adapter.clear() + refresh() + } + }, + cancelButtonTitle = "닫기", + cancelButtonClick = {} + ).show(screenWidth) + } + + private fun onClickModify(roomDetail: GetRoomDetailResponse) { + startActivity( + Intent(applicationContext, LiveRoomEditActivity::class.java).apply { + putExtra(Constants.EXTRA_ROOM_DETAIL, roomDetail) + } + ) + } +} diff --git a/app/src/main/res/layout/activity_live_reservation_all.xml b/app/src/main/res/layout/activity_live_reservation_all.xml new file mode 100644 index 0000000..425c7ab --- /dev/null +++ b/app/src/main/res/layout/activity_live_reservation_all.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_calendar.xml b/app/src/main/res/layout/item_calendar.xml new file mode 100644 index 0000000..f7c4298 --- /dev/null +++ b/app/src/main/res/layout/item_calendar.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_calendar_selected.xml b/app/src/main/res/layout/item_calendar_selected.xml new file mode 100644 index 0000000..39a3764 --- /dev/null +++ b/app/src/main/res/layout/item_calendar_selected.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 71929ee..1bdecb4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -94,4 +94,6 @@ #26FFFFFF #979797 #660FD4 + #2F90B7 + #A94400