diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt index 57f3005ad..d9aa24364 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/LuckyNumberDao.kt @@ -13,4 +13,7 @@ interface LuckyNumberDao : BaseDao { @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") fun load(studentId: Int, date: LocalDate): Flow + + @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date >= :start AND date <= :end") + fun getAll(studentId: Int, start: LocalDate, end: LocalDate): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt index 801292b42..4f2dcc542 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import java.time.LocalDate import java.time.LocalDate.now import javax.inject.Inject import javax.inject.Singleton @@ -33,6 +34,9 @@ class LuckyNumberRepository @Inject constructor( } ) + fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) = + luckyNumberDb.getAll(student.studentId, start, end) + suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map { if (it?.isNotified == false) it else null }.first() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt index 3de235857..0a73fe15d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberFragment.kt @@ -9,6 +9,8 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.databinding.FragmentLuckyNumberBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.getThemeAttrColor import javax.inject.Inject @@ -42,6 +44,7 @@ class LuckyNumberFragment : luckyNumberSwipe.setOnRefreshListener(presenter::onSwipeRefresh) luckyNumberSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary)) luckyNumberSwipe.setProgressBackgroundColorSchemeColor(requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)) + luckyNumberHistoryButton.setOnClickListener { openLuckyNumberHistory() } luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() } luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() } } @@ -79,6 +82,10 @@ class LuckyNumberFragment : binding.luckyNumberContent.visibility = if (show) VISIBLE else GONE } + override fun openLuckyNumberHistory() { + (activity as? MainActivity)?.pushView(LuckyNumberHistoryFragment.newInstance()) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt index a680c83eb..0c05a1566 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberView.kt @@ -24,4 +24,6 @@ interface LuckyNumberView : BaseView { fun enableSwipe(enable: Boolean) fun showContent(show: Boolean) + + fun openLuckyNumberHistory() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt new file mode 100644 index 000000000..7c09c96fe --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryAdapter.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.databinding.ItemLuckyNumberHistoryBinding +import io.github.wulkanowy.utils.toFormattedString +import io.github.wulkanowy.utils.weekDayName +import java.util.Locale +import javax.inject.Inject + +class LuckyNumberHistoryAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemLuckyNumberHistoryBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("DefaultLocale") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + with(holder.binding) { + luckyNumberHistoryWeekName.text = item.date.weekDayName.capitalize() + luckyNumberHistoryDate.text = item.date.toFormattedString() + luckyNumberHistory.text = item.luckyNumber.toString() + } + } + + class ItemViewHolder(val binding: ItemLuckyNumberHistoryBinding) : RecyclerView.ViewHolder(binding.root) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt new file mode 100644 index 000000000..6e991ba13 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -0,0 +1,134 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import android.os.Bundle +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import androidx.recyclerview.widget.LinearLayoutManager +import com.wdullaer.materialdatetimepicker.date.DatePickerDialog +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.databinding.FragmentLuckyNumberHistoryBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.SchooldaysRangeLimiter +import io.github.wulkanowy.utils.dpToPx +import io.github.wulkanowy.utils.getThemeAttrColor +import java.time.LocalDate +import javax.inject.Inject + +@AndroidEntryPoint +class LuckyNumberHistoryFragment : + BaseFragment(R.layout.fragment_lucky_number_history), LuckyNumberHistoryView, + MainView.TitledView { + + @Inject + lateinit var presenter: LuckyNumberHistoryPresenter + + @Inject + lateinit var luckyNumberHistoryAdapter: LuckyNumberHistoryAdapter + + companion object { + fun newInstance() = LuckyNumberHistoryFragment() + } + + override val titleStringId: Int + get() = R.string.lucky_number_history_title + + override val isViewEmpty get() = luckyNumberHistoryAdapter.items.isEmpty() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLuckyNumberHistoryBinding.bind(view) + messageContainer = binding.luckyNumberHistoryRecycler + presenter.onAttachView(this) + } + + override fun initView() { + with(binding.luckyNumberHistoryRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = luckyNumberHistoryAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + luckyNumberHistoryNavDate.setOnClickListener { presenter.onPickDate() } + luckyNumberHistoryErrorRetry.setOnClickListener { presenter.onRetry() } + luckyNumberHistoryErrorDetails.setOnClickListener { presenter.onDetailsClick() } + + luckyNumberHistoryPreviousButton.setOnClickListener { presenter.onPreviousWeek() } + luckyNumberHistoryNextButton.setOnClickListener { presenter.onNextWeek() } + + luckyNumberHistoryNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } + } + + override fun updateData(data: List) { + with(luckyNumberHistoryAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(luckyNumberHistoryAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun showEmpty(show: Boolean) { + binding.luckyNumberHistoryEmpty.visibility = if (show) VISIBLE else GONE + } + + override fun showErrorView(show: Boolean) { + binding.luckyNumberHistoryError.visibility = if (show) VISIBLE else GONE + } + + override fun setErrorDetails(message: String) { + binding.luckyNumberHistoryErrorMessage.text = message + } + + override fun updateNavigationWeek(date: String) { + binding.luckyNumberHistoryNavDate.text = date + } + + override fun showProgress(show: Boolean) { + binding.luckyNumberHistoryProgress.visibility = if (show) VISIBLE else GONE + } + + override fun showPreButton(show: Boolean) { + binding.luckyNumberHistoryPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE + } + + override fun showNextButton(show: Boolean) { + binding.luckyNumberHistoryNextButton.visibility = if (show) VISIBLE else View.INVISIBLE + } + + override fun showDatePickerDialog(currentDate: LocalDate) { + val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth -> + presenter.onDateSet(year, month + 1, dayOfMonth) + } + val datePickerDialog = DatePickerDialog.newInstance(dateSetListener, + currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth) + + with(datePickerDialog) { + setDateRangeLimiter(SchooldaysRangeLimiter()) + version = DatePickerDialog.Version.VERSION_2 + scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL + vibrate(false) + show(this@LuckyNumberHistoryFragment.parentFragmentManager, null) + } + } + + override fun showContent(show: Boolean) { + binding.luckyNumberHistoryRecycler.visibility = if (show) VISIBLE else GONE + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt new file mode 100644 index 000000000..556dda759 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryPresenter.kt @@ -0,0 +1,151 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.previousOrSameSchoolDay +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import java.time.LocalDate +import javax.inject.Inject + +class LuckyNumberHistoryPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val luckyNumberRepository: LuckyNumberRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + var currentDate: LocalDate = LocalDate.now().previousOrSameSchoolDay + + override fun onAttachView(view: LuckyNumberHistoryView) { + super.onAttachView(view) + view.run { + initView() + reloadNavigation() + showContent(false) + } + Timber.i("Lucky number history view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + private fun loadData() { + flowWithResource { + val student = studentRepository.getCurrentStudent() + luckyNumberRepository.getLuckyNumberHistory(student, currentDate.monday, currentDate.sunday) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading lucky number history started") + Status.SUCCESS -> { + if (!it.data?.first().isNullOrEmpty()) { + Timber.i("Loading lucky number result: Success") + view?.apply { + updateData(it.data!!.first()) + showContent(true) + showEmpty(false) + showErrorView(false) + showProgress(false) + } + analytics.logEvent( + "load_items", + "type" to "lucky_number_history", + "numbers" to it.data + ) + } else { + Timber.i("Loading lucky number history result: No lucky numbers found") + view?.run { + showContent(false) + showEmpty(true) + showErrorView(false) + } + } + } + Status.ERROR -> { + Timber.i("Loading lucky number history result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + showProgress(false) + } + }.launch() + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + private fun reloadView(date: LocalDate) { + currentDate = date + Timber.i("Reload lucky number history view with the date ${currentDate.toFormattedString()}") + view?.apply { + showProgress(true) + showContent(false) + showEmpty(false) + showErrorView(false) + clearData() + reloadNavigation() + } + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun reloadNavigation() { + view?.apply { + showPreButton(!currentDate.minusDays(7).isHolidays) + showNextButton(!currentDate.plusDays(7).isHolidays) + updateNavigationWeek("${currentDate.monday.toFormattedString("dd.MM")} - " + + currentDate.sunday.toFormattedString("dd.MM")) + } + } + + fun onDateSet(year: Int, month: Int, day: Int) { + reloadView(LocalDate.of(year, month, day)) + loadData() + } + + fun onPickDate() { + view?.showDatePickerDialog(currentDate) + } + + fun onPreviousWeek() { + reloadView(currentDate.minusDays(7)) + loadData() + } + + fun onNextWeek() { + reloadView(currentDate.plusDays(7)) + loadData() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt new file mode 100644 index 000000000..331e4ff86 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryView.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.luckynumber.history + +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate + +interface LuckyNumberHistoryView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun updateNavigationWeek(date: String) + + fun showProgress(show: Boolean) + + fun showPreButton(show: Boolean) + + fun showNextButton(show: Boolean) + + fun showDatePickerDialog(currentDate: LocalDate) + + fun showContent(show: Boolean) + + fun onDestroyView() +} diff --git a/app/src/main/res/layout/fragment_lucky_number.xml b/app/src/main/res/layout/fragment_lucky_number.xml index f6de01e69..b2d4f40ab 100644 --- a/app/src/main/res/layout/fragment_lucky_number.xml +++ b/app/src/main/res/layout/fragment_lucky_number.xml @@ -67,6 +67,26 @@ android:textSize="20sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_lucky_number_history.xml b/app/src/main/res/layout/item_lucky_number_history.xml new file mode 100644 index 000000000..79a0fcb29 --- /dev/null +++ b/app/src/main/res/layout/item_lucky_number_history.xml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b42ba2e25..574c21b04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -306,6 +306,11 @@ No info about the lucky number Lucky number for today Today\'s lucky number is: %d + Show history + + + Lucky number history + No info about lucky numbers Mobile devices