├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── feature-request.md
│ └── usage-help.md
└── workflows
│ ├── check.yml
│ ├── gradle-wrapper.yaml
│ └── publish-release.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── build.gradle.kts
├── buildSrc
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── com
│ └── kizitonwose
│ └── calendar
│ └── buildsrc
│ └── Build.kt
├── compose-multiplatform
├── library
│ ├── .gitignore
│ ├── api
│ │ ├── android
│ │ │ └── library.api
│ │ ├── desktop
│ │ │ └── library.api
│ │ └── library.klib.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ ├── androidMain
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ └── data
│ │ │ └── Utils.android.kt
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ ├── compose
│ │ │ ├── Calendar.kt
│ │ │ ├── CalendarDefaults.kt
│ │ │ ├── CalendarInfo.kt
│ │ │ ├── CalendarLayoutInfo.kt
│ │ │ ├── CalendarMonths.kt
│ │ │ ├── CalendarState.kt
│ │ │ ├── ContentHeightMode.kt
│ │ │ ├── ItemPlacementInfo.kt
│ │ │ ├── heatmapcalendar
│ │ │ │ ├── HeatMapCalendar.kt
│ │ │ │ ├── HeatMapCalendarState.kt
│ │ │ │ ├── HeatMapWeek.kt
│ │ │ │ └── HeatMapWeekHeaderPosition.kt
│ │ │ ├── weekcalendar
│ │ │ │ ├── WeekCalendar.kt
│ │ │ │ ├── WeekCalendarLayoutInfo.kt
│ │ │ │ └── WeekCalendarState.kt
│ │ │ └── yearcalendar
│ │ │ │ ├── YearCalendarLayoutInfo.kt
│ │ │ │ ├── YearCalendarMonths.kt
│ │ │ │ ├── YearCalendarState.kt
│ │ │ │ ├── YearContentHeightMode.kt
│ │ │ │ └── YearItemPlacementInfo.kt
│ │ │ ├── core
│ │ │ ├── CalendarDay.kt
│ │ │ ├── CalendarMonth.kt
│ │ │ ├── CalendarYear.kt
│ │ │ ├── DayPosition.kt
│ │ │ ├── ExperimentalCalendarApi.kt
│ │ │ ├── Extensions.kt
│ │ │ ├── OutDateStyle.kt
│ │ │ ├── Week.kt
│ │ │ ├── WeekDay.kt
│ │ │ ├── WeekDayPosition.kt
│ │ │ ├── Year.kt
│ │ │ ├── YearMonth.kt
│ │ │ ├── format
│ │ │ │ └── Format.kt
│ │ │ └── serializers
│ │ │ │ ├── YearMonthSerializers.kt
│ │ │ │ └── YearSerializers.kt
│ │ │ └── data
│ │ │ ├── DataStore.kt
│ │ │ ├── Extensions.kt
│ │ │ ├── MonthData.kt
│ │ │ ├── Utils.kt
│ │ │ ├── VisibleItemState.kt
│ │ │ ├── WeekData.kt
│ │ │ └── YearData.kt
│ │ ├── commonTest
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ ├── compose
│ │ │ ├── CalendarStateTest.kt
│ │ │ ├── HeatMapCalendarStateTest.kt
│ │ │ ├── StateSaverTest.kt
│ │ │ ├── WeekCalendarStateTest.kt
│ │ │ └── YearCalendarStateTest.kt
│ │ │ ├── core
│ │ │ ├── ExtensionTest.kt
│ │ │ ├── YearMonthSerializationTest.kt
│ │ │ ├── YearMonthTest.kt
│ │ │ ├── YearSerializationTest.kt
│ │ │ └── YearTest.kt
│ │ │ ├── data
│ │ │ ├── HeatMapDataTest.kt
│ │ │ ├── MonthDataTest.kt
│ │ │ ├── WeekDataTest.kt
│ │ │ └── YearDataTest.kt
│ │ │ └── utils
│ │ │ └── Utils.kt
│ │ ├── desktopMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ └── data
│ │ │ └── Utils.desktop.kt
│ │ ├── iosMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ ├── core
│ │ │ └── Extensions.ios.kt
│ │ │ └── data
│ │ │ └── Utils.ios.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ └── core
│ │ │ ├── Converters.kt
│ │ │ └── Extensions.jvm.kt
│ │ └── wasmJsMain
│ │ └── kotlin
│ │ └── com
│ │ └── kizitonwose
│ │ └── calendar
│ │ ├── core
│ │ ├── Extensions.wasmJs.kt
│ │ └── FirstDayFromMap.kt
│ │ └── data
│ │ └── Utils.wasmJs.kt
└── sample
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ ├── kotlin
│ │ ├── Device.android.kt
│ │ └── com
│ │ │ └── kizitonwose
│ │ │ └── calendar
│ │ │ └── compose
│ │ │ └── multiplatform
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ └── strings.xml
│ ├── commonMain
│ └── kotlin
│ │ ├── App.kt
│ │ ├── AppIcons.kt
│ │ ├── Colors.kt
│ │ ├── ContinuousSelectionHelper.kt
│ │ ├── Device.kt
│ │ ├── Example10Page.kt
│ │ ├── Example11Page.kt
│ │ ├── Example1Page.kt
│ │ ├── Example2Page.kt
│ │ ├── Example2PageHighlight.kt
│ │ ├── Example3Page.kt
│ │ ├── Example4Page.kt
│ │ ├── Example5Page.kt
│ │ ├── Example6Page.kt
│ │ ├── Example7Page.kt
│ │ ├── Example8Page.kt
│ │ ├── Example9Page.kt
│ │ ├── Example9PageAnimatedVisibility.kt
│ │ ├── Flight.kt
│ │ ├── Format.kt
│ │ ├── ListPage.kt
│ │ ├── PageAnimation.kt
│ │ ├── Scaffold.kt
│ │ ├── SimpleCalendarTitle.kt
│ │ ├── Theme.kt
│ │ └── Utils.kt
│ ├── desktopMain
│ └── kotlin
│ │ ├── Device.desktop.kt
│ │ └── Main.kt
│ ├── iosMain
│ └── kotlin
│ │ ├── Device.ios.kt
│ │ ├── Format.ios.kt
│ │ └── MainViewController.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── Format.jvm.kt
│ └── wasmJsMain
│ ├── kotlin
│ ├── Device.wasmJs.kt
│ ├── Format.wasmJs.kt
│ └── Main.kt
│ └── resources
│ ├── index.html
│ └── styles.css
├── compose
├── .gitignore
├── api
│ └── compose.api
├── build.gradle.kts
├── gradle.properties
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── kizitonwose
│ │ └── calendar
│ │ └── compose
│ │ ├── Calendar.kt
│ │ ├── CalendarDefaults.kt
│ │ ├── CalendarInfo.kt
│ │ ├── CalendarLayoutInfo.kt
│ │ ├── CalendarMonths.kt
│ │ ├── CalendarState.kt
│ │ ├── ContentHeightMode.kt
│ │ ├── ItemPlacementInfo.kt
│ │ ├── VisibleItemState.kt
│ │ ├── heatmapcalendar
│ │ ├── HeatMapCalendar.kt
│ │ ├── HeatMapCalendarState.kt
│ │ ├── HeatMapWeek.kt
│ │ └── HeatMapWeekHeaderPosition.kt
│ │ ├── weekcalendar
│ │ ├── WeekCalendar.kt
│ │ ├── WeekCalendarLayoutInfo.kt
│ │ └── WeekCalendarState.kt
│ │ └── yearcalendar
│ │ ├── YearCalendarLayoutInfo.kt
│ │ ├── YearCalendarMonths.kt
│ │ ├── YearCalendarState.kt
│ │ ├── YearContentHeightMode.kt
│ │ └── YearItemPlacementInfo.kt
│ └── test
│ └── java
│ └── com
│ └── kizitonwose
│ └── calendar
│ └── compose
│ ├── CalendarStateTest.kt
│ ├── HeatMapCalendarStateTest.kt
│ ├── StateSaverTest.kt
│ ├── WeekCalendarStateTest.kt
│ └── YearCalendarStateTest.kt
├── core
├── .gitignore
├── api
│ └── core.api
├── build.gradle.kts
├── gradle.properties
└── src
│ └── main
│ └── java
│ └── com
│ └── kizitonwose
│ └── calendar
│ └── core
│ ├── CalendarDay.kt
│ ├── CalendarMonth.kt
│ ├── CalendarYear.kt
│ ├── DayPosition.kt
│ ├── ExperimentalCalendarApi.kt
│ ├── Extensions.kt
│ ├── OutDateStyle.kt
│ ├── Week.kt
│ ├── WeekDay.kt
│ └── WeekDayPosition.kt
├── data
├── .gitignore
├── api
│ └── data.api
├── build.gradle.kts
├── gradle.properties
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── kizitonwose
│ │ └── calendar
│ │ └── data
│ │ ├── DataStore.kt
│ │ ├── Extensions.kt
│ │ ├── MonthData.kt
│ │ ├── Utils.kt
│ │ ├── WeekData.kt
│ │ └── YearData.kt
│ └── test
│ └── java
│ └── com
│ └── kizitonwose
│ └── calendar
│ └── data
│ ├── DayOfWeekTest.kt
│ ├── HeatMapDataTest.kt
│ ├── MonthDataTest.kt
│ ├── Utils.kt
│ ├── WeekDataTest.kt
│ └── YearDataTest.kt
├── docs
├── Compose.md
├── MigrationGuide.md
└── View.md
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── in_out_dates.png
└── showcase.png
├── kotlin-js-store
└── yarn.lock
├── sample
├── .gitignore
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── kizitonwose
│ │ └── calendar
│ │ └── sample
│ │ ├── CalendarComposeTest.kt
│ │ ├── CalendarViewTest.kt
│ │ ├── WeekCalendarViewTest.kt
│ │ └── utils
│ │ ├── TestDayViewContainer.kt
│ │ └── TestUtils.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── kizitonwose
│ │ └── calendar
│ │ └── sample
│ │ ├── HomeActivity.kt
│ │ ├── compose
│ │ ├── CalendarComposeActivity.kt
│ │ ├── Example10Page.kt
│ │ ├── Example11Page.kt
│ │ ├── Example1Page.kt
│ │ ├── Example2Page.kt
│ │ ├── Example2PageHighlight.kt
│ │ ├── Example3Page.kt
│ │ ├── Example4Page.kt
│ │ ├── Example5Page.kt
│ │ ├── Example6Page.kt
│ │ ├── Example7Page.kt
│ │ ├── Example8Page.kt
│ │ ├── Example9Page.kt
│ │ ├── Example9PageAnimatedVisibility.kt
│ │ ├── ListPage.kt
│ │ ├── PageAnimation.kt
│ │ ├── Scaffold.kt
│ │ ├── SimpleCalendarTitle.kt
│ │ ├── Theme.kt
│ │ └── Utils.kt
│ │ ├── shared
│ │ ├── ContinuousSelectionHelper.kt
│ │ ├── Flight.kt
│ │ ├── StatusBarColorLifecycleObserver.kt
│ │ └── Utils.kt
│ │ └── view
│ │ ├── BaseFragment.kt
│ │ ├── CalendarViewActivity.kt
│ │ ├── CalendarViewOptionsAdapter.kt
│ │ ├── Example10Fragment.kt
│ │ ├── Example1Fragment.kt
│ │ ├── Example2Fragment.kt
│ │ ├── Example3Fragment.kt
│ │ ├── Example4Fragment.kt
│ │ ├── Example5Fragment.kt
│ │ ├── Example6Fragment.kt
│ │ ├── Example7Fragment.kt
│ │ ├── Example8Fragment.kt
│ │ ├── Example9Fragment.kt
│ │ ├── Typeface.kt
│ │ └── Utils.kt
│ └── res
│ ├── anim
│ ├── fade_in.xml
│ ├── fade_out.xml
│ ├── slide_in_left.xml
│ ├── slide_in_right.xml
│ ├── slide_in_up.xml
│ ├── slide_out_down.xml
│ ├── slide_out_left.xml
│ └── slide_out_right.xml
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── example_1_selected_bg.xml
│ ├── example_1_today_bg.xml
│ ├── example_2_selected_bg.xml
│ ├── example_3_selected_bg.xml
│ ├── example_3_today_bg.xml
│ ├── example_4_continuous_selected_bg_end.xml
│ ├── example_4_continuous_selected_bg_middle.xml
│ ├── example_4_continuous_selected_bg_start.xml
│ ├── example_4_single_selected_bg.xml
│ ├── example_4_today_bg.xml
│ ├── example_5_selected_bg.xml
│ ├── example_8_selected_bg.xml
│ ├── example_8_today_bg.xml
│ ├── ic_add.xml
│ ├── ic_airplane_landing.xml
│ ├── ic_airplane_takeoff.xml
│ ├── ic_check.xml
│ ├── ic_chevron_left.xml
│ ├── ic_chevron_right.xml
│ ├── ic_close.xml
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── calendar_day_legend_container.xml
│ ├── calendar_day_legend_text.xml
│ ├── calendar_view_activity.xml
│ ├── calendar_view_options_item_view.xml
│ ├── example_10_fragment.xml
│ ├── example_1_calendar_day.xml
│ ├── example_1_fragment.xml
│ ├── example_2_calendar_day.xml
│ ├── example_2_calendar_header.xml
│ ├── example_2_fragment.xml
│ ├── example_3_calendar_day.xml
│ ├── example_3_calendar_header.xml
│ ├── example_3_event_item_view.xml
│ ├── example_3_fragment.xml
│ ├── example_4_calendar_day.xml
│ ├── example_4_calendar_header.xml
│ ├── example_4_fragment.xml
│ ├── example_5_calendar_day.xml
│ ├── example_5_calendar_header.xml
│ ├── example_5_event_item_view.xml
│ ├── example_5_fragment.xml
│ ├── example_6_calendar_day.xml
│ ├── example_6_calendar_header.xml
│ ├── example_6_fragment.xml
│ ├── example_7_calendar_day.xml
│ ├── example_7_fragment.xml
│ ├── example_8_calendar_day.xml
│ ├── example_8_calendar_footer.xml
│ ├── example_8_calendar_header.xml
│ ├── example_8_fragment.xml
│ ├── example_9_calendar_day.xml
│ ├── example_9_calendar_month_header.xml
│ ├── example_9_calendar_year_header.xml
│ ├── example_9_fragment.xml
│ └── home_activity.xml
│ ├── menu
│ ├── example_10_menu.xml
│ ├── example_2_menu.xml
│ └── example_4_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── settings.gradle.kts
└── view
├── .gitignore
├── api
└── view.api
├── build.gradle.kts
├── gradle.properties
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── kizitonwose
│ └── calendar
│ └── view
│ ├── Binder.kt
│ ├── CalendarView.kt
│ ├── DaySize.kt
│ ├── LayoutHelper.kt
│ ├── MarginValues.kt
│ ├── MonthHeight.kt
│ ├── WeekCalendarView.kt
│ ├── YearCalendarView.kt
│ └── internal
│ ├── CalendarLayoutManager.kt
│ ├── CalendarPageSnapHelper.kt
│ ├── CalendarPageSnapHelperLegacy.kt
│ ├── CustomViewClass.kt
│ ├── DayHolder.kt
│ ├── Extensions.kt
│ ├── ItemRoot.kt
│ ├── WeekHolder.kt
│ ├── monthcalendar
│ ├── MonthCalendarAdapter.kt
│ ├── MonthCalendarLayoutManager.kt
│ └── MonthViewHolder.kt
│ ├── weekcalendar
│ ├── WeekCalendarAdapter.kt
│ ├── WeekCalendarLayoutManager.kt
│ └── WeekViewHolder.kt
│ └── yearcalendar
│ ├── YearCalendarAdapter.kt
│ ├── YearCalendarLayoutManager.kt
│ ├── YearMonthHolder.kt
│ ├── YearRoot.kt
│ └── YearViewHolder.kt
└── res
└── values
└── attrs.xml
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | indent_style = space
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 |
9 | [**/generated/**/*]
10 | ktlint = disabled
11 |
12 | [*.{kt,kts}]
13 | ij_kotlin_allow_trailing_comma = true
14 | ij_kotlin_allow_trailing_comma_on_call_site = true
15 | ktlint_standard_multiline-expression-wrapping = disabled
16 | ktlint_standard_function-expression-body = disabled
17 | ktlint_standard_chain-method-continuation = disabled
18 | ktlint_standard_class-signature = disabled
19 | # string-template-indent requires multiline-expression-wrapping to be enabled
20 | ktlint_standard_string-template-indent = disabled
21 | ktlint_standard_parameter-list-wrapping = disabled
22 | ktlint_standard_function-signature = disabled
23 | ktlint_standard_blank-line-before-declaration = disabled
24 | ktlint_standard_value-argument-comment = disabled
25 | ktlint_function_naming_ignore_when_annotated_with = Composable
26 | ktlint_standard_annotation = disabled
27 | max_line_length = 200
28 |
29 | [**/test/**/*.kt]
30 | ktlint_experimental = disabled
31 |
32 | [*.md]
33 | trim_trailing_whitespace = false
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a ticket for a bug.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Library information:
11 | - Version: [e.g. 2.0.0]
12 | - View or Compose module: [e.g. view]
13 |
14 | ### Describe the bug**
15 | A clear and concise description of what the bug is.
16 |
17 | ### To Reproduce (if applicable)
18 | Steps to reproduce the behavior:
19 | 1. Go to '....'
20 | 2. Click on '....'
21 | 4. See '....'
22 |
23 | ### Expected behavior (if applicable)
24 | A clear and concise description of what you expected to happen.
25 |
26 | ### Screenshots? (if applicable)
27 | If applicable, add screenshots or screen recordings to help explain the problem.
28 |
29 | ### Additional information
30 | Add any other information about the bug here.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Create a ticket to discuss a new feature.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Feature
11 | A clear and concise description of the feature.
12 |
13 | ### Tech (if applicable)
14 | Add any other information that could help the development of this feature.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/usage-help.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Usage help
3 | about: Ask a question regarding library usage.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Library information:
11 | - Version: [e.g. 2.0.0]
12 | - View or Compose module: [e.g. View]
13 |
14 | ### Question:
15 | [e.g. How do I do X or Y?]
--------------------------------------------------------------------------------
/.github/workflows/gradle-wrapper.yaml:
--------------------------------------------------------------------------------
1 | name: Gradle Wrapper Validation
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - 'gradlew'
7 | - 'gradlew.bat'
8 | - 'gradle/wrapper/**'
9 |
10 | jobs:
11 | validate:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: gradle/actions/wrapper-validation@v3
16 |
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 |
8 | jobs:
9 | # Use macOS so iOS artifacts are published
10 | publish-release:
11 | name: Publish Release
12 | runs-on: macos-latest
13 | if: github.repository == 'kizitonwose/Calendar'
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-java@v4
17 | with:
18 | distribution: 'zulu'
19 | java-version: 17
20 | - uses: gradle/actions/setup-gradle@v4
21 | - name: Deploy to Maven
22 | run: ./gradlew publish
23 | env:
24 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
25 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
26 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SONATYPE_NEXUS_SIGNING_KEY }}
27 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SONATYPE_NEXUS_SIGNING_KEY_ID }}
28 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SONATYPE_NEXUS_SIGNING_KEY_PASSWORD }}
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .DS_Store
5 | /build
6 | /captures
7 | .externalNativeBuild
8 | .idea
9 | /.kotlin
10 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Kizito Nwose
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | java {
6 | toolchain {
7 | languageVersion.set(JavaLanguageVersion.of(17))
8 | }
9 | }
10 |
11 | kotlin {
12 | jvmToolchain {
13 | languageVersion.set(JavaLanguageVersion.of(17))
14 | }
15 | }
16 |
17 | repositories {
18 | mavenCentral()
19 | }
20 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Build.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused", "ConstPropertyName")
2 |
3 | package com.kizitonwose.calendar.buildsrc
4 |
5 | import org.gradle.api.JavaVersion
6 | import org.gradle.jvm.toolchain.JavaLanguageVersion
7 |
8 | object Config {
9 | val compatibleJavaVersion = JavaVersion.VERSION_17
10 | val compatibleJavaLanguageVersion = JavaLanguageVersion.of(compatibleJavaVersion.majorVersion)
11 | }
12 |
13 | object Version {
14 | val android = "2.7.1-SNAPSHOT"
15 | val multiplatfrom = "2.7.1-SNAPSHOT"
16 |
17 | fun String.isNoPublish() = this == VERSION_NO_PUBLISH
18 | }
19 |
20 | private val VERSION_NO_PUBLISH = "NO_PUBLISH"
21 |
22 | object Android {
23 | const val minSdk = 21
24 | const val targetSdk = 35
25 | const val compileSdk = 35
26 | }
27 |
28 | val multiplatformProjects = listOf("library")
29 | val androidProjects = listOf("core", "data", "view", "compose")
30 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/compose-multiplatform/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=compose-multiplatform
2 | POM_DESCRIPTION=A highly customizable calendar library for Compose Multiplatform, backed by LazyRow/LazyColumn.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/androidMain/kotlin/com/kizitonwose/calendar/data/Utils.android.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import android.util.Log
4 |
5 | internal actual fun log(tag: String, message: String) = Log.w(tag, message).asUnit()
6 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.gestures.FlingBehavior
5 | import androidx.compose.foundation.gestures.ScrollableDefaults
6 | import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
7 | import androidx.compose.foundation.gestures.snapping.SnapPosition
8 | import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
9 | import androidx.compose.foundation.lazy.LazyListState
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.remember
12 |
13 | internal object CalendarDefaults {
14 | /**
15 | * The default implementation in [rememberSnapFlingBehavior] snaps to the center of the layout
16 | * but we want to snap to the start. For example, in a vertical calendar, when the layout size
17 | * is larger than the item size(e.g two or more visible months), we don't want the item's
18 | * center to be at the center of the layout when it snaps, instead we want the item's top
19 | * to be at the top of the layout.
20 | */
21 | @OptIn(ExperimentalFoundationApi::class)
22 | @Composable
23 | private fun pagedFlingBehavior(state: LazyListState): FlingBehavior {
24 | val snappingLayout = remember(state) {
25 | val provider = SnapLayoutInfoProvider(state, SnapPosition.Start)
26 | CalendarSnapLayoutInfoProvider(provider)
27 | }
28 | return rememberSnapFlingBehavior(snappingLayout)
29 | }
30 |
31 | @Composable
32 | private fun continuousFlingBehavior(): FlingBehavior = ScrollableDefaults.flingBehavior()
33 |
34 | @Composable
35 | fun flingBehavior(isPaged: Boolean, state: LazyListState): FlingBehavior {
36 | return if (isPaged) pagedFlingBehavior(state) else continuousFlingBehavior()
37 | }
38 | }
39 |
40 | @ExperimentalFoundationApi
41 | @Suppress("FunctionName")
42 | private fun CalendarSnapLayoutInfoProvider(
43 | snapLayoutInfoProvider: SnapLayoutInfoProvider,
44 | ): SnapLayoutInfoProvider = object : SnapLayoutInfoProvider by snapLayoutInfoProvider {
45 | /**
46 | * In compose 1.3, the default was single page snapping (zero), but this changed
47 | * in compose 1.4 to decayed page snapping which is not great for calendar usage.
48 | */
49 | override fun calculateApproachOffset(velocity: Float, decayOffset: Float): Float = 0f
50 | }
51 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kizitonwose.calendar.core.OutDateStyle
5 | import kotlinx.datetime.DayOfWeek
6 |
7 | @Immutable
8 | internal data class CalendarInfo(
9 | val indexCount: Int,
10 | private val firstDayOfWeek: DayOfWeek? = null,
11 | private val outDateStyle: OutDateStyle? = null,
12 | )
13 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.lazy.LazyListItemInfo
4 | import androidx.compose.foundation.lazy.LazyListLayoutInfo
5 | import com.kizitonwose.calendar.core.CalendarMonth
6 |
7 | /**
8 | * Contains useful information about the currently displayed layout state of the calendar.
9 | * For example you can get the list of currently displayed months.
10 | *
11 | * Use [CalendarState.layoutInfo] to retrieve this.
12 | * @see LazyListLayoutInfo
13 | */
14 | public class CalendarLayoutInfo(info: LazyListLayoutInfo, private val month: (Int) -> CalendarMonth) :
15 | LazyListLayoutInfo by info {
16 | /**
17 | * The list of [CalendarItemInfo] representing all the currently visible months.
18 | */
19 | public val visibleMonthsInfo: List
20 | get() = visibleItemsInfo.map {
21 | CalendarItemInfo(it, month(it.index))
22 | }
23 | }
24 |
25 | /**
26 | * Contains useful information about an individual [CalendarMonth] on the calendar.
27 | *
28 | * @param month The month in the list.
29 | *
30 | * @see CalendarLayoutInfo
31 | * @see LazyListItemInfo
32 | */
33 | public class CalendarItemInfo(info: LazyListItemInfo, public val month: CalendarMonth) : LazyListItemInfo by info
34 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/ContentHeightMode.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.layout.aspectRatio
4 | import androidx.compose.foundation.layout.fillMaxHeight
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.ui.Modifier
7 |
8 | /**
9 | * Determines how the height of the day content is calculated.
10 | */
11 | public enum class ContentHeightMode {
12 | /**
13 | * The day container will wrap its height. This allows you to
14 | * use [Modifier.aspectRatio] if you want square day content
15 | * or [Modifier.height] if you want a specific height value
16 | * for the day content.
17 | */
18 | Wrap,
19 |
20 | /**
21 | * The days in each month will spread to fill the parent's height after
22 | * any available header and footer content height has been accounted for.
23 | * This allows you to use [Modifier.fillMaxHeight] for the day content
24 | * height. With this option, your Calendar composable should also
25 | * be created with [Modifier.fillMaxHeight] or [Modifier.height].
26 | */
27 | Fill,
28 | }
29 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/ItemPlacementInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.gestures.Orientation
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.runtime.Stable
6 | import androidx.compose.runtime.withFrameNanos
7 | import androidx.compose.ui.layout.LayoutCoordinates
8 | import androidx.compose.ui.unit.round
9 | import kotlinx.coroutines.isActive
10 | import kotlin.coroutines.coroutineContext
11 |
12 | @Immutable
13 | internal data class ItemCoordinates(
14 | val itemRootCoordinates: LayoutCoordinates,
15 | val firstDayCoordinates: LayoutCoordinates,
16 | )
17 |
18 | @Stable
19 | internal class ItemPlacementInfo {
20 | private var itemCoordinates: ItemCoordinates? = null
21 |
22 | fun onItemPlaced(itemCoordinates: ItemCoordinates) {
23 | this.itemCoordinates = itemCoordinates
24 | }
25 |
26 | suspend fun awaitFistDayOffsetAndSize(orientation: Orientation): OffsetSize? {
27 | var itemCoordinates = this.itemCoordinates
28 | while (coroutineContext.isActive && itemCoordinates == null) {
29 | withFrameNanos {}
30 | itemCoordinates = this.itemCoordinates
31 | }
32 | if (itemCoordinates == null) {
33 | return null
34 | }
35 | val (itemRootCoordinates, firstDayCoordinates) = itemCoordinates
36 | val daySize = firstDayCoordinates.size
37 | val dayOffset = itemRootCoordinates.localPositionOf(firstDayCoordinates).round()
38 | return when (orientation) {
39 | Orientation.Vertical -> OffsetSize(
40 | offset = dayOffset.y,
41 | size = daySize.height,
42 | )
43 |
44 | Orientation.Horizontal -> {
45 | OffsetSize(
46 | offset = dayOffset.x,
47 | size = daySize.width,
48 | )
49 | }
50 | }
51 | }
52 |
53 | @Immutable
54 | internal data class OffsetSize(
55 | val offset: Int,
56 | val size: Int,
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.heatmapcalendar
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kizitonwose.calendar.compose.HeatMapCalendar
5 | import com.kizitonwose.calendar.core.CalendarDay
6 |
7 | /**
8 | * Represents a week on the heatmap calendar.
9 | *
10 | * This model exists only as a wrapper class with the [Immutable] annotation for compose.
11 | * The alternative would be to use the `kotlinx.ImmutableList` type for the `days` value
12 | * which is used ONLY in the dayContent parameter of the [HeatMapCalendar] but then we
13 | * would force that dependency on the library consumers.
14 | *
15 | * @param days the days in this week.
16 | */
17 | @Immutable
18 | public data class HeatMapWeek(val days: List)
19 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.heatmapcalendar
2 |
3 | import com.kizitonwose.calendar.compose.HeatMapCalendar
4 |
5 | /**
6 | * Determines the position of the week header
7 | * composable (Mon, Tue, Wed...) in the [HeatMapCalendar]
8 | */
9 | public enum class HeatMapWeekHeaderPosition {
10 | /**
11 | * The header is positioned at the start of the calendar.
12 | */
13 | Start,
14 |
15 | /**
16 | * The header is positioned at the end of the calendar.
17 | */
18 | End,
19 | }
20 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.weekcalendar
2 |
3 | import androidx.compose.foundation.lazy.LazyListItemInfo
4 | import androidx.compose.foundation.lazy.LazyListLayoutInfo
5 | import com.kizitonwose.calendar.core.Week
6 |
7 | /**
8 | * Contains useful information about the currently displayed layout state of the calendar.
9 | * For example you can get the list of currently displayed months.
10 | *
11 | * Use [WeekCalendarState.layoutInfo] to retrieve this.
12 | *
13 | * @see LazyListLayoutInfo
14 | */
15 | public class WeekCalendarLayoutInfo(
16 | info: LazyListLayoutInfo,
17 | private val getIndexData: (Int) -> Week,
18 | ) : LazyListLayoutInfo by info {
19 | /**
20 | * The list of [WeekCalendarItemInfo] representing all the currently visible weeks.
21 | */
22 | public val visibleWeeksInfo: List
23 | get() = visibleItemsInfo.map { info ->
24 | WeekCalendarItemInfo(info, getIndexData(info.index))
25 | }
26 | }
27 |
28 | /**
29 | * Contains useful information about an individual week on the calendar.
30 | *
31 | * @param week The week in the list.
32 |
33 | * @see WeekCalendarLayoutInfo
34 | * @see LazyListItemInfo
35 | */
36 | public class WeekCalendarItemInfo(info: LazyListItemInfo, public val week: Week) :
37 | LazyListItemInfo by info
38 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/yearcalendar/YearCalendarLayoutInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.yearcalendar
2 |
3 | import androidx.compose.foundation.lazy.LazyListItemInfo
4 | import androidx.compose.foundation.lazy.LazyListLayoutInfo
5 | import com.kizitonwose.calendar.core.CalendarYear
6 |
7 | /**
8 | * Contains useful information about the currently displayed layout state of the calendar.
9 | * For example you can get the list of currently displayed years.
10 | *
11 | * Use [YearCalendarState.layoutInfo] to retrieve this.
12 | *
13 | * @see LazyListLayoutInfo
14 | */
15 | public class YearCalendarLayoutInfo(
16 | info: LazyListLayoutInfo,
17 | private val getIndexData: (Int) -> CalendarYear,
18 | ) : LazyListLayoutInfo by info {
19 | /**
20 | * The list of [YearCalendarItemInfo] representing all the currently visible years.
21 | */
22 | public val visibleYearsInfo: List
23 | get() = visibleItemsInfo.map { info ->
24 | YearCalendarItemInfo(info, getIndexData(info.index))
25 | }
26 | }
27 |
28 | /**
29 | * Contains useful information about an individual year on the calendar.
30 | *
31 | * @param year The year in the list.
32 |
33 | * @see YearCalendarLayoutInfo
34 | * @see LazyListItemInfo
35 | */
36 | public class YearCalendarItemInfo(info: LazyListItemInfo, public val year: CalendarYear) :
37 | LazyListItemInfo by info
38 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/yearcalendar/YearContentHeightMode.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.yearcalendar
2 |
3 | import androidx.compose.foundation.layout.aspectRatio
4 | import androidx.compose.foundation.layout.fillMaxHeight
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.ui.Modifier
7 |
8 | /**
9 | * Determines how the height of the month content is calculated.
10 | */
11 | public enum class YearContentHeightMode {
12 | /**
13 | * The calendar months and days will wrap content height. This allows
14 | * you to use [Modifier.aspectRatio] if you want square day content
15 | * or [Modifier.height] if you want a specific height value
16 | * for the day content.
17 | */
18 | Wrap,
19 |
20 | /**
21 | * The calendar months will be distributed uniformly to fill the
22 | * parent's height. However, the days within the calendar months will
23 | * wrap content height. This allows you to spread the calendar months
24 | * evenly across the screen while using [Modifier.aspectRatio] if you
25 | * want square day content or [Modifier.height] if you want a specific
26 | * height value for the day content.
27 | */
28 | Fill,
29 |
30 | /**
31 | * The calendar months and days will uniformly stretch to fill the
32 | * parent's height. This allows you to use [Modifier.fillMaxHeight] for
33 | * the day content height. With this option, your Calendar composable should
34 | * also be created with [Modifier.fillMaxHeight] or [Modifier.height].
35 | */
36 | Stretch,
37 | }
38 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarDay.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.datetime.LocalDate
5 |
6 | /**
7 | * Represents a day on the calendar.
8 | *
9 | * @param date the date for this day.
10 | * @param position the [DayPosition] for this day.
11 | */
12 | @Immutable
13 | public data class CalendarDay(val date: LocalDate, val position: DayPosition)
14 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarMonth.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | /**
6 | * Represents a month on the calendar.
7 | *
8 | * @param yearMonth the calendar month value.
9 | * @param weekDays the weeks in this month.
10 | */
11 | @Immutable
12 | @ConsistentCopyVisibility
13 | public data class CalendarMonth internal constructor(
14 | val yearMonth: YearMonth,
15 | val weekDays: List>,
16 | ) {
17 | override fun equals(other: Any?): Boolean {
18 | if (this === other) return true
19 | if (other == null || this::class != other::class) return false
20 |
21 | other as CalendarMonth
22 |
23 | if (yearMonth != other.yearMonth) return false
24 | if (weekDays.first().first() != other.weekDays.first().first()) return false
25 | if (weekDays.last().last() != other.weekDays.last().last()) return false
26 |
27 | return true
28 | }
29 |
30 | override fun hashCode(): Int {
31 | var result = yearMonth.hashCode()
32 | result = 31 * result + weekDays.first().first().hashCode()
33 | result = 31 * result + weekDays.last().last().hashCode()
34 | return result
35 | }
36 |
37 | override fun toString(): String {
38 | return "CalendarMonth { " +
39 | "first = ${weekDays.first().first()}, " +
40 | "last = ${weekDays.last().last()} " +
41 | "} "
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarYear.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | /**
6 | * Represents a year on the calendar.
7 | *
8 | * @param year the calendar year value.
9 | * @param months the months in this year.
10 | */
11 | @Immutable
12 | public data class CalendarYear(
13 | val year: Year,
14 | val months: List,
15 | ) {
16 | override fun equals(other: Any?): Boolean {
17 | if (this === other) return true
18 | if (other == null || this::class != other::class) return false
19 |
20 | other as CalendarYear
21 |
22 | if (year != other.year) return false
23 | if (months.first() != other.months.first()) return false
24 | if (months.last() != other.months.last()) return false
25 |
26 | return true
27 | }
28 |
29 | override fun hashCode(): Int {
30 | var result = year.hashCode()
31 | result = 31 * result + months.first().hashCode()
32 | result = 31 * result + months.last().hashCode()
33 | return result
34 | }
35 |
36 | override fun toString(): String {
37 | return "CalendarYear { " +
38 | "year = $year, " +
39 | "firstMonth = ${months.first()}, " +
40 | "lastMonth = ${months.last()} " +
41 | "} "
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/DayPosition.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | /**
4 | * Describes the position of a [CalendarDay] in the month.
5 | */
6 | public enum class DayPosition {
7 | /**
8 | * The day is positioned at the start of the month to
9 | * ensure proper alignment of the first day of the week.
10 | * The day belongs to the previous month on the calendar.
11 | */
12 | InDate,
13 |
14 | /**
15 | * The day belongs to the current month on the calendar.
16 | */
17 | MonthDate,
18 |
19 | /**
20 | * The day is positioned at the end of the month to
21 | * to fill the remaining days after the days in the month.
22 | * The day belongs to the next month on the calendar.
23 | * @see [OutDateStyle]
24 | */
25 | OutDate,
26 | }
27 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/ExperimentalCalendarApi.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | @RequiresOptIn(
4 | message = "This calendar API is experimental and is " +
5 | "likely to change or to be removed in the future.",
6 | level = RequiresOptIn.Level.ERROR,
7 | )
8 | @Retention(AnnotationRetention.BINARY)
9 | public annotation class ExperimentalCalendarApi
10 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/OutDateStyle.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | /**
4 | * Determines how [DayPosition.OutDate] are
5 | * generated for each month on the calendar.
6 | */
7 | public enum class OutDateStyle {
8 | /**
9 | * The calendar will generate outDates until it reaches
10 | * the end of the month row. This means that if a month
11 | * has 5 rows, it will display 5 rows and if a month
12 | * has 6 rows, it will display 6 rows.
13 | */
14 | EndOfRow,
15 |
16 | /**
17 | * The calendar will generate outDates until it
18 | * reaches the end of a 6 x 7 grid on each month.
19 | * This means that all months will have 6 rows.
20 | */
21 | EndOfGrid,
22 | }
23 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Week.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | /**
6 | * Represents a week on the week-based calendar.
7 | *
8 | * @param days the days in this week.
9 | */
10 | @Immutable
11 | @ConsistentCopyVisibility
12 | public data class Week internal constructor(val days: List) {
13 | override fun equals(other: Any?): Boolean {
14 | if (this === other) return true
15 | if (other == null || this::class != other::class) return false
16 |
17 | other as Week
18 |
19 | if (days.first() != other.days.first()) return false
20 | if (days.last() != other.days.last()) return false
21 |
22 | return true
23 | }
24 |
25 | override fun hashCode(): Int {
26 | var result = days.first().hashCode()
27 | result = 31 * result + days.last().hashCode()
28 | return result
29 | }
30 |
31 | override fun toString(): String {
32 | return "Week { " +
33 | "first = ${days.first()}, " +
34 | "last = ${days.last()} " +
35 | "} "
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDay.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.datetime.LocalDate
5 |
6 | /**
7 | * Represents a day on the week calendar.
8 | *
9 | * @param date the date for this day.
10 | * @param position the [WeekDayPosition] for this day.
11 | */
12 | @Immutable
13 | public data class WeekDay(val date: LocalDate, val position: WeekDayPosition)
14 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDayPosition.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | /**
4 | * Describes the position of a [WeekDay] on the calendar.
5 | */
6 | public enum class WeekDayPosition {
7 | /**
8 | * The day is positioned at the start of the calendar to
9 | * ensure proper alignment of the first day of the week.
10 | * The day is before the provided start date.
11 | */
12 | InDate,
13 |
14 | /**
15 | * The day is in the range of the provided start and end dates.
16 | */
17 | RangeDate,
18 |
19 | /**
20 | * The day is positioned at the end of the calendar to to fill the
21 | * remaining days in the last week after the provided end date.
22 | * The day is after the provided end date.
23 | */
24 | OutDate,
25 | }
26 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/format/Format.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core.format
2 |
3 | import com.kizitonwose.calendar.core.Year
4 | import com.kizitonwose.calendar.core.YearMonth
5 | import com.kizitonwose.calendar.core.atMonth
6 | import com.kizitonwose.calendar.core.atStartOfMonth
7 | import com.kizitonwose.calendar.core.yearMonth
8 | import kotlinx.datetime.LocalDate
9 | import kotlinx.datetime.format.char
10 |
11 | private val ISO_YEAR_MONTH by lazy {
12 | LocalDate.Format {
13 | year()
14 | char('-')
15 | monthNumber()
16 | }
17 | }
18 |
19 | private val ISO_YEAR by lazy {
20 | LocalDate.Format { year() }
21 | }
22 |
23 | private val ISO_LOCAL_DATE by lazy {
24 | LocalDate.Formats.ISO
25 | }
26 |
27 | internal fun LocalDate.toIso8601String() = ISO_LOCAL_DATE.format(this)
28 |
29 | internal fun YearMonth.toIso8601String() = ISO_YEAR_MONTH.format(atStartOfMonth())
30 |
31 | internal fun Year.toIso8601String() = ISO_YEAR.format(atMonth(1).atStartOfMonth())
32 |
33 | internal fun String.fromIso8601LocalDate(): LocalDate =
34 | LocalDate.parse(this, ISO_LOCAL_DATE)
35 |
36 | internal fun String.fromIso8601YearMonth(): YearMonth =
37 | LocalDate.parse("$this-01", ISO_LOCAL_DATE).yearMonth
38 |
39 | internal fun String.fromIso8601Year(): Year =
40 | Year(LocalDate.parse("$this-01-01", ISO_LOCAL_DATE).year)
41 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/DataStore.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | /**
4 | * Basically [MutableMap.getOrPut] but allows us read the map
5 | * in multiple places without calling `getOrPut` everywhere.
6 | */
7 | internal class DataStore(
8 | private val store: MutableMap = HashMap(),
9 | private val create: (offset: Int) -> V,
10 | ) : MutableMap by store {
11 | override fun get(key: Int): V {
12 | val value = store[key]
13 | return if (value == null) {
14 | val data = create(key)
15 | put(key, data)
16 | data
17 | } else {
18 | value
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.CalendarDay
4 | import com.kizitonwose.calendar.core.DayPosition
5 | import com.kizitonwose.calendar.core.YearMonth
6 | import com.kizitonwose.calendar.core.minusMonths
7 | import com.kizitonwose.calendar.core.plusMonths
8 | import com.kizitonwose.calendar.core.yearMonth
9 | import kotlinx.datetime.DayOfWeek
10 |
11 | // E.g DayOfWeek.SATURDAY.daysUntil(DayOfWeek.TUESDAY) = 3
12 | internal fun DayOfWeek.daysUntil(other: DayOfWeek) = (7 + (other.ordinal - ordinal)) % 7
13 |
14 | // E.g DayOfWeek.SATURDAY.plusDays(3) = DayOfWeek.TUESDAY
15 | internal fun DayOfWeek.plusDays(days: Int): DayOfWeek {
16 | val amount = (days % 7)
17 | return DayOfWeek.entries[(ordinal + (amount + 7)) % 7]
18 | }
19 |
20 | // Find the actual month on the calendar where this date is shown.
21 | internal val CalendarDay.positionYearMonth: YearMonth
22 | get() = when (position) {
23 | DayPosition.InDate -> date.yearMonth.plusMonths(1)
24 | DayPosition.MonthDate -> date.yearMonth
25 | DayPosition.OutDate -> date.yearMonth.minusMonths(1)
26 | }
27 |
28 | internal inline fun Iterable.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
29 | val result = indexOfFirst(predicate)
30 | return if (result == -1) null else result
31 | }
32 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | internal fun > checkRange(start: T, end: T) {
4 | check(end >= start) {
5 | "start: $start is greater than end: $end"
6 | }
7 | }
8 |
9 | internal fun T.asUnit() = Unit
10 |
11 | internal expect fun log(tag: String, message: String)
12 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/VisibleItemState.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | internal class VisibleItemState(
7 | val firstVisibleItemIndex: Int = 0,
8 | val firstVisibleItemScrollOffset: Int = 0,
9 | )
10 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/WeekData.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.Week
4 | import com.kizitonwose.calendar.core.WeekDay
5 | import com.kizitonwose.calendar.core.WeekDayPosition
6 | import com.kizitonwose.calendar.core.minusDays
7 | import com.kizitonwose.calendar.core.plusDays
8 | import com.kizitonwose.calendar.core.plusWeeks
9 | import com.kizitonwose.calendar.core.weeksUntil
10 | import kotlinx.datetime.DayOfWeek
11 | import kotlinx.datetime.LocalDate
12 |
13 | internal data class WeekDateRange(
14 | val startDateAdjusted: LocalDate,
15 | val endDateAdjusted: LocalDate,
16 | )
17 |
18 | internal fun getWeekCalendarAdjustedRange(
19 | startDate: LocalDate,
20 | endDate: LocalDate,
21 | firstDayOfWeek: DayOfWeek,
22 | ): WeekDateRange {
23 | val inDays = firstDayOfWeek.daysUntil(startDate.dayOfWeek)
24 | val startDateAdjusted = startDate.minusDays(inDays)
25 | val weeksBetween = startDateAdjusted.weeksUntil(endDate)
26 | val endDateAdjusted = startDateAdjusted.plusWeeks(weeksBetween).plusDays(6)
27 | return WeekDateRange(startDateAdjusted = startDateAdjusted, endDateAdjusted = endDateAdjusted)
28 | }
29 |
30 | internal fun getWeekCalendarData(
31 | startDateAdjusted: LocalDate,
32 | offset: Int,
33 | desiredStartDate: LocalDate,
34 | desiredEndDate: LocalDate,
35 | ): WeekData {
36 | val firstDayInWeek = startDateAdjusted.plusWeeks(offset)
37 | return WeekData(firstDayInWeek, desiredStartDate, desiredEndDate)
38 | }
39 |
40 | internal data class WeekData(
41 | private val firstDayInWeek: LocalDate,
42 | private val desiredStartDate: LocalDate,
43 | private val desiredEndDate: LocalDate,
44 | ) {
45 | val week: Week = Week((0 until 7).map { dayOffset -> getDay(dayOffset) })
46 |
47 | private fun getDay(dayOffset: Int): WeekDay {
48 | val date = firstDayInWeek.plusDays(dayOffset)
49 | val position = when {
50 | date < desiredStartDate -> WeekDayPosition.InDate
51 | date > desiredEndDate -> WeekDayPosition.OutDate
52 | else -> WeekDayPosition.RangeDate
53 | }
54 | return WeekDay(date, position)
55 | }
56 | }
57 |
58 | internal fun getWeekIndex(startDateAdjusted: LocalDate, date: LocalDate): Int {
59 | return startDateAdjusted.weeksUntil(date)
60 | }
61 |
62 | internal fun getWeekIndicesCount(startDateAdjusted: LocalDate, endDateAdjusted: LocalDate): Int {
63 | // Add one to include the start week itself!
64 | return getWeekIndex(startDateAdjusted, endDateAdjusted) + 1
65 | }
66 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/YearData.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.CalendarYear
4 | import com.kizitonwose.calendar.core.OutDateStyle
5 | import com.kizitonwose.calendar.core.Year
6 | import com.kizitonwose.calendar.core.atMonth
7 | import com.kizitonwose.calendar.core.plusYears
8 | import com.kizitonwose.calendar.core.yearsUntil
9 | import kotlinx.datetime.DayOfWeek
10 | import kotlinx.datetime.Month
11 |
12 | internal fun getCalendarYearData(
13 | startYear: Year,
14 | offset: Int,
15 | firstDayOfWeek: DayOfWeek,
16 | outDateStyle: OutDateStyle,
17 | ): CalendarYear {
18 | val year = startYear.plusYears(offset)
19 | val months = List(Month.entries.size) { index ->
20 | getCalendarMonthData(
21 | startMonth = year.atMonth(Month.JANUARY),
22 | offset = index,
23 | firstDayOfWeek = firstDayOfWeek,
24 | outDateStyle = outDateStyle,
25 | ).calendarMonth
26 | }
27 | return CalendarYear(year, months)
28 | }
29 |
30 | internal fun getYearIndex(startYear: Year, targetYear: Year): Int {
31 | return startYear.yearsUntil(targetYear)
32 | }
33 |
34 | internal fun getYearIndicesCount(startYear: Year, endYear: Year): Int {
35 | // Add one to include the start year itself!
36 | return getYearIndex(startYear, endYear) + 1
37 | }
38 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonTest/kotlin/com/kizitonwose/calendar/core/YearSerializationTest.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import com.kizitonwose.calendar.core.serializers.YearComponentSerializer
4 | import com.kizitonwose.calendar.core.serializers.YearIso8601Serializer
5 | import kotlinx.serialization.KSerializer
6 | import kotlinx.serialization.SerializationException
7 | import kotlinx.serialization.json.Json
8 | import kotlinx.serialization.serializer
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 | import kotlin.test.assertFailsWith
12 |
13 | class YearSerializationTest {
14 | private fun iso8601Serialization(serializer: KSerializer) {
15 | for ((localDate, json) in listOf(
16 | Pair(Year(2020), "\"2020\""),
17 | Pair(Year(-2020), "\"-2020\""),
18 | Pair(Year(2019), "\"2019\""),
19 | )) {
20 | assertEquals(json, Json.encodeToString(serializer, localDate))
21 | assertEquals(localDate, Json.decodeFromString(serializer, json))
22 | }
23 | }
24 |
25 | private fun componentSerialization(serializer: KSerializer) {
26 | for ((localDate, json) in listOf(
27 | Pair(Year(2020), "{\"year\":2020}"),
28 | Pair(Year(-2020), "{\"year\":-2020}"),
29 | Pair(Year(2019), "{\"year\":2019}"),
30 | )) {
31 | assertEquals(json, Json.encodeToString(serializer, localDate))
32 | assertEquals(localDate, Json.decodeFromString(serializer, json))
33 | }
34 | // all components must be present
35 | assertFailsWith {
36 | Json.decodeFromString(serializer, "{}")
37 | }
38 | // invalid values must fail to construct
39 | assertFailsWith {
40 | Json.decodeFromString(serializer, "{\"year\":1000000000000}")
41 | }
42 | }
43 |
44 | @Test
45 | fun testIso8601Serialization() {
46 | iso8601Serialization(YearIso8601Serializer)
47 | }
48 |
49 | @Test
50 | fun testComponentSerialization() {
51 | componentSerialization(YearComponentSerializer)
52 | }
53 |
54 | @Test
55 | fun testDefaultSerializers() {
56 | // should be the same as the ISO 8601
57 | iso8601Serialization(Json.serializersModule.serializer())
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/commonTest/kotlin/com/kizitonwose/calendar/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.utils
2 |
3 | import com.kizitonwose.calendar.core.OutDateStyle
4 | import com.kizitonwose.calendar.core.YearMonth
5 | import com.kizitonwose.calendar.core.minusMonths
6 | import com.kizitonwose.calendar.core.plusMonths
7 | import com.kizitonwose.calendar.data.getCalendarMonthData
8 | import kotlinx.datetime.DayOfWeek
9 |
10 | internal infix fun Pair.toTriple(that: C): Triple = Triple(first, second, that)
11 |
12 | internal fun YearMonth.weeksInMonth(firstDayOfWeek: DayOfWeek) = getCalendarMonthData(
13 | startMonth = this,
14 | offset = 0,
15 | firstDayOfWeek = firstDayOfWeek,
16 | outDateStyle = OutDateStyle.EndOfRow,
17 | ).calendarMonth.weekDays.count()
18 |
19 | internal val YearMonth.nextMonth: YearMonth
20 | get() = this.plusMonths(1)
21 |
22 | internal val YearMonth.previousMonth: YearMonth
23 | get() = this.minusMonths(1)
24 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/desktopMain/kotlin/com/kizitonwose/calendar/data/Utils.desktop.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import java.util.logging.Level
4 | import java.util.logging.Logger
5 |
6 | internal actual fun log(tag: String, message: String) =
7 | logger.warning("$tag : $message")
8 |
9 | private val logger = Logger.getLogger("Calendar").apply {
10 | level = Level.WARNING
11 | }
12 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/iosMain/kotlin/com/kizitonwose/calendar/core/Extensions.ios.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.ui.text.intl.Locale
4 | import kotlinx.datetime.DayOfWeek
5 | import platform.Foundation.NSCalendar
6 | import platform.Foundation.NSLocale
7 |
8 | /**
9 | * Returns the first day of the week from the provided locale.
10 | */
11 | public actual fun firstDayOfWeekFromLocale(locale: Locale): DayOfWeek {
12 | val firstWeekday = NSCalendar.currentCalendar.let {
13 | it.setLocale(NSLocale(locale.toLanguageTag()))
14 | // https://developer.apple.com/documentation/foundation/calendar/2293656-firstweekday
15 | // Value is one-based, starting from sunday
16 | it.firstWeekday.toInt()
17 | }
18 | // Get the index value from a sunday-based array.
19 | return daysOfWeek(firstDayOfWeek = DayOfWeek.SUNDAY)[firstWeekday - 1]
20 | }
21 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/iosMain/kotlin/com/kizitonwose/calendar/data/Utils.ios.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import platform.Foundation.NSLog
4 |
5 | internal actual fun log(tag: String, message: String) =
6 | NSLog("$tag : $message")
7 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Converters.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import java.time.Year as jtYear
4 | import java.time.YearMonth as jtYearMonth
5 |
6 | public fun YearMonth.toJavaYearMonth(): jtYearMonth = jtYearMonth.of(year, month)
7 |
8 | public fun jtYearMonth.toKotlinYearMonth(): YearMonth = YearMonth(year, month)
9 |
10 | public fun Year.toJavaYear(): jtYear = jtYear.of(value)
11 |
12 | public fun jtYear.toKotlinYear(): Year = Year(value)
13 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Extensions.jvm.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.ui.text.intl.Locale
4 | import java.time.DayOfWeek
5 | import java.time.temporal.WeekFields
6 | import java.util.Locale as JavaLocale
7 |
8 | public actual fun firstDayOfWeekFromLocale(locale: Locale): DayOfWeek =
9 | WeekFields.of(JavaLocale.forLanguageTag(locale.toLanguageTag())).firstDayOfWeek
10 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/Extensions.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.ui.text.intl.Locale
4 | import kotlinx.datetime.DayOfWeek
5 |
6 | /**
7 | * Returns the first day of the week from the provided locale.
8 | */
9 | public actual fun firstDayOfWeekFromLocale(locale: Locale): DayOfWeek {
10 | return try {
11 | val firstDay = jsFirstDayFromTag(locale.toLanguageTag())
12 | daysOfWeek(firstDayOfWeek = DayOfWeek.MONDAY)[firstDay - 1]
13 | // Unavailable on Firefox
14 | } catch (e: Exception) {
15 | firstDayFromMap(locale)
16 | }
17 | }
18 |
19 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo#firstday
20 | private fun jsFirstDayFromTag(languageTag: String): Int = js("new Intl.Locale(languageTag).weekInfo?.firstDay")
21 |
--------------------------------------------------------------------------------
/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/data/Utils.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | internal actual fun log(tag: String, message: String) =
4 | consoleLog("$tag : $message")
5 |
6 | @JsFun("(output) => console.log(output)")
7 | private external fun consoleLog(vararg output: String?)
8 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/kotlin/Device.android.kt:
--------------------------------------------------------------------------------
1 | actual fun isMobile(): Boolean = true
2 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/kotlin/com/kizitonwose/calendar/compose/multiplatform/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.multiplatform
2 |
3 | import App
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.tooling.preview.Preview
9 |
10 | class MainActivity : ComponentActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContent {
14 | App()
15 | }
16 | }
17 | }
18 |
19 | @Preview
20 | @Composable
21 | fun AppAndroidPreview() {
22 | App()
23 | }
24 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Calendar Multiplatform Sample
3 |
4 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/commonMain/kotlin/Colors.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.graphics.Color
2 |
3 | object Colors {
4 | val example1Selection = Color(0xFFFCCA3E)
5 | val example1Bg = Color(0xFF3A284C)
6 | val example1BgLight = Color(0xFF433254)
7 | val example1BgSecondary = Color(0xFF51356E)
8 | val example1WhiteLight = Color(0x4DFFFFFF)
9 | val example4GrayPast = Color(0xFFBEBEBE)
10 | val example4Gray = Color(0xFF474747)
11 | val example5PageBgColor = Color(0xFF0E0E0E)
12 | val example5ItemViewBgColor = Color(0xFF1B1B1B)
13 | val example5ToolbarColor = Color(0xFF282828)
14 | val example5TextGrey = Color(0xFFDCDCDC)
15 | val example5TextGreyLight = Color(0xFF616161)
16 | val example6MonthBgColor = Color(0xFFB2EBF2)
17 | val example6MonthBgColor2 = Color(0xFFF2C4B2)
18 | val example6MonthBgColor3 = Color(0xFFB2B8F2)
19 | val example7Yellow = Color(0xFFFFEB3B)
20 | val primary = Color(0xFF3F51B5)
21 | val accent = Color(0xFFFF4081)
22 | }
23 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/commonMain/kotlin/ContinuousSelectionHelper.kt:
--------------------------------------------------------------------------------
1 | import com.kizitonwose.calendar.core.atEndOfMonth
2 | import com.kizitonwose.calendar.core.atStartOfMonth
3 | import com.kizitonwose.calendar.core.yearMonth
4 | import kotlinx.datetime.LocalDate
5 | import kotlinx.datetime.daysUntil
6 | import kotlin.LazyThreadSafetyMode.NONE
7 |
8 | data class DateSelection(val startDate: LocalDate? = null, val endDate: LocalDate? = null) {
9 | val daysBetween by lazy(NONE) {
10 | if (startDate == null || endDate == null) {
11 | null
12 | } else {
13 | startDate.daysUntil(endDate)
14 | }
15 | }
16 | }
17 |
18 | private val rangeFormatter = LocalDate.Formats.ISO
19 | fun dateRangeDisplayText(startDate: LocalDate, endDate: LocalDate): String {
20 | return "Selected: ${rangeFormatter.format(startDate)} - ${rangeFormatter.format(endDate)}"
21 | }
22 |
23 | object ContinuousSelectionHelper {
24 | fun getSelection(
25 | clickedDate: LocalDate,
26 | dateSelection: DateSelection,
27 | ): DateSelection {
28 | val (selectionStartDate, selectionEndDate) = dateSelection
29 | return if (selectionStartDate != null) {
30 | if (clickedDate < selectionStartDate || selectionEndDate != null) {
31 | DateSelection(startDate = clickedDate, endDate = null)
32 | } else if (clickedDate != selectionStartDate) {
33 | DateSelection(startDate = selectionStartDate, endDate = clickedDate)
34 | } else {
35 | DateSelection(startDate = clickedDate, endDate = null)
36 | }
37 | } else {
38 | DateSelection(startDate = clickedDate, endDate = null)
39 | }
40 | }
41 |
42 | fun isInDateBetweenSelection(
43 | inDate: LocalDate,
44 | startDate: LocalDate,
45 | endDate: LocalDate,
46 | ): Boolean {
47 | if (startDate.yearMonth == endDate.yearMonth) return false
48 | if (inDate.yearMonth == startDate.yearMonth) return true
49 | val firstDateInThisMonth = inDate.yearMonth.next.atStartOfMonth()
50 | return firstDateInThisMonth in startDate..endDate && startDate != firstDateInThisMonth
51 | }
52 |
53 | fun isOutDateBetweenSelection(
54 | outDate: LocalDate,
55 | startDate: LocalDate,
56 | endDate: LocalDate,
57 | ): Boolean {
58 | if (startDate.yearMonth == endDate.yearMonth) return false
59 | if (outDate.yearMonth == endDate.yearMonth) return true
60 | val lastDateInThisMonth = outDate.yearMonth.previous.atEndOfMonth()
61 | return lastDateInThisMonth in startDate..endDate && endDate != lastDateInThisMonth
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/commonMain/kotlin/Device.kt:
--------------------------------------------------------------------------------
1 | expect fun isMobile(): Boolean
2 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/commonMain/kotlin/Format.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.text.intl.Locale
2 | import androidx.compose.ui.text.toUpperCase
3 | import com.kizitonwose.calendar.core.Week
4 | import com.kizitonwose.calendar.core.YearMonth
5 | import com.kizitonwose.calendar.core.yearMonth
6 | import kotlinx.datetime.DayOfWeek
7 | import kotlinx.datetime.Month
8 |
9 | fun YearMonth.displayText(short: Boolean = false): String {
10 | return "${month.displayText(short = short)} $year"
11 | }
12 |
13 | fun Month.displayText(short: Boolean = true): String {
14 | return getDisplayName(short, enLocale)
15 | }
16 |
17 | fun DayOfWeek.displayText(uppercase: Boolean = false, narrow: Boolean = false): String {
18 | return getDisplayName(narrow, enLocale).let { value ->
19 | if (uppercase) value.toUpperCase(enLocale) else value
20 | }
21 | }
22 |
23 | expect fun Month.getDisplayName(short: Boolean, locale: Locale): String
24 |
25 | expect fun DayOfWeek.getDisplayName(narrow: Boolean = false, locale: Locale): String
26 |
27 | private val enLocale = Locale("en-US")
28 |
29 | fun getWeekPageTitle(week: Week): String {
30 | val firstDate = week.days.first().date
31 | val lastDate = week.days.last().date
32 | return when {
33 | firstDate.yearMonth == lastDate.yearMonth -> {
34 | firstDate.yearMonth.displayText()
35 | }
36 |
37 | firstDate.year == lastDate.year -> {
38 | "${firstDate.month.displayText(short = false)} - ${lastDate.yearMonth.displayText()}"
39 | }
40 |
41 | else -> {
42 | "${firstDate.yearMonth.displayText()} - ${lastDate.yearMonth.displayText()}"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/commonMain/kotlin/Scaffold.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.foundation.layout.PaddingValues
2 | import androidx.compose.foundation.layout.calculateEndPadding
3 | import androidx.compose.foundation.layout.calculateStartPadding
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.compositionLocalOf
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.platform.LocalLayoutDirection
9 |
10 | val LocalScaffoldPaddingValues = compositionLocalOf { PaddingValues() }
11 |
12 | @Composable
13 | fun Modifier.applyScaffoldHorizontalPaddings() =
14 | padding(
15 | start = LocalScaffoldPaddingValues.current.calculateStartPadding(LocalLayoutDirection.current),
16 | end = LocalScaffoldPaddingValues.current.calculateEndPadding(LocalLayoutDirection.current),
17 | )
18 |
19 | @Composable
20 | fun Modifier.applyScaffoldTopPadding() =
21 | padding(top = LocalScaffoldPaddingValues.current.calculateTopPadding())
22 |
23 | @Composable
24 | fun Modifier.applyScaffoldBottomPadding() =
25 | padding(bottom = LocalScaffoldPaddingValues.current.calculateBottomPadding())
26 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/commonMain/kotlin/Theme.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.material3.lightColorScheme
2 | import androidx.compose.ui.graphics.Color
3 |
4 | val SampleColorScheme = lightColorScheme(
5 | primary = Color(0xFF3F51B5),
6 | onPrimary = Color.White,
7 | primaryContainer = Color(0xFF3F51B5),
8 | onPrimaryContainer = Color.White,
9 | inversePrimary = Color.Black,
10 | secondary = Color(0xFF3F51B5),
11 | onSecondary = Color.White,
12 | secondaryContainer = Color(0xFF3F51B5),
13 | onSecondaryContainer = Color.White,
14 | tertiary = Color(0xFF3F51B5),
15 | onTertiary = Color.White,
16 | tertiaryContainer = Color(0xFF3F51B5),
17 | onTertiaryContainer = Color.White,
18 | background = Color.White,
19 | onBackground = Color.Black,
20 | surface = Color.White,
21 | onSurface = Color.Black,
22 | surfaceVariant = Color.White,
23 | onSurfaceVariant = Color.Black,
24 | surfaceTint = Color.White,
25 | inverseSurface = Color(0xFF121212),
26 | inverseOnSurface = Color.White,
27 | error = Color(0xFFB00020),
28 | onError = Color.White,
29 | errorContainer = Color(0xFFB00020),
30 | onErrorContainer = Color.White,
31 | outline = Color(0xFFAFAFAF),
32 | outlineVariant = Color(0xFFCCCCCC),
33 | scrim = Color.Black,
34 | surfaceBright = Color.White,
35 | surfaceContainer = Color.White,
36 | surfaceContainerHigh = Color.White,
37 | surfaceContainerHighest = Color.White,
38 | surfaceContainerLow = Color.White,
39 | surfaceContainerLowest = Color.White,
40 | surfaceDim = Color.White,
41 | )
42 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/desktopMain/kotlin/Device.desktop.kt:
--------------------------------------------------------------------------------
1 | actual fun isMobile(): Boolean = false
2 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/desktopMain/kotlin/Main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.Window
2 | import androidx.compose.ui.window.application
3 |
4 | fun main() = application {
5 | Window(
6 | onCloseRequest = ::exitApplication,
7 | title = "Calendar Sample",
8 | ) {
9 | App()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/iosMain/kotlin/Device.ios.kt:
--------------------------------------------------------------------------------
1 | actual fun isMobile(): Boolean = true
2 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/iosMain/kotlin/Format.ios.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.text.intl.Locale
2 | import com.kizitonwose.calendar.core.daysOfWeek
3 | import kotlinx.datetime.DayOfWeek
4 | import kotlinx.datetime.Month
5 | import platform.Foundation.NSCalendar
6 | import platform.Foundation.NSLocale
7 |
8 | actual fun Month.getDisplayName(short: Boolean, locale: Locale): String =
9 | NSCalendar.currentCalendar.let {
10 | it.setLocale(NSLocale(locale.toLanguageTag()))
11 | it.monthSymbols[Month.entries.indexOf(this)] as String
12 | }
13 |
14 | actual fun DayOfWeek.getDisplayName(narrow: Boolean, locale: Locale): String =
15 | NSCalendar.currentCalendar.let {
16 | it.setLocale(NSLocale(locale.toLanguageTag()))
17 | val values = if (narrow) {
18 | it.veryShortWeekdaySymbols
19 | } else {
20 | it.shortWeekdaySymbols
21 | }
22 | values[sundayBasedWeek.indexOf(this)] as String
23 | }
24 |
25 | private val sundayBasedWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.SUNDAY)
26 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/iosMain/kotlin/MainViewController.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 |
3 | @Suppress("FunctionName")
4 | fun MainViewController() = ComposeUIViewController { App() }
5 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/jvmMain/kotlin/Format.jvm.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.text.intl.Locale
2 | import kotlinx.datetime.DayOfWeek
3 | import kotlinx.datetime.Month
4 | import java.time.format.TextStyle
5 | import java.util.Locale as JavaLocale
6 |
7 | actual fun Month.getDisplayName(short: Boolean, locale: Locale): String {
8 | val style = if (short) TextStyle.SHORT else TextStyle.FULL
9 | return getDisplayName(style, JavaLocale.forLanguageTag(locale.toLanguageTag()))
10 | }
11 |
12 | actual fun DayOfWeek.getDisplayName(narrow: Boolean, locale: Locale): String {
13 | val style = if (narrow) TextStyle.NARROW else TextStyle.SHORT
14 | return getDisplayName(style, JavaLocale.forLanguageTag(locale.toLanguageTag()))
15 | }
16 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/wasmJsMain/kotlin/Device.wasmJs.kt:
--------------------------------------------------------------------------------
1 | actual fun isMobile(): Boolean = false
2 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/wasmJsMain/kotlin/Format.wasmJs.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.text.capitalize
2 | import androidx.compose.ui.text.intl.Locale
3 | import androidx.compose.ui.text.toLowerCase
4 | import kotlinx.datetime.DayOfWeek
5 | import kotlinx.datetime.Month
6 |
7 | actual fun Month.getDisplayName(short: Boolean, locale: Locale): String {
8 | val name = name.toLowerCase(enLocale).capitalize(enLocale)
9 | return if (short) name.take(3) else name
10 | }
11 |
12 | actual fun DayOfWeek.getDisplayName(narrow: Boolean, locale: Locale): String {
13 | return name.toLowerCase(enLocale).capitalize(enLocale).take(if (narrow) 1 else 3)
14 | }
15 |
16 | private val enLocale = Locale("en-US")
17 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/wasmJsMain/kotlin/Main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.ExperimentalComposeUiApi
2 | import androidx.compose.ui.window.ComposeViewport
3 | import kotlinx.browser.document
4 |
5 | @OptIn(ExperimentalComposeUiApi::class)
6 | fun main() {
7 | ComposeViewport(document.body!!) {
8 | App()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/wasmJsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Calendar Library
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/compose-multiplatform/sample/src/wasmJsMain/resources/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0;
5 | padding: 0;
6 | overflow: hidden;
7 | }
--------------------------------------------------------------------------------
/compose/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/compose/build.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | import com.kizitonwose.calendar.buildsrc.Android
3 | import com.kizitonwose.calendar.buildsrc.Config
4 | import com.kizitonwose.calendar.buildsrc.Version
5 |
6 | plugins {
7 | alias(libs.plugins.androidLibrary)
8 | alias(libs.plugins.kotlinAndroid)
9 | alias(libs.plugins.composeCompiler)
10 | alias(libs.plugins.mavenPublish)
11 | }
12 |
13 | android {
14 | compileSdk = Android.compileSdk
15 | namespace = "com.kizitonwose.calendar.compose"
16 | defaultConfig {
17 | minSdk = Android.minSdk
18 | }
19 | java {
20 | toolchain {
21 | languageVersion.set(Config.compatibleJavaLanguageVersion)
22 | }
23 | }
24 | kotlin {
25 | jvmToolchain {
26 | languageVersion.set(Config.compatibleJavaLanguageVersion)
27 | }
28 | }
29 | buildFeatures {
30 | compose = true
31 | }
32 | }
33 |
34 | dependencies {
35 | api(project(":core"))
36 | implementation(project(":data"))
37 | implementation(libs.kotlin.stdlib)
38 |
39 | implementation(libs.compose.ui.ui)
40 | implementation(libs.compose.ui.tooling)
41 | implementation(libs.compose.foundation)
42 | implementation(libs.compose.runtime)
43 |
44 | testImplementation(platform(libs.test.junit5.bom))
45 | testImplementation(libs.test.junit5.api)
46 | testRuntimeOnly(libs.test.junit5.engine)
47 | testRuntimeOnly(libs.test.junit.platform.launcher)
48 | }
49 |
50 | mavenPublishing {
51 | coordinates(version = Version.android)
52 | }
53 |
--------------------------------------------------------------------------------
/compose/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=compose
2 | POM_DESCRIPTION=A highly customizable calendar library for Android, backed by LazyRow/LazyColumn.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/compose/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.gestures.FlingBehavior
5 | import androidx.compose.foundation.gestures.ScrollableDefaults
6 | import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
7 | import androidx.compose.foundation.gestures.snapping.SnapPosition
8 | import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
9 | import androidx.compose.foundation.lazy.LazyListState
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.remember
12 |
13 | internal object CalendarDefaults {
14 | /**
15 | * The default implementation in [rememberSnapFlingBehavior] snaps to the center of the layout
16 | * but we want to snap to the start. For example, in a vertical calendar, when the layout size
17 | * is larger than the item size(e.g two or more visible months), we don't want the item's
18 | * center to be at the center of the layout when it snaps, instead we want the item's top
19 | * to be at the top of the layout.
20 | */
21 | @OptIn(ExperimentalFoundationApi::class)
22 | @Composable
23 | private fun pagedFlingBehavior(state: LazyListState): FlingBehavior {
24 | val snappingLayout = remember(state) {
25 | val provider = SnapLayoutInfoProvider(state, SnapPosition.Start)
26 | CalendarSnapLayoutInfoProvider(provider)
27 | }
28 | return rememberSnapFlingBehavior(snappingLayout)
29 | }
30 |
31 | @Composable
32 | private fun continuousFlingBehavior(): FlingBehavior = ScrollableDefaults.flingBehavior()
33 |
34 | @Composable
35 | fun flingBehavior(isPaged: Boolean, state: LazyListState): FlingBehavior {
36 | return if (isPaged) pagedFlingBehavior(state) else continuousFlingBehavior()
37 | }
38 | }
39 |
40 | @ExperimentalFoundationApi
41 | @Suppress("FunctionName")
42 | private fun CalendarSnapLayoutInfoProvider(
43 | snapLayoutInfoProvider: SnapLayoutInfoProvider,
44 | ): SnapLayoutInfoProvider = object : SnapLayoutInfoProvider by snapLayoutInfoProvider {
45 | /**
46 | * In compose 1.3, the default was single page snapping (zero), but this changed
47 | * in compose 1.4 to decayed page snapping which is not great for calendar usage.
48 | */
49 | override fun calculateApproachOffset(velocity: Float, decayOffset: Float): Float = 0f
50 | }
51 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kizitonwose.calendar.core.OutDateStyle
5 | import java.time.DayOfWeek
6 |
7 | @Immutable
8 | internal data class CalendarInfo(
9 | val indexCount: Int,
10 | private val firstDayOfWeek: DayOfWeek? = null,
11 | private val outDateStyle: OutDateStyle? = null,
12 | )
13 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.lazy.LazyListItemInfo
4 | import androidx.compose.foundation.lazy.LazyListLayoutInfo
5 | import com.kizitonwose.calendar.core.CalendarMonth
6 |
7 | /**
8 | * Contains useful information about the currently displayed layout state of the calendar.
9 | * For example you can get the list of currently displayed months.
10 | *
11 | * Use [CalendarState.layoutInfo] to retrieve this.
12 | * @see LazyListLayoutInfo
13 | */
14 | public class CalendarLayoutInfo(info: LazyListLayoutInfo, private val month: (Int) -> CalendarMonth) :
15 | LazyListLayoutInfo by info {
16 | /**
17 | * The list of [CalendarItemInfo] representing all the currently visible months.
18 | */
19 | public val visibleMonthsInfo: List
20 | get() = visibleItemsInfo.map {
21 | CalendarItemInfo(it, month(it.index))
22 | }
23 | }
24 |
25 | /**
26 | * Contains useful information about an individual [CalendarMonth] on the calendar.
27 | *
28 | * @param month The month in the list.
29 | *
30 | * @see CalendarLayoutInfo
31 | * @see LazyListItemInfo
32 | */
33 | public class CalendarItemInfo(info: LazyListItemInfo, public val month: CalendarMonth) : LazyListItemInfo by info
34 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/ContentHeightMode.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.layout.aspectRatio
4 | import androidx.compose.foundation.layout.fillMaxHeight
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.ui.Modifier
7 |
8 | /**
9 | * Determines how the height of the day content is calculated.
10 | */
11 | public enum class ContentHeightMode {
12 | /**
13 | * The day container will wrap its height. This allows you to
14 | * use [Modifier.aspectRatio] if you want square day content
15 | * or [Modifier.height] if you want a specific height value
16 | * for the day content.
17 | */
18 | Wrap,
19 |
20 | /**
21 | * The days in each month will spread to fill the parent's height after
22 | * any available header and footer content height has been accounted for.
23 | * This allows you to use [Modifier.fillMaxHeight] for the day content
24 | * height. With this option, your Calendar composable should also
25 | * be created with [Modifier.fillMaxHeight] or [Modifier.height].
26 | */
27 | Fill,
28 | }
29 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/ItemPlacementInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.foundation.gestures.Orientation
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.runtime.Stable
6 | import androidx.compose.runtime.withFrameNanos
7 | import androidx.compose.ui.layout.LayoutCoordinates
8 | import androidx.compose.ui.unit.round
9 | import kotlinx.coroutines.isActive
10 | import kotlin.coroutines.coroutineContext
11 |
12 | @Immutable
13 | internal data class ItemCoordinates(
14 | val itemRootCoordinates: LayoutCoordinates,
15 | val firstDayCoordinates: LayoutCoordinates,
16 | )
17 |
18 | @Stable
19 | internal class ItemPlacementInfo {
20 | private var itemCoordinates: ItemCoordinates? = null
21 |
22 | fun onItemPlaced(itemCoordinates: ItemCoordinates) {
23 | this.itemCoordinates = itemCoordinates
24 | }
25 |
26 | suspend fun awaitFistDayOffsetAndSize(orientation: Orientation): OffsetSize? {
27 | var itemCoordinates = this.itemCoordinates
28 | while (coroutineContext.isActive && itemCoordinates == null) {
29 | withFrameNanos {}
30 | itemCoordinates = this.itemCoordinates
31 | }
32 | if (itemCoordinates == null) {
33 | return null
34 | }
35 | val (itemRootCoordinates, firstDayCoordinates) = itemCoordinates
36 | val daySize = firstDayCoordinates.size
37 | val dayOffset = itemRootCoordinates.localPositionOf(firstDayCoordinates).round()
38 | return when (orientation) {
39 | Orientation.Vertical -> OffsetSize(
40 | offset = dayOffset.y,
41 | size = daySize.height,
42 | )
43 |
44 | Orientation.Horizontal -> {
45 | OffsetSize(
46 | offset = dayOffset.x,
47 | size = daySize.width,
48 | )
49 | }
50 | }
51 | }
52 |
53 | @Immutable
54 | internal data class OffsetSize(
55 | val offset: Int,
56 | val size: Int,
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/VisibleItemState.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import androidx.compose.runtime.Immutable
4 | import java.io.Serializable
5 |
6 | @Immutable
7 | internal class VisibleItemState(
8 | val firstVisibleItemIndex: Int = 0,
9 | val firstVisibleItemScrollOffset: Int = 0,
10 | ) : Serializable
11 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.heatmapcalendar
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kizitonwose.calendar.compose.HeatMapCalendar
5 | import com.kizitonwose.calendar.core.CalendarDay
6 | import java.io.Serializable
7 |
8 | /**
9 | * Represents a week on the heatmap calendar.
10 | *
11 | * This model exists only as a wrapper class with the [Immutable] annotation for compose.
12 | * The alternative would be to use the `kotlinx.ImmutableList` type for the `days` value
13 | * which is used ONLY in the dayContent parameter of the [HeatMapCalendar] but then we
14 | * would force that dependency on the library consumers.
15 | *
16 | * @param days the days in this week.
17 | */
18 | @Immutable
19 | public data class HeatMapWeek(val days: List) : Serializable
20 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.heatmapcalendar
2 |
3 | import com.kizitonwose.calendar.compose.HeatMapCalendar
4 |
5 | /**
6 | * Determines the position of the week header
7 | * composable (Mon, Tue, Wed...) in the [HeatMapCalendar]
8 | */
9 | public enum class HeatMapWeekHeaderPosition {
10 | /**
11 | * The header is positioned at the start of the calendar.
12 | */
13 | Start,
14 |
15 | /**
16 | * The header is positioned at the end of the calendar.
17 | */
18 | End,
19 | }
20 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.weekcalendar
2 |
3 | import androidx.compose.foundation.lazy.LazyListItemInfo
4 | import androidx.compose.foundation.lazy.LazyListLayoutInfo
5 | import com.kizitonwose.calendar.core.Week
6 |
7 | /**
8 | * Contains useful information about the currently displayed layout state of the calendar.
9 | * For example you can get the list of currently displayed months.
10 | *
11 | * Use [WeekCalendarState.layoutInfo] to retrieve this.
12 | *
13 | * @see LazyListLayoutInfo
14 | */
15 | public class WeekCalendarLayoutInfo(
16 | info: LazyListLayoutInfo,
17 | private val getIndexData: (Int) -> Week,
18 | ) : LazyListLayoutInfo by info {
19 | /**
20 | * The list of [WeekCalendarItemInfo] representing all the currently visible weeks.
21 | */
22 | public val visibleWeeksInfo: List
23 | get() = visibleItemsInfo.map { info ->
24 | WeekCalendarItemInfo(info, getIndexData(info.index))
25 | }
26 | }
27 |
28 | /**
29 | * Contains useful information about an individual week on the calendar.
30 | *
31 | * @param week The week in the list.
32 |
33 | * @see WeekCalendarLayoutInfo
34 | * @see LazyListItemInfo
35 | */
36 | public class WeekCalendarItemInfo(info: LazyListItemInfo, public val week: Week) :
37 | LazyListItemInfo by info
38 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/yearcalendar/YearCalendarLayoutInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.yearcalendar
2 |
3 | import androidx.compose.foundation.lazy.LazyListItemInfo
4 | import androidx.compose.foundation.lazy.LazyListLayoutInfo
5 | import com.kizitonwose.calendar.core.CalendarYear
6 |
7 | /**
8 | * Contains useful information about the currently displayed layout state of the calendar.
9 | * For example you can get the list of currently displayed years.
10 | *
11 | * Use [YearCalendarState.layoutInfo] to retrieve this.
12 | *
13 | * @see LazyListLayoutInfo
14 | */
15 | public class YearCalendarLayoutInfo(
16 | info: LazyListLayoutInfo,
17 | private val getIndexData: (Int) -> CalendarYear,
18 | ) : LazyListLayoutInfo by info {
19 | /**
20 | * The list of [YearCalendarItemInfo] representing all the currently visible years.
21 | */
22 | public val visibleYearsInfo: List
23 | get() = visibleItemsInfo.map { info ->
24 | YearCalendarItemInfo(info, getIndexData(info.index))
25 | }
26 | }
27 |
28 | /**
29 | * Contains useful information about an individual year on the calendar.
30 | *
31 | * @param year The year in the list.
32 |
33 | * @see YearCalendarLayoutInfo
34 | * @see LazyListItemInfo
35 | */
36 | public class YearCalendarItemInfo(info: LazyListItemInfo, public val year: CalendarYear) :
37 | LazyListItemInfo by info
38 |
--------------------------------------------------------------------------------
/compose/src/main/java/com/kizitonwose/calendar/compose/yearcalendar/YearContentHeightMode.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose.yearcalendar
2 |
3 | import androidx.compose.foundation.layout.aspectRatio
4 | import androidx.compose.foundation.layout.fillMaxHeight
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.ui.Modifier
7 |
8 | /**
9 | * Determines how the height of the month content is calculated.
10 | */
11 | public enum class YearContentHeightMode {
12 | /**
13 | * The calendar months and days will wrap content height. This allows
14 | * you to use [Modifier.aspectRatio] if you want square day content
15 | * or [Modifier.height] if you want a specific height value
16 | * for the day content.
17 | */
18 | Wrap,
19 |
20 | /**
21 | * The calendar months will be distributed uniformly to fill the
22 | * parent's height. However, the days within the calendar months will
23 | * wrap content height. This allows you to spread the calendar months
24 | * evenly across the screen while using [Modifier.aspectRatio] if you
25 | * want square day content or [Modifier.height] if you want a specific
26 | * height value for the day content.
27 | */
28 | Fill,
29 |
30 | /**
31 | * The calendar months and days will uniformly stretch to fill the
32 | * parent's height. This allows you to use [Modifier.fillMaxHeight] for
33 | * the day content height. With this option, your Calendar composable should
34 | * also be created with [Modifier.fillMaxHeight] or [Modifier.height].
35 | */
36 | Stretch,
37 | }
38 |
--------------------------------------------------------------------------------
/compose/src/test/java/com/kizitonwose/calendar/compose/HeatMapCalendarStateTest.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapCalendarState
4 | import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale
5 | import org.junit.jupiter.api.Assertions.assertEquals
6 | import org.junit.jupiter.api.Test
7 | import java.time.DayOfWeek
8 | import java.time.LocalDate
9 | import java.time.YearMonth
10 |
11 | class HeatMapCalendarStateTest {
12 | @Test
13 | fun `start month update is reflected in the state`() {
14 | val now = YearMonth.now()
15 | val updatedStartMonth = now.minusMonths(4)
16 | val state = createState(
17 | startMonth = now,
18 | endMonth = now,
19 | )
20 |
21 | assertEquals(state.store[0].yearMonth, now)
22 |
23 | state.startMonth = updatedStartMonth
24 |
25 | assertEquals(state.store[0].yearMonth, updatedStartMonth)
26 | }
27 |
28 | @Test
29 | fun `end month update is reflected in the state`() {
30 | val now = YearMonth.now()
31 | val updatedEndMonth = now.plusMonths(4)
32 | val state = createState(
33 | startMonth = now,
34 | endMonth = now,
35 | )
36 |
37 | assertEquals(state.store[0].yearMonth, now)
38 |
39 | state.endMonth = updatedEndMonth
40 |
41 | assertEquals(state.store[4].yearMonth, updatedEndMonth)
42 | }
43 |
44 | @Test
45 | fun `first day of the week update is reflected in the state`() {
46 | val firstDayOfWeek = LocalDate.now().dayOfWeek
47 |
48 | val state = createState(firstDayOfWeek = firstDayOfWeek)
49 |
50 | state.store[0].weekDays.forEach { week ->
51 | assertEquals(week.first().date.dayOfWeek, firstDayOfWeek)
52 | }
53 |
54 | do {
55 | val updatedFirstDayOfWeek = state.firstDayOfWeek.plus(1)
56 | state.firstDayOfWeek = updatedFirstDayOfWeek
57 |
58 | state.store[0].weekDays.forEach { week ->
59 | assertEquals(week.first().date.dayOfWeek, updatedFirstDayOfWeek)
60 | }
61 | } while (firstDayOfWeek != state.firstDayOfWeek)
62 | }
63 |
64 | private fun createState(
65 | startMonth: YearMonth = YearMonth.now(),
66 | endMonth: YearMonth = startMonth,
67 | firstVisibleMonth: YearMonth = startMonth,
68 | firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale(),
69 | visibleItemState: VisibleItemState = VisibleItemState(),
70 | ) = HeatMapCalendarState(
71 | startMonth = startMonth,
72 | endMonth = endMonth,
73 | firstVisibleMonth = firstVisibleMonth,
74 | firstDayOfWeek = firstDayOfWeek,
75 | visibleItemState = visibleItemState,
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/compose/src/test/java/com/kizitonwose/calendar/compose/WeekCalendarStateTest.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.compose
2 |
3 | import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState
4 | import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale
5 | import org.junit.jupiter.api.Assertions.assertEquals
6 | import org.junit.jupiter.api.Assertions.assertTrue
7 | import org.junit.jupiter.api.Test
8 | import java.time.DayOfWeek
9 | import java.time.LocalDate
10 |
11 | class WeekCalendarStateTest {
12 | @Test
13 | fun `start date update is reflected in the state`() {
14 | val now = LocalDate.now()
15 | val updatedStartDate = now.minusDays(7)
16 | val state = createState(
17 | startDate = now,
18 | endDate = now,
19 | )
20 |
21 | assertTrue(state.store[0].days.map { it.date }.contains(now))
22 |
23 | state.startDate = updatedStartDate
24 |
25 | assertTrue(state.store[0].days.map { it.date }.contains(updatedStartDate))
26 | }
27 |
28 | @Test
29 | fun `end date update is reflected in the state`() {
30 | val now = LocalDate.now()
31 | val updatedEndDate = now.plusDays(7)
32 | val state = createState(
33 | startDate = now,
34 | endDate = now,
35 | )
36 |
37 | assertTrue(state.store[0].days.map { it.date }.contains(now))
38 |
39 | state.endDate = updatedEndDate
40 |
41 | assertTrue(state.store[1].days.map { it.date }.contains(updatedEndDate))
42 | }
43 |
44 | @Test
45 | fun `first day of the week update is reflected in the state`() {
46 | val firstDayOfWeek = LocalDate.now().dayOfWeek
47 |
48 | val state = createState(firstDayOfWeek = firstDayOfWeek)
49 |
50 | assertEquals(state.store[0].days.first().date.dayOfWeek, firstDayOfWeek)
51 |
52 | do {
53 | val updatedFirstDayOfWeek = state.firstDayOfWeek.plus(1)
54 | state.firstDayOfWeek = updatedFirstDayOfWeek
55 |
56 | assertEquals(state.store[0].days.first().date.dayOfWeek, updatedFirstDayOfWeek)
57 | } while (firstDayOfWeek != state.firstDayOfWeek)
58 | }
59 |
60 | private fun createState(
61 | startDate: LocalDate = LocalDate.now(),
62 | endDate: LocalDate = startDate,
63 | firstVisibleWeekDate: LocalDate = startDate,
64 | firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale(),
65 | visibleItemState: VisibleItemState = VisibleItemState(),
66 | ) = WeekCalendarState(
67 | startDate = startDate,
68 | endDate = endDate,
69 | firstVisibleWeekDate = firstVisibleWeekDate,
70 | firstDayOfWeek = firstDayOfWeek,
71 | visibleItemState = visibleItemState,
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.kizitonwose.calendar.buildsrc.Config
2 | import com.kizitonwose.calendar.buildsrc.Version
3 |
4 | plugins {
5 | alias(libs.plugins.kotlinJvm)
6 | alias(libs.plugins.mavenPublish)
7 | }
8 |
9 | java {
10 | toolchain {
11 | languageVersion.set(Config.compatibleJavaLanguageVersion)
12 | }
13 | }
14 |
15 | kotlin {
16 | jvmToolchain {
17 | languageVersion.set(Config.compatibleJavaLanguageVersion)
18 | }
19 | }
20 |
21 | dependencies {
22 | compileOnly(libs.compose.runtime) // Only needed for @Immutable annotation.
23 | }
24 |
25 | mavenPublishing {
26 | coordinates(version = Version.android)
27 | }
28 |
--------------------------------------------------------------------------------
/core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=core
2 | POM_DESCRIPTION=Calendar core module for shared public classes and extensions.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/CalendarDay.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import java.io.Serializable
5 | import java.time.LocalDate
6 |
7 | /**
8 | * Represents a day on the calendar.
9 | *
10 | * @param date the date for this day.
11 | * @param position the [DayPosition] for this day.
12 | */
13 | @Immutable
14 | public data class CalendarDay(val date: LocalDate, val position: DayPosition) : Serializable
15 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/CalendarMonth.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import java.io.Serializable
5 | import java.time.YearMonth
6 |
7 | /**
8 | * Represents a month on the calendar.
9 | *
10 | * @param yearMonth the calendar month value.
11 | * @param weekDays the weeks in this month.
12 | */
13 | @Immutable
14 | public data class CalendarMonth(
15 | val yearMonth: YearMonth,
16 | val weekDays: List>,
17 | ) : Serializable {
18 | override fun equals(other: Any?): Boolean {
19 | if (this === other) return true
20 | if (javaClass != other?.javaClass) return false
21 |
22 | other as CalendarMonth
23 |
24 | if (yearMonth != other.yearMonth) return false
25 | if (weekDays.first().first() != other.weekDays.first().first()) return false
26 | if (weekDays.last().last() != other.weekDays.last().last()) return false
27 |
28 | return true
29 | }
30 |
31 | override fun hashCode(): Int {
32 | var result = yearMonth.hashCode()
33 | result = 31 * result + weekDays.first().first().hashCode()
34 | result = 31 * result + weekDays.last().last().hashCode()
35 | return result
36 | }
37 |
38 | override fun toString(): String {
39 | return "CalendarMonth { " +
40 | "yearMonth = $yearMonth, " +
41 | "firstDay = ${weekDays.first().first()}, " +
42 | "lastDay = ${weekDays.last().last()} " +
43 | "} "
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/CalendarYear.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import java.io.Serializable
5 | import java.time.Year
6 |
7 | /**
8 | * Represents a year on the calendar.
9 | *
10 | * @param year the calendar year value.
11 | * @param months the months in this year.
12 | */
13 | @Immutable
14 | public data class CalendarYear(
15 | val year: Year,
16 | val months: List,
17 | ) : Serializable {
18 | override fun equals(other: Any?): Boolean {
19 | if (this === other) return true
20 | if (javaClass != other?.javaClass) return false
21 |
22 | other as CalendarYear
23 |
24 | if (year != other.year) return false
25 | if (months.first() != other.months.first()) return false
26 | if (months.last() != other.months.last()) return false
27 |
28 | return true
29 | }
30 |
31 | override fun hashCode(): Int {
32 | var result = year.hashCode()
33 | result = 31 * result + months.first().hashCode()
34 | result = 31 * result + months.last().hashCode()
35 | return result
36 | }
37 |
38 | override fun toString(): String {
39 | return "CalendarYear { " +
40 | "year = $year, " +
41 | "firstMonth = ${months.first()}, " +
42 | "lastMonth = ${months.last()} " +
43 | "} "
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/DayPosition.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | /**
4 | * Describes the position of a [CalendarDay] in the month.
5 | */
6 | public enum class DayPosition {
7 | /**
8 | * The day is positioned at the start of the month to
9 | * ensure proper alignment of the first day of the week.
10 | * The day belongs to the previous month on the calendar.
11 | */
12 | InDate,
13 |
14 | /**
15 | * The day belongs to the current month on the calendar.
16 | */
17 | MonthDate,
18 |
19 | /**
20 | * The day is positioned at the end of the month to
21 | * to fill the remaining days after the days in the month.
22 | * The day belongs to the next month on the calendar.
23 | * @see [OutDateStyle]
24 | */
25 | OutDate,
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/ExperimentalCalendarApi.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | @RequiresOptIn(
4 | message = "This calendar API is experimental and is " +
5 | "likely to change or to be removed in the future.",
6 | level = RequiresOptIn.Level.ERROR,
7 | )
8 | @Retention(AnnotationRetention.BINARY)
9 | public annotation class ExperimentalCalendarApi
10 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import java.time.DayOfWeek
4 | import java.time.LocalDate
5 | import java.time.YearMonth
6 | import java.time.temporal.WeekFields
7 | import java.util.Locale
8 |
9 | /**
10 | * Returns the days of week values such that the desired
11 | * [firstDayOfWeek] property is at the start position.
12 | *
13 | * @see [firstDayOfWeekFromLocale]
14 | */
15 | @JvmOverloads
16 | public fun daysOfWeek(firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale()): List {
17 | val pivot = 7 - firstDayOfWeek.ordinal
18 | val daysOfWeek = DayOfWeek.entries
19 | // Order `daysOfWeek` array so that firstDayOfWeek is at the start position.
20 | return daysOfWeek.takeLast(pivot) + daysOfWeek.dropLast(pivot)
21 | }
22 |
23 | /**
24 | * Returns the first day of the week from the provided locale.
25 | */
26 | @JvmOverloads
27 | public fun firstDayOfWeekFromLocale(locale: Locale = Locale.getDefault()): DayOfWeek = WeekFields.of(locale).firstDayOfWeek
28 |
29 | /**
30 | * Returns a [LocalDate] at the start of the month.
31 | *
32 | * Complements [YearMonth.atEndOfMonth].
33 | */
34 | public fun YearMonth.atStartOfMonth(): LocalDate = this.atDay(1)
35 |
36 | public val LocalDate.yearMonth: YearMonth
37 | get() = YearMonth.of(year, month)
38 |
39 | public val YearMonth.nextMonth: YearMonth
40 | get() = this.plusMonths(1)
41 |
42 | public val YearMonth.previousMonth: YearMonth
43 | get() = this.minusMonths(1)
44 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/OutDateStyle.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | /**
4 | * Determines how [DayPosition.OutDate] are
5 | * generated for each month on the calendar.
6 | */
7 | public enum class OutDateStyle {
8 | /**
9 | * The calendar will generate outDates until it reaches
10 | * the end of the month row. This means that if a month
11 | * has 5 rows, it will display 5 rows and if a month
12 | * has 6 rows, it will display 6 rows.
13 | */
14 | EndOfRow,
15 |
16 | /**
17 | * The calendar will generate outDates until it
18 | * reaches the end of a 6 x 7 grid on each month.
19 | * This means that all months will have 6 rows.
20 | */
21 | EndOfGrid,
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/Week.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import java.io.Serializable
5 |
6 | /**
7 | * Represents a week on the week-based calendar.
8 | *
9 | * @param days the days in this week.
10 | */
11 | @Immutable
12 | public data class Week(val days: List) : Serializable {
13 | override fun equals(other: Any?): Boolean {
14 | if (this === other) return true
15 | if (javaClass != other?.javaClass) return false
16 |
17 | other as Week
18 |
19 | if (days.first() != other.days.first()) return false
20 | if (days.last() != other.days.last()) return false
21 |
22 | return true
23 | }
24 |
25 | override fun hashCode(): Int {
26 | var result = days.first().hashCode()
27 | result = 31 * result + days.last().hashCode()
28 | return result
29 | }
30 |
31 | override fun toString(): String {
32 | return "Week { " +
33 | "first = ${days.first()}, " +
34 | "last = ${days.last()} " +
35 | "} "
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/WeekDay.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | import androidx.compose.runtime.Immutable
4 | import java.io.Serializable
5 | import java.time.LocalDate
6 |
7 | /**
8 | * Represents a day on the week calendar.
9 | *
10 | * @param date the date for this day.
11 | * @param position the [WeekDayPosition] for this day.
12 | */
13 | @Immutable
14 | public data class WeekDay(val date: LocalDate, val position: WeekDayPosition) : Serializable
15 |
--------------------------------------------------------------------------------
/core/src/main/java/com/kizitonwose/calendar/core/WeekDayPosition.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.core
2 |
3 | /**
4 | * Describes the position of a [WeekDay] on the calendar.
5 | */
6 | public enum class WeekDayPosition {
7 | /**
8 | * The day is positioned at the start of the calendar to
9 | * ensure proper alignment of the first day of the week.
10 | * The day is before the provided start date.
11 | */
12 | InDate,
13 |
14 | /**
15 | * The day is in the range of the provided start and end dates.
16 | */
17 | RangeDate,
18 |
19 | /**
20 | * The day is positioned at the end of the calendar to to fill the
21 | * remaining days in the last week after the provided end date.
22 | * The day is after the provided end date.
23 | */
24 | OutDate,
25 | }
26 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | import com.kizitonwose.calendar.buildsrc.Config
3 | import com.kizitonwose.calendar.buildsrc.Version
4 |
5 | plugins {
6 | alias(libs.plugins.kotlinJvm)
7 | alias(libs.plugins.mavenPublish)
8 | }
9 |
10 | java {
11 | toolchain {
12 | languageVersion.set(Config.compatibleJavaLanguageVersion)
13 | }
14 | }
15 |
16 | kotlin {
17 | jvmToolchain {
18 | languageVersion.set(Config.compatibleJavaLanguageVersion)
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation(project(":core"))
24 | implementation(libs.kotlin.stdlib)
25 |
26 | testImplementation(platform(libs.test.junit5.bom))
27 | testImplementation(libs.test.junit5.api)
28 | testRuntimeOnly(libs.test.junit5.engine)
29 | testRuntimeOnly(libs.test.junit.platform.launcher)
30 | }
31 |
32 | mavenPublishing {
33 | coordinates(version = Version.android)
34 | }
35 |
--------------------------------------------------------------------------------
/data/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=data
2 | POM_DESCRIPTION=Calendar data module for shared internal logic.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kizitonwose/calendar/data/DataStore.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | /**
4 | * Basically [MutableMap.getOrPut] but allows us read the map
5 | * in multiple places without calling `getOrPut` everywhere.
6 | */
7 | public class DataStore(
8 | private val store: MutableMap = HashMap(),
9 | private val create: (offset: Int) -> V,
10 | ) : MutableMap by store {
11 | override fun get(key: Int): V {
12 | val value = store[key]
13 | return if (value == null) {
14 | val data = create(key)
15 | put(key, data)
16 | data
17 | } else {
18 | value
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kizitonwose/calendar/data/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.CalendarDay
4 | import com.kizitonwose.calendar.core.DayPosition
5 | import com.kizitonwose.calendar.core.nextMonth
6 | import com.kizitonwose.calendar.core.previousMonth
7 | import com.kizitonwose.calendar.core.yearMonth
8 | import java.time.DayOfWeek
9 | import java.time.YearMonth
10 |
11 | // E.g DayOfWeek.SATURDAY.daysUntil(DayOfWeek.TUESDAY) = 3
12 | public fun DayOfWeek.daysUntil(other: DayOfWeek): Int = (7 + (other.ordinal - ordinal)) % 7
13 |
14 | // Find the actual month on the calendar where this date is shown.
15 | public val CalendarDay.positionYearMonth: YearMonth
16 | get() = when (position) {
17 | DayPosition.InDate -> date.yearMonth.nextMonth
18 | DayPosition.MonthDate -> date.yearMonth
19 | DayPosition.OutDate -> date.yearMonth.previousMonth
20 | }
21 |
22 | public inline fun Iterable.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
23 | val result = indexOfFirst(predicate)
24 | return if (result == -1) null else result
25 | }
26 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kizitonwose/calendar/data/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | public fun > checkRange(start: T, end: T) {
4 | check(end >= start) {
5 | "start: $start is greater than end: $end"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kizitonwose/calendar/data/WeekData.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.Week
4 | import com.kizitonwose.calendar.core.WeekDay
5 | import com.kizitonwose.calendar.core.WeekDayPosition
6 | import java.time.DayOfWeek
7 | import java.time.LocalDate
8 | import java.time.temporal.ChronoUnit
9 |
10 | public data class WeekDateRange(
11 | val startDateAdjusted: LocalDate,
12 | val endDateAdjusted: LocalDate,
13 | )
14 |
15 | public fun getWeekCalendarAdjustedRange(
16 | startDate: LocalDate,
17 | endDate: LocalDate,
18 | firstDayOfWeek: DayOfWeek,
19 | ): WeekDateRange {
20 | val inDays = firstDayOfWeek.daysUntil(startDate.dayOfWeek)
21 | val startDateAdjusted = startDate.minusDays(inDays.toLong())
22 | val weeksBetween =
23 | ChronoUnit.WEEKS.between(startDateAdjusted, endDate).toInt()
24 | val endDateAdjusted = startDateAdjusted.plusWeeks(weeksBetween.toLong()).plusDays(6)
25 | return WeekDateRange(startDateAdjusted = startDateAdjusted, endDateAdjusted = endDateAdjusted)
26 | }
27 |
28 | public fun getWeekCalendarData(
29 | startDateAdjusted: LocalDate,
30 | offset: Int,
31 | desiredStartDate: LocalDate,
32 | desiredEndDate: LocalDate,
33 | ): WeekData {
34 | val firstDayInWeek = startDateAdjusted.plusWeeks(offset.toLong())
35 | return WeekData(firstDayInWeek, desiredStartDate, desiredEndDate)
36 | }
37 |
38 | @ConsistentCopyVisibility
39 | public data class WeekData internal constructor(
40 | private val firstDayInWeek: LocalDate,
41 | private val desiredStartDate: LocalDate,
42 | private val desiredEndDate: LocalDate,
43 | ) {
44 | val week: Week = Week((0 until 7).map { dayOffset -> getDay(dayOffset) })
45 |
46 | private fun getDay(dayOffset: Int): WeekDay {
47 | val date = firstDayInWeek.plusDays(dayOffset.toLong())
48 | val position = when {
49 | date < desiredStartDate -> WeekDayPosition.InDate
50 | date > desiredEndDate -> WeekDayPosition.OutDate
51 | else -> WeekDayPosition.RangeDate
52 | }
53 | return WeekDay(date, position)
54 | }
55 | }
56 |
57 | public fun getWeekIndex(startDateAdjusted: LocalDate, date: LocalDate): Int {
58 | return ChronoUnit.WEEKS.between(startDateAdjusted, date).toInt()
59 | }
60 |
61 | public fun getWeekIndicesCount(startDateAdjusted: LocalDate, endDateAdjusted: LocalDate): Int {
62 | // Add one to include the start week itself!
63 | return getWeekIndex(startDateAdjusted, endDateAdjusted) + 1
64 | }
65 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kizitonwose/calendar/data/YearData.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.CalendarYear
4 | import com.kizitonwose.calendar.core.OutDateStyle
5 | import java.time.DayOfWeek
6 | import java.time.Month
7 | import java.time.Year
8 | import java.time.temporal.ChronoUnit
9 |
10 | public fun getCalendarYearData(
11 | startYear: Year,
12 | offset: Int,
13 | firstDayOfWeek: DayOfWeek,
14 | outDateStyle: OutDateStyle,
15 | ): CalendarYear {
16 | val year = startYear.plusYears(offset.toLong())
17 | val months = List(Month.entries.size) { index ->
18 | getCalendarMonthData(
19 | startMonth = year.atMonth(Month.JANUARY),
20 | offset = index,
21 | firstDayOfWeek = firstDayOfWeek,
22 | outDateStyle = outDateStyle,
23 | ).calendarMonth
24 | }
25 | return CalendarYear(year, months)
26 | }
27 |
28 | public fun getYearIndex(startYear: Year, targetYear: Year): Int {
29 | return ChronoUnit.YEARS.between(startYear, targetYear).toInt()
30 | }
31 |
32 | public fun getYearIndicesCount(startYear: Year, endYear: Year): Int {
33 | // Add one to include the start year itself!
34 | return getYearIndex(startYear, endYear) + 1
35 | }
36 |
--------------------------------------------------------------------------------
/data/src/test/java/com/kizitonwose/calendar/data/DayOfWeekTest.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import com.kizitonwose.calendar.core.daysOfWeek
4 | import org.junit.jupiter.api.Assertions.assertEquals
5 | import org.junit.jupiter.api.Test
6 | import java.time.DayOfWeek
7 |
8 | class DayOfWeekTest {
9 | @Test
10 | fun `days until works as expected`() {
11 | assertEquals(5, DayOfWeek.FRIDAY.daysUntil(DayOfWeek.WEDNESDAY))
12 | assertEquals(2, DayOfWeek.TUESDAY.daysUntil(DayOfWeek.THURSDAY))
13 | assertEquals(0, DayOfWeek.SUNDAY.daysUntil(DayOfWeek.SUNDAY))
14 | assertEquals(0, DayOfWeek.WEDNESDAY.daysUntil(DayOfWeek.WEDNESDAY))
15 | assertEquals(3, DayOfWeek.SATURDAY.daysUntil(DayOfWeek.TUESDAY))
16 | assertEquals(5, DayOfWeek.WEDNESDAY.daysUntil(DayOfWeek.MONDAY))
17 | assertEquals(1, DayOfWeek.THURSDAY.daysUntil(DayOfWeek.FRIDAY))
18 | assertEquals(6, DayOfWeek.MONDAY.daysUntil(DayOfWeek.SUNDAY))
19 | assertEquals(6, DayOfWeek.SUNDAY.daysUntil(DayOfWeek.SATURDAY))
20 | }
21 |
22 | @Test
23 | fun `first day of the week works as expected`() {
24 | DayOfWeek.entries.forEach { dayOfWeek ->
25 | assertEquals(dayOfWeek, daysOfWeek(firstDayOfWeek = dayOfWeek).first())
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/data/src/test/java/com/kizitonwose/calendar/data/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.data
2 |
3 | import java.time.DayOfWeek
4 | import java.time.YearMonth
5 | import java.time.temporal.WeekFields
6 |
7 | fun YearMonth.weeksInMonth(firstDayOfWeek: DayOfWeek) = atEndOfMonth().get(WeekFields.of(firstDayOfWeek, 1).weekOfMonth())
8 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.defaults.buildfeatures.resvalues=false
2 | android.defaults.buildfeatures.shaders=false
3 | android.useAndroidX=true
4 |
5 | kotlin.code.style=official
6 |
7 | org.gradle.caching=true
8 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx4g
9 | org.gradle.parallel=true
10 |
11 | SONATYPE_HOST=S01
12 | RELEASE_SIGNING_ENABLED=true
13 |
14 | GROUP=com.kizitonwose.calendar
15 |
16 | POM_NAME=Calendar
17 | POM_INCEPTION_YEAR=2019
18 | POM_URL=https://github.com/kizitonwose/calendar
19 | POM_SCM_URL=https://github.com/kizitonwose/calendar
20 | POM_SCM_CONNECTION=scm:git:git://github.com/kizitonwose/calendar.git
21 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/kizitonwose/calendar.git
22 |
23 | POM_LICENCE_NAME=MIT License
24 | POM_LICENCE_URL=https://github.com/kizitonwose/calendar/blob/main/LICENSE.md
25 | POM_LICENCE_DIST=repo
26 |
27 | POM_DEVELOPER_ID=kizitonwose
28 | POM_DEVELOPER_NAME=Kizito Nwose
29 | POM_DEVELOPER_URL=https://github.com/kizitonwose
30 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 18 20:39:23 CEST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/images/in_out_dates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/images/in_out_dates.png
--------------------------------------------------------------------------------
/images/showcase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/images/showcase.png
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | import com.kizitonwose.calendar.buildsrc.Android
3 | import com.kizitonwose.calendar.buildsrc.Config
4 |
5 | plugins {
6 | alias(libs.plugins.androidApplication)
7 | alias(libs.plugins.kotlinAndroid)
8 | alias(libs.plugins.composeCompiler)
9 | }
10 |
11 | android {
12 | compileSdk = Android.compileSdk
13 | namespace = "com.kizitonwose.calendar.sample"
14 | defaultConfig {
15 | applicationId = "com.kizitonwose.calendar.sample"
16 | minSdk = Android.minSdk
17 | targetSdk = Android.targetSdk
18 | versionCode = 1
19 | versionName = "1.0"
20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 | buildFeatures {
23 | viewBinding = true
24 | compose = true
25 | }
26 | buildTypes {
27 | named("release") {
28 | isMinifyEnabled = true
29 | }
30 | }
31 | java {
32 | toolchain {
33 | languageVersion.set(Config.compatibleJavaLanguageVersion)
34 | }
35 | }
36 | kotlin {
37 | jvmToolchain {
38 | languageVersion.set(Config.compatibleJavaLanguageVersion)
39 | }
40 | }
41 | compileOptions {
42 | isCoreLibraryDesugaringEnabled = true
43 | }
44 | }
45 |
46 | dependencies {
47 | implementation(project(":view"))
48 | implementation(project(":compose"))
49 | coreLibraryDesugaring(libs.desugar)
50 | implementation(libs.kotlin.stdlib)
51 |
52 | implementation(libs.androidx.appcompat)
53 | implementation(libs.androidx.core.ktx)
54 | implementation(libs.androidx.constraintlayout)
55 | implementation(libs.androidx.cardview)
56 | implementation(libs.material.view)
57 |
58 | implementation(libs.compose.ui.ui)
59 | implementation(libs.compose.ui.tooling)
60 | implementation(libs.compose.foundation)
61 | implementation(libs.compose.runtime)
62 | implementation(libs.compose.material3)
63 | implementation(libs.compose.activity)
64 | implementation(libs.compose.navigation)
65 |
66 | testImplementation(libs.test.junit4)
67 |
68 | androidTestImplementation(libs.androidx.test.espresso.core)
69 | androidTestImplementation(libs.androidx.test.espresso.contrib) // RecyclerView actions.
70 | androidTestImplementation(libs.androidx.test.runner)
71 | androidTestImplementation(libs.androidx.test.rules)
72 | androidTestImplementation(libs.androidx.test.junit)
73 |
74 | androidTestImplementation(libs.compose.ui.test.junit4)
75 | debugImplementation(libs.compose.ui.test.manifest) // Compose test runner activity
76 | }
77 |
--------------------------------------------------------------------------------
/sample/src/androidTest/java/com/kizitonwose/calendar/sample/utils/TestDayViewContainer.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.utils
2 |
3 | import android.view.View
4 | import com.kizitonwose.calendar.view.ViewContainer
5 |
6 | internal class TestDayViewContainer(view: View) : ViewContainer(view)
7 |
--------------------------------------------------------------------------------
/sample/src/androidTest/java/com/kizitonwose/calendar/sample/utils/TestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.utils
2 |
3 | import android.graphics.Point
4 | import android.graphics.Rect
5 | import android.view.View
6 | import androidx.annotation.IdRes
7 | import androidx.recyclerview.widget.RecyclerView
8 | import androidx.test.espresso.Espresso
9 | import androidx.test.espresso.action.ViewActions.click
10 | import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
11 | import androidx.test.espresso.matcher.ViewMatchers
12 | import androidx.test.ext.junit.rules.ActivityScenarioRule
13 | import androidx.test.platform.app.InstrumentationRegistry
14 | import com.kizitonwose.calendar.sample.R
15 | import java.lang.Thread.sleep
16 |
17 | internal fun View.getRectInWindow(): Rect {
18 | val point = getLocationInWindow()
19 | return Rect(point.x, point.y, point.x + width, point.y + height)
20 | }
21 |
22 | internal fun View.getLocationInWindow(): Point {
23 | val location = IntArray(2).apply(::getLocationInWindow)
24 | return Point(location[0], location[1])
25 | }
26 |
27 | internal fun runOnMain(action: () -> Unit) {
28 | InstrumentationRegistry.getInstrumentation().runOnMainSync(action)
29 | }
30 |
31 | internal fun openExampleAt(position: Int) {
32 | Espresso.onView(ViewMatchers.withId(R.id.examplesRecyclerview))
33 | .perform(actionOnItemAtPosition(position, click()))
34 | }
35 |
36 | internal fun ActivityScenarioRule<*>.getView(@IdRes id: Int): T {
37 | lateinit var view: T
38 | this.scenario.onActivity { activity ->
39 | view = activity.findViewById(id)
40 | }
41 | sleep(1000)
42 | return view
43 | }
44 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/HomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.core.view.ViewCompat
7 | import androidx.core.view.WindowInsetsCompat.Type.systemBars
8 | import androidx.core.view.updatePadding
9 | import com.kizitonwose.calendar.sample.compose.CalendarComposeActivity
10 | import com.kizitonwose.calendar.sample.databinding.HomeActivityBinding
11 | import com.kizitonwose.calendar.sample.view.CalendarViewActivity
12 |
13 | class HomeActivity : AppCompatActivity() {
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | val binding = HomeActivityBinding.inflate(layoutInflater)
17 | setContentView(binding.root)
18 | setSupportActionBar(binding.activityToolbar)
19 | applyInsets(binding)
20 | handleClicks(binding)
21 | }
22 |
23 | private fun handleClicks(binding: HomeActivityBinding) {
24 | binding.calendarViewSample.setOnClickListener {
25 | startActivity(Intent(this, CalendarViewActivity::class.java))
26 | }
27 | binding.calendarComposeSample.setOnClickListener {
28 | startActivity(Intent(this, CalendarComposeActivity::class.java))
29 | }
30 | }
31 |
32 | private fun applyInsets(binding: HomeActivityBinding) {
33 | ViewCompat.setOnApplyWindowInsetsListener(
34 | binding.root,
35 | ) { _, windowInsets ->
36 | val insets = windowInsets.getInsets(systemBars())
37 | binding.activityAppBar.updatePadding(top = insets.top)
38 | binding.root.updatePadding(
39 | left = insets.left,
40 | right = insets.right,
41 | bottom = insets.bottom,
42 | )
43 | windowInsets
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/compose/Scaffold.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.compose
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.foundation.layout.calculateEndPadding
5 | import androidx.compose.foundation.layout.calculateStartPadding
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.compositionLocalOf
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.platform.LocalLayoutDirection
11 |
12 | val LocalScaffoldPaddingValues = compositionLocalOf { PaddingValues() }
13 |
14 | @Composable
15 | fun Modifier.applyScaffoldHorizontalPaddings() =
16 | padding(
17 | start = LocalScaffoldPaddingValues.current.calculateStartPadding(LocalLayoutDirection.current),
18 | end = LocalScaffoldPaddingValues.current.calculateEndPadding(LocalLayoutDirection.current),
19 | )
20 |
21 | @Composable
22 | fun Modifier.applyScaffoldTopPadding() =
23 | padding(top = LocalScaffoldPaddingValues.current.calculateTopPadding())
24 |
25 | @Composable
26 | fun Modifier.applyScaffoldBottomPadding() =
27 | padding(bottom = LocalScaffoldPaddingValues.current.calculateBottomPadding())
28 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/compose/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.compose
2 |
3 | import androidx.compose.material3.lightColorScheme
4 | import androidx.compose.ui.graphics.Color
5 |
6 | val SampleColorScheme = lightColorScheme(
7 | primary = Color(0xFF3F51B5),
8 | onPrimary = Color.White,
9 | primaryContainer = Color(0xFF3F51B5),
10 | onPrimaryContainer = Color.White,
11 | inversePrimary = Color.Black,
12 | secondary = Color(0xFF3F51B5),
13 | onSecondary = Color.White,
14 | secondaryContainer = Color(0xFF3F51B5),
15 | onSecondaryContainer = Color.White,
16 | tertiary = Color(0xFF3F51B5),
17 | onTertiary = Color.White,
18 | tertiaryContainer = Color(0xFF3F51B5),
19 | onTertiaryContainer = Color.White,
20 | background = Color.White,
21 | onBackground = Color.Black,
22 | surface = Color.White,
23 | onSurface = Color.Black,
24 | surfaceVariant = Color.White,
25 | onSurfaceVariant = Color.Black,
26 | surfaceTint = Color.White,
27 | inverseSurface = Color(0xFF121212),
28 | inverseOnSurface = Color.White,
29 | error = Color(0xFFB00020),
30 | onError = Color.White,
31 | errorContainer = Color(0xFFB00020),
32 | onErrorContainer = Color.White,
33 | outline = Color(0xFFAFAFAF),
34 | outlineVariant = Color(0xFFCCCCCC),
35 | scrim = Color.Black,
36 | surfaceBright = Color.White,
37 | surfaceContainer = Color.White,
38 | surfaceContainerHigh = Color.White,
39 | surfaceContainerHighest = Color.White,
40 | surfaceContainerLow = Color.White,
41 | surfaceContainerLowest = Color.White,
42 | surfaceDim = Color.White,
43 | )
44 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/shared/StatusBarColorLifecycleObserver.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.shared
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import android.view.View
6 | import androidx.annotation.ColorInt
7 | import androidx.core.graphics.ColorUtils
8 | import androidx.lifecycle.DefaultLifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import com.kizitonwose.calendar.sample.R
11 | import com.kizitonwose.calendar.sample.view.getColorCompat
12 | import java.lang.ref.WeakReference
13 |
14 | @Suppress("DEPRECATION")
15 | class StatusBarColorLifecycleObserver(
16 | activity: Activity,
17 | @ColorInt private val color: Int,
18 | ) : DefaultLifecycleObserver {
19 | private val isLightColor = ColorUtils.calculateLuminance(color) > 0.5
20 | private val defaultStatusBarColor = activity.getColorCompat(R.color.colorPrimary)
21 | private val activity = WeakReference(activity)
22 |
23 | override fun onStart(owner: LifecycleOwner) {
24 | activity.get()?.window?.apply {
25 | statusBarColor = color
26 | if (isLightColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
27 | decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
28 | }
29 | }
30 | }
31 |
32 | override fun onStop(owner: LifecycleOwner) {
33 | activity.get()?.window?.apply {
34 | statusBarColor = defaultStatusBarColor
35 | if (isLightColor) decorView.systemUiVisibility = 0
36 | }
37 | }
38 |
39 | override fun onDestroy(owner: LifecycleOwner) = activity.clear()
40 | }
41 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/shared/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.shared
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.ContextWrapper
6 | import com.kizitonwose.calendar.core.Week
7 | import com.kizitonwose.calendar.core.yearMonth
8 | import java.time.DayOfWeek
9 | import java.time.Month
10 | import java.time.Year
11 | import java.time.YearMonth
12 | import java.time.format.TextStyle
13 | import java.time.temporal.ChronoUnit
14 | import java.util.Locale
15 |
16 | fun YearMonth.displayText(short: Boolean = false): String {
17 | return "${this.month.displayText(short = short)} ${this.year}"
18 | }
19 |
20 | fun Month.displayText(short: Boolean = true): String {
21 | val style = if (short) TextStyle.SHORT else TextStyle.FULL
22 | return getDisplayName(style, Locale.ENGLISH)
23 | }
24 |
25 | fun DayOfWeek.displayText(uppercase: Boolean = false, narrow: Boolean = false): String {
26 | val style = if (narrow) TextStyle.NARROW else TextStyle.SHORT
27 | return getDisplayName(style, Locale.ENGLISH).let { value ->
28 | if (uppercase) value.uppercase(Locale.ENGLISH) else value
29 | }
30 | }
31 |
32 | fun Context.findActivity(): Activity {
33 | var context = this
34 | while (context is ContextWrapper) {
35 | if (context is Activity) return context
36 | context = context.baseContext
37 | }
38 | throw IllegalStateException("no activity")
39 | }
40 |
41 | fun getWeekPageTitle(week: Week): String {
42 | val firstDate = week.days.first().date
43 | val lastDate = week.days.last().date
44 | return when {
45 | firstDate.yearMonth == lastDate.yearMonth -> {
46 | firstDate.yearMonth.displayText()
47 | }
48 | firstDate.year == lastDate.year -> {
49 | "${firstDate.month.displayText(short = false)} - ${lastDate.yearMonth.displayText()}"
50 | }
51 | else -> {
52 | "${firstDate.yearMonth.displayText()} - ${lastDate.yearMonth.displayText()}"
53 | }
54 | }
55 | }
56 |
57 | fun Year.yearsUntil(other: Year) = ChronoUnit.YEARS.between(this, other)
58 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/view/Typeface.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.view
2 |
3 | import android.content.Context
4 | import android.graphics.Typeface
5 | import androidx.core.graphics.TypefaceCompat
6 |
7 | /**
8 | * See the comment on the [TypefaceCompat.create] method
9 | * with the weight param for various weight definitions.
10 | */
11 | object Typeface {
12 | fun normal(context: Context) = TypefaceCompat.create(context, Typeface.SANS_SERIF, 400, false)
13 | fun medium(context: Context) = TypefaceCompat.create(context, Typeface.SANS_SERIF, 500, false)
14 | fun semiBold(context: Context) = TypefaceCompat.create(context, Typeface.SANS_SERIF, 600, false)
15 | fun bold(context: Context) = TypefaceCompat.create(context, Typeface.SANS_SERIF, 700, false)
16 | }
17 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/kizitonwose/calendar/sample/view/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.sample.view
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import android.util.TypedValue
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.TextView
10 | import androidx.annotation.ColorRes
11 | import androidx.annotation.DrawableRes
12 | import androidx.core.content.ContextCompat
13 | import androidx.fragment.app.Fragment
14 | import androidx.lifecycle.findViewTreeLifecycleOwner
15 | import com.kizitonwose.calendar.sample.shared.StatusBarColorLifecycleObserver
16 |
17 | fun View.makeVisible() {
18 | visibility = View.VISIBLE
19 | }
20 |
21 | fun View.makeInVisible() {
22 | visibility = View.INVISIBLE
23 | }
24 |
25 | fun View.makeGone() {
26 | visibility = View.GONE
27 | }
28 |
29 | fun dpToPx(dp: Int, context: Context): Int =
30 | TypedValue.applyDimension(
31 | TypedValue.COMPLEX_UNIT_DIP,
32 | dp.toFloat(),
33 | context.resources.displayMetrics,
34 | ).toInt()
35 |
36 | internal val Context.layoutInflater: LayoutInflater
37 | get() = LayoutInflater.from(this)
38 |
39 | internal val Context.inputMethodManager
40 | get() = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
41 |
42 | internal fun Context.getDrawableCompat(@DrawableRes drawable: Int): Drawable =
43 | requireNotNull(ContextCompat.getDrawable(this, drawable))
44 |
45 | internal fun Context.getColorCompat(@ColorRes color: Int) =
46 | ContextCompat.getColor(this, color)
47 |
48 | internal fun TextView.setTextColorRes(@ColorRes color: Int) =
49 | setTextColor(context.getColorCompat(color))
50 |
51 | fun Fragment.addStatusBarColorUpdate(@ColorRes colorRes: Int) {
52 | view?.findViewTreeLifecycleOwner()?.lifecycle?.addObserver(
53 | StatusBarColorLifecycleObserver(
54 | requireActivity(),
55 | requireContext().getColorCompat(colorRes),
56 | ),
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/slide_in_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/slide_in_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/slide_in_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/slide_out_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/slide_out_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/slide_out_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_1_selected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_1_today_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_2_selected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_3_selected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_3_today_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_4_continuous_selected_bg_end.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_4_continuous_selected_bg_middle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_4_continuous_selected_bg_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_4_single_selected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_4_today_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_5_selected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_8_selected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/example_8_today_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_airplane_landing.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_airplane_takeoff.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_check.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_chevron_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_chevron_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/calendar_day_legend_container.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/calendar_day_legend_text.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/calendar_view_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
25 |
26 |
31 |
32 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/calendar_view_options_item_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_10_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
25 |
26 |
27 |
28 |
38 |
39 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_1_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_2_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_2_calendar_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
22 |
23 |
32 |
33 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_2_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
24 |
25 |
32 |
33 |
34 |
35 |
43 |
44 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_3_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_3_calendar_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_3_event_item_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_4_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
24 |
25 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_4_calendar_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_5_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
25 |
26 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_5_calendar_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_6_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_6_calendar_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
23 |
24 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_6_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_7_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
25 |
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_7_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_8_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_8_calendar_footer.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_8_calendar_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_8_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
19 |
20 |
25 |
26 |
35 |
36 |
45 |
46 |
47 |
48 |
49 |
50 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_9_calendar_day.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_9_calendar_month_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
22 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_9_calendar_year_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/example_9_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
25 |
26 |
27 |
28 |
37 |
38 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/home_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
24 |
25 |
26 |
27 |
32 |
33 |
39 |
40 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/example_10_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/example_2_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/example_4_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kizitonwose/Calendar/54a745ab5ad6cf0c6632e3917147bc5d208e33d5/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #FF4081
5 | #fff
6 | #000
7 | #3A284C
8 | #433254
9 | #51356E
10 | #4DFFFFFF
11 | #D9FFFFFF
12 | #D9000000
13 | #D9FFFFFF
14 | #F44336
15 | #D9000000
16 | #D9FFFFFF
17 | #1973E8
18 | #D2E3FC
19 | #607D8B
20 | #474747
21 | #008489
22 | #0F000000
23 | #BEBEBE
24 | #0E0E0E
25 | #1B1B1B
26 | #282828
27 | #DCDCDC
28 | #616161
29 | #B2EBF2
30 | #F2C4B2
31 | #B2B8F2
32 | #5D4037
33 | #455A64
34 | #EF6C00
35 | #388E3C
36 | #00796B
37 | #C2185B
38 | #C62828
39 | #1565C0
40 | #0097A7
41 | #BF000000
42 | #D9FFFFFF
43 | #FFEB3B
44 | #FCCA3E
45 | #BEBEBE
46 |
47 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | @Suppress("UnstableApiUsage")
15 | dependencyResolutionManagement {
16 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
17 | repositories {
18 | google {
19 | content {
20 | includeGroupByRegex("com\\.android.*")
21 | includeGroupByRegex("com\\.google.*")
22 | includeGroupByRegex("androidx.*")
23 | }
24 | }
25 | mavenCentral()
26 | }
27 | }
28 | include(":core")
29 | include(":data")
30 | include(":view")
31 | include(":compose")
32 | include(":sample")
33 | include(":compose-multiplatform:library")
34 | include(":compose-multiplatform:sample")
35 |
36 | rootProject.name = "Calendar"
37 |
--------------------------------------------------------------------------------
/view/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/view/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.kizitonwose.calendar.buildsrc.Android
2 | import com.kizitonwose.calendar.buildsrc.Config
3 | import com.kizitonwose.calendar.buildsrc.Version
4 |
5 | plugins {
6 | alias(libs.plugins.androidLibrary)
7 | alias(libs.plugins.kotlinAndroid)
8 | alias(libs.plugins.mavenPublish)
9 | }
10 |
11 | android {
12 | compileSdk = Android.compileSdk
13 | namespace = "com.kizitonwose.calendar.view"
14 | defaultConfig {
15 | minSdk = Android.minSdk
16 | }
17 | java {
18 | toolchain {
19 | languageVersion.set(Config.compatibleJavaLanguageVersion)
20 | }
21 | }
22 | kotlin {
23 | jvmToolchain {
24 | languageVersion.set(Config.compatibleJavaLanguageVersion)
25 | }
26 | }
27 | }
28 |
29 | dependencies {
30 | api(project(":core"))
31 | implementation(project(":data"))
32 | implementation(libs.kotlin.stdlib)
33 | implementation(libs.androidx.core.ktx)
34 |
35 | // Expose RecyclerView which is CalendarView"s superclass.
36 | api(libs.androidx.recyclerview)
37 | }
38 |
39 | mavenPublishing {
40 | coordinates(version = Version.android)
41 | }
42 |
--------------------------------------------------------------------------------
/view/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=view
2 | POM_DESCRIPTION=A highly customizable calendar library for Android, backed by RecyclerView.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/view/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/Binder.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view
2 |
3 | import android.view.View
4 | import com.kizitonwose.calendar.core.CalendarDay
5 | import com.kizitonwose.calendar.core.CalendarMonth
6 | import com.kizitonwose.calendar.core.CalendarYear
7 | import com.kizitonwose.calendar.core.Week
8 | import com.kizitonwose.calendar.core.WeekDay
9 |
10 | public open class ViewContainer(public val view: View)
11 |
12 | /**
13 | * Responsible for managing a view for a given [Data].
14 | * [create] will be called only once but [bind] will
15 | * be called whenever the view needs to be used to
16 | * bind an instance of the associated [Data].
17 | */
18 | public interface Binder {
19 | public fun create(view: View): Container
20 | public fun bind(container: Container, data: Data)
21 | }
22 |
23 | public interface YearHeaderFooterBinder : Binder
24 |
25 | public typealias YearScrollListener = (CalendarYear) -> Unit
26 |
27 | public interface MonthHeaderFooterBinder : Binder
28 |
29 | public interface MonthDayBinder : Binder
30 |
31 | public typealias MonthScrollListener = (CalendarMonth) -> Unit
32 |
33 | public interface WeekHeaderFooterBinder : Binder
34 |
35 | public interface WeekDayBinder : Binder
36 |
37 | public typealias WeekScrollListener = (Week) -> Unit
38 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/DaySize.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view
2 |
3 | import android.view.ViewGroup
4 |
5 | /**
6 | * Determines how the size of each day on the calendar is calculated.
7 | *
8 | * These values work independently in the [CalendarView] and [WeekCalendarView] classes.
9 | * However, for the [YearCalendarView] class, these values work together with the [MonthHeight].
10 | */
11 | public enum class DaySize {
12 | /**
13 | * Each day will have both width and height matching
14 | * the width of the calendar month/week divided by 7.
15 | */
16 | Square,
17 |
18 | /**
19 | * Each day will have its width matching the width of the
20 | * calendar month/week divided by 7, and its height matching the
21 | * height of the calendar divided by the number of weeks
22 | * in the index - could be 4, 5 or 6 for the month calendar,
23 | * and 1 for the week calendar. Use this if you want each
24 | * month or week to fill the parent's width and height.
25 | */
26 | Rectangle,
27 |
28 | /**
29 | * Each day will have its width matching the width of
30 | * the calendar month/week divided by 7. This day is allowed
31 | * to determine its height by setting a specific value
32 | * or using [ViewGroup.LayoutParams.WRAP_CONTENT].
33 | */
34 | SeventhWidth,
35 |
36 | /**
37 | * The day is allowed to determine its width and height by
38 | * setting specific values or using [ViewGroup.LayoutParams.WRAP_CONTENT].
39 | */
40 | FreeForm,
41 |
42 | ;
43 |
44 | internal val parentDecidesWidth: Boolean
45 | get() = this == Square || this == SeventhWidth || this == Rectangle
46 |
47 | internal val parentDecidesHeight: Boolean
48 | get() = this == Rectangle
49 | }
50 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/LayoutHelper.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view
2 |
3 | import androidx.recyclerview.widget.LinearLayoutManager
4 | import androidx.recyclerview.widget.RecyclerView
5 | import com.kizitonwose.calendar.view.internal.CalendarLayoutManager
6 |
7 | /**
8 | * Helper class with properties that match the methods that can
9 | * be overridden in the internal [LinearLayoutManager]. This is
10 | * an abstract class instead of an interface so we can have
11 | * default values for properties as we need to call `super`
12 | * for properties that are not provided (null).
13 | */
14 | public abstract class LayoutHelper {
15 | /**
16 | * Calculates the amount of extra space (in pixels) that should be laid out by
17 | * [CalendarLayoutManager] and stores the result in [extraLayoutSpace].
18 | * [extraLayoutSpace[0]] should be used for the extra space at the top in a vertical
19 | * calendar or left in a horizontal calendar, and extraLayoutSpace[1] should be used for
20 | * the extra space at the bottom in a vertical calendar or right in a horizontal calendar.
21 | *
22 | * @see [LinearLayoutManager.calculateExtraLayoutSpace]
23 | */
24 | public open val calculateExtraLayoutSpace: ((state: RecyclerView.State, extraLayoutSpace: IntArray) -> Unit)? = null
25 | }
26 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/MarginValues.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view
2 |
3 | import androidx.annotation.Px
4 |
5 | public data class MarginValues(
6 | @Px val start: Int = 0,
7 | @Px val top: Int = 0,
8 | @Px val end: Int = 0,
9 | @Px val bottom: Int = 0,
10 | ) {
11 | public constructor(
12 | @Px horizontal: Int = 0,
13 | @Px vertical: Int = 0,
14 | ) : this(
15 | start = horizontal,
16 | top = vertical,
17 | end = horizontal,
18 | bottom = vertical,
19 | )
20 |
21 | public companion object {
22 | public val ZERO: MarginValues = MarginValues()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/MonthHeight.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view
2 |
3 | /**
4 | * Determines how the height of each month row on the year-based
5 | * calendar is calculated.
6 | *
7 | * **This class is only relevant for [YearCalendarView].**
8 | */
9 | public enum class MonthHeight {
10 | /**
11 | * Each month row height is determined by the [DaySize] value set on the calendar.
12 | * Effectively, this is `wrap-content` if the value is [DaySize.Square],
13 | * [DaySize.SeventhWidth], or [DaySize.FreeForm], and will be equal to the calendar height
14 | * divided by the number of rows if the value is [DaySize.Rectangle].
15 | *
16 | * When used together with [DaySize.Rectangle], the calendar months and days will
17 | * uniformly stretch to fill the parent's height.
18 | */
19 | FollowDaySize,
20 |
21 | /**
22 | * Each month row height will be the calender height divided by the number
23 | * of rows on the calendar. This means that the calendar months will be distributed
24 | * uniformly to fill the parent's height. However, the day content height will
25 | * independently determine its height.
26 | *
27 | * This allows you to spread the calendar months evenly across the screen while
28 | * using a [DaySize] value of [DaySize.Square] if you want square day content
29 | * or [DaySize.SeventhWidth] if you want to set a specific height value for
30 | * the day content.
31 | */
32 | Fill,
33 | }
34 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/internal/CustomViewClass.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view.internal
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.view.ViewGroup
6 |
7 | internal inline fun customViewOrRoot(
8 | customViewClass: String?,
9 | rootLayout: ViewGroup,
10 | setupRoot: (ViewGroup) -> Unit,
11 | ): ViewGroup {
12 | return customViewClass?.let {
13 | val customLayout = runCatching {
14 | Class.forName(it)
15 | .getDeclaredConstructor(Context::class.java)
16 | .newInstance(rootLayout.context) as ViewGroup
17 | }.onFailure {
18 | Log.e(
19 | "Calendar",
20 | "Failure loading custom class $customViewClass, " +
21 | "check that $customViewClass is a ViewGroup and the " +
22 | "single argument context constructor is available. " +
23 | "For an example on how to use a custom class, see: $EXAMPLE_CUSTOM_CLASS_URL",
24 | it,
25 | )
26 | }.getOrNull()
27 |
28 | customLayout?.apply {
29 | setupRoot(this)
30 | addView(rootLayout)
31 | }
32 | } ?: rootLayout.apply { setupRoot(this) }
33 | }
34 |
35 | private const val EXAMPLE_CUSTOM_CLASS_URL =
36 | "https://github.com/kizitonwose/Calendar/blob/3dfb2d2e91d5e443b540ff411113a05268e4b8d2/sample/src/main/java/com/kizitonwose/calendar/sample/view/Example6Fragment.kt#L29"
37 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/internal/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view.internal
2 |
3 | import android.graphics.Rect
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.annotation.LayoutRes
8 |
9 | internal fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
10 | return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
11 | }
12 |
13 | internal const val NO_INDEX = -1
14 |
15 | internal fun Rect.intersects(other: Rect): Boolean {
16 | return if (this.isEmpty || other.isEmpty) {
17 | false
18 | } else {
19 | Rect.intersects(this, other)
20 | }
21 | }
22 |
23 | internal fun missingField(field: String) = "`$field` is not set. Have you called `setup()`?"
24 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/internal/monthcalendar/MonthCalendarLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view.internal.monthcalendar
2 |
3 | import com.kizitonwose.calendar.core.CalendarDay
4 | import com.kizitonwose.calendar.view.CalendarView
5 | import com.kizitonwose.calendar.view.LayoutHelper
6 | import com.kizitonwose.calendar.view.MarginValues
7 | import com.kizitonwose.calendar.view.internal.CalendarLayoutManager
8 | import com.kizitonwose.calendar.view.internal.dayTag
9 | import java.time.YearMonth
10 |
11 | internal class MonthCalendarLayoutManager(private val calView: CalendarView) :
12 | CalendarLayoutManager(calView, calView.orientation) {
13 | private val adapter: MonthCalendarAdapter
14 | get() = calView.adapter as MonthCalendarAdapter
15 |
16 | override fun getaItemAdapterPosition(data: YearMonth): Int = adapter.getAdapterPosition(data)
17 | override fun getaDayAdapterPosition(data: CalendarDay): Int = adapter.getAdapterPosition(data)
18 | override fun getDayTag(data: CalendarDay): Int = dayTag(data.date)
19 | override fun getItemMargins(): MarginValues = calView.monthMargins
20 | override fun scrollPaged(): Boolean = calView.scrollPaged
21 | override fun notifyScrollListenerIfNeeded() = adapter.notifyMonthScrollListenerIfNeeded()
22 | override fun getLayoutHelper(): LayoutHelper? = calView.layoutHelper
23 | }
24 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/internal/monthcalendar/MonthViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view.internal.monthcalendar
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.kizitonwose.calendar.core.CalendarDay
7 | import com.kizitonwose.calendar.core.CalendarMonth
8 | import com.kizitonwose.calendar.view.MonthHeaderFooterBinder
9 | import com.kizitonwose.calendar.view.ViewContainer
10 | import com.kizitonwose.calendar.view.internal.WeekHolder
11 |
12 | internal class MonthViewHolder(
13 | rootLayout: ViewGroup,
14 | private val headerView: View?,
15 | private val footerView: View?,
16 | private val weekHolders: List>,
17 | private val monthHeaderBinder: MonthHeaderFooterBinder?,
18 | private val monthFooterBinder: MonthHeaderFooterBinder?,
19 | ) : RecyclerView.ViewHolder(rootLayout) {
20 | private var headerContainer: ViewContainer? = null
21 | private var footerContainer: ViewContainer? = null
22 | private lateinit var month: CalendarMonth
23 |
24 | fun bindMonth(month: CalendarMonth) {
25 | this.month = month
26 | headerView?.let { view ->
27 | val headerContainer = headerContainer ?: monthHeaderBinder!!.create(view).also {
28 | headerContainer = it
29 | }
30 | monthHeaderBinder?.bind(headerContainer, month)
31 | }
32 | weekHolders.forEachIndexed { index, week ->
33 | week.bindWeekView(month.weekDays.getOrNull(index).orEmpty())
34 | }
35 | footerView?.let { view ->
36 | val footerContainer = footerContainer ?: monthFooterBinder!!.create(view).also {
37 | footerContainer = it
38 | }
39 | monthFooterBinder?.bind(footerContainer, month)
40 | }
41 | }
42 |
43 | fun reloadDay(day: CalendarDay) {
44 | weekHolders.firstOrNull { it.reloadDay(day) }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/internal/weekcalendar/WeekCalendarLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view.internal.weekcalendar
2 |
3 | import com.kizitonwose.calendar.view.LayoutHelper
4 | import com.kizitonwose.calendar.view.MarginValues
5 | import com.kizitonwose.calendar.view.WeekCalendarView
6 | import com.kizitonwose.calendar.view.internal.CalendarLayoutManager
7 | import com.kizitonwose.calendar.view.internal.dayTag
8 | import java.time.LocalDate
9 |
10 | internal class WeekCalendarLayoutManager(private val calView: WeekCalendarView) :
11 | CalendarLayoutManager(calView, HORIZONTAL) {
12 | private val adapter: WeekCalendarAdapter
13 | get() = calView.adapter as WeekCalendarAdapter
14 |
15 | override fun getaItemAdapterPosition(data: LocalDate): Int = adapter.getAdapterPosition(data)
16 | override fun getaDayAdapterPosition(data: LocalDate): Int = adapter.getAdapterPosition(data)
17 | override fun getDayTag(data: LocalDate): Int = dayTag(data)
18 | override fun getItemMargins(): MarginValues = calView.weekMargins
19 | override fun scrollPaged(): Boolean = calView.scrollPaged
20 | override fun notifyScrollListenerIfNeeded() = adapter.notifyWeekScrollListenerIfNeeded()
21 | override fun getLayoutHelper(): LayoutHelper? = calView.layoutHelper
22 | }
23 |
--------------------------------------------------------------------------------
/view/src/main/java/com/kizitonwose/calendar/view/internal/weekcalendar/WeekViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.kizitonwose.calendar.view.internal.weekcalendar
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.kizitonwose.calendar.core.Week
7 | import com.kizitonwose.calendar.core.WeekDay
8 | import com.kizitonwose.calendar.view.ViewContainer
9 | import com.kizitonwose.calendar.view.WeekHeaderFooterBinder
10 | import com.kizitonwose.calendar.view.internal.WeekHolder
11 |
12 | internal class WeekViewHolder(
13 | rootLayout: ViewGroup,
14 | private val headerView: View?,
15 | private val footerView: View?,
16 | private val weekHolder: WeekHolder,
17 | private val weekHeaderBinder: WeekHeaderFooterBinder?,
18 | private val weekFooterBinder: WeekHeaderFooterBinder?,
19 | ) : RecyclerView.ViewHolder(rootLayout) {
20 | private var headerContainer: ViewContainer? = null
21 | private var footerContainer: ViewContainer? = null
22 |
23 | lateinit var week: Week
24 |
25 | fun bindWeek(week: Week) {
26 | this.week = week
27 | headerView?.let { view ->
28 | val headerContainer = headerContainer ?: weekHeaderBinder!!.create(view).also {
29 | headerContainer = it
30 | }
31 | weekHeaderBinder?.bind(headerContainer, week)
32 | }
33 | weekHolder.bindWeekView(week.days)
34 | footerView?.let { view ->
35 | val footerContainer = footerContainer ?: weekFooterBinder!!.create(view).also {
36 | footerContainer = it
37 | }
38 | weekFooterBinder?.bind(footerContainer, week)
39 | }
40 | }
41 |
42 | fun reloadDay(day: WeekDay) {
43 | weekHolder.reloadDay(day)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------