Skip to content
This repository has been archived by the owner on Jan 10, 2024. It is now read-only.

Commit

Permalink
Calendar Month view (#1558)
Browse files Browse the repository at this point in the history
* Provisional month view

* Deleted unnecessary import statement

* Update calendar_month_view.xml

* New approach for the month view

* improved the desingn

* Apply suggestions from code review

* Applyed formatting suggestions from code review

* renamed some functions

* implemented the swithcing behaviour via an enum instead of two bools

* renamed another function

* Clarifed that monday is the first day of the Week

* Apply suggestions from code review

Co-authored-by: Marius Wagner <[email protected]>

* formatting fixes

* formatting fix

---------

Co-authored-by: ge78fug <[email protected]>
Co-authored-by: Marius Wagner <[email protected]>
  • Loading branch information
3 people authored May 26, 2023
1 parent d0b4cab commit 1912fc2
Show file tree
Hide file tree
Showing 17 changed files with 458 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import de.tum.`in`.tumcampusapp.utils.Const
import de.tum.`in`.tumcampusapp.utils.Utils
import de.tum.`in`.tumcampusapp.utils.sync.SyncManager
import org.joda.time.DateTime
import org.joda.time.LocalDate
import java.util.*

/**
Expand Down Expand Up @@ -61,6 +62,22 @@ class CalendarController(private val context: Context) : ProvidesCard, ProvidesN
fun getFromDbNotCancelledBetweenDates(begin: DateTime, end: DateTime) =
applyEventColors(calendarDao.getAllNotCancelledBetweenDates(begin, end))

fun getEventsForMonth(date: LocalDate): Map<String, List<CalendarItem>> {
val startOfMonth = date.withDayOfMonth(1)
val endOfMonth = date.withDayOfMonth(date.dayOfMonth().maximumValue)
val events = getFromDbBetweenDates(startOfMonth.toDateTimeAtCurrentTime(), endOfMonth.toDateTimeAtCurrentTime())
val eventMap = mutableMapOf<String, MutableList<CalendarItem>>()
for (event in events) {
val day = event.dtstart.toLocalDate().dayOfMonth.toString()
if (!eventMap.containsKey(day)) {
eventMap[day] = mutableListOf(event)
} else {
eventMap[day]?.add(event)
}
}
return eventMap
}

private fun applyEventColors(calendarItems: List<CalendarItem>): List<CalendarItem> {
calendarItems.forEach {
it.color = eventColorProvider.getColor(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import android.os.Bundle
import de.tum.`in`.tumcampusapp.utils.ThemedAlertDialogBuilder
import android.provider.CalendarContract
import android.text.format.DateUtils
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.*
import android.widget.TextView
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alamkanak.weekview.DateTimeInterpreter
import com.alamkanak.weekview.WeekViewDisplayable
import com.zhuinden.fragmentviewbindingdelegatekt.viewBinding
Expand All @@ -38,6 +38,7 @@ import io.reactivex.schedulers.Schedulers
import org.joda.time.DateTime
import org.joda.time.LocalDate
import org.joda.time.format.DateTimeFormat
import java.time.YearMonth
import java.util.*

class CalendarFragment :
Expand All @@ -61,7 +62,27 @@ class CalendarFragment :
value ?: ""
}

private var isWeekMode = false
private enum class ViewMode {
DAY {
override fun numberOfVisibleDays(): Int {
return 1
}
},
WEEK {
override fun numberOfVisibleDays(): Int {
return 5
}
},
MONTH {
override fun numberOfVisibleDays(): Int {
return 30
}
};

abstract fun numberOfVisibleDays(): Int
}

private var viewMode = ViewMode.MONTH

private var isFetched: Boolean = false
private var menuItemSwitchView: MenuItem? = null
Expand All @@ -73,6 +94,11 @@ class CalendarFragment :

private val binding by viewBinding(FragmentCalendarBinding::bind)

private lateinit var monthYearText: TextView
private lateinit var monthRecyclerView: RecyclerView
private var selectedDate: LocalDate = LocalDate.now()
private lateinit var monthViewAdapter: MonthViewAdapter

override val swipeRefreshLayout get() = binding.swipeRefreshLayout
override val layoutAllErrorsBinding get() = binding.layoutAllErrors

Expand Down Expand Up @@ -108,10 +134,22 @@ class CalendarFragment :

showDate?.let { openEvent(eventId) }

isWeekMode = Utils.getSettingBool(requireContext(), Const.CALENDAR_WEEK_MODE, false)
viewMode = ViewMode.valueOf(Utils.getSetting(requireContext(), Const.CALENDAR_VIEW_MODE, ViewMode.MONTH.toString()))

disableRefresh()

monthYearText = binding.layoutMonth.monthYearText
monthRecyclerView = binding.layoutMonth.monthGrid
refreshMonthView()
binding.layoutMonth.monthBackButton.setOnClickListener {
selectedDate = selectedDate.minusMonths(1)
refreshMonthView()
}
binding.layoutMonth.monthForwardButton.setOnClickListener {
selectedDate = selectedDate.plusMonths(1)
refreshMonthView()
}

// Tracks whether the user has used the calendar module before. This is used in determining when to prompt for a
// Google Play store review
Utils.setSetting(requireContext(), Const.HAS_VISITED_CALENDAR, true)
Expand All @@ -120,7 +158,7 @@ class CalendarFragment :
override fun onStart() {
super.onStart()
refreshWeekView()

refreshMonthView()
// In case the timezone changes when reopening the calendar, while the app is still open, this ensures
// that the lectures are still adjusted to the new timezone
loadEvents(CacheControl.BYPASS_CACHE)
Expand Down Expand Up @@ -197,8 +235,12 @@ class CalendarFragment :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_switch_view_mode -> {
isWeekMode = !isWeekMode
Utils.setSetting(requireContext(), Const.CALENDAR_WEEK_MODE, isWeekMode)
viewMode = when (viewMode) {
ViewMode.DAY -> ViewMode.WEEK
ViewMode.WEEK -> ViewMode.MONTH
ViewMode.MONTH -> ViewMode.DAY
}
Utils.setSetting(requireContext(), Const.CALENDAR_VIEW_MODE, viewMode.toString())
refreshWeekView()
return true
}
Expand Down Expand Up @@ -439,15 +481,25 @@ class CalendarFragment :
}

private fun refreshWeekView() {
setupDateTimeInterpreter(isWeekMode)
val icon: Int
setupDateTimeInterpreter(viewMode == ViewMode.WEEK)

if (isWeekMode) {
icon = R.drawable.ic_outline_calendar_view_day_24px
binding.weekView.numberOfVisibleDays = 5
} else {
icon = R.drawable.ic_outline_view_column_24px
binding.weekView.numberOfVisibleDays = 1
val icon = when (viewMode) {
ViewMode.DAY -> R.drawable.ic_outline_calendar_view_month_24px
ViewMode.WEEK -> R.drawable.ic_outline_calendar_view_day_24px
ViewMode.MONTH -> R.drawable.ic_outline_view_column_24px
}

when (viewMode) {
ViewMode.DAY, ViewMode.WEEK -> {
binding.layoutWeek.visibility = View.VISIBLE
binding.layoutMonth.root.visibility = View.GONE
binding.weekView.numberOfVisibleDays = viewMode.numberOfVisibleDays()
}

ViewMode.MONTH -> {
binding.layoutWeek.visibility = View.GONE
binding.layoutMonth.root.visibility = View.VISIBLE
}
}

// Go to current date or the one given in the intent
Expand Down Expand Up @@ -514,6 +566,44 @@ class CalendarFragment :
.show()
}

private fun refreshMonthView() {
monthYearText.text = formatLocalDate(selectedDate)
val daysInMonth = daysInMonth(selectedDate)

val eventMap = calendarController.getEventsForMonth(selectedDate)

if (!::monthViewAdapter.isInitialized) {
monthViewAdapter = MonthViewAdapter(daysInMonth, eventMap)
monthRecyclerView.adapter = monthViewAdapter
} else {
monthViewAdapter.updateData(daysInMonth, eventMap)
}

val layoutManager: RecyclerView.LayoutManager = GridLayoutManager(requireContext(), 7)
monthRecyclerView.layoutManager = layoutManager
}

private fun daysInMonth(date: LocalDate): ArrayList<String> {
val daysInMonthArray: ArrayList<String> = ArrayList()
val yearMonth = YearMonth.of(date.year, date.monthOfYear)
val daysInMonth = yearMonth.lengthOfMonth()
val firstOfMonth = date.withDayOfMonth(1)
var dayOfWeek = firstOfMonth.dayOfWeek().get() - 1 // Monday is the first day of the week in Europe
for (i in 1..42) {
if (i <= dayOfWeek || i > daysInMonth + dayOfWeek) {
daysInMonthArray.add("")
} else {
daysInMonthArray.add((i - dayOfWeek).toString())
}
}
return daysInMonthArray
}

private fun formatLocalDate(date: LocalDate): String {
val formatter = DateTimeFormat.forPattern("MMMM yyyy")
return formatter.print(date.withDayOfMonth(1))
}

override fun onDestroyView() {
super.onDestroyView()
menuItemSwitchView = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.tum.`in`.tumcampusapp.component.tumui.calendar

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import de.tum.`in`.tumcampusapp.R
import de.tum.`in`.tumcampusapp.component.tumui.calendar.model.CalendarItem

class MonthViewAdapter(private var daysOfMonth: ArrayList<String>, private var eventMap: Map<String, List<CalendarItem>>) :
RecyclerView.Adapter<MonthViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MonthViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.calendar_month_view_cell, parent, false)
val layoutParams = view.layoutParams
layoutParams.height = (parent.height * 1 / 6)
return MonthViewHolder(view)
}

override fun onBindViewHolder(holder: MonthViewHolder, position: Int) {
val events = eventMap[daysOfMonth[position]]

holder.dayOfMonth.text = daysOfMonth[position]
holder.eventsContainer.adapter = events?.let { MonthViewEventAdapter(it) }
holder.eventsContainer.layoutManager = LinearLayoutManager(holder.itemView.context)
}

override fun getItemCount(): Int {
return daysOfMonth.size
}

fun updateData(daysOfMonth: ArrayList<String>, eventMap: Map<String, List<CalendarItem>>) {
this.daysOfMonth = daysOfMonth
this.eventMap = eventMap
notifyDataSetChanged()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.tum.`in`.tumcampusapp.component.tumui.calendar

import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import de.tum.`in`.tumcampusapp.R
import de.tum.`in`.tumcampusapp.component.tumui.calendar.model.CalendarItem

class MonthViewEventAdapter(private val events: List<CalendarItem>) : RecyclerView.Adapter<MonthViewEventViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MonthViewEventViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.calendar_month_event_view, parent, false)
return MonthViewEventViewHolder(view)
}

override fun onBindViewHolder(holder: MonthViewEventViewHolder, position: Int) {
val event = events[position]
holder.title.text = event.title
val background = holder.title.background as GradientDrawable
background.setColor(event.color!!)
}

override fun getItemCount(): Int {
return events.size
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.tum.`in`.tumcampusapp.component.tumui.calendar

import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import de.tum.`in`.tumcampusapp.R

class MonthViewEventViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val title: TextView = view.findViewById(R.id.eventTitle)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.tum.`in`.tumcampusapp.component.tumui.calendar

import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import de.tum.`in`.tumcampusapp.R

class MonthViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

val dayOfMonth: TextView = itemView.findViewById(R.id.cellDayNumber)

val eventsContainer: RecyclerView = itemView.findViewById(R.id.eventsContainer)
}
2 changes: 1 addition & 1 deletion app/src/main/java/de/tum/in/tumcampusapp/utils/Const.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Const {
const val CAFETERIA_DATE = "cafeteriaDate"
const val CAFETERIAS = "cafeterias"
const val CAFETERIA_BY_LOCATION_SETTINGS_ID = "-1"
const val CALENDAR_WEEK_MODE = "calender_week_mode"
const val CALENDAR_VIEW_MODE = "calender_view_mode"
const val EVENT_BOOKED_MODE = "event_booked_mode"
const val DATABASE_NAME = "tca.db"
const val DATE = "date"
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/drawable/grid_monthly_calendar.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="0.5dp"
android:color="@color/navigation_bar_divider" />
</shape>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_arrow_forward.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@color/color_primary"
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
</vector>
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/ic_outline_calendar_view_month_24px.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@color/text_primary"
android:pathData="M4,5v13h17V5H4zM14,7v9h-3V7H14zM6,7h3v9H6V7zM19,16h-3V7h3V16z" />
<path
android:fillColor="@color/text_primary"
android:pathData="M4,10.5h17v2h-15z" />
</vector>
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/rounded_corners_monthly_calendar.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10px" />

<padding
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp" />
<solid android:color="@color/white" />
</shape>
21 changes: 21 additions & 0 deletions app/src/main/res/layout/calendar_month_event_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/eventTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="0.5dp"
android:layout_marginRight="2dp"
android:layout_marginVertical="0.5dp"
android:background="@drawable/rounded_corners_monthly_calendar"
android:maxLines="2"
android:text="Event Title"
android:textColor="@color/white"
android:textSize="8sp" />

</LinearLayout>

Loading

0 comments on commit 1912fc2

Please sign in to comment.