Skip to content

Commit

Permalink
Add swipe functionality (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
boguszpawlowski authored Oct 16, 2021
1 parent 77804a1 commit 76e11ad
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 80 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Library and it's snapshots are available on Maven Central repository.
- Month and week headers
- Customizable month container
- Fully customizable day content
- Horizontal swipe for changing a current month

## Basic Usage

Expand Down Expand Up @@ -110,6 +111,7 @@ please check out `CustomSelectionSample` file.
Apart from rendering your own components inside the calendar, you can modify it by passing different properties.:
- `showAdjacentMonths` - whenever to render days from adjacent months. Defaults to `true`.
- `firstDayOfWeek` - you can pass the `DayOfWeek` which you want you week to start with. It defaults to the first day of week of the `Locale.default()`.
- `horizontalScrollEnabled` - a Boolean flag which enables month to be changed by a horizontal swipe. Defaults to `true`.

Apart from this, `Calendar` you can pass a `Modifier` object like in any other composable.

Expand Down
32 changes: 5 additions & 27 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object MavenPublish {
}

object AndroidSdk {
const val Min = 24
const val Min = 23
const val Compile = 30
const val Target = Compile
const val BuildTools = "30.0.2"
Expand All @@ -38,6 +38,7 @@ object Kotlin {
const val DokkaGradlePlugin = "org.jetbrains.dokka:dokka-gradle-plugin:1.5.0"

const val StdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$Version"
const val Reflect = "org.jetbrains.kotlin:kotlin-reflect:$Version"
const val SafeArgsPlugin = "androidx.navigation:navigation-safe-args-gradle-plugin:2.2.0"

const val AndroidPluginId = "android"
Expand Down Expand Up @@ -149,23 +150,14 @@ object DetektLib {
const val Cli = "io.gitlab.arturbosch.detekt:detekt-cli:$Version"
}

object Koin {
const val Version = "2.2.2"

const val Core = "org.koin:koin-core:$Version"
const val Android = "org.koin:koin-android:$Version"
const val ViewModel = "org.koin:koin-androidx-viewmodel:$Version"
const val Compose = "org.koin:koin-androidx-compose:$Version"
}

object Timber {
const val Version = "4.7.1"
const val Core = "com.jakewharton.timber:timber:$Version"
}

object Compose {
const val Version = "1.0.1"
const val AccompanistVersion = "0.10.0"
const val AccompanistVersion = "0.17.0"

const val Runtime = "androidx.compose.runtime:runtime:$Version"
const val Compiler = "androidx.compose.compiler:compiler:$Version"
Expand All @@ -175,27 +167,12 @@ object Compose {
const val Ui = "androidx.compose.ui:ui:$Version"
const val UiTooling = "androidx.compose.ui:ui-tooling:$Version"
const val MaterialIconsExtended = "androidx.compose.material:material-icons-extended:$Version"
const val AccompanistCoil = "dev.chrisbanes.accompanist:accompanist-coil:$AccompanistVersion"
const val AccompanistPager = "com.google.accompanist:accompanist-pager:$AccompanistVersion"
const val Navigation = "androidx.navigation:navigation-compose:2.4.0-alpha04"
const val Testing = "androidx.compose.ui:ui-test:$Version"
const val JunitTesting = "androidx.compose.ui:ui-test-junit4:$Version"
}

object Firebase {
const val CrashlyticsPlugin = "com.google.firebase:firebase-crashlytics-gradle:2.5.2"
const val GoogleServicesPlugin = "com.google.gms:google-services:4.3.5"
const val AppDistributionPlugin = "com.google.firebase:firebase-appdistribution-gradle:1.3.1"

const val CrashlyticsPluginId = "com.google.firebase.crashlytics"
const val GoogleServicesPluginId = "com.google.gms.google-services"
const val AppDistributionPluginId = "com.google.firebase.appdistribution"

const val Bom = "com.google.firebase:firebase-bom:26.8.0"

const val Analytics = "com.google.firebase:firebase-analytics-ktx"
const val Crashlytics = "com.google.firebase:firebase-crashlytics-ktx"
}

object Debug {
const val LeakCanary = "com.squareup.leakcanary:leakcanary-android:2.7"
const val FoQA = "pl.droidsonroids.foqa:foqa:0.1.24"
Expand Down Expand Up @@ -224,4 +201,5 @@ object AndroidXTest {

object ComposeTest {
const val Core = "androidx.compose.ui:ui-test-junit4:${Compose.Version}"
const val Manifest = "androidx.compose.ui:ui-test-manifest:${Compose.Version}"
}
2 changes: 1 addition & 1 deletion detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ style:
TrailingWhitespace:
active: true
UnderscoresInNumericLiterals:
active: true
active: false
acceptableDecimalLength: 3
UnnecessaryAbstractClass:
active: true
Expand Down
9 changes: 8 additions & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,27 @@ android {
freeCompilerArgs = freeCompilerArgs + "-Xexplicit-api=strict"
}
}
packagingOptions {
exclude("META-INF/*")
}
}

dependencies {
implementation(Kotlin.StdLib)
implementation(Compose.Runtime)
implementation(Compose.Ui)
implementation(Compose.UiTooling)
implementation(Compose.AccompanistPager)
implementation(Compose.Foundation)
implementation(Compose.FoundationLayout)
implementation(Compose.Material)
implementation(Timber.Core)

testImplementation(Kotest.Assertions)
testImplementation(Kotest.RunnerJunit5)
testImplementation(Kotlin.Reflect)

debugImplementation(ComposeTest.Manifest)
androidTestImplementation(ComposeTest.Core)
}

plugins.withId("com.vanniktech.maven.publish") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.github.boguszpawlowski.composecalendar

import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performGesture
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import org.junit.Rule
import org.junit.Test
import java.time.YearMonth

internal class ScrollBehaviorTest {

private val initialMonth = YearMonth.of(2012, 5)

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun monthChangeTest() {
composeTestRule.setContent {
StaticCalendar(
calendarState = rememberCalendarState(initialMonth = initialMonth)
)
}

composeTestRule.onNodeWithTag("Decrement").performClick()
composeTestRule.onNodeWithTag("MonthLabel", true).assertTextEquals("April")
composeTestRule.onNodeWithTag("Increment").performClick()
composeTestRule.onNodeWithTag("MonthLabel", true).assertTextEquals("May")
}

@Test
fun scrollMonthChangeTest() {
composeTestRule.setContent {
StaticCalendar(
calendarState = rememberCalendarState(initialMonth = initialMonth)
)
}

composeTestRule.onNodeWithTag("MonthPager").performGesture { swipeLeft() }
composeTestRule.onNodeWithTag("MonthLabel", true).assertTextEquals("June")
composeTestRule.onNodeWithTag("MonthPager").performGesture { swipeRight() }
composeTestRule.onNodeWithTag("MonthLabel", true).assertTextEquals("May")
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@file:Suppress("MatchingDeclarationName")
@file:Suppress("MatchingDeclarationName", "LongParameterList")

package io.github.boguszpawlowski.composecalendar

import androidx.compose.foundation.layout.Box
Expand All @@ -15,13 +16,15 @@ import io.github.boguszpawlowski.composecalendar.day.DayState
import io.github.boguszpawlowski.composecalendar.day.DefaultDay
import io.github.boguszpawlowski.composecalendar.header.DefaultMonthHeader
import io.github.boguszpawlowski.composecalendar.header.MonthState
import io.github.boguszpawlowski.composecalendar.month.Month
import io.github.boguszpawlowski.composecalendar.month.DaysOfWeek
import io.github.boguszpawlowski.composecalendar.month.MonthContent
import io.github.boguszpawlowski.composecalendar.month.MonthPager
import io.github.boguszpawlowski.composecalendar.selection.DynamicSelectionState
import io.github.boguszpawlowski.composecalendar.selection.EmptySelectionState
import io.github.boguszpawlowski.composecalendar.selection.SelectionMode
import io.github.boguszpawlowski.composecalendar.selection.SelectionState
import io.github.boguszpawlowski.composecalendar.week.DefaultWeekHeader
import io.github.boguszpawlowski.composecalendar.week.rotateRight
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth
Expand Down Expand Up @@ -55,6 +58,7 @@ public class CalendarState<T : SelectionState>(
* @param firstDayOfWeek first day of a week, defaults to current locale's
* @param today current day, defaults to [LocalDate.now]
* @param showAdjacentMonths whenever to show or hide the days from adjacent months
* @param horizontalSwipeEnabled whenever user is able to change the month by horizontal swipe
* @param calendarState state of the composable
* @param dayContent composable rendering the current day
* @param monthHeader header for showing the current month and controls for changing it
Expand All @@ -67,6 +71,7 @@ public fun SelectableCalendar(
firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
today: LocalDate = LocalDate.now(),
showAdjacentMonths: Boolean = true,
horizontalSwipeEnabled: Boolean = true,
calendarState: CalendarState<DynamicSelectionState> = rememberSelectableCalendarState(),
dayContent: @Composable BoxScope.(DayState<DynamicSelectionState>) -> Unit = { DefaultDay(it) },
monthHeader: @Composable ColumnScope.(MonthState) -> Unit = { DefaultMonthHeader(it) },
Expand All @@ -80,6 +85,7 @@ public fun SelectableCalendar(
firstDayOfWeek = firstDayOfWeek,
today = today,
showAdjacentMonths = showAdjacentMonths,
horizontalSwipeEnabled = horizontalSwipeEnabled,
calendarState = calendarState,
dayContent = dayContent,
monthHeader = monthHeader,
Expand All @@ -103,6 +109,7 @@ public fun SelectableCalendar(
* @param firstDayOfWeek first day of a week, defaults to current locale's
* @param today current day, defaults to [LocalDate.now]
* @param showAdjacentMonths whenever to show or hide the days from adjacent months
* @param horizontalSwipeEnabled whenever user is able to change the month by horizontal swipe
* @param calendarState state of the composable
* @param dayContent composable rendering the current day
* @param monthHeader header for showing the current month and controls for changing it
Expand All @@ -115,6 +122,7 @@ public fun StaticCalendar(
firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
today: LocalDate = LocalDate.now(),
showAdjacentMonths: Boolean = true,
horizontalSwipeEnabled: Boolean = true,
calendarState: CalendarState<EmptySelectionState> = rememberCalendarState(),
dayContent: @Composable BoxScope.(DayState<EmptySelectionState>) -> Unit = { DefaultDay(it) },
monthHeader: @Composable ColumnScope.(MonthState) -> Unit = { DefaultMonthHeader(it) },
Expand All @@ -128,6 +136,7 @@ public fun StaticCalendar(
firstDayOfWeek = firstDayOfWeek,
today = today,
showAdjacentMonths = showAdjacentMonths,
horizontalSwipeEnabled = horizontalSwipeEnabled,
calendarState = calendarState,
dayContent = dayContent,
monthHeader = monthHeader,
Expand All @@ -146,6 +155,7 @@ public fun StaticCalendar(
* @param firstDayOfWeek first day of a week, defaults to current locale's
* @param today current day, defaults to [LocalDate.now]
* @param showAdjacentMonths whenever to show or hide the days from adjacent months
* @param horizontalSwipeEnabled whenever user is able to change the month by horizontal swipe
* @param calendarState state of the composable
* @param dayContent composable rendering the current day
* @param monthHeader header for showing the current month and controls for changing it
Expand All @@ -159,26 +169,46 @@ public fun <T : SelectionState> Calendar(
firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
today: LocalDate = LocalDate.now(),
showAdjacentMonths: Boolean = true,
horizontalSwipeEnabled: Boolean = true,
dayContent: @Composable BoxScope.(DayState<T>) -> Unit = { DefaultDay(it) },
monthHeader: @Composable ColumnScope.(MonthState) -> Unit = { DefaultMonthHeader(it) },
weekHeader: @Composable BoxScope.(List<DayOfWeek>) -> Unit = { DefaultWeekHeader(it) },
monthContainer: @Composable (content: @Composable (PaddingValues) -> Unit) -> Unit = { content ->
Box { content(PaddingValues()) }
},
) {

val daysOfWeek = remember(firstDayOfWeek) {
DayOfWeek.values().rotateRight(DaysOfWeek - firstDayOfWeek.ordinal)
}

Column(
modifier = modifier,
) {
monthHeader(calendarState.monthState)
MonthContent(
showAdjacentMonths = showAdjacentMonths,
month = Month(calendarState.monthState.currentMonth, currentDate = today),
selectionState = calendarState.selectionState,
firstDayOfWeek = firstDayOfWeek,
dayContent = dayContent,
weekHeader = weekHeader,
monthContainer = monthContainer,
)
if (horizontalSwipeEnabled) {
MonthPager(
showAdjacentMonths = showAdjacentMonths,
monthState = calendarState.monthState,
selectionState = calendarState.selectionState,
today = today,
daysOfWeek = daysOfWeek,
dayContent = dayContent,
weekHeader = weekHeader,
monthContainer = monthContainer,
)
} else {
MonthContent(
currentMonth = calendarState.monthState.currentMonth,
showAdjacentMonths = showAdjacentMonths,
selectionState = calendarState.selectionState,
today = today,
daysOfWeek = daysOfWeek,
dayContent = dayContent,
weekHeader = weekHeader,
monthContainer = monthContainer,
)
}
}
}

Expand All @@ -199,7 +229,11 @@ public fun rememberSelectableCalendarState(
monthState: MonthState = rememberSaveable(saver = MonthState.Saver()) {
MonthState(initialMonth = initialMonth)
},
selectionState: DynamicSelectionState = rememberSaveable(saver = DynamicSelectionState.Saver(onSelectionChanged)) {
selectionState: DynamicSelectionState = rememberSaveable(
saver = DynamicSelectionState.Saver(
onSelectionChanged
)
) {
DynamicSelectionState(onSelectionChanged, initialSelection, initialSelectionMode)
},
): CalendarState<DynamicSelectionState> = remember { CalendarState(monthState, selectionState) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp

/**
Expand All @@ -31,20 +32,27 @@ public fun DefaultMonthHeader(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
IconButton(onClick = { monthState.currentMonth = monthState.currentMonth.minusMonths(1) }) {
IconButton(
modifier = Modifier.testTag("Decrement"),
onClick = { monthState.currentMonth = monthState.currentMonth.minusMonths(1) }
) {
Image(
imageVector = Icons.Default.KeyboardArrowLeft,
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface),
contentDescription = "Previous",
)
}
Text(
modifier = Modifier.testTag("MonthLabel"),
text = monthState.currentMonth.month.name.lowercase().replaceFirstChar { it.titlecase() },
style = MaterialTheme.typography.h4
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = monthState.currentMonth.year.toString(), style = MaterialTheme.typography.h4)
IconButton(onClick = { monthState.currentMonth = monthState.currentMonth.plusMonths(1) }) {
IconButton(
modifier = Modifier.testTag("Increment"),
onClick = { monthState.currentMonth = monthState.currentMonth.plusMonths(1) }
) {
Image(
imageVector = Icons.Default.KeyboardArrowRight,
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface),
Expand Down

This file was deleted.

Loading

0 comments on commit 76e11ad

Please sign in to comment.