Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YearCalendarView class to the view module #570

Merged
merged 18 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/Compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,35 @@ fun MainScreen() {
}
```

`HorizontalYearCalendar` and `VerticalYearCalendar`:

```kotlin
@Composable
fun MainScreen() {
val currentYear = remember { Year.now() }
val startYear = remember { currentYear.minusYears(100) } // Adjust as needed
val endYear = remember { currentYear.plusYears(100) } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library

val state = rememberYearCalendarState(
startYear = startYear,
endYear = endYear,
firstVisibleYear = currentYear,
firstDayOfWeek = firstDayOfWeek,
)
HorizontalYearCalendar(
state = state,
dayContent = { Day(it) },
)

// If you need a vertical year calendar.
// VerticalYearCalendar(
// state = state,
// dayContent = { Day(it) }
// )
}
```

Your `Day` composable in its simplest form would be:

```kotlin
Expand Down
161 changes: 136 additions & 25 deletions docs/View.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,12 @@ class CalendarViewTest {

@Test
fun findVisibleDaysAndMonthsWorksOnVerticalOrientation() {
openExampleAt(1)
openExampleAt(7)

val calendarView = getView<CalendarView>(R.id.exTwoCalendar)
val calendarView = getView<CalendarView>(R.id.exEightCalendar)

runOnMain {
calendarView.orientation = RecyclerView.VERTICAL
// Scroll to a random date
calendarView.scrollToDate(LocalDate.now().plusDays(120))
}
Expand All @@ -266,11 +267,12 @@ class CalendarViewTest {

@Test
fun findVisibleDaysAndMonthsWorksOnHorizontalOrientation() {
openExampleAt(0)
openExampleAt(7)

val calendarView = getView<CalendarView>(R.id.exOneCalendar)
val calendarView = getView<CalendarView>(R.id.exEightCalendar)

runOnMain {
calendarView.orientation = RecyclerView.HORIZONTAL
// Scroll to a random date
calendarView.scrollToDate(LocalDate.now().plusDays(120))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.kizitonwose.calendar.sample.view

import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import com.kizitonwose.calendar.sample.R

interface HasToolbar {
Expand All @@ -18,6 +22,13 @@ abstract class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes) {
val activityToolbar: Toolbar
get() = (requireActivity() as CalendarViewActivity).binding.activityToolbar

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (this is MenuProvider) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.CREATED)
}
}

override fun onStart() {
super.onStart()
if (this is HasToolbar) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ class CalendarViewOptionsAdapter(val onClick: (ExampleItem) -> Unit) :
R.string.example_8_subtitle,
horizontal,
) { Example8Fragment() },
ExampleItem(
R.string.example_9_title,
R.string.example_9_subtitle,
horizontal,
) { Example9Fragment() },
ExampleItem(
R.string.example_10_title,
R.string.example_10_subtitle,
horizontal,
) { Example10Fragment() },
)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionsViewHolder {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package com.kizitonwose.calendar.sample.view

import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.core.view.MenuProvider
import androidx.core.view.children
import androidx.core.view.updatePaddingRelative
import com.kizitonwose.calendar.core.CalendarDay
import com.kizitonwose.calendar.core.CalendarMonth
import com.kizitonwose.calendar.core.DayPosition
import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale
import com.kizitonwose.calendar.sample.R
import com.kizitonwose.calendar.sample.databinding.Example10FragmentBinding
import com.kizitonwose.calendar.sample.databinding.Example9CalendarDayBinding
import com.kizitonwose.calendar.sample.databinding.Example9CalendarMonthHeaderBinding
import com.kizitonwose.calendar.sample.shared.displayText
import com.kizitonwose.calendar.view.MarginValues
import com.kizitonwose.calendar.view.MonthDayBinder
import com.kizitonwose.calendar.view.MonthHeaderFooterBinder
import com.kizitonwose.calendar.view.ViewContainer
import java.time.LocalDate
import java.time.Year

class Example10Fragment : BaseFragment(R.layout.example_10_fragment), HasToolbar, HasBackButton, MenuProvider {
override val toolbar: Toolbar
get() = binding.exTenToolbar

override val titleRes: Int = R.string.example_10_title

private lateinit var binding: Example10FragmentBinding

private var selectedDate: LocalDate? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = Example10FragmentBinding.bind(view)
val config = requireContext().resources.configuration
val isTablet = config.smallestScreenWidthDp >= 600

configureBinders(isTablet)

binding.exTenToolbar.updatePaddingRelative(end = dpToPx(if (isTablet) 42 else 6, requireContext()))

binding.exTenCalendar.apply {
val currentYear = Year.now()
monthVerticalSpacing = dpToPx(20, requireContext())
monthHorizontalSpacing = dpToPx(if (isTablet) 52 else 10, requireContext())
yearMargins = MarginValues(
vertical = dpToPx(if (isTablet) 20 else 6, requireContext()),
horizontal = dpToPx(if (isTablet) 52 else 14, requireContext()),
)
yearScrollListener = { year ->
binding.exTenToolbar.title = year.year.value.toString()
}
setup(
currentYear.minusYears(100),
currentYear.plusYears(100),
firstDayOfWeekFromLocale(),
)
scrollToYear(currentYear)
}
}

private fun configureBinders(isTablet: Boolean) {
val calendarView = binding.exTenCalendar

class DayViewContainer(view: View) : ViewContainer(view) {
// Will be set when this container is bound. See the dayBinder.
lateinit var day: CalendarDay
val textView = Example9CalendarDayBinding.bind(view).exNineDayText.apply {
textSize = if (isTablet) 10f else 9f
}

init {
textView.setOnClickListener {
if (day.position == DayPosition.MonthDate) {
if (selectedDate == day.date) {
selectedDate = null
calendarView.notifyDayChanged(day)
} else {
val oldDate = selectedDate
selectedDate = day.date
calendarView.notifyDateChanged(day.date)
oldDate?.let { calendarView.notifyDateChanged(oldDate) }
}
}
}
}
}

calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
override fun create(view: View) = DayViewContainer(view)
override fun bind(container: DayViewContainer, data: CalendarDay) {
container.day = data
val textView = container.textView
textView.text = data.date.dayOfMonth.toString()

if (data.position == DayPosition.MonthDate) {
textView.makeVisible()
when (data.date) {
selectedDate -> {
textView.setTextColorRes(R.color.example_2_white)
textView.setBackgroundResource(R.drawable.example_2_selected_bg)
}

else -> {
textView.setTextColorRes(R.color.example_2_black)
textView.background = null
}
}
} else {
textView.makeInVisible()
}
}
}

val monthNameTypeFace = Typeface.semiBold(requireContext())

class MonthViewContainer(view: View) : ViewContainer(view) {
val bind = Example9CalendarMonthHeaderBinding.bind(view)
val textView = bind.exNineMonthHeaderText.apply {
setTypeface(monthNameTypeFace)
textSize = if (isTablet) 16f else 14f
updatePaddingRelative(start = dpToPx(if (isTablet) 10 else 6, requireContext()))
}
val legendLayout = bind.legendLayout.root
}

val legendTypeface = Typeface.medium(requireContext())

calendarView.monthHeaderBinder =
object : MonthHeaderFooterBinder<MonthViewContainer> {
override fun create(view: View) = MonthViewContainer(view)
override fun bind(container: MonthViewContainer, data: CalendarMonth) {
container.textView.text = data.yearMonth.month.displayText(short = false)
// Setup each header day text if we have not done that already.
if (container.legendLayout.tag == null) {
container.legendLayout.tag = true
val daysOfWeek = data.weekDays.first().map { it.date.dayOfWeek }
container.legendLayout.children.map { it as TextView }
.forEachIndexed { index, tv ->
tv.text = daysOfWeek[index].displayText(uppercase = true, narrow = true)
tv.setTextColorRes(R.color.example_3_black)
tv.textSize = if (isTablet) 14f else 11f
tv.setTypeface(legendTypeface)
}
}
}
}
}

override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.example_10_menu, menu)
}

override fun onMenuItemSelected(item: MenuItem): Boolean = with(binding.exTenCalendar) {
return when (item.itemId) {
R.id.menuItemPrevious -> {
findFirstVisibleYear()?.year?.let { visibleYear ->
smoothScrollToYear(visibleYear.minusYears(1))
}
true
}

R.id.menuItemNext -> {
findFirstVisibleYear()?.year?.let { visibleYear ->
smoothScrollToYear(visibleYear.plusYears(1))
}
true
}

else -> false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.core.view.MenuProvider
import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar
import com.kizitonwose.calendar.core.CalendarDay
Expand All @@ -25,7 +26,7 @@ import java.time.LocalDate
import java.time.YearMonth
import java.time.format.DateTimeFormatter

class Example2Fragment : BaseFragment(R.layout.example_2_fragment), HasToolbar, HasBackButton {
class Example2Fragment : BaseFragment(R.layout.example_2_fragment), HasToolbar, HasBackButton, MenuProvider {
override val toolbar: Toolbar
get() = binding.exTwoToolbar

Expand All @@ -38,7 +39,6 @@ class Example2Fragment : BaseFragment(R.layout.example_2_fragment), HasToolbar,

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
binding = Example2FragmentBinding.bind(view)
val daysOfWeek = daysOfWeek()
binding.legendLayout.root.children.forEachIndexed { index, child ->
Expand All @@ -57,22 +57,20 @@ class Example2Fragment : BaseFragment(R.layout.example_2_fragment), HasToolbar,
}

private lateinit var menuItem: MenuItem
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.example_2_menu, menu)
menuItem = menu.getItem(0)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.menuItemDone) {
val date = selectedDate ?: return false
menuItem = menu.findItem(R.id.menuItemDone)
menuItem.setOnMenuItemClickListener click@{
val date = selectedDate ?: return@click true
val text = "Selected: ${DateTimeFormatter.ofPattern("d MMMM yyyy").format(date)}"
Snackbar.make(requireView(), text, Snackbar.LENGTH_SHORT).show()
parentFragmentManager.popBackStack()
return true
return@click true
}
return super.onOptionsItemSelected(item)
}

override fun onMenuItemSelected(item: MenuItem): Boolean = true

private fun configureBinders() {
val calendarView = binding.exTwoCalendar

Expand Down Expand Up @@ -113,10 +111,12 @@ class Example2Fragment : BaseFragment(R.layout.example_2_fragment), HasToolbar,
textView.setTextColorRes(R.color.example_2_white)
textView.setBackgroundResource(R.drawable.example_2_selected_bg)
}

today -> {
textView.setTextColorRes(R.color.example_2_red)
textView.background = null
}

else -> {
textView.setTextColorRes(R.color.example_2_black)
textView.background = null
Expand Down
Loading
Loading