diff --git a/.editorconfig b/.editorconfig index 6b37b845..d9e3ecad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,9 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true +[**/generated/**/*] +ktlint = disabled + [*.{kt,kts}] ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma_on_call_site = true diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b605d661..24660920 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -38,6 +38,20 @@ jobs: - name: Run Ktlint run: ./gradlew lintKotlin + api-check: + name: API Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 17 + - uses: gradle/actions/setup-gradle@v3 + with: + gradle-home-cache-cleanup: true + - run: ./gradlew apiCheck + unit-tests: name: Unit tests runs-on: ubuntu-latest diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f08d6421..f7ee8973 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -6,9 +6,10 @@ on: - '*.*.*' jobs: + # Use macOS so iOS artifacts are published publish-release: name: Publish Release - runs-on: ubuntu-latest + runs-on: macos-latest if: github.repository == 'kizitonwose/Calendar' steps: - name: Checkout Repository @@ -35,7 +36,7 @@ jobs: - name: Deploy Release to Maven run: ./gradlew publish - if: "!endsWith(env.VERSION_NAME, '-SNAPSHOT')" + # if: "!endsWith(env.VERSION_NAME, '-SNAPSHOT')" env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index f4a4300a..9fbcc4ad 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -7,9 +7,10 @@ on: - main jobs: + # Use macOS so iOS artifacts are published publish-snapshot: name: Publish Snapshot - runs-on: ubuntu-latest + runs-on: macos-latest if: github.repository == 'kizitonwose/Calendar' steps: - name: Checkout Repository @@ -36,7 +37,7 @@ jobs: - name: Publish Snapshot to Maven run: ./gradlew publish - if: endsWith(env.VERSION_NAME, '-SNAPSHOT') + # if: endsWith(env.VERSION_NAME, '-SNAPSHOT') env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} diff --git a/.gitignore b/.gitignore index 1a4f120c..16547d40 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /captures .externalNativeBuild .idea +/.kotlin diff --git a/README.md b/README.md index 33fb1cc1..4fb89cce 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # Calendar -A highly customizable calendar library for Android, backed by RecyclerView for the view system, and LazyRow/LazyColumn for compose. +A highly customizable calendar library for Android and Compose Multiplatform, backed by RecyclerView for the view system, and LazyRow/LazyColumn for compose. [![Tests](https://github.com/kizitonwose/Calendar/workflows/Check/badge.svg?branch=main)](https://github.com/kizitonwose/Calendar/actions) -[![Maven Central](https://img.shields.io/badge/dynamic/xml.svg?label=Maven%20Central&color=blue&url=https://repo1.maven.org/maven2/com/kizitonwose/calendar/core/maven-metadata.xml&query=(//metadata/versioning/versions/version)[not(contains(text(),%27-%27))][last()])](https://central.sonatype.com/search?q=g:com.kizitonwose.calendar) -[![Maven Central Beta](https://img.shields.io/badge/dynamic/xml.svg?label=Maven%20Central%20Beta&color=slateblue&url=https://repo1.maven.org/maven2/com/kizitonwose/calendar/core/maven-metadata.xml&query=(//metadata/versioning/versions/version)[contains(text(),%27beta%27)][last()])](https://central.sonatype.com/search?q=g:com.kizitonwose.calendar) +[![Android Library](https://img.shields.io/badge/dynamic/xml.svg?label=Android%20Library&color=blue&url=https://repo1.maven.org/maven2/com/kizitonwose/calendar/core/maven-metadata.xml&query=(//metadata/versioning/versions/version)[not(contains(text(),%27-%27))][last()])](https://central.sonatype.com/search?q=g:com.kizitonwose.calendar) +[![Android Library Beta](https://img.shields.io/badge/dynamic/xml.svg?label=Android%20Library%20Beta&color=slateblue&url=https://repo1.maven.org/maven2/com/kizitonwose/calendar/core/maven-metadata.xml&query=(//metadata/versioning/versions/version)[contains(text(),%27beta%27)][last()])](https://central.sonatype.com/search?q=g:com.kizitonwose.calendar) +[![Multiplatform Library Alpha](https://img.shields.io/badge/dynamic/xml.svg?label=Multiplatform%20Library%20Alpha&color=slateblue&url=https://repo1.maven.org/maven2/com/kizitonwose/calendar/compose-multiplatform/maven-metadata.xml&query=(//metadata/versioning/versions/version)[contains(text(),%27alpha%27)][last()])](https://central.sonatype.com/search?q=g:com.kizitonwose.calendar) [![License](https://img.shields.io/badge/License-MIT-0097A7.svg)](https://github.com/kizitonwose/Calendar/blob/main/LICENSE.md) [![Twitter](https://img.shields.io/badge/Twitter-@kizitonwose-9C27B0.svg)](https://twitter.com/kizitonwose) @@ -39,17 +40,25 @@ A highly customizable calendar library for Android, backed by RecyclerView for t It's important to check out the sample app. There are lots of examples provided for both view and compose implementations. Most techniques that you would want to implement are already done in the examples. -Download the sample app [here](https://github.com/kizitonwose/Calendar/releases/download/2.0.0/sample.apk) +Download the Android sample app [here](https://github.com/kizitonwose/Calendar/releases/download/2.0.0/sample.apk) -View the sample app's source code [here](https://github.com/kizitonwose/Calendar/tree/main/sample) +View the Android sample app's source code [here](https://github.com/kizitonwose/Calendar/tree/main/sample) + +View the multiplatform sample project online at https://calendar.kizitonwose.dev + +View the multiplatform sample project's source code [here](https://github.com/kizitonwose/Calendar/tree/main/compose-multiplatform/sample) ## Setup +The library provides two compose artifacts: `com.kizitonwose.calendar:compose` which uses the [java.time](https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) APIs for pure Android projects and `com.kizitonwose.calendar:compose-multiplatform` which uses the [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) library for Compose Multiplatform projects. + +The Android view library artifact `com.kizitonwose.calendar:view` also uses the `java.time` APIs and can exist alongside the Android compose artifact in an Android project if needed. + #### Step 1 -**This step is required ONLY if your app's `minSdkVersion` is below 26. Jump to [step 2](#step-2) if this does not apply to you.** +**This step is required ONLY if your Android app's `minSdkVersion` is below 26. Jump to [step 2](#step-2) if this does not apply to you.** -Apps with `minSdkVersion` below 26 have to enable [Java 8+ API desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) for backward compatibility since `java.time` classes were added in Java 8 which is supported natively starting from Android SDK 26. To set up your project for desugaring, you need to first ensure that you are using [Android Gradle plugin](https://developer.android.com/studio/releases/gradle-plugin#updating-plugin) 4.0.0 or higher. +Android apps with `minSdkVersion` below 26 have to enable [Java 8+ API desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) for backward compatibility since `java.time` classes were added in Java 8 which is supported natively starting from Android SDK 26. To set up your project for desugaring, you need to first ensure that you are using [Android Gradle plugin](https://developer.android.com/studio/releases/gradle-plugin#updating-plugin) 4.0.0 or higher. Then include the following in your app's `build.gradle` file: @@ -81,17 +90,27 @@ dependencies { You can find the latest version of `desugar_jdk_libs` [here](https://mvnrepository.com/artifact/com.android.tools/desugar_jdk_libs). -#### Step 2 +#### Step 2A - For pure Android projects without multiplatform setup -Add the desired calendar library (view or compose) to your app `build.gradle`: +Add the desired calendar library (view or compose) to your app's `build.gradle.kts`: -```groovy +```kotlin dependencies { - // The view calendar library - implementation 'com.kizitonwose.calendar:view:' + // The view calendar library for Android + implementation("com.kizitonwose.calendar:view:") + + // The compose calendar library for Android + implementation("com.kizitonwose.calendar:compose:") +} +``` + +#### Step 2B - For Compose Multiplatform projects - // The compose calendar library - implementation 'com.kizitonwose.calendar:compose:' +Add the multiplatform calendar library to your project's `build.gradle.kts`: + +```kotlin +commonMain.dependencies { + implementation("com.kizitonwose.calendar:compose-multiplatform:") } ``` @@ -99,18 +118,18 @@ You can find the latest version of the library on the maven central badge above. Snapshots of the development version are available in [Sonatype’s snapshots repository](https://s01.oss.sonatype.org/content/repositories/snapshots/com/kizitonwose/calendar/). -If you're upgrading from version 1.x.x to 2.x.x, see the [migration guide](https://github.com/kizitonwose/calendar/blob/main/docs/MigrationGuide.md). +#### Compose UI version compatibility For the compose calendar library, ensure that you are using the library version that matches the Compose UI version in your project. If you use a version of the library that has a higher version of Compose UI than the one in your project, gradle will upgrade the Compose UI version in your project via transitive dependency. -| Compose UI | Calendar Library | -|:----------:|:----------------:| -| 1.2.x | 2.0.x | -| 1.3.x | 2.1.x - 2.2.x | -| 1.4.x | 2.3.x | -| 1.5.x | 2.4.x | -| 1.6.x | 2.5.x | -| 1.7.x | 2.6.x | +| Compose UI | Android Calendar Library | Multiplatform Calendar Library | +|:----------:|:------------------------:|:------------------------------:| +| 1.2.x | 2.0.x | - | +| 1.3.x | 2.1.x - 2.2.x | - | +| 1.4.x | 2.3.x | - | +| 1.5.x | 2.4.x | - | +| 1.6.x | 2.5.x | - | +| 1.7.x | 2.6.x | 2.6.x | ## Usage @@ -119,6 +138,10 @@ You can find the relevant documentation for the library in the links below. |[View-based documentation](https://github.com/kizitonwose/Calendar/blob/main/docs/View.md)|[Compose documentation](https://github.com/kizitonwose/Calendar/blob/main/docs/Compose.md)| |:-:|:-:| +## Migration + +If you're upgrading from calendar library version 1.x.x to 2.x.x, see the [migration guide](https://github.com/kizitonwose/calendar/blob/main/docs/MigrationGuide.md). + ## Share your creations Made a cool calendar with this library? Share an image [here](https://github.com/kizitonwose/Calendar/issues/1). diff --git a/build.gradle.kts b/build.gradle.kts index 39482b65..d716f9e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin + plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false @@ -6,13 +9,30 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinter) apply false alias(libs.plugins.mavenPublish) apply false + alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.jetbrainsCompose) apply false alias(libs.plugins.versionCheck) + alias(libs.plugins.bcv) } allprojects { apply(plugin = rootProject.libs.plugins.kotlinter.get().pluginId) + + plugins.withType().configureEach { + extensions.configure { + if ("sample" !in project.name) { + explicitApi() + } + } + } } -tasks.register("clean").configure { - delete(rootProject.layout.buildDirectory) +apiValidation { + ignoredProjects += listOf( + "sample", + ) } + +// tasks.register("clean").configure { +// delete(rootProject.layout.buildDirectory) +// } diff --git a/buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Dependencies.kt b/buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Build.kt similarity index 86% rename from buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Dependencies.kt rename to buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Build.kt index af701d2f..69ba66a0 100644 --- a/buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Dependencies.kt +++ b/buildSrc/src/main/java/com/kizitonwose/calendar/buildsrc/Build.kt @@ -11,6 +11,11 @@ object Config { val compatibleJavaLanguageVersion = JavaLanguageVersion.of(compatibleJavaVersion.majorVersion.toInt()) } +object Versions { + val core = "2.6.0-SNAPSHOT" + val multiplatfrom = "2.6.0-SNAPSHOT" +} + object Android { const val minSdkViewLibrary = 19 const val minSdkComposeLibrary = 21 diff --git a/compose-multiplatform/library/.gitignore b/compose-multiplatform/library/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/compose-multiplatform/library/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/compose-multiplatform/library/api/android/library.api b/compose-multiplatform/library/api/android/library.api new file mode 100644 index 00000000..a64c47e0 --- /dev/null +++ b/compose-multiplatform/library/api/android/library.api @@ -0,0 +1,341 @@ +public final class com/kizitonwose/calendar/compose/CalendarItemInfo : androidx/compose/foundation/lazy/LazyListItemInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListItemInfo;Lcom/kizitonwose/calendar/core/CalendarMonth;)V + public fun getContentType ()Ljava/lang/Object; + public fun getIndex ()I + public fun getKey ()Ljava/lang/Object; + public final fun getMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun getOffset ()I + public fun getSize ()I +} + +public final class com/kizitonwose/calendar/compose/CalendarKt { + public static final fun HeatMapCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState;Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition;ZLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + public static final fun HorizontalCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/CalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lcom/kizitonwose/calendar/compose/ContentHeightMode;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun VerticalCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/CalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lcom/kizitonwose/calendar/compose/ContentHeightMode;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun WeekCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V +} + +public final class com/kizitonwose/calendar/compose/CalendarLayoutInfo : androidx/compose/foundation/lazy/LazyListLayoutInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListLayoutInfo;Lkotlin/jvm/functions/Function1;)V + public fun getAfterContentPadding ()I + public fun getBeforeContentPadding ()I + public fun getMainAxisItemSpacing ()I + public fun getOrientation ()Landroidx/compose/foundation/gestures/Orientation; + public fun getReverseLayout ()Z + public fun getTotalItemsCount ()I + public fun getViewportEndOffset ()I + public fun getViewportSize-YbymL2g ()J + public fun getViewportStartOffset ()I + public fun getVisibleItemsInfo ()Ljava/util/List; + public final fun getVisibleMonthsInfo ()Ljava/util/List; +} + +public final class com/kizitonwose/calendar/compose/CalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/CalendarState$Companion; + public final fun animateScrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/CalendarLayoutInfo; + public final fun getOutDateStyle ()Lcom/kizitonwose/calendar/core/OutDateStyle; + public final fun getStartMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setOutDateStyle (Lcom/kizitonwose/calendar/core/OutDateStyle;)V + public final fun setStartMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V +} + +public final class com/kizitonwose/calendar/compose/CalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/CalendarStateKt { + public static final fun rememberCalendarState (Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Ljava/time/DayOfWeek;Lcom/kizitonwose/calendar/core/OutDateStyle;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/CalendarState; +} + +public final class com/kizitonwose/calendar/compose/ComposableSingletons$CalendarMonthsKt { + public static final field INSTANCE Lcom/kizitonwose/calendar/compose/ComposableSingletons$CalendarMonthsKt; + public static field lambda-1 Lkotlin/jvm/functions/Function5; + public static field lambda-2 Lkotlin/jvm/functions/Function5; + public fun ()V + public final fun getLambda-1$library_release ()Lkotlin/jvm/functions/Function5; + public final fun getLambda-2$library_release ()Lkotlin/jvm/functions/Function5; +} + +public final class com/kizitonwose/calendar/compose/ContentHeightMode : java/lang/Enum { + public static final field Fill Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static final field Wrap Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static fun values ()[Lcom/kizitonwose/calendar/compose/ContentHeightMode; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState$Companion; + public final fun animateScrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/CalendarLayoutInfo; + public final fun getStartMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setStartMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarStateKt { + public static final fun rememberHeatMapCalendarState (Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Ljava/time/DayOfWeek;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek; + public fun equals (Ljava/lang/Object;)Z + public final fun getDays ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition : java/lang/Enum { + public static final field End Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static final field Start Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static fun values ()[Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarItemInfo : androidx/compose/foundation/lazy/LazyListItemInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListItemInfo;Lcom/kizitonwose/calendar/core/Week;)V + public fun getContentType ()Ljava/lang/Object; + public fun getIndex ()I + public fun getKey ()Ljava/lang/Object; + public fun getOffset ()I + public fun getSize ()I + public final fun getWeek ()Lcom/kizitonwose/calendar/core/Week; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo : androidx/compose/foundation/lazy/LazyListLayoutInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListLayoutInfo;Lkotlin/jvm/functions/Function1;)V + public fun getAfterContentPadding ()I + public fun getBeforeContentPadding ()I + public fun getMainAxisItemSpacing ()I + public fun getOrientation ()Landroidx/compose/foundation/gestures/Orientation; + public fun getReverseLayout ()Z + public fun getTotalItemsCount ()I + public fun getViewportEndOffset ()I + public fun getViewportSize-YbymL2g ()J + public fun getViewportStartOffset ()I + public fun getVisibleItemsInfo ()Ljava/util/List; + public final fun getVisibleWeeksInfo ()Ljava/util/List; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState$Companion; + public final fun animateScrollToWeek (Lkotlinx/datetime/LocalDate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndDate ()Lkotlinx/datetime/LocalDate; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo; + public final fun getStartDate ()Lkotlinx/datetime/LocalDate; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToWeek (Lkotlinx/datetime/LocalDate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndDate (Lkotlinx/datetime/LocalDate;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setStartDate (Lkotlinx/datetime/LocalDate;)V +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarStateKt { + public static final fun rememberWeekCalendarState (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Ljava/time/DayOfWeek;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState; +} + +public final class com/kizitonwose/calendar/core/CalendarDay { + public static final field $stable I + public fun (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)V + public final fun component1 ()Lkotlinx/datetime/LocalDate; + public final fun component2 ()Lcom/kizitonwose/calendar/core/DayPosition; + public final fun copy (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)Lcom/kizitonwose/calendar/core/CalendarDay; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/CalendarDay;Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/CalendarDay; + public fun equals (Ljava/lang/Object;)Z + public final fun getDate ()Lkotlinx/datetime/LocalDate; + public final fun getPosition ()Lcom/kizitonwose/calendar/core/DayPosition; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/CalendarMonth { + public static final field $stable I + public final fun component1 ()Lcom/kizitonwose/calendar/core/YearMonth; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lcom/kizitonwose/calendar/core/YearMonth;Ljava/util/List;)Lcom/kizitonwose/calendar/core/CalendarMonth; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/CalendarMonth;Lcom/kizitonwose/calendar/core/YearMonth;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun equals (Ljava/lang/Object;)Z + public final fun getWeekDays ()Ljava/util/List; + public final fun getYearMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/ConvertersKt { + public static final fun toJavaYearMonth (Lcom/kizitonwose/calendar/core/YearMonth;)Ljava/time/YearMonth; + public static final fun toKotlinYearMonth (Ljava/time/YearMonth;)Lcom/kizitonwose/calendar/core/YearMonth; +} + +public final class com/kizitonwose/calendar/core/DayPosition : java/lang/Enum { + public static final field InDate Lcom/kizitonwose/calendar/core/DayPosition; + public static final field MonthDate Lcom/kizitonwose/calendar/core/DayPosition; + public static final field OutDate Lcom/kizitonwose/calendar/core/DayPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/DayPosition; + public static fun values ()[Lcom/kizitonwose/calendar/core/DayPosition; +} + +public final class com/kizitonwose/calendar/core/ExtensionsKt { + public static final fun daysOfWeek (Ljava/time/DayOfWeek;)Ljava/util/List; + public static synthetic fun daysOfWeek$default (Ljava/time/DayOfWeek;ILjava/lang/Object;)Ljava/util/List; + public static final fun getYearMonth (Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/core/YearMonth; + public static final fun now (Lkotlinx/datetime/LocalDate$Companion;Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; + public static synthetic fun now$default (Lkotlinx/datetime/LocalDate$Companion;Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;ILjava/lang/Object;)Lkotlinx/datetime/LocalDate; +} + +public final class com/kizitonwose/calendar/core/Extensions_jvmKt { + public static final fun firstDayOfWeekFromLocale (Landroidx/compose/ui/text/intl/Locale;)Ljava/time/DayOfWeek; + public static synthetic fun firstDayOfWeekFromLocale$default (Landroidx/compose/ui/text/intl/Locale;ILjava/lang/Object;)Ljava/time/DayOfWeek; +} + +public final class com/kizitonwose/calendar/core/OutDateStyle : java/lang/Enum { + public static final field EndOfGrid Lcom/kizitonwose/calendar/core/OutDateStyle; + public static final field EndOfRow Lcom/kizitonwose/calendar/core/OutDateStyle; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/OutDateStyle; + public static fun values ()[Lcom/kizitonwose/calendar/core/OutDateStyle; +} + +public final class com/kizitonwose/calendar/core/Week { + public static final field $stable I + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/kizitonwose/calendar/core/Week; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/Week;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/Week; + public fun equals (Ljava/lang/Object;)Z + public final fun getDays ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/WeekDay { + public static final field $stable I + public fun (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;)V + public final fun component1 ()Lkotlinx/datetime/LocalDate; + public final fun component2 ()Lcom/kizitonwose/calendar/core/WeekDayPosition; + public final fun copy (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;)Lcom/kizitonwose/calendar/core/WeekDay; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/WeekDay;Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/WeekDay; + public fun equals (Ljava/lang/Object;)Z + public final fun getDate ()Lkotlinx/datetime/LocalDate; + public final fun getPosition ()Lcom/kizitonwose/calendar/core/WeekDayPosition; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/WeekDayPosition : java/lang/Enum { + public static final field InDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static final field OutDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static final field RangeDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static fun values ()[Lcom/kizitonwose/calendar/core/WeekDayPosition; +} + +public final class com/kizitonwose/calendar/core/YearMonth : java/io/Serializable, java/lang/Comparable { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/core/YearMonth$Companion; + public fun (II)V + public fun (ILjava/time/Month;)V + public fun compareTo (Lcom/kizitonwose/calendar/core/YearMonth;)I + public synthetic fun compareTo (Ljava/lang/Object;)I + public final fun component1 ()I + public final fun component2 ()Ljava/time/Month; + public final fun copy (ILjava/time/Month;)Lcom/kizitonwose/calendar/core/YearMonth; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/YearMonth;ILjava/time/Month;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/YearMonth; + public fun equals (Ljava/lang/Object;)Z + public final fun getMonth ()Ljava/time/Month; + public final fun getYear ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/YearMonth$Companion { + public final fun now (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lcom/kizitonwose/calendar/core/YearMonth; + public static synthetic fun now$default (Lcom/kizitonwose/calendar/core/YearMonth$Companion;Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/YearMonth; +} + +public final class com/kizitonwose/calendar/core/YearMonthKt { + public static final fun atDay (Lcom/kizitonwose/calendar/core/YearMonth;I)Lkotlinx/datetime/LocalDate; + public static final fun atEndOfMonth (Lcom/kizitonwose/calendar/core/YearMonth;)Lkotlinx/datetime/LocalDate; + public static final fun atStartOfMonth (Lcom/kizitonwose/calendar/core/YearMonth;)Lkotlinx/datetime/LocalDate; + public static final fun lengthOfMonth (Lcom/kizitonwose/calendar/core/YearMonth;)I + public static final fun minus (Lcom/kizitonwose/calendar/core/YearMonth;ILkotlinx/datetime/DateTimeUnit$MonthBased;)Lcom/kizitonwose/calendar/core/YearMonth; + public static final fun monthsUntil (Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;)I + public static final fun plus (Lcom/kizitonwose/calendar/core/YearMonth;ILkotlinx/datetime/DateTimeUnit$MonthBased;)Lcom/kizitonwose/calendar/core/YearMonth; +} + +public final class com/kizitonwose/calendar/data/WeekData { + public static final field $stable I + public final fun copy (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/data/WeekData; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/WeekData;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;ILjava/lang/Object;)Lcom/kizitonwose/calendar/data/WeekData; + public fun equals (Ljava/lang/Object;)Z + public final fun getWeek ()Lcom/kizitonwose/calendar/core/Week; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/data/WeekDataKt { + public static final fun getWeekCalendarAdjustedRange (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Ljava/time/DayOfWeek;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public static final fun getWeekCalendarData (Lkotlinx/datetime/LocalDate;ILkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/data/WeekData; + public static final fun getWeekIndex (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)I + public static final fun getWeekIndicesCount (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)I +} + +public final class com/kizitonwose/calendar/data/WeekDateRange { + public static final field $stable I + public fun (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)V + public final fun component1 ()Lkotlinx/datetime/LocalDate; + public final fun component2 ()Lkotlinx/datetime/LocalDate; + public final fun copy (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/WeekDateRange;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;ILjava/lang/Object;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public fun equals (Ljava/lang/Object;)Z + public final fun getEndDateAdjusted ()Lkotlinx/datetime/LocalDate; + public final fun getStartDateAdjusted ()Lkotlinx/datetime/LocalDate; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/compose-multiplatform/library/api/desktop/library.api b/compose-multiplatform/library/api/desktop/library.api new file mode 100644 index 00000000..87fdf312 --- /dev/null +++ b/compose-multiplatform/library/api/desktop/library.api @@ -0,0 +1,341 @@ +public final class com/kizitonwose/calendar/compose/CalendarItemInfo : androidx/compose/foundation/lazy/LazyListItemInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListItemInfo;Lcom/kizitonwose/calendar/core/CalendarMonth;)V + public fun getContentType ()Ljava/lang/Object; + public fun getIndex ()I + public fun getKey ()Ljava/lang/Object; + public final fun getMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun getOffset ()I + public fun getSize ()I +} + +public final class com/kizitonwose/calendar/compose/CalendarKt { + public static final fun HeatMapCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState;Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition;ZLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + public static final fun HorizontalCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/CalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lcom/kizitonwose/calendar/compose/ContentHeightMode;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun VerticalCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/CalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lcom/kizitonwose/calendar/compose/ContentHeightMode;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun WeekCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V +} + +public final class com/kizitonwose/calendar/compose/CalendarLayoutInfo : androidx/compose/foundation/lazy/LazyListLayoutInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListLayoutInfo;Lkotlin/jvm/functions/Function1;)V + public fun getAfterContentPadding ()I + public fun getBeforeContentPadding ()I + public fun getMainAxisItemSpacing ()I + public fun getOrientation ()Landroidx/compose/foundation/gestures/Orientation; + public fun getReverseLayout ()Z + public fun getTotalItemsCount ()I + public fun getViewportEndOffset ()I + public fun getViewportSize-YbymL2g ()J + public fun getViewportStartOffset ()I + public fun getVisibleItemsInfo ()Ljava/util/List; + public final fun getVisibleMonthsInfo ()Ljava/util/List; +} + +public final class com/kizitonwose/calendar/compose/CalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/CalendarState$Companion; + public final fun animateScrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/CalendarLayoutInfo; + public final fun getOutDateStyle ()Lcom/kizitonwose/calendar/core/OutDateStyle; + public final fun getStartMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setOutDateStyle (Lcom/kizitonwose/calendar/core/OutDateStyle;)V + public final fun setStartMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V +} + +public final class com/kizitonwose/calendar/compose/CalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/CalendarStateKt { + public static final fun rememberCalendarState (Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Ljava/time/DayOfWeek;Lcom/kizitonwose/calendar/core/OutDateStyle;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/CalendarState; +} + +public final class com/kizitonwose/calendar/compose/ComposableSingletons$CalendarMonthsKt { + public static final field INSTANCE Lcom/kizitonwose/calendar/compose/ComposableSingletons$CalendarMonthsKt; + public static field lambda-1 Lkotlin/jvm/functions/Function5; + public static field lambda-2 Lkotlin/jvm/functions/Function5; + public fun ()V + public final fun getLambda-1$library ()Lkotlin/jvm/functions/Function5; + public final fun getLambda-2$library ()Lkotlin/jvm/functions/Function5; +} + +public final class com/kizitonwose/calendar/compose/ContentHeightMode : java/lang/Enum { + public static final field Fill Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static final field Wrap Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static fun values ()[Lcom/kizitonwose/calendar/compose/ContentHeightMode; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState$Companion; + public final fun animateScrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/CalendarLayoutInfo; + public final fun getStartMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToMonth (Lcom/kizitonwose/calendar/core/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setStartMonth (Lcom/kizitonwose/calendar/core/YearMonth;)V +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarStateKt { + public static final fun rememberHeatMapCalendarState (Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;Ljava/time/DayOfWeek;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek; + public fun equals (Ljava/lang/Object;)Z + public final fun getDays ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition : java/lang/Enum { + public static final field End Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static final field Start Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static fun values ()[Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarItemInfo : androidx/compose/foundation/lazy/LazyListItemInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListItemInfo;Lcom/kizitonwose/calendar/core/Week;)V + public fun getContentType ()Ljava/lang/Object; + public fun getIndex ()I + public fun getKey ()Ljava/lang/Object; + public fun getOffset ()I + public fun getSize ()I + public final fun getWeek ()Lcom/kizitonwose/calendar/core/Week; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo : androidx/compose/foundation/lazy/LazyListLayoutInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListLayoutInfo;Lkotlin/jvm/functions/Function1;)V + public fun getAfterContentPadding ()I + public fun getBeforeContentPadding ()I + public fun getMainAxisItemSpacing ()I + public fun getOrientation ()Landroidx/compose/foundation/gestures/Orientation; + public fun getReverseLayout ()Z + public fun getTotalItemsCount ()I + public fun getViewportEndOffset ()I + public fun getViewportSize-YbymL2g ()J + public fun getViewportStartOffset ()I + public fun getVisibleItemsInfo ()Ljava/util/List; + public final fun getVisibleWeeksInfo ()Ljava/util/List; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState$Companion; + public final fun animateScrollToWeek (Lkotlinx/datetime/LocalDate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndDate ()Lkotlinx/datetime/LocalDate; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo; + public final fun getStartDate ()Lkotlinx/datetime/LocalDate; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToWeek (Lkotlinx/datetime/LocalDate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndDate (Lkotlinx/datetime/LocalDate;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setStartDate (Lkotlinx/datetime/LocalDate;)V +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarStateKt { + public static final fun rememberWeekCalendarState (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Ljava/time/DayOfWeek;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState; +} + +public final class com/kizitonwose/calendar/core/CalendarDay { + public static final field $stable I + public fun (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)V + public final fun component1 ()Lkotlinx/datetime/LocalDate; + public final fun component2 ()Lcom/kizitonwose/calendar/core/DayPosition; + public final fun copy (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)Lcom/kizitonwose/calendar/core/CalendarDay; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/CalendarDay;Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/CalendarDay; + public fun equals (Ljava/lang/Object;)Z + public final fun getDate ()Lkotlinx/datetime/LocalDate; + public final fun getPosition ()Lcom/kizitonwose/calendar/core/DayPosition; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/CalendarMonth { + public static final field $stable I + public final fun component1 ()Lcom/kizitonwose/calendar/core/YearMonth; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lcom/kizitonwose/calendar/core/YearMonth;Ljava/util/List;)Lcom/kizitonwose/calendar/core/CalendarMonth; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/CalendarMonth;Lcom/kizitonwose/calendar/core/YearMonth;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun equals (Ljava/lang/Object;)Z + public final fun getWeekDays ()Ljava/util/List; + public final fun getYearMonth ()Lcom/kizitonwose/calendar/core/YearMonth; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/ConvertersKt { + public static final fun toJavaYearMonth (Lcom/kizitonwose/calendar/core/YearMonth;)Ljava/time/YearMonth; + public static final fun toKotlinYearMonth (Ljava/time/YearMonth;)Lcom/kizitonwose/calendar/core/YearMonth; +} + +public final class com/kizitonwose/calendar/core/DayPosition : java/lang/Enum { + public static final field InDate Lcom/kizitonwose/calendar/core/DayPosition; + public static final field MonthDate Lcom/kizitonwose/calendar/core/DayPosition; + public static final field OutDate Lcom/kizitonwose/calendar/core/DayPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/DayPosition; + public static fun values ()[Lcom/kizitonwose/calendar/core/DayPosition; +} + +public final class com/kizitonwose/calendar/core/ExtensionsKt { + public static final fun daysOfWeek (Ljava/time/DayOfWeek;)Ljava/util/List; + public static synthetic fun daysOfWeek$default (Ljava/time/DayOfWeek;ILjava/lang/Object;)Ljava/util/List; + public static final fun getYearMonth (Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/core/YearMonth; + public static final fun now (Lkotlinx/datetime/LocalDate$Companion;Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; + public static synthetic fun now$default (Lkotlinx/datetime/LocalDate$Companion;Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;ILjava/lang/Object;)Lkotlinx/datetime/LocalDate; +} + +public final class com/kizitonwose/calendar/core/Extensions_jvmKt { + public static final fun firstDayOfWeekFromLocale (Landroidx/compose/ui/text/intl/Locale;)Ljava/time/DayOfWeek; + public static synthetic fun firstDayOfWeekFromLocale$default (Landroidx/compose/ui/text/intl/Locale;ILjava/lang/Object;)Ljava/time/DayOfWeek; +} + +public final class com/kizitonwose/calendar/core/OutDateStyle : java/lang/Enum { + public static final field EndOfGrid Lcom/kizitonwose/calendar/core/OutDateStyle; + public static final field EndOfRow Lcom/kizitonwose/calendar/core/OutDateStyle; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/OutDateStyle; + public static fun values ()[Lcom/kizitonwose/calendar/core/OutDateStyle; +} + +public final class com/kizitonwose/calendar/core/Week { + public static final field $stable I + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/kizitonwose/calendar/core/Week; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/Week;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/Week; + public fun equals (Ljava/lang/Object;)Z + public final fun getDays ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/WeekDay { + public static final field $stable I + public fun (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;)V + public final fun component1 ()Lkotlinx/datetime/LocalDate; + public final fun component2 ()Lcom/kizitonwose/calendar/core/WeekDayPosition; + public final fun copy (Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;)Lcom/kizitonwose/calendar/core/WeekDay; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/WeekDay;Lkotlinx/datetime/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/WeekDay; + public fun equals (Ljava/lang/Object;)Z + public final fun getDate ()Lkotlinx/datetime/LocalDate; + public final fun getPosition ()Lcom/kizitonwose/calendar/core/WeekDayPosition; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/WeekDayPosition : java/lang/Enum { + public static final field InDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static final field OutDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static final field RangeDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static fun values ()[Lcom/kizitonwose/calendar/core/WeekDayPosition; +} + +public final class com/kizitonwose/calendar/core/YearMonth : java/io/Serializable, java/lang/Comparable { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/core/YearMonth$Companion; + public fun (II)V + public fun (ILjava/time/Month;)V + public fun compareTo (Lcom/kizitonwose/calendar/core/YearMonth;)I + public synthetic fun compareTo (Ljava/lang/Object;)I + public final fun component1 ()I + public final fun component2 ()Ljava/time/Month; + public final fun copy (ILjava/time/Month;)Lcom/kizitonwose/calendar/core/YearMonth; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/YearMonth;ILjava/time/Month;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/YearMonth; + public fun equals (Ljava/lang/Object;)Z + public final fun getMonth ()Ljava/time/Month; + public final fun getYear ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/YearMonth$Companion { + public final fun now (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lcom/kizitonwose/calendar/core/YearMonth; + public static synthetic fun now$default (Lcom/kizitonwose/calendar/core/YearMonth$Companion;Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/YearMonth; +} + +public final class com/kizitonwose/calendar/core/YearMonthKt { + public static final fun atDay (Lcom/kizitonwose/calendar/core/YearMonth;I)Lkotlinx/datetime/LocalDate; + public static final fun atEndOfMonth (Lcom/kizitonwose/calendar/core/YearMonth;)Lkotlinx/datetime/LocalDate; + public static final fun atStartOfMonth (Lcom/kizitonwose/calendar/core/YearMonth;)Lkotlinx/datetime/LocalDate; + public static final fun lengthOfMonth (Lcom/kizitonwose/calendar/core/YearMonth;)I + public static final fun minus (Lcom/kizitonwose/calendar/core/YearMonth;ILkotlinx/datetime/DateTimeUnit$MonthBased;)Lcom/kizitonwose/calendar/core/YearMonth; + public static final fun monthsUntil (Lcom/kizitonwose/calendar/core/YearMonth;Lcom/kizitonwose/calendar/core/YearMonth;)I + public static final fun plus (Lcom/kizitonwose/calendar/core/YearMonth;ILkotlinx/datetime/DateTimeUnit$MonthBased;)Lcom/kizitonwose/calendar/core/YearMonth; +} + +public final class com/kizitonwose/calendar/data/WeekData { + public static final field $stable I + public final fun copy (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/data/WeekData; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/WeekData;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;ILjava/lang/Object;)Lcom/kizitonwose/calendar/data/WeekData; + public fun equals (Ljava/lang/Object;)Z + public final fun getWeek ()Lcom/kizitonwose/calendar/core/Week; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/data/WeekDataKt { + public static final fun getWeekCalendarAdjustedRange (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Ljava/time/DayOfWeek;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public static final fun getWeekCalendarData (Lkotlinx/datetime/LocalDate;ILkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/data/WeekData; + public static final fun getWeekIndex (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)I + public static final fun getWeekIndicesCount (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)I +} + +public final class com/kizitonwose/calendar/data/WeekDateRange { + public static final field $stable I + public fun (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)V + public final fun component1 ()Lkotlinx/datetime/LocalDate; + public final fun component2 ()Lkotlinx/datetime/LocalDate; + public final fun copy (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/WeekDateRange;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;ILjava/lang/Object;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public fun equals (Ljava/lang/Object;)Z + public final fun getEndDateAdjusted ()Lkotlinx/datetime/LocalDate; + public final fun getStartDateAdjusted ()Lkotlinx/datetime/LocalDate; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/compose-multiplatform/library/build.gradle.kts b/compose-multiplatform/library/build.gradle.kts new file mode 100644 index 00000000..e405c8e1 --- /dev/null +++ b/compose-multiplatform/library/build.gradle.kts @@ -0,0 +1,116 @@ +import com.kizitonwose.calendar.buildsrc.Android +import com.kizitonwose.calendar.buildsrc.Config +import com.kizitonwose.calendar.buildsrc.Versions +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidLibrary) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.mavenPublish) +} + +kotlin { + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "calendar" + browser {} + binaries.library() + } + + androidTarget { + publishLibraryVariants("release") + } + + jvm("desktop") + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + applyDefaultHierarchyTemplate() + + sourceSets { + val commonMain by getting + val wasmJsMain by getting + val nativeMain by getting + val desktopMain by getting + val androidMain by getting + val jvmMain by creating { + dependsOn(commonMain) + } + androidMain.dependsOn(jvmMain) + androidMain.dependencies { + implementation(compose.preview) + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + api(libs.kotlinx.datetime) + } + val nonJvmMain by creating { + dependsOn(commonMain) + nativeMain.dependsOn(this) + wasmJsMain.dependsOn(this) + dependencies {} + } + desktopMain.dependsOn(jvmMain) + desktopMain.dependencies { + implementation(compose.desktop.currentOs) + } + } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} + +android { + namespace = "com.kizitonwose.calendar.compose.multiplatform" + compileSdk = Android.compileSdk + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + + defaultConfig { + minSdk = Android.minSdkComposeLibrary + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + java { + toolchain { + languageVersion.set(Config.compatibleJavaLanguageVersion) + } + } + kotlin { + jvmToolchain { + languageVersion.set(Config.compatibleJavaLanguageVersion) + } + } + buildFeatures { + compose = true + } + dependencies { + debugImplementation(compose.uiTooling) + } +} + +mavenPublishing { + coordinates(version = Versions.multiplatfrom) +} diff --git a/compose-multiplatform/library/gradle.properties b/compose-multiplatform/library/gradle.properties new file mode 100644 index 00000000..c4254cbc --- /dev/null +++ b/compose-multiplatform/library/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=compose-multiplatform +POM_DESCRIPTION=A highly customizable calendar library for Compose Multiplatform, backed by LazyRow/LazyColumn. +POM_PACKAGING=aar diff --git a/compose-multiplatform/library/src/androidMain/AndroidManifest.xml b/compose-multiplatform/library/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/compose-multiplatform/library/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/Calendar.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/Calendar.kt new file mode 100644 index 00000000..981d03e9 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/Calendar.kt @@ -0,0 +1,295 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.kizitonwose.calendar.compose.CalendarDefaults.flingBehavior +import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapCalendarImpl +import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapCalendarState +import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapWeek +import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapWeekHeaderPosition +import com.kizitonwose.calendar.compose.heatmapcalendar.rememberHeatMapCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarImpl +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.Week +import com.kizitonwose.calendar.core.WeekDay +import kotlinx.datetime.DayOfWeek + +/** + * A horizontally scrolling calendar. + * + * @param modifier the modifier to apply to this calendar. + * @param state the state object to be used to control or observe the calendar's properties. + * Examples: `startMonth`, `endMonth`, `firstDayOfWeek`, `firstVisibleMonth`, `outDateStyle`. + * @param calendarScrollPaged the scrolling behavior of the calendar. When `true`, the calendar will + * snap to the nearest month after a scroll or swipe action. When `false`, the calendar scrolls normally. + * @param userScrollEnabled whether the scrolling via the user gestures or accessibility actions + * is allowed. You can still scroll programmatically using the state even when it is disabled. + * @param reverseLayout reverse the direction of scrolling and layout. When `true`, months will be + * composed from the end to the start and [CalendarState.startMonth] will be located at the end. + * @param contentPadding a padding around the whole calendar. This will add padding for the + * content after it has been clipped, which is not possible via [modifier] param. You can use it + * to add a padding before the first month or after the last one. If you want to add a spacing + * between each month use the [monthContainer] composable. + * @param contentHeightMode Determines how the height of the day content is calculated. + * @param dayContent a composable block which describes the day content. + * @param monthHeader a composable block which describes the month header content. The header is + * placed above each month on the calendar. + * @param monthBody a composable block which describes the month body content. This is the container + * where all the month days are placed, excluding the header and footer. This is useful if you + * want to customize the day container, for example, with a background color or other effects. + * The actual body content is provided in the block and must be called after your desired + * customisations are rendered. + * @param monthFooter a composable block which describes the month footer content. The footer is + * placed below each month on the calendar. + * @param monthContainer a composable block which describes the entire month content. This is the + * container where all the month contents are placed (header => days => footer). This is useful if + * you want to customize the month container, for example, with a background color or other effects. + * The actual container content is provided in the block and must be called after your desired + * customisations are rendered. + */ +@Composable +public fun HorizontalCalendar( + modifier: Modifier = Modifier, + state: CalendarState = rememberCalendarState(), + calendarScrollPaged: Boolean = true, + userScrollEnabled: Boolean = true, + reverseLayout: Boolean = false, + contentPadding: PaddingValues = PaddingValues(0.dp), + contentHeightMode: ContentHeightMode = ContentHeightMode.Wrap, + dayContent: @Composable BoxScope.(CalendarDay) -> Unit, + monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, + monthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit)? = null, + monthFooter: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, + monthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit)? = null, +): Unit = Calendar( + modifier = modifier, + state = state, + calendarScrollPaged = calendarScrollPaged, + userScrollEnabled = userScrollEnabled, + isHorizontal = true, + reverseLayout = reverseLayout, + contentHeightMode = contentHeightMode, + dayContent = dayContent, + monthHeader = monthHeader, + monthBody = monthBody, + monthFooter = monthFooter, + monthContainer = monthContainer, + contentPadding = contentPadding, +) + +/** + * A vertically scrolling calendar. + * + * @param modifier the modifier to apply to this calendar. + * @param state the state object to be used to control or observe the calendar's properties. + * Examples: `startMonth`, `endMonth`, `firstDayOfWeek`, `firstVisibleMonth`, `outDateStyle`. + * @param calendarScrollPaged the scrolling behavior of the calendar. When `true`, the calendar will + * snap to the nearest month after a scroll or swipe action. When `false`, the calendar scrolls normally. + * @param userScrollEnabled whether the scrolling via the user gestures or accessibility actions + * is allowed. You can still scroll programmatically using the state even when it is disabled. + * @param reverseLayout reverse the direction of scrolling and layout. When `true`, months will be + * composed from the end to the start and [CalendarState.startMonth] will be located at the end. + * @param contentPadding a padding around the whole calendar. This will add padding for the + * content after it has been clipped, which is not possible via [modifier] param. You can use it + * to add a padding before the first month or after the last one. If you want to add a spacing + * between each month use the [monthContainer] composable. + * @param contentHeightMode Determines how the height of the day content is calculated. + * @param dayContent a composable block which describes the day content. + * @param monthHeader a composable block which describes the month header content. The header is + * placed above each month on the calendar. + * @param monthBody a composable block which describes the month body content. This is the container + * where all the month days are placed, excluding the header and footer. This is useful if you + * want to customize the day container, for example, with a background color or other effects. + * The actual body content is provided in the block and must be called after your desired + * customisations are rendered. + * @param monthFooter a composable block which describes the month footer content. The footer is + * placed below each month on the calendar. + * @param monthContainer a composable block which describes the entire month content. This is the + * container where all the month contents are placed (header => days => footer). This is useful if + * you want to customize the month container, for example, with a background color or other effects. + * The actual container content is provided in the block and must be called after your desired + * customisations are rendered. + */ +@Composable +public fun VerticalCalendar( + modifier: Modifier = Modifier, + state: CalendarState = rememberCalendarState(), + calendarScrollPaged: Boolean = false, + userScrollEnabled: Boolean = true, + reverseLayout: Boolean = false, + contentPadding: PaddingValues = PaddingValues(0.dp), + contentHeightMode: ContentHeightMode = ContentHeightMode.Wrap, + dayContent: @Composable BoxScope.(CalendarDay) -> Unit, + monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, + monthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit)? = null, + monthFooter: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, + monthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit)? = null, +): Unit = Calendar( + modifier = modifier, + state = state, + calendarScrollPaged = calendarScrollPaged, + userScrollEnabled = userScrollEnabled, + isHorizontal = false, + reverseLayout = reverseLayout, + contentHeightMode = contentHeightMode, + dayContent = dayContent, + monthHeader = monthHeader, + monthBody = monthBody, + monthFooter = monthFooter, + monthContainer = monthContainer, + contentPadding = contentPadding, +) + +@Composable +private fun Calendar( + modifier: Modifier, + state: CalendarState, + calendarScrollPaged: Boolean, + userScrollEnabled: Boolean, + isHorizontal: Boolean, + reverseLayout: Boolean, + contentPadding: PaddingValues, + contentHeightMode: ContentHeightMode, + dayContent: @Composable BoxScope.(CalendarDay) -> Unit, + monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)?, + monthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit)?, + monthFooter: (@Composable ColumnScope.(CalendarMonth) -> Unit)?, + monthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit)?, +) { + if (isHorizontal) { + LazyRow( + modifier = modifier, + state = state.listState, + flingBehavior = flingBehavior(calendarScrollPaged, state.listState), + userScrollEnabled = userScrollEnabled, + reverseLayout = reverseLayout, + contentPadding = contentPadding, + ) { + CalendarMonths( + monthCount = state.calendarInfo.indexCount, + monthData = { offset -> state.store[offset] }, + contentHeightMode = contentHeightMode, + dayContent = dayContent, + monthHeader = monthHeader, + monthBody = monthBody, + monthFooter = monthFooter, + monthContainer = monthContainer, + ) + } + } else { + LazyColumn( + modifier = modifier, + state = state.listState, + flingBehavior = flingBehavior(calendarScrollPaged, state.listState), + userScrollEnabled = userScrollEnabled, + reverseLayout = reverseLayout, + contentPadding = contentPadding, + ) { + CalendarMonths( + monthCount = state.calendarInfo.indexCount, + monthData = { offset -> state.store[offset] }, + contentHeightMode = contentHeightMode, + dayContent = dayContent, + monthHeader = monthHeader, + monthBody = monthBody, + monthFooter = monthFooter, + monthContainer = monthContainer, + ) + } + } +} + +/** + * A horizontally scrolling week calendar. + * + * @param modifier the modifier to apply to this calendar. + * @param state the state object to be used to control or observe the calendar's properties. + * Examples: `startDate`, `endDate`, `firstDayOfWeek`, `firstVisibleWeek`. + * @param calendarScrollPaged the scrolling behavior of the calendar. When `true`, the calendar will + * snap to the nearest week after a scroll or swipe action. When `false`, the calendar scrolls normally. + * Note that when `false`, you should set the desired day width on the [dayContent] composable. + * @param userScrollEnabled whether the scrolling via the user gestures or accessibility actions + * is allowed. You can still scroll programmatically using the state even when it is disabled. + * @param reverseLayout reverse the direction of scrolling and layout. When `true`, weeks will be + * composed from the end to the start and [WeekCalendarState.startDate] will be located at the end. + * @param contentPadding a padding around the whole calendar. This will add padding for the + * content after it has been clipped, which is not possible via [modifier] param. You can use it + * to add a padding before the first week or after the last one. + * @param dayContent a composable block which describes the day content. + * @param weekHeader a composable block which describes the week header content. The header is + * placed above each week on the calendar. + * @param weekFooter a composable block which describes the week footer content. The footer is + * placed below each week on the calendar. + */ +@Composable +public fun WeekCalendar( + modifier: Modifier = Modifier, + state: WeekCalendarState = rememberWeekCalendarState(), + calendarScrollPaged: Boolean = true, + userScrollEnabled: Boolean = true, + reverseLayout: Boolean = false, + contentPadding: PaddingValues = PaddingValues(0.dp), + dayContent: @Composable BoxScope.(WeekDay) -> Unit, + weekHeader: (@Composable ColumnScope.(Week) -> Unit)? = null, + weekFooter: (@Composable ColumnScope.(Week) -> Unit)? = null, +): Unit = WeekCalendarImpl( + modifier = modifier, + state = state, + calendarScrollPaged = calendarScrollPaged, + userScrollEnabled = userScrollEnabled, + reverseLayout = reverseLayout, + dayContent = dayContent, + weekHeader = weekHeader, + weekFooter = weekFooter, + contentPadding = contentPadding, +) + +/** + * A horizontal scrolling heatmap calendar, useful for showing how data changes over time. + * A popular example is the user contribution chart on GitHub. + * + * @param modifier the modifier to apply to this calendar. + * @param state the state object to be used to control or observe the calendar's properties. + * Examples: `startMonth`, `endMonth`, `firstDayOfWeek`, `firstVisibleMonth`. + * @param weekHeaderPosition Determines the position for the [weekHeader] composable. + * @param userScrollEnabled whether the scrolling via the user gestures or accessibility actions + * is allowed. You can still scroll programmatically using the state even when it is disabled. + * @param contentPadding a padding around the whole calendar. This will add padding for the + * content after it has been clipped, which is not possible via [modifier] param. You can use it + * to add a padding before the first month or after the last one. + * @param dayContent a composable block which describes the day content. + * @param weekHeader a composable block which describes the day of week (Mon, Tue, Wed...) on the + * horizontal axis of the calendar. The position is determined by the [weekHeaderPosition] property. + * @param monthHeader a composable block which describes the month header content. The header is + * placed above each month on the calendar. + */ +@Composable +public fun HeatMapCalendar( + modifier: Modifier = Modifier, + state: HeatMapCalendarState = rememberHeatMapCalendarState(), + weekHeaderPosition: HeatMapWeekHeaderPosition = HeatMapWeekHeaderPosition.Start, + userScrollEnabled: Boolean = true, + contentPadding: PaddingValues = PaddingValues(0.dp), + dayContent: @Composable ColumnScope.(day: CalendarDay, week: HeatMapWeek) -> Unit, + weekHeader: (@Composable ColumnScope.(DayOfWeek) -> Unit)? = null, + monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, +): Unit = HeatMapCalendarImpl( + modifier = modifier, + state = state, + weekHeaderPosition = weekHeaderPosition, + userScrollEnabled = userScrollEnabled, + dayContent = dayContent, + weekHeader = weekHeader, + monthHeader = monthHeader, + contentPadding = contentPadding, +) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarDefaults.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarDefaults.kt new file mode 100644 index 00000000..5bb39f4e --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarDefaults.kt @@ -0,0 +1,50 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.ScrollableDefaults +import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider +import androidx.compose.foundation.gestures.snapping.SnapPosition +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember + +internal object CalendarDefaults { + /** + * The default implementation in [rememberSnapFlingBehavior] snaps to the center of the layout + * but we want to snap to the start. For example, in a vertical calendar, when the layout size + * is larger than the item size(e.g two or more visible months), we don't want the item's + * center to be at the center of the layout when it snaps, instead we want the item's top + * to be at the top of the layout. + */ + @OptIn(ExperimentalFoundationApi::class) + @Composable + private fun pagedFlingBehavior(state: LazyListState): FlingBehavior { + val snappingLayout = remember(state) { + val provider = SnapLayoutInfoProvider(state, SnapPosition.Start) + CalendarSnapLayoutInfoProvider(provider) + } + return rememberSnapFlingBehavior(snappingLayout) + } + + @Composable + private fun continuousFlingBehavior(): FlingBehavior = ScrollableDefaults.flingBehavior() + + @Composable + fun flingBehavior(isPaged: Boolean, state: LazyListState): FlingBehavior { + return if (isPaged) pagedFlingBehavior(state) else continuousFlingBehavior() + } +} + +@ExperimentalFoundationApi +@Suppress("FunctionName") +private fun CalendarSnapLayoutInfoProvider( + snapLayoutInfoProvider: SnapLayoutInfoProvider, +): SnapLayoutInfoProvider = object : SnapLayoutInfoProvider by snapLayoutInfoProvider { + /** + * In compose 1.3, the default was single page snapping (zero), but this changed + * in compose 1.4 to decayed page snapping which is not great for calendar usage. + */ + override fun calculateApproachOffset(velocity: Float, decayOffset: Float): Float = 0f +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarInfo.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarInfo.kt new file mode 100644 index 00000000..52107343 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarInfo.kt @@ -0,0 +1,12 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.runtime.Immutable +import com.kizitonwose.calendar.core.OutDateStyle +import kotlinx.datetime.DayOfWeek + +@Immutable +internal data class CalendarInfo( + val indexCount: Int, + private val firstDayOfWeek: DayOfWeek? = null, + private val outDateStyle: OutDateStyle? = null, +) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt new file mode 100644 index 00000000..6fce652c --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt @@ -0,0 +1,33 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListLayoutInfo +import com.kizitonwose.calendar.core.CalendarMonth + +/** + * Contains useful information about the currently displayed layout state of the calendar. + * For example you can get the list of currently displayed months. + * + * Use [CalendarState.layoutInfo] to retrieve this. + * @see LazyListLayoutInfo + */ +public class CalendarLayoutInfo(info: LazyListLayoutInfo, private val month: (Int) -> CalendarMonth) : + LazyListLayoutInfo by info { + /** + * The list of [CalendarItemInfo] representing all the currently visible months. + */ + public val visibleMonthsInfo: List + get() = visibleItemsInfo.map { + CalendarItemInfo(it, month(it.index)) + } +} + +/** + * Contains useful information about an individual [CalendarMonth] on the calendar. + * + * @param month The month in the list. + * + * @see CalendarLayoutInfo + * @see LazyListItemInfo + */ +public class CalendarItemInfo(info: LazyListItemInfo, public val month: CalendarMonth) : LazyListItemInfo by info diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarMonths.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarMonths.kt new file mode 100644 index 00000000..1b246f67 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarMonths.kt @@ -0,0 +1,89 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import com.kizitonwose.calendar.core.CalendarMonth + +@Suppress("FunctionName") +internal fun LazyListScope.CalendarMonths( + monthCount: Int, + monthData: (offset: Int) -> CalendarMonth, + contentHeightMode: ContentHeightMode, + dayContent: @Composable BoxScope.(com.kizitonwose.calendar.core.CalendarDay) -> Unit, + monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)?, + monthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit)?, + monthFooter: (@Composable ColumnScope.(CalendarMonth) -> Unit)?, + monthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit)?, +) { + items( + count = monthCount, + key = { offset -> monthData(offset).yearMonth }, + ) { offset -> + val month = monthData(offset) + val fillHeight = when (contentHeightMode) { + ContentHeightMode.Wrap -> false + ContentHeightMode.Fill -> true + } + val hasContainer = monthContainer != null + monthContainer.or(defaultMonthContainer)(month) { + Column( + modifier = Modifier + .then(if (hasContainer) Modifier.fillMaxWidth() else Modifier.fillParentMaxWidth()) + .then( + if (fillHeight) { + if (hasContainer) Modifier.fillMaxHeight() else Modifier.fillParentMaxHeight() + } else { + Modifier.wrapContentHeight() + }, + ), + ) { + monthHeader?.invoke(this, month) + monthBody.or(defaultMonthBody)(month) { + Column( + modifier = Modifier + .fillMaxWidth() + .then(if (fillHeight) Modifier.weight(1f) else Modifier.wrapContentHeight()), + ) { + for (week in month.weekDays) { + Row( + modifier = Modifier + .fillMaxWidth() + .then(if (fillHeight) Modifier.weight(1f) else Modifier.wrapContentHeight()), + ) { + for (day in week) { + Box( + modifier = Modifier + .weight(1f) + .clipToBounds(), + ) { + dayContent(day) + } + } + } + } + } + } + monthFooter?.invoke(this, month) + } + } + } +} + +private val defaultMonthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit) = + { _, container -> container() } + +private val defaultMonthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit) = + { _, content -> content() } + +private fun T?.or(default: T) = this ?: default diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarState.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarState.kt new file mode 100644 index 00000000..3ae6bd8a --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/CalendarState.kt @@ -0,0 +1,294 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.foundation.MutatePriority +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.gestures.ScrollableState +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.lazy.LazyListLayoutInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.kizitonwose.calendar.core.OutDateStyle +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.kizitonwose.calendar.core.now +import com.kizitonwose.calendar.data.DataStore +import com.kizitonwose.calendar.data.VisibleItemState +import com.kizitonwose.calendar.data.checkDateRange +import com.kizitonwose.calendar.data.getCalendarMonthData +import com.kizitonwose.calendar.data.getMonthIndex +import com.kizitonwose.calendar.data.getMonthIndicesCount +import kotlinx.datetime.DayOfWeek + +/** + * Creates a [CalendarState] that is remembered across compositions. + * + * @param startMonth the initial value for [CalendarState.startMonth] + * @param endMonth the initial value for [CalendarState.endMonth] + * @param firstDayOfWeek the initial value for [CalendarState.firstDayOfWeek] + * @param firstVisibleMonth the initial value for [CalendarState.firstVisibleMonth] + * @param outDateStyle the initial value for [CalendarState.outDateStyle] + */ +@Composable +public fun rememberCalendarState( + startMonth: YearMonth = YearMonth.now(), + endMonth: YearMonth = startMonth, + firstVisibleMonth: YearMonth = startMonth, + firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale(), + outDateStyle: OutDateStyle = OutDateStyle.EndOfRow, +): CalendarState { + return rememberSaveable( + inputs = arrayOf( + startMonth, + endMonth, + firstVisibleMonth, + firstDayOfWeek, + outDateStyle, + ), + saver = CalendarState.Saver, + ) { + CalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstDayOfWeek = firstDayOfWeek, + firstVisibleMonth = firstVisibleMonth, + outDateStyle = outDateStyle, + visibleItemState = null, + ) + } +} + +/** + * A state object that can be hoisted to control and observe calendar properties. + * + * This should be created via [rememberCalendarState]. + * + * @param startMonth the first month on the calendar. + * @param endMonth the last month on the calendar. + * @param firstDayOfWeek the first day of week on the calendar. + * @param firstVisibleMonth the initial value for [CalendarState.firstVisibleMonth] + * @param outDateStyle the preferred style for out date generation. + */ +@Stable +public class CalendarState internal constructor( + startMonth: YearMonth, + endMonth: YearMonth, + firstDayOfWeek: DayOfWeek, + firstVisibleMonth: YearMonth, + outDateStyle: OutDateStyle, + visibleItemState: VisibleItemState?, +) : ScrollableState { + /** Backing state for [startMonth] */ + private var _startMonth by mutableStateOf(startMonth) + + /** The first month on the calendar. */ + public var startMonth: YearMonth + get() = _startMonth + set(value) { + if (value != startMonth) { + _startMonth = value + monthDataChanged() + } + } + + /** Backing state for [endMonth] */ + private var _endMonth by mutableStateOf(endMonth) + + /** The last month on the calendar. */ + public var endMonth: YearMonth + get() = _endMonth + set(value) { + if (value != endMonth) { + _endMonth = value + monthDataChanged() + } + } + + /** Backing state for [firstDayOfWeek] */ + private var _firstDayOfWeek by mutableStateOf(firstDayOfWeek) + + /** The first day of week on the calendar. */ + public var firstDayOfWeek: DayOfWeek + get() = _firstDayOfWeek + set(value) { + if (value != firstDayOfWeek) { + _firstDayOfWeek = value + monthDataChanged() + } + } + + /** Backing state for [outDateStyle] */ + private var _outDateStyle by mutableStateOf(outDateStyle) + + /** The preferred style for out date generation. */ + public var outDateStyle: OutDateStyle + get() = _outDateStyle + set(value) { + if (value != outDateStyle) { + _outDateStyle = value + monthDataChanged() + } + } + + /** + * The first month that is visible. + * + * @see [lastVisibleMonth] + */ + public val firstVisibleMonth: com.kizitonwose.calendar.core.CalendarMonth by derivedStateOf { + store[listState.firstVisibleItemIndex] + } + + /** + * The last month that is visible. + * + * @see [firstVisibleMonth] + */ + public val lastVisibleMonth: com.kizitonwose.calendar.core.CalendarMonth by derivedStateOf { + store[listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0] + } + + /** + * The object of [CalendarLayoutInfo] calculated during the last layout pass. For example, + * you can use it to calculate what items are currently visible. + * + * Note that this property is observable and is updated after every scroll or remeasure. + * If you use it in the composable function it will be recomposed on every change causing + * potential performance issues including infinity recomposition loop. + * Therefore, avoid using it in the composition. + * + * If you need to use it in the composition then consider wrapping the calculation into a + * derived state in order to only have recompositions when the derived value changes. + * See Example6Page in the sample app for usage. + * + * If you want to run some side effects like sending an analytics event or updating a state + * based on this value consider using "snapshotFlow". + * + * see [LazyListLayoutInfo] + */ + public val layoutInfo: CalendarLayoutInfo + get() = CalendarLayoutInfo(listState.layoutInfo) { index -> store[index] } + + /** + * [InteractionSource] that will be used to dispatch drag events when this + * calendar is being dragged. If you want to know whether the fling (or animated scroll) is in + * progress, use [isScrollInProgress]. + */ + public val interactionSource: InteractionSource + get() = listState.interactionSource + + internal val listState = LazyListState( + firstVisibleItemIndex = visibleItemState?.firstVisibleItemIndex + ?: getScrollIndex(firstVisibleMonth) ?: 0, + firstVisibleItemScrollOffset = visibleItemState?.firstVisibleItemScrollOffset ?: 0, + ) + + internal var calendarInfo by mutableStateOf(CalendarInfo(indexCount = 0)) + + internal val store = DataStore { offset -> + getCalendarMonthData( + startMonth = this.startMonth, + offset = offset, + firstDayOfWeek = this.firstDayOfWeek, + outDateStyle = this.outDateStyle, + ).calendarMonth + } + + init { + monthDataChanged() // Update monthIndexCount initially. + } + + private fun monthDataChanged() { + store.clear() + checkDateRange(startMonth, endMonth) + // Read the firstDayOfWeek and outDateStyle properties to ensure recomposition + // even though they are unused in the CalendarInfo. Alternatively, we could use + // mutableStateMapOf() as the backing store for DataStore() to ensure recomposition + // but not sure how compose handles recomposition of a lazy list that reads from + // such map when an entry unrelated to the visible indices changes. + calendarInfo = CalendarInfo( + indexCount = getMonthIndicesCount(startMonth, endMonth), + firstDayOfWeek = firstDayOfWeek, + outDateStyle = outDateStyle, + ) + } + + /** + * Instantly brings the [month] to the top of the viewport. + * + * @param month the month to which to scroll. Must be within the + * range of [startMonth] and [endMonth]. + * + * @see [animateScrollToMonth] + */ + public suspend fun scrollToMonth(month: YearMonth) { + listState.scrollToItem(getScrollIndex(month) ?: return) + } + + /** + * Animate (smooth scroll) to the given [month]. + * + * @param month the month to which to scroll. Must be within the + * range of [startMonth] and [endMonth]. + */ + public suspend fun animateScrollToMonth(month: YearMonth) { + listState.animateScrollToItem(getScrollIndex(month) ?: return) + } + + private fun getScrollIndex(month: YearMonth): Int? { + if (month !in startMonth..endMonth) { + println("CalendarState - Attempting to scroll out of range: $month") + return null + } + return getMonthIndex(startMonth, month) + } + + /** + * Whether this [ScrollableState] is currently scrolling by gesture, fling or programmatically. + */ + override val isScrollInProgress: Boolean + get() = listState.isScrollInProgress + + override fun dispatchRawDelta(delta: Float): Float = listState.dispatchRawDelta(delta) + + override suspend fun scroll( + scrollPriority: MutatePriority, + block: suspend ScrollScope.() -> Unit, + ): Unit = listState.scroll(scrollPriority, block) + + public companion object { + internal val Saver: Saver = listSaver( + save = { + listOf( + it.startMonth, + it.endMonth, + it.firstVisibleMonth.yearMonth, + it.firstDayOfWeek, + it.outDateStyle, + it.listState.firstVisibleItemIndex, + it.listState.firstVisibleItemScrollOffset, + ) + }, + restore = { + CalendarState( + startMonth = it[0] as YearMonth, + endMonth = it[1] as YearMonth, + firstVisibleMonth = it[2] as YearMonth, + firstDayOfWeek = it[3] as DayOfWeek, + outDateStyle = it[4] as OutDateStyle, + visibleItemState = VisibleItemState( + firstVisibleItemIndex = it[5] as Int, + firstVisibleItemScrollOffset = it[6] as Int, + ), + ) + }, + ) + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/ContentHeightMode.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/ContentHeightMode.kt new file mode 100644 index 00000000..d7611f71 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/ContentHeightMode.kt @@ -0,0 +1,28 @@ +package com.kizitonwose.calendar.compose + +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.ui.Modifier + +/** + * Determines how the height of the day content is calculated. + */ +public enum class ContentHeightMode { + /** + * The day container will wrap its height. This allows you to + * use [Modifier.aspectRatio] if you want square day content + * or [Modifier.height] if you want a specific height value + * for the day content. + */ + Wrap, + + /** + * The days in each month will spread to fill the parent's height after + * any available header and footer content height has been accounted for. + * This allows you to use [Modifier.fillMaxHeight] for the day content + * height. With this option, your Calendar composable should also + * be created with [Modifier.fillMaxHeight] or [Modifier.height]. + */ + Fill, +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendar.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendar.kt new file mode 100644 index 00000000..875aeb46 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendar.kt @@ -0,0 +1,91 @@ +package com.kizitonwose.calendar.compose.heatmapcalendar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.daysOfWeek +import kotlinx.datetime.DayOfWeek + +@Composable +internal fun HeatMapCalendarImpl( + modifier: Modifier, + state: HeatMapCalendarState, + userScrollEnabled: Boolean, + weekHeaderPosition: HeatMapWeekHeaderPosition, + contentPadding: PaddingValues, + dayContent: @Composable ColumnScope.(day: CalendarDay, week: HeatMapWeek) -> Unit, + weekHeader: (@Composable ColumnScope.(DayOfWeek) -> Unit)? = null, + monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.Bottom, + ) { + if (weekHeaderPosition == HeatMapWeekHeaderPosition.Start && weekHeader != null) { + WeekHeaderColumn( + horizontalAlignment = Alignment.End, + firstDayOfWeek = state.firstDayOfWeek, + weekHeader = weekHeader, + ) + } + LazyRow( + modifier = Modifier.weight(1f), + state = state.listState, + userScrollEnabled = userScrollEnabled, + contentPadding = contentPadding, + ) { + items( + count = state.calendarInfo.indexCount, + key = { offset -> state.store[offset].yearMonth }, + ) { offset -> + val calendarMonth = state.store[offset] + Column(modifier = Modifier.width(IntrinsicSize.Max)) { + monthHeader?.invoke(this, calendarMonth) + Row { + for (week in calendarMonth.weekDays) { + Column { + for (day in week) { + dayContent(day, HeatMapWeek(week)) + } + } + } + } + } + } + } + if (weekHeaderPosition == HeatMapWeekHeaderPosition.End && weekHeader != null) { + WeekHeaderColumn( + horizontalAlignment = Alignment.Start, + firstDayOfWeek = state.firstDayOfWeek, + weekHeader = weekHeader, + ) + } + } +} + +@Composable +private fun WeekHeaderColumn( + horizontalAlignment: Alignment.Horizontal, + firstDayOfWeek: DayOfWeek, + weekHeader: @Composable (ColumnScope.(DayOfWeek) -> Unit), +) { + Column( + modifier = Modifier.width(IntrinsicSize.Max), + verticalArrangement = Arrangement.SpaceEvenly, + horizontalAlignment = horizontalAlignment, + ) { + for (dayOfWeek in daysOfWeek(firstDayOfWeek)) { + weekHeader(dayOfWeek) + } + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt new file mode 100644 index 00000000..bbf0fc97 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt @@ -0,0 +1,265 @@ +package com.kizitonwose.calendar.compose.heatmapcalendar + +import androidx.compose.foundation.MutatePriority +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.gestures.ScrollableState +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.kizitonwose.calendar.compose.CalendarInfo +import com.kizitonwose.calendar.compose.CalendarLayoutInfo +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.kizitonwose.calendar.core.now +import com.kizitonwose.calendar.data.DataStore +import com.kizitonwose.calendar.data.VisibleItemState +import com.kizitonwose.calendar.data.checkDateRange +import com.kizitonwose.calendar.data.getHeatMapCalendarMonthData +import com.kizitonwose.calendar.data.getMonthIndex +import com.kizitonwose.calendar.data.getMonthIndicesCount +import kotlinx.datetime.DayOfWeek + +/** + * Creates a [HeatMapCalendarState] that is remembered across compositions. + * + * @param startMonth the initial value for [HeatMapCalendarState.startMonth] + * @param endMonth the initial value for [HeatMapCalendarState.endMonth] + * @param firstDayOfWeek the initial value for [HeatMapCalendarState.firstDayOfWeek] + * @param firstVisibleMonth the initial value for [HeatMapCalendarState.firstVisibleMonth] + */ +@Composable +public fun rememberHeatMapCalendarState( + startMonth: YearMonth = YearMonth.now(), + endMonth: YearMonth = startMonth, + firstVisibleMonth: YearMonth = startMonth, + firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale(), +): HeatMapCalendarState { + return rememberSaveable( + inputs = arrayOf( + startMonth, + endMonth, + firstVisibleMonth, + firstDayOfWeek, + ), + saver = HeatMapCalendarState.Saver, + ) { + HeatMapCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstDayOfWeek = firstDayOfWeek, + firstVisibleMonth = firstVisibleMonth, + visibleItemState = null, + ) + } +} + +/** + * A state object that can be hoisted to control and observe calendar properties. + * + * This should be created via [rememberHeatMapCalendarState]. + * + * @param startMonth the first month on the calendar. + * @param endMonth the last month on the calendar. + * @param firstDayOfWeek the first day of week on the calendar. + * @param firstVisibleMonth the initial value for [HeatMapCalendarState.firstVisibleMonth] + */ +@Stable +public class HeatMapCalendarState internal constructor( + startMonth: YearMonth, + endMonth: YearMonth, + firstVisibleMonth: YearMonth, + firstDayOfWeek: DayOfWeek, + visibleItemState: VisibleItemState?, +) : ScrollableState { + /** Backing state for [startMonth] */ + private var _startMonth by mutableStateOf(startMonth) + + /** The first month on the calendar. */ + public var startMonth: YearMonth + get() = _startMonth + set(value) { + if (value != startMonth) { + _startMonth = value + monthDataChanged() + } + } + + /** Backing state for [endMonth] */ + private var _endMonth by mutableStateOf(endMonth) + + /** The last month on the calendar. */ + public var endMonth: YearMonth + get() = _endMonth + set(value) { + if (value != endMonth) { + _endMonth = value + monthDataChanged() + } + } + + /** Backing state for [firstDayOfWeek] */ + private var _firstDayOfWeek by mutableStateOf(firstDayOfWeek) + + /** The first day of week on the calendar. */ + public var firstDayOfWeek: DayOfWeek + get() = _firstDayOfWeek + set(value) { + if (value != firstDayOfWeek) { + _firstDayOfWeek = value + monthDataChanged() + } + } + + /** + * The first month that is visible. + * + * @see [lastVisibleMonth] + */ + public val firstVisibleMonth: CalendarMonth by derivedStateOf { + store[listState.firstVisibleItemIndex] + } + + /** + * The last month that is visible. + * + * @see [firstVisibleMonth] + */ + public val lastVisibleMonth: CalendarMonth by derivedStateOf { + store[listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0] + } + + /** + * The object of [CalendarLayoutInfo] calculated during the last layout pass. For example, + * you can use it to calculate what items are currently visible. + * + * Note that this property is observable and is updated after every scroll or remeasure. + * If you use it in the composable function it will be recomposed on every change causing + * potential performance issues including infinity recomposition loop. + * Therefore, avoid using it in the composition. + * + * If you need to use it in the composition then consider wrapping the calculation into a + * derived state in order to only have recompositions when the derived value changes. + * See Example6Page in the sample app for usage. + * + * If you want to run some side effects like sending an analytics event or updating a state + * based on this value consider using "snapshotFlow". + */ + public val layoutInfo: CalendarLayoutInfo + get() = CalendarLayoutInfo(listState.layoutInfo) { index -> store[index] } + + /** + * [InteractionSource] that will be used to dispatch drag events when this + * calendar is being dragged. If you want to know whether the fling (or animated scroll) is in + * progress, use [isScrollInProgress]. + */ + public val interactionSource: InteractionSource + get() = listState.interactionSource + + internal val listState = LazyListState( + firstVisibleItemIndex = visibleItemState?.firstVisibleItemIndex + ?: getScrollIndex(firstVisibleMonth) ?: 0, + firstVisibleItemScrollOffset = visibleItemState?.firstVisibleItemScrollOffset ?: 0, + ) + + internal var calendarInfo by mutableStateOf(CalendarInfo(indexCount = 0)) + + internal val store = DataStore { offset -> + getHeatMapCalendarMonthData( + startMonth = this.startMonth, + offset = offset, + firstDayOfWeek = this.firstDayOfWeek, + ).calendarMonth + } + + init { + monthDataChanged() // Update monthIndexCount initially. + } + + private fun monthDataChanged() { + store.clear() + checkDateRange(startMonth, endMonth) + calendarInfo = CalendarInfo( + indexCount = getMonthIndicesCount(startMonth, endMonth), + firstDayOfWeek = firstDayOfWeek, + ) + } + + /** + * Instantly brings the [month] to the top of the viewport. + * + * @param month the month to which to scroll. Must be within the + * range of [startMonth] and [endMonth]. + * + * @see [animateScrollToMonth] + */ + public suspend fun scrollToMonth(month: YearMonth) { + listState.scrollToItem(getScrollIndex(month) ?: return) + } + + /** + * Animate (smooth scroll) to the given [month]. + * + * @param month the month to which to scroll. Must be within the + * range of [startMonth] and [endMonth]. + */ + public suspend fun animateScrollToMonth(month: YearMonth) { + listState.animateScrollToItem(getScrollIndex(month) ?: return) + } + + private fun getScrollIndex(month: YearMonth): Int? { + if (month !in startMonth..endMonth) { + println("CalendarState - Attempting to scroll out of range: $month") + return null + } + return getMonthIndex(startMonth, month) + } + + /** + * Whether this [ScrollableState] is currently scrolling by gesture, fling or programmatically. + */ + override val isScrollInProgress: Boolean + get() = listState.isScrollInProgress + + override fun dispatchRawDelta(delta: Float): Float = listState.dispatchRawDelta(delta) + + override suspend fun scroll( + scrollPriority: MutatePriority, + block: suspend ScrollScope.() -> Unit, + ): Unit = listState.scroll(scrollPriority, block) + + public companion object { + internal val Saver: Saver = listSaver( + save = { + listOf( + it.startMonth, + it.endMonth, + it.firstVisibleMonth.yearMonth, + it.firstDayOfWeek, + it.listState.firstVisibleItemIndex, + it.listState.firstVisibleItemScrollOffset, + ) + }, + restore = { + HeatMapCalendarState( + startMonth = it[0] as YearMonth, + endMonth = it[1] as YearMonth, + firstVisibleMonth = it[2] as YearMonth, + firstDayOfWeek = it[3] as DayOfWeek, + visibleItemState = VisibleItemState( + firstVisibleItemIndex = it[4] as Int, + firstVisibleItemScrollOffset = it[5] as Int, + ), + ) + }, + ) + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt new file mode 100644 index 00000000..b0ebf682 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt @@ -0,0 +1,18 @@ +package com.kizitonwose.calendar.compose.heatmapcalendar + +import androidx.compose.runtime.Immutable +import com.kizitonwose.calendar.compose.HeatMapCalendar +import com.kizitonwose.calendar.core.CalendarDay + +/** + * Represents a week on the heatmap calendar. + * + * This model exists only as a wrapper class with the [Immutable] annotation for compose. + * The alternative would be to use the `kotlinx.ImmutableList` type for the `days` value + * which is used ONLY in the dayContent parameter of the [HeatMapCalendar] but then we + * would force that dependency on the library consumers. + * + * @param days the days in this week. + */ +@Immutable +public data class HeatMapWeek(val days: List) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt new file mode 100644 index 00000000..41cebfe0 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt @@ -0,0 +1,19 @@ +package com.kizitonwose.calendar.compose.heatmapcalendar + +import com.kizitonwose.calendar.compose.HeatMapCalendar + +/** + * Determines the position of the week header + * composable (Mon, Tue, Wed...) in the [HeatMapCalendar] + */ +public enum class HeatMapWeekHeaderPosition { + /** + * The header is positioned at the start of the calendar. + */ + Start, + + /** + * The header is positioned at the end of the calendar. + */ + End, +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendar.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendar.kt new file mode 100644 index 00000000..63f57649 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendar.kt @@ -0,0 +1,71 @@ +package com.kizitonwose.calendar.compose.weekcalendar + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import com.kizitonwose.calendar.compose.CalendarDefaults.flingBehavior +import com.kizitonwose.calendar.core.Week +import com.kizitonwose.calendar.core.WeekDay +import com.kizitonwose.calendar.core.toJvmSerializableLocalDate + +@Composable +internal fun WeekCalendarImpl( + modifier: Modifier, + state: WeekCalendarState, + calendarScrollPaged: Boolean, + userScrollEnabled: Boolean, + reverseLayout: Boolean, + contentPadding: PaddingValues, + dayContent: @Composable BoxScope.(WeekDay) -> Unit, + weekHeader: (@Composable ColumnScope.(Week) -> Unit)? = null, + weekFooter: (@Composable ColumnScope.(Week) -> Unit)? = null, +) { + LazyRow( + modifier = modifier, + state = state.listState, + flingBehavior = flingBehavior(calendarScrollPaged, state.listState), + userScrollEnabled = userScrollEnabled, + reverseLayout = reverseLayout, + contentPadding = contentPadding, + ) { + items( + count = state.weekIndexCount, + key = { offset -> state.store[offset].days.first().date.toJvmSerializableLocalDate() }, + ) { offset -> + val week = state.store[offset] + Column( + modifier = Modifier + .then( + if (calendarScrollPaged) { + Modifier.fillParentMaxWidth() + } else { + Modifier.width(IntrinsicSize.Max) + }, + ), + ) { + weekHeader?.invoke(this, week) + Row { + for (date in week.days) { + Box( + modifier = Modifier + .then(if (calendarScrollPaged) Modifier.weight(1f) else Modifier) + .clipToBounds(), + ) { + dayContent(date) + } + } + } + weekFooter?.invoke(this, week) + } + } + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt new file mode 100644 index 00000000..302e051f --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt @@ -0,0 +1,37 @@ +package com.kizitonwose.calendar.compose.weekcalendar + +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListLayoutInfo +import com.kizitonwose.calendar.core.Week + +/** + * Contains useful information about the currently displayed layout state of the calendar. + * For example you can get the list of currently displayed months. + * + * Use [WeekCalendarState.layoutInfo] to retrieve this. + * + * @see LazyListLayoutInfo + */ +public class WeekCalendarLayoutInfo( + info: LazyListLayoutInfo, + private val getIndexData: (Int) -> Week, +) : LazyListLayoutInfo by info { + /** + * The list of [WeekCalendarItemInfo] representing all the currently visible weeks. + */ + public val visibleWeeksInfo: List + get() = visibleItemsInfo.map { info -> + WeekCalendarItemInfo(info, getIndexData(info.index)) + } +} + +/** + * Contains useful information about an individual week on the calendar. + * + * @param week The week in the list. + + * @see WeekCalendarLayoutInfo + * @see LazyListItemInfo + */ +public class WeekCalendarItemInfo(info: LazyListItemInfo, public val week: Week) : + LazyListItemInfo by info diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt new file mode 100644 index 00000000..22016034 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt @@ -0,0 +1,293 @@ +package com.kizitonwose.calendar.compose.weekcalendar + +import androidx.compose.foundation.MutatePriority +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.gestures.ScrollableState +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.kizitonwose.calendar.core.JvmSerializableLocalDate +import com.kizitonwose.calendar.core.Week +import com.kizitonwose.calendar.core.WeekDayPosition +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.atEndOfMonth +import com.kizitonwose.calendar.core.atStartOfMonth +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.kizitonwose.calendar.core.now +import com.kizitonwose.calendar.core.toJvmSerializableLocalDate +import com.kizitonwose.calendar.core.toLocalDate +import com.kizitonwose.calendar.data.DataStore +import com.kizitonwose.calendar.data.VisibleItemState +import com.kizitonwose.calendar.data.getWeekCalendarAdjustedRange +import com.kizitonwose.calendar.data.getWeekCalendarData +import com.kizitonwose.calendar.data.getWeekIndex +import com.kizitonwose.calendar.data.getWeekIndicesCount +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate + +/** + * Creates a [WeekCalendarState] that is remembered across compositions. + * + * @param startDate the initial value for [WeekCalendarState.startDate] + * @param endDate the initial value for [WeekCalendarState.endDate] + * @param firstDayOfWeek the initial value for [WeekCalendarState.firstDayOfWeek] + * @param firstVisibleWeekDate the date which will have its week visible initially. + */ +@Composable +public fun rememberWeekCalendarState( + startDate: LocalDate = YearMonth.now().atStartOfMonth(), + endDate: LocalDate = YearMonth.now().atEndOfMonth(), + firstVisibleWeekDate: LocalDate = LocalDate.now(), + firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale(), +): WeekCalendarState { + return rememberSaveable( + inputs = arrayOf( + startDate, + endDate, + firstVisibleWeekDate, + firstDayOfWeek, + ), + saver = WeekCalendarState.Saver, + ) { + WeekCalendarState( + startDate = startDate, + endDate = endDate, + firstVisibleWeekDate = firstVisibleWeekDate, + firstDayOfWeek = firstDayOfWeek, + visibleItemState = null, + ) + } +} + +/** + * A state object that can be hoisted to control and observe calendar properties. + * + * This should be created via [rememberWeekCalendarState]. + * + * @param startDate the desired first date on the calendar. The actual first date will be the + * first day in the week to which this date belongs, depending on the provided [firstDayOfWeek]. + * Such days will have their [WeekDayPosition] set to [WeekDayPosition.InDate]. + * @param endDate the desired last date on the calendar. The actual last date will be the last + * day in the week to which this date belongs. Such days will have their [WeekDayPosition] set + * to [WeekDayPosition.OutDate]. + * @param firstDayOfWeek the first day of week on the calendar. + * @param firstVisibleWeekDate the date which will have its week visible initially. + */ +@Stable +public class WeekCalendarState internal constructor( + startDate: LocalDate, + endDate: LocalDate, + firstVisibleWeekDate: LocalDate, + firstDayOfWeek: DayOfWeek, + visibleItemState: VisibleItemState?, +) : ScrollableState { + /** + * The adjusted first date on the calendar to ensure proper alignment + * of the provided [firstDayOfWeek]. + */ + private var startDateAdjusted by mutableStateOf(startDate) + + /** + * The adjusted last date on the calendar to fill the remaining days in the + * last week after the provided end date. + */ + private var endDateAdjusted by mutableStateOf(endDate) + + /** Backing state for [startDate] */ + private var _startDate by mutableStateOf(startDate) + + /** + * The desired first date on the calendar. The actual first date will be the first day + * in the week to which this date belongs, depending on the provided [firstDayOfWeek]. + * Such days will have their [WeekDayPosition] set to [WeekDayPosition.InDate] + */ + public var startDate: LocalDate + get() = _startDate + set(value) { + if (value != _startDate) { + _startDate = value + adjustDateRange() + } + } + + /** Backing state for [endDate] */ + private var _endDate by mutableStateOf(endDate) + + /** + * The desired last date on the calendar. The actual last date will be the last day + * in the week to which this date belongs. Such days will have their [WeekDayPosition] + * set to [WeekDayPosition.OutDate] + */ + public var endDate: LocalDate + get() = _endDate + set(value) { + if (value != _endDate) { + _endDate = value + adjustDateRange() + } + } + + /** Backing state for [firstDayOfWeek] */ + private var _firstDayOfWeek by mutableStateOf(firstDayOfWeek) + + /** The first day of week on the calendar. */ + public var firstDayOfWeek: DayOfWeek + get() = _firstDayOfWeek + set(value) { + if (value != _firstDayOfWeek) { + _firstDayOfWeek = value + adjustDateRange() + } + } + + /** + * The first week that is visible. + */ + public val firstVisibleWeek: Week by derivedStateOf { + store[listState.firstVisibleItemIndex] + } + + /** + * The last week that is visible. + */ + public val lastVisibleWeek: Week by derivedStateOf { + store[listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0] + } + + /** + * The object of [WeekCalendarLayoutInfo] calculated during the last layout pass. For example, + * you can use it to calculate what items are currently visible. + * + * Note that this property is observable and is updated after every scroll or remeasure. + * If you use it in the composable function it will be recomposed on every change causing + * potential performance issues including infinity recomposition loop. + * Therefore, avoid using it in the composition. + * + * If you need to use it in the composition then consider wrapping the calculation into a + * derived state in order to only have recompositions when the derived value changes. + * See Example5Page in the sample app for usage. + * + * If you want to run some side effects like sending an analytics event or updating a state + * based on this value consider using "snapshotFlow". + */ + public val layoutInfo: WeekCalendarLayoutInfo + get() = WeekCalendarLayoutInfo(listState.layoutInfo) { index -> store[index] } + + internal val store = DataStore { offset -> + getWeekCalendarData( + startDateAdjusted = this.startDateAdjusted, + offset = offset, + desiredStartDate = this.startDate, + desiredEndDate = this.endDate, + ).week + } + + internal var weekIndexCount by mutableIntStateOf(0) + + internal val listState = run { + // Update date range and weekIndexCount initially. + // Since getScrollIndex requires the adjusted start date, it is necessary to do this + // before finding the first visible index. + adjustDateRange() + val item = visibleItemState ?: run { + VisibleItemState(firstVisibleItemIndex = getScrollIndex(firstVisibleWeekDate) ?: 0) + } + LazyListState( + firstVisibleItemIndex = item.firstVisibleItemIndex, + firstVisibleItemScrollOffset = item.firstVisibleItemScrollOffset, + ) + } + + private fun adjustDateRange() { + val data = getWeekCalendarAdjustedRange(startDate, endDate, firstDayOfWeek) + startDateAdjusted = data.startDateAdjusted + endDateAdjusted = data.endDateAdjusted + store.clear() + weekIndexCount = getWeekIndicesCount(startDateAdjusted, endDateAdjusted) + } + + /** + * Instantly brings the week containing the given [date] to the top of the viewport. + * + * @param date the week to which to scroll. + * + * @see [animateScrollToWeek] + */ + public suspend fun scrollToWeek(date: LocalDate) { + listState.scrollToItem(getScrollIndex(date) ?: return) + } + + /** + * Animate (smooth scroll) to the week containing the given [date]. + * + * @param date the week to which to scroll. + */ + public suspend fun animateScrollToWeek(date: LocalDate) { + listState.animateScrollToItem(getScrollIndex(date) ?: return) + } + + /** + * [InteractionSource] that will be used to dispatch drag events when this + * calendar is being dragged. If you want to know whether the fling (or animated scroll) is in + * progress, use [isScrollInProgress]. + */ + public val interactionSource: InteractionSource + get() = listState.interactionSource + + /** + * Whether this [ScrollableState] is currently scrolling by gesture, fling or programmatically. + */ + override val isScrollInProgress: Boolean + get() = listState.isScrollInProgress + + override fun dispatchRawDelta(delta: Float): Float = listState.dispatchRawDelta(delta) + + override suspend fun scroll( + scrollPriority: MutatePriority, + block: suspend ScrollScope.() -> Unit, + ): Unit = listState.scroll(scrollPriority, block) + + private fun getScrollIndex(date: LocalDate): Int? { + if (date !in startDateAdjusted..endDateAdjusted) { + println("WeekCalendarState - Attempting to scroll out of range; $date") + return null + } + return getWeekIndex(startDateAdjusted, date) + } + + public companion object { + internal val Saver: Saver = listSaver( + save = { + listOf( + it.startDate.toJvmSerializableLocalDate(), + it.endDate.toJvmSerializableLocalDate(), + it.firstVisibleWeek.days.first().date.toJvmSerializableLocalDate(), + it.firstDayOfWeek, + it.listState.firstVisibleItemIndex, + it.listState.firstVisibleItemScrollOffset, + ) + }, + restore = { + WeekCalendarState( + startDate = (it[0] as JvmSerializableLocalDate).toLocalDate(), + endDate = (it[1] as JvmSerializableLocalDate).toLocalDate(), + firstVisibleWeekDate = (it[2] as JvmSerializableLocalDate).toLocalDate(), + firstDayOfWeek = it[3] as DayOfWeek, + visibleItemState = VisibleItemState( + firstVisibleItemIndex = it[4] as Int, + firstVisibleItemScrollOffset = it[5] as Int, + ), + ) + }, + ) + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarDay.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarDay.kt new file mode 100644 index 00000000..07be0a3f --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarDay.kt @@ -0,0 +1,13 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.runtime.Immutable +import kotlinx.datetime.LocalDate + +/** + * Represents a day on the calendar. + * + * @param date the date for this day. + * @param position the [DayPosition] for this day. + */ +@Immutable +public data class CalendarDay(val date: LocalDate, val position: DayPosition) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarMonth.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarMonth.kt new file mode 100644 index 00000000..5429245c --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/CalendarMonth.kt @@ -0,0 +1,42 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.runtime.Immutable + +/** + * Represents a month on the calendar. + * + * @param yearMonth the calendar month value. + * @param weekDays the weeks in this month. + */ +@Immutable +public data class CalendarMonth internal constructor( + val yearMonth: YearMonth, + val weekDays: List>, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as CalendarMonth + + if (yearMonth != other.yearMonth) return false + if (weekDays.first().first() != other.weekDays.first().first()) return false + if (weekDays.last().last() != other.weekDays.last().last()) return false + + return true + } + + override fun hashCode(): Int { + var result = yearMonth.hashCode() + result = 31 * result + weekDays.first().first().hashCode() + result = 31 * result + weekDays.last().last().hashCode() + return result + } + + override fun toString(): String { + return "CalendarMonth { " + + "first = ${weekDays.first().first()}, " + + "last = ${weekDays.last().last()} " + + "} " + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/DayPosition.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/DayPosition.kt new file mode 100644 index 00000000..53dddafd --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/DayPosition.kt @@ -0,0 +1,26 @@ +package com.kizitonwose.calendar.core + +/** + * Describes the position of a [CalendarDay] in the month. + */ +public enum class DayPosition { + /** + * The day is positioned at the start of the month to + * ensure proper alignment of the first day of the week. + * The day belongs to the previous month on the calendar. + */ + InDate, + + /** + * The day belongs to the current month on the calendar. + */ + MonthDate, + + /** + * The day is positioned at the end of the month to + * to fill the remaining days after the days in the month. + * The day belongs to the next month on the calendar. + * @see [OutDateStyle] + */ + OutDate, +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Extensions.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Extensions.kt new file mode 100644 index 00000000..71fcd83f --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Extensions.kt @@ -0,0 +1,69 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.ui.text.intl.Locale +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.isoDayNumber +import kotlinx.datetime.minus +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn +import kotlinx.datetime.until + +/** + * Returns the days of week values such that the desired + * [firstDayOfWeek] property is at the start position. + * + * @see [firstDayOfWeekFromLocale] + */ +public fun daysOfWeek(firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale()): List { + val pivot = 7 - firstDayOfWeek.ordinal + val daysOfWeek = DayOfWeek.entries + // Order `daysOfWeek` array so that firstDayOfWeek is at the start position. + return daysOfWeek.takeLast(pivot) + daysOfWeek.dropLast(pivot) +} + +/** + * Returns the first day of the week from the provided locale. + */ +public expect fun firstDayOfWeekFromLocale(locale: Locale = Locale.current): DayOfWeek + +/** + * Obtains the current [LocalDate] from the specified [clock] and [timeZone]. + * + * Using this method allows the use of an alternate clock or timezone for testing. + */ +public fun LocalDate.Companion.now( + clock: Clock = Clock.System, + timeZone: TimeZone = TimeZone.currentSystemDefault(), +): LocalDate = clock.todayIn(timeZone) + +/** + * Returns the [YearMonth] value for this date. + */ +public val LocalDate.yearMonth: YearMonth + get() = YearMonth(year, month) + +internal fun YearMonth.plusMonths(value: Int): YearMonth = plus(value, DateTimeUnit.MONTH) + +internal fun YearMonth.minusMonths(value: Int): YearMonth = minus(value, DateTimeUnit.MONTH) + +internal fun LocalDate.plusDays(value: Int): LocalDate = plus(value, DateTimeUnit.DAY) + +internal fun LocalDate.minusDays(value: Int): LocalDate = minus(value, DateTimeUnit.DAY) + +internal fun LocalDate.plusWeeks(value: Int): LocalDate = plus(value, DateTimeUnit.WEEK) + +internal fun LocalDate.minusWeeks(value: Int): LocalDate = minus(value, DateTimeUnit.WEEK) + +internal fun LocalDate.plusMonths(value: Int): LocalDate = plus(value, DateTimeUnit.MONTH) + +internal fun LocalDate.minusMonths(value: Int): LocalDate = minus(value, DateTimeUnit.MONTH) + +internal fun LocalDate.weeksUntil(other: LocalDate): Int = + until(other, DateTimeUnit.WEEK) + +// E.g DayOfWeek.SATURDAY.daysUntil(DayOfWeek.TUESDAY) = 3 +internal fun DayOfWeek.daysUntil(other: DayOfWeek) = (7 + (other.isoDayNumber - isoDayNumber)) % 7 diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.kt new file mode 100644 index 00000000..dba7ae32 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.kt @@ -0,0 +1,3 @@ +package com.kizitonwose.calendar.core + +public expect interface JvmSerializable diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/JvmSerializableLocalDate.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/JvmSerializableLocalDate.kt new file mode 100644 index 00000000..b0693f30 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/JvmSerializableLocalDate.kt @@ -0,0 +1,21 @@ +package com.kizitonwose.calendar.core + +import kotlinx.datetime.LocalDate + +internal data class JvmSerializableLocalDate( + val year: Int, + val monthNumber: Int, + val dayOfMonth: Int, +) : JvmSerializable + +internal fun JvmSerializableLocalDate.toLocalDate() = LocalDate( + year = year, + monthNumber = monthNumber, + dayOfMonth = dayOfMonth, +) + +internal fun LocalDate.toJvmSerializableLocalDate() = JvmSerializableLocalDate( + year = year, + monthNumber = monthNumber, + dayOfMonth = dayOfMonth, +) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/OutDateStyle.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/OutDateStyle.kt new file mode 100644 index 00000000..560bcb35 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/OutDateStyle.kt @@ -0,0 +1,22 @@ +package com.kizitonwose.calendar.core + +/** + * Determines how [DayPosition.OutDate] are + * generated for each month on the calendar. + */ +public enum class OutDateStyle { + /** + * The calendar will generate outDates until it reaches + * the end of the month row. This means that if a month + * has 5 rows, it will display 5 rows and if a month + * has 6 rows, it will display 6 rows. + */ + EndOfRow, + + /** + * The calendar will generate outDates until it + * reaches the end of a 6 x 7 grid on each month. + * This means that all months will have 6 rows. + */ + EndOfGrid, +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Week.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Week.kt new file mode 100644 index 00000000..61e41815 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/Week.kt @@ -0,0 +1,36 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.runtime.Immutable + +/** + * Represents a week on the week-based calendar. + * + * @param days the days in this week. + */ +@Immutable +public data class Week internal constructor(val days: List) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as Week + + if (days.first() != other.days.first()) return false + if (days.last() != other.days.last()) return false + + return true + } + + override fun hashCode(): Int { + var result = days.first().hashCode() + result = 31 * result + days.last().hashCode() + return result + } + + override fun toString(): String { + return "Week { " + + "first = ${days.first()}, " + + "last = ${days.last()} " + + "} " + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDay.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDay.kt new file mode 100644 index 00000000..8271341d --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDay.kt @@ -0,0 +1,13 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.runtime.Immutable +import kotlinx.datetime.LocalDate + +/** + * Represents a day on the week calendar. + * + * @param date the date for this day. + * @param position the [WeekDayPosition] for this day. + */ +@Immutable +public data class WeekDay(val date: LocalDate, val position: WeekDayPosition) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDayPosition.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDayPosition.kt new file mode 100644 index 00000000..6c3c5e47 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/WeekDayPosition.kt @@ -0,0 +1,25 @@ +package com.kizitonwose.calendar.core + +/** + * Describes the position of a [WeekDay] on the calendar. + */ +public enum class WeekDayPosition { + /** + * The day is positioned at the start of the calendar to + * ensure proper alignment of the first day of the week. + * The day is before the provided start date. + */ + InDate, + + /** + * The day is in the range of the provided start and end dates. + */ + RangeDate, + + /** + * The day is positioned at the end of the calendar to to fill the + * remaining days in the last week after the provided end date. + * The day is after the provided end date. + */ + OutDate, +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/YearMonth.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/YearMonth.kt new file mode 100644 index 00000000..61e2af50 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/core/YearMonth.kt @@ -0,0 +1,120 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.runtime.Immutable +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeArithmeticException +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.Month +import kotlinx.datetime.TimeZone +import kotlinx.datetime.daysUntil +import kotlinx.datetime.minus +import kotlinx.datetime.monthsUntil +import kotlinx.datetime.number +import kotlinx.datetime.plus + +@Immutable +public data class YearMonth(val year: Int, val month: Month) : Comparable, JvmSerializable { + public constructor(year: Int, monthNumber: Int) : + this(year = year, month = Month(monthNumber)) + + init { + try { + atStartOfMonth() + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("YearMonth value ${month.number}-$year is out of range", e) + } + } + + /** + * Same as java.time.YearMonth.compareTo() + */ + override fun compareTo(other: YearMonth): Int { + var cmp = (year - other.year) + if (cmp == 0) { + cmp = (month.number - other.month.number) + } + return cmp + } + + public companion object { + /** + * Obtains the current [YearMonth] from the specified [clock] and [timeZone]. + * + * Using this method allows the use of an alternate clock or timezone for testing. + */ + public fun now( + clock: Clock = Clock.System, + timeZone: TimeZone = TimeZone.currentSystemDefault(), + ): YearMonth = LocalDate.now(clock, timeZone).yearMonth + } +} + +/** + * Returns the first [LocalDate] in this year-month. + * + * @see YearMonth.atDay + */ +public fun YearMonth.atStartOfMonth(): LocalDate = atDay(1) + +/** + * Returns the last [LocalDate] in this year-month. + * + * @see YearMonth.atDay + */ +public fun YearMonth.atEndOfMonth(): LocalDate = atDay(lengthOfMonth()) + +/** + * Returns the [LocalDate] at the specified [day] in this year-month. + */ +public fun YearMonth.atDay(day: Int): LocalDate = LocalDate(year, month, day) + +/** + * Returns the number of days in this month, taking account of the year. + * + * For example, a date in February would return 29 in a leap year and 28 otherwise. + */ +public fun YearMonth.lengthOfMonth(): Int { + val thisMonthStart = atStartOfMonth() + val nextMonthStart = thisMonthStart.plusMonths(1) + return thisMonthStart.daysUntil(nextMonthStart) +} + +/** + * Returns the number of whole months between two year-month values. + * + * The value is rounded toward zero. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a + * positive result or [Int.MIN_VALUE] for a negative result. + * + * @see LocalDate.monthsUntil + */ +public fun YearMonth.monthsUntil(other: YearMonth): Int = + atStartOfMonth().monthsUntil(other.atStartOfMonth()) + +/** + * Returns a [YearMonth] that results from adding the [value] number of the + * specified [unit] to this year-month. + * + * If the [value] is positive, the returned year-month is later than this year-month. + * If the [value] is negative, the returned year-month is earlier than this year-month. + * + * @throws DateTimeArithmeticException if the result exceeds the boundaries + * of [YearMonth] which is essentially the [LocalDate] boundaries. + */ +public fun YearMonth.plus(value: Int, unit: DateTimeUnit.MonthBased): YearMonth = + atStartOfMonth().plus(value, unit).yearMonth + +/** + * Returns a [YearMonth] that results from subtracting the [value] number of the + * specified [unit] from this year-month. + * + * If the [value] is positive, the returned year-month is earlier than this year-month. + * If the [value] is negative, the returned year-month is later than this year-month. + * + * @throws DateTimeArithmeticException if the result exceeds the boundaries + * of [YearMonth] which is essentially the [LocalDate] boundaries. + */ +public fun YearMonth.minus(value: Int, unit: DateTimeUnit.MonthBased): YearMonth = + atStartOfMonth().minus(value, unit).yearMonth diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/DataStore.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/DataStore.kt new file mode 100644 index 00000000..365460d2 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/DataStore.kt @@ -0,0 +1,21 @@ +package com.kizitonwose.calendar.data + +/** + * Basically [MutableMap.getOrPut] but allows us read the map + * in multiple places without calling `getOrPut` everywhere. + */ +internal class DataStore( + private val store: MutableMap = HashMap(), + private val create: (offset: Int) -> V, +) : MutableMap by store { + override fun get(key: Int): V { + val value = store[key] + return if (value == null) { + val data = create(key) + put(key, data) + data + } else { + value + } + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/MonthData.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/MonthData.kt new file mode 100644 index 00000000..c940b023 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/MonthData.kt @@ -0,0 +1,95 @@ +package com.kizitonwose.calendar.data + +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.OutDateStyle +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.atStartOfMonth +import com.kizitonwose.calendar.core.daysUntil +import com.kizitonwose.calendar.core.lengthOfMonth +import com.kizitonwose.calendar.core.minusDays +import com.kizitonwose.calendar.core.minusMonths +import com.kizitonwose.calendar.core.monthsUntil +import com.kizitonwose.calendar.core.plusDays +import com.kizitonwose.calendar.core.plusMonths +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.datetime.DayOfWeek + +internal data class MonthData internal constructor( + private val month: YearMonth, + private val inDays: Int, + private val outDays: Int, +) { + private val totalDays = inDays + month.lengthOfMonth() + outDays + + private val firstDay = month.atStartOfMonth().minusDays(inDays) + + private val rows = (0 until totalDays).chunked(7) + + private val previousMonth = month.minusMonths(1) + + private val nextMonth = month.plusMonths(1) + + val calendarMonth = CalendarMonth(month, rows.map { week -> week.map { dayOffset -> getDay(dayOffset) } }) + + private fun getDay(dayOffset: Int): CalendarDay { + val date = firstDay.plusDays(dayOffset) + val position = when (date.yearMonth) { + month -> DayPosition.MonthDate + previousMonth -> DayPosition.InDate + nextMonth -> DayPosition.OutDate + else -> throw IllegalArgumentException("Invalid date: $date in month: $month") + } + return CalendarDay(date, position) + } +} + +internal fun getCalendarMonthData( + startMonth: YearMonth, + offset: Int, + firstDayOfWeek: DayOfWeek, + outDateStyle: OutDateStyle, +): MonthData { + val month = startMonth.plusMonths(offset) + val firstDay = month.atStartOfMonth() + val inDays = firstDayOfWeek.daysUntil(firstDay.dayOfWeek) + val outDays = (inDays + month.lengthOfMonth()).let { inAndMonthDays -> + val endOfRowDays = if (inAndMonthDays % 7 != 0) 7 - (inAndMonthDays % 7) else 0 + val endOfGridDays = if (outDateStyle == OutDateStyle.EndOfRow) { + 0 + } else { + val weeksInMonth = (inAndMonthDays + endOfRowDays) / 7 + (6 - weeksInMonth) * 7 + } + return@let endOfRowDays + endOfGridDays + } + return MonthData(month, inDays, outDays) +} + +internal fun getHeatMapCalendarMonthData( + startMonth: YearMonth, + offset: Int, + firstDayOfWeek: DayOfWeek, +): MonthData { + val month = startMonth.plusMonths(offset) + val firstDay = month.atStartOfMonth() + val inDays = if (offset == 0) { + firstDayOfWeek.daysUntil(firstDay.dayOfWeek) + } else { + -firstDay.dayOfWeek.daysUntil(firstDayOfWeek) + } + val outDays = (inDays + month.lengthOfMonth()).let { inAndMonthDays -> + if (inAndMonthDays % 7 != 0) 7 - (inAndMonthDays % 7) else 0 + } + return MonthData(month, inDays, outDays) +} + +internal fun getMonthIndex(startMonth: YearMonth, targetMonth: YearMonth): Int { + return startMonth.monthsUntil(targetMonth) +} + +internal fun getMonthIndicesCount(startMonth: YearMonth, endMonth: YearMonth): Int { + // Add one to include the start month itself! + return getMonthIndex(startMonth, endMonth) + 1 +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/Utils.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/Utils.kt new file mode 100644 index 00000000..0b36ece9 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/Utils.kt @@ -0,0 +1,16 @@ +package com.kizitonwose.calendar.data + +import com.kizitonwose.calendar.core.YearMonth +import kotlinx.datetime.LocalDate + +internal fun checkDateRange(startMonth: YearMonth, endMonth: YearMonth) { + check(endMonth >= startMonth) { + "startMonth: $startMonth is greater than endMonth: $endMonth" + } +} + +internal fun checkDateRange(startDate: LocalDate, endDate: LocalDate) { + check(endDate >= startDate) { + "startDate: $startDate is greater than endDate: $endDate" + } +} diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/VisibleItemState.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/VisibleItemState.kt new file mode 100644 index 00000000..497afc3a --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/VisibleItemState.kt @@ -0,0 +1,9 @@ +package com.kizitonwose.calendar.data + +import androidx.compose.runtime.Immutable + +@Immutable +internal class VisibleItemState( + val firstVisibleItemIndex: Int = 0, + val firstVisibleItemScrollOffset: Int = 0, +) diff --git a/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/WeekData.kt b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/WeekData.kt new file mode 100644 index 00000000..6865c1d8 --- /dev/null +++ b/compose-multiplatform/library/src/commonMain/kotlin/com/kizitonwose/calendar/data/WeekData.kt @@ -0,0 +1,66 @@ +package com.kizitonwose.calendar.data + +import com.kizitonwose.calendar.core.Week +import com.kizitonwose.calendar.core.WeekDay +import com.kizitonwose.calendar.core.WeekDayPosition +import com.kizitonwose.calendar.core.daysUntil +import com.kizitonwose.calendar.core.minusDays +import com.kizitonwose.calendar.core.plusDays +import com.kizitonwose.calendar.core.plusWeeks +import com.kizitonwose.calendar.core.weeksUntil +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate + +public data class WeekDateRange( + val startDateAdjusted: LocalDate, + val endDateAdjusted: LocalDate, +) + +public fun getWeekCalendarAdjustedRange( + startDate: LocalDate, + endDate: LocalDate, + firstDayOfWeek: DayOfWeek, +): WeekDateRange { + val inDays = firstDayOfWeek.daysUntil(startDate.dayOfWeek) + val startDateAdjusted = startDate.minusDays(inDays) + val weeksBetween = startDateAdjusted.weeksUntil(endDate) + val endDateAdjusted = startDateAdjusted.plusWeeks(weeksBetween).plusDays(6) + return WeekDateRange(startDateAdjusted = startDateAdjusted, endDateAdjusted = endDateAdjusted) +} + +public fun getWeekCalendarData( + startDateAdjusted: LocalDate, + offset: Int, + desiredStartDate: LocalDate, + desiredEndDate: LocalDate, +): WeekData { + val firstDayInWeek = startDateAdjusted.plusWeeks(offset) + return WeekData(firstDayInWeek, desiredStartDate, desiredEndDate) +} + +public data class WeekData internal constructor( + private val firstDayInWeek: LocalDate, + private val desiredStartDate: LocalDate, + private val desiredEndDate: LocalDate, +) { + val week: Week = Week((0 until 7).map { dayOffset -> getDay(dayOffset) }) + + private fun getDay(dayOffset: Int): WeekDay { + val date = firstDayInWeek.plusDays(dayOffset) + val position = when { + date < desiredStartDate -> WeekDayPosition.InDate + date > desiredEndDate -> WeekDayPosition.OutDate + else -> WeekDayPosition.RangeDate + } + return WeekDay(date, position) + } +} + +public fun getWeekIndex(startDateAdjusted: LocalDate, date: LocalDate): Int { + return startDateAdjusted.weeksUntil(date) +} + +public fun getWeekIndicesCount(startDateAdjusted: LocalDate, endDateAdjusted: LocalDate): Int { + // Add one to include the start week itself! + return getWeekIndex(startDateAdjusted, endDateAdjusted) + 1 +} diff --git a/compose-multiplatform/library/src/iosMain/kotlin/com/kizitonwose/calendar/core/Extensions.ios.kt b/compose-multiplatform/library/src/iosMain/kotlin/com/kizitonwose/calendar/core/Extensions.ios.kt new file mode 100644 index 00000000..2a03b858 --- /dev/null +++ b/compose-multiplatform/library/src/iosMain/kotlin/com/kizitonwose/calendar/core/Extensions.ios.kt @@ -0,0 +1,20 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.ui.text.intl.Locale +import kotlinx.datetime.DayOfWeek +import platform.Foundation.NSCalendar +import platform.Foundation.NSLocale + +/** + * Returns the first day of the week from the provided locale. + */ +public actual fun firstDayOfWeekFromLocale(locale: Locale): DayOfWeek { + val firstWeekday = NSCalendar.currentCalendar.let { + it.setLocale(NSLocale(locale.toLanguageTag())) + // https://developer.apple.com/documentation/foundation/calendar/2293656-firstweekday + // Value is one-based, starting from sunday + it.firstWeekday.toInt() + } + // Get the index value from a sunday-based array. + return daysOfWeek(firstDayOfWeek = DayOfWeek.SUNDAY)[firstWeekday - 1] +} diff --git a/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Converters.kt b/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Converters.kt new file mode 100644 index 00000000..c78263e5 --- /dev/null +++ b/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Converters.kt @@ -0,0 +1,7 @@ +package com.kizitonwose.calendar.core + +import java.time.YearMonth as jtYearMonth + +public fun YearMonth.toJavaYearMonth(): jtYearMonth = jtYearMonth.of(year, month) + +public fun jtYearMonth.toKotlinYearMonth(): YearMonth = YearMonth(year, month) diff --git a/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Extensions.jvm.kt b/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Extensions.jvm.kt new file mode 100644 index 00000000..c99f00dd --- /dev/null +++ b/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/Extensions.jvm.kt @@ -0,0 +1,9 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.ui.text.intl.Locale +import java.time.DayOfWeek +import java.time.temporal.WeekFields +import java.util.Locale as JavaLocale + +public actual fun firstDayOfWeekFromLocale(locale: Locale): DayOfWeek = + WeekFields.of(JavaLocale.forLanguageTag(locale.toLanguageTag())).firstDayOfWeek diff --git a/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.jvm.kt b/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.jvm.kt new file mode 100644 index 00000000..199695a3 --- /dev/null +++ b/compose-multiplatform/library/src/jvmMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.jvm.kt @@ -0,0 +1,3 @@ +package com.kizitonwose.calendar.core + +public actual typealias JvmSerializable = java.io.Serializable diff --git a/compose-multiplatform/library/src/nonJvmMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.nonJvm.kt b/compose-multiplatform/library/src/nonJvmMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.nonJvm.kt new file mode 100644 index 00000000..7147a4bd --- /dev/null +++ b/compose-multiplatform/library/src/nonJvmMain/kotlin/com/kizitonwose/calendar/core/JvmSerializable.nonJvm.kt @@ -0,0 +1,3 @@ +package com.kizitonwose.calendar.core + +public actual interface JvmSerializable diff --git a/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/Extensions.wasmJs.kt b/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/Extensions.wasmJs.kt new file mode 100644 index 00000000..de8d954c --- /dev/null +++ b/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/Extensions.wasmJs.kt @@ -0,0 +1,20 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.ui.text.intl.Locale +import kotlinx.datetime.DayOfWeek + +/** + * Returns the first day of the week from the provided locale. + */ +public actual fun firstDayOfWeekFromLocale(locale: Locale): DayOfWeek { + return try { + val firstDay = jsFirstDayFromTag(locale.toLanguageTag()) + daysOfWeek(firstDayOfWeek = DayOfWeek.MONDAY)[firstDay - 1] + // Unavailable on Firefox + } catch (e: Exception) { + firstDayFromMap(locale) + } +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo#firstday +private fun jsFirstDayFromTag(languageTag: String): Int = js("new Intl.Locale(languageTag).weekInfo?.firstDay") diff --git a/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/FirstDayFromMap.kt b/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/FirstDayFromMap.kt new file mode 100644 index 00000000..e4a0e91f --- /dev/null +++ b/compose-multiplatform/library/src/wasmJsMain/kotlin/com/kizitonwose/calendar/core/FirstDayFromMap.kt @@ -0,0 +1,168 @@ +package com.kizitonwose.calendar.core + +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.toLowerCase +import kotlinx.datetime.DayOfWeek + +internal fun firstDayFromMap(locale: Locale): DayOfWeek = + firstDays[locale.region]?.let { firstDayValues[it] } ?: DayOfWeek.MONDAY + +private val enLocale = Locale("en-US") +private var firstDayValues = DayOfWeek.entries.associateBy { + it.name.toLowerCase(enLocale).take(3) +} + +// https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-core/supplemental/weekData.json#L7 +private val firstDays = mapOf( + "001" to "mon", + "AD" to "mon", + "AE" to "sat", + "AF" to "sat", + "AG" to "sun", + "AI" to "mon", + "AL" to "mon", + "AM" to "mon", + "AN" to "mon", + "AR" to "mon", + "AS" to "sun", + "AT" to "mon", + "AU" to "mon", + "AX" to "mon", + "AZ" to "mon", + "BA" to "mon", + "BD" to "sun", + "BE" to "mon", + "BG" to "mon", + "BH" to "sat", + "BM" to "mon", + "BN" to "mon", + "BR" to "sun", + "BS" to "sun", + "BT" to "sun", + "BW" to "sun", + "BY" to "mon", + "BZ" to "sun", + "CA" to "sun", + "CH" to "mon", + "CL" to "mon", + "CM" to "mon", + "CN" to "mon", + "CO" to "sun", + "CR" to "mon", + "CY" to "mon", + "CZ" to "mon", + "DE" to "mon", + "DJ" to "sat", + "DK" to "mon", + "DM" to "sun", + "DO" to "sun", + "DZ" to "sat", + "EC" to "mon", + "EE" to "mon", + "EG" to "sat", + "ES" to "mon", + "ET" to "sun", + "FI" to "mon", + "FJ" to "mon", + "FO" to "mon", + "FR" to "mon", + "GB" to "mon", + "GB-alt-variant" to "sun", + "GE" to "mon", + "GF" to "mon", + "GP" to "mon", + "GR" to "mon", + "GT" to "sun", + "GU" to "sun", + "HK" to "sun", + "HN" to "sun", + "HR" to "mon", + "HU" to "mon", + "ID" to "sun", + "IE" to "mon", + "IL" to "sun", + "IN" to "sun", + "IQ" to "sat", + "IR" to "sat", + "IS" to "mon", + "IT" to "mon", + "JM" to "sun", + "JO" to "sat", + "JP" to "sun", + "KE" to "sun", + "KG" to "mon", + "KH" to "sun", + "KR" to "sun", + "KW" to "sat", + "KZ" to "mon", + "LA" to "sun", + "LB" to "mon", + "LI" to "mon", + "LK" to "mon", + "LT" to "mon", + "LU" to "mon", + "LV" to "mon", + "LY" to "sat", + "MC" to "mon", + "MD" to "mon", + "ME" to "mon", + "MH" to "sun", + "MK" to "mon", + "MM" to "sun", + "MN" to "mon", + "MO" to "sun", + "MQ" to "mon", + "MT" to "sun", + "MV" to "fri", + "MX" to "sun", + "MY" to "mon", + "MZ" to "sun", + "NI" to "sun", + "NL" to "mon", + "NO" to "mon", + "NP" to "sun", + "NZ" to "mon", + "OM" to "sat", + "PA" to "sun", + "PE" to "sun", + "PH" to "sun", + "PK" to "sun", + "PL" to "mon", + "PR" to "sun", + "PT" to "sun", + "PY" to "sun", + "QA" to "sat", + "RE" to "mon", + "RO" to "mon", + "RS" to "mon", + "RU" to "mon", + "SA" to "sun", + "SD" to "sat", + "SE" to "mon", + "SG" to "sun", + "SI" to "mon", + "SK" to "mon", + "SM" to "mon", + "SV" to "sun", + "SY" to "sat", + "TH" to "sun", + "TJ" to "mon", + "TM" to "mon", + "TR" to "mon", + "TT" to "sun", + "TW" to "sun", + "UA" to "mon", + "UM" to "sun", + "US" to "sun", + "UY" to "mon", + "UZ" to "mon", + "VA" to "mon", + "VE" to "sun", + "VI" to "sun", + "VN" to "mon", + "WS" to "sun", + "XK" to "mon", + "YE" to "sun", + "ZA" to "sun", + "ZW" to "sun", +) diff --git a/compose-multiplatform/sample/.gitignore b/compose-multiplatform/sample/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/compose-multiplatform/sample/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/compose-multiplatform/sample/build.gradle.kts b/compose-multiplatform/sample/build.gradle.kts new file mode 100644 index 00000000..b247be8b --- /dev/null +++ b/compose-multiplatform/sample/build.gradle.kts @@ -0,0 +1,144 @@ +import com.kizitonwose.calendar.buildsrc.Android +import com.kizitonwose.calendar.buildsrc.Config +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.composeCompiler) +} + +kotlin { + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "composeApp" + browser { + commonWebpackConfig { + outputFileName = "composeApp.js" + devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { + static = (static ?: mutableListOf()).apply { + // Serve sources to debug inside browser + add(project.projectDir.path) + } + } + } + } + binaries.executable() + } + + androidTarget {} + + jvm("desktop") + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + applyDefaultHierarchyTemplate() + + sourceSets { + val commonMain by getting + val wasmJsMain by getting + val nativeMain by getting + val desktopMain by getting + val androidMain by getting + val jvmMain by creating { + dependsOn(commonMain) + } + androidMain.dependsOn(jvmMain) + androidMain.dependencies { + implementation(compose.preview) + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) +// implementation("com.kizitonwose.calendar:compose-multiplatform:2.6.0-alpha02") + implementation(project(":compose-multiplatform:library")) + implementation(libs.jetbrains.compose.navigation) + } + val nonJvmMain by creating { + dependsOn(commonMain) + nativeMain.dependsOn(this) + wasmJsMain.dependsOn(this) + dependencies {} + } + desktopMain.dependsOn(jvmMain) + desktopMain.dependencies { + implementation(compose.desktop.currentOs) + } + } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} + +android { + namespace = "com.kizitonwose.calendar.compose.multiplatform" + compileSdk = Android.compileSdk + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + + defaultConfig { + applicationId = "com.kizitonwose.calendar.compose.multiplatform" + minSdk = Android.minSdkSampleApp + targetSdk = Android.targetSdk + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + java { + toolchain { + languageVersion.set(Config.compatibleJavaLanguageVersion) + } + } + kotlin { + jvmToolchain { + languageVersion.set(Config.compatibleJavaLanguageVersion) + } + } + buildFeatures { + compose = true + } + dependencies { + debugImplementation(compose.uiTooling) + } +} + +compose.desktop { + application { + mainClass = "MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "com.kizitonwose.calendar.compose.multiplatform" + packageVersion = "1.0.0" + } + } +} diff --git a/compose-multiplatform/sample/src/androidMain/AndroidManifest.xml b/compose-multiplatform/sample/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..b87d9441 --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/compose-multiplatform/sample/src/androidMain/kotlin/Device.android.kt b/compose-multiplatform/sample/src/androidMain/kotlin/Device.android.kt new file mode 100644 index 00000000..3b8f0b67 --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/kotlin/Device.android.kt @@ -0,0 +1 @@ +actual fun isMobile(): Boolean = true diff --git a/compose-multiplatform/sample/src/androidMain/kotlin/com/kizitonwose/calendar/compose/multiplatform/MainActivity.kt b/compose-multiplatform/sample/src/androidMain/kotlin/com/kizitonwose/calendar/compose/multiplatform/MainActivity.kt new file mode 100644 index 00000000..be455aec --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/kotlin/com/kizitonwose/calendar/compose/multiplatform/MainActivity.kt @@ -0,0 +1,23 @@ +package com.kizitonwose.calendar.compose.multiplatform + +import App +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + App() + } + } +} + +@Preview +@Composable +fun AppAndroidPreview() { + App() +} diff --git a/compose-multiplatform/sample/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/compose-multiplatform/sample/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/compose-multiplatform/sample/src/androidMain/res/drawable/ic_launcher_background.xml b/compose-multiplatform/sample/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..e93e11ad --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a571e600 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..61da551c Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c41dd285 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..db5080a7 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..6dba46da Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..da31a871 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..15ac6817 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b216f2d3 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..f25a4197 Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..e96783cc Binary files /dev/null and b/compose-multiplatform/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/compose-multiplatform/sample/src/androidMain/res/values/strings.xml b/compose-multiplatform/sample/src/androidMain/res/values/strings.xml new file mode 100644 index 00000000..5b10d4cb --- /dev/null +++ b/compose-multiplatform/sample/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + Calendar Multiplatform Sample + diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/App.kt b/compose-multiplatform/sample/src/commonMain/kotlin/App.kt new file mode 100644 index 00000000..33d27d97 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/App.kt @@ -0,0 +1,191 @@ +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.launch +import org.jetbrains.compose.ui.tooling.preview.Preview +import kotlin.math.roundToInt + +@Composable +@Preview +fun App() { + MaterialTheme(MaterialTheme.colorScheme.copy(primary = Colors.primary)) { + BoxWithConstraints( + Modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter, + ) { + if (maxWidth >= 600.dp) { + val widthPx = maxWidth.value.roundToInt() + val count = if (widthPx in 650..800) 2 else widthPx / 400 + Row { + repeat(count) { + VerticalDivider() + Demo(modifier = Modifier.weight(1f)) + } + } + } else { + Demo(modifier = Modifier.fillMaxSize()) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun Demo(modifier: Modifier = Modifier) { + var toolBarTitle by remember { mutableStateOf("") } + var toolBarVisible by remember { mutableStateOf(true) } + var toolBarBackButtonVisible by remember { mutableStateOf(true) } + val navController = rememberNavController() + val coroutineScope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + LaunchedEffect(navController) { + navController.currentBackStackEntryFlow.collect { backStackEntry -> + val page = Page.valueOf(backStackEntry.destination.route ?: return@collect) + toolBarTitle = page.title + toolBarVisible = page.showToolBar + toolBarBackButtonVisible = page != Page.List + } + } + Scaffold( + modifier = modifier, + topBar = { + if (toolBarVisible) { + Column { + ExampleToolbar( + title = toolBarTitle, + colors = if (isMobile()) blueToolbar else whiteToolbar, + navigationIcon = navIcon@{ + if (toolBarBackButtonVisible) { + NavigationIcon( + tint = if (isMobile()) Color.White else Color.Black, + ) { + navController.popBackStack() + } + } + }, + ) + // Add divider to separate the white toolbar. + if (!isMobile()) { + HorizontalDivider() + } + } + } + }, + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + content = { + AppNavHost( + modifier = Modifier + .padding(it), + navController = navController, + showSnack = { message -> + coroutineScope.launch { + snackbarHostState.showSnackbar(message) + } + }, + ) + }, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExampleToolbar( + title: String, + colors: TopAppBarColors = blueToolbar, + modifier: Modifier = Modifier, + navigationIcon: @Composable () -> Unit = {}, +) = TopAppBar( + modifier = modifier.height(64.dp), + colors = colors, + title = { + Box(Modifier.fillMaxHeight()) { + Text( + modifier = Modifier.align(Alignment.Center), + text = title, + ) + } + }, + navigationIcon = navigationIcon, +) + +@Composable +private fun AppNavHost( + modifier: Modifier = Modifier, + navController: NavHostController = rememberNavController(), + showSnack: (String) -> Unit = {}, +) { + NavHost( + modifier = modifier, + navController = navController, + startDestination = Page.List.name, + ) { + composable(Page.List.name) { + ListPage { page -> navController.navigate(page.name) } + } + horizontallyAnimatedComposable(Page.Example1.name) { Example1Page() } + verticallyAnimatedComposable(Page.Example2.name) { + Example2Page( + close = { navController.popBackStack() }, + dateSelected = { startDate, endDate -> + navController.popBackStack() + showSnack(dateRangeDisplayText(startDate, endDate)) + }, + ) + } + verticallyAnimatedComposable(Page.Example3.name) { Example3Page { navController.popBackStack() } } + horizontallyAnimatedComposable(Page.Example4.name) { Example4Page() } + horizontallyAnimatedComposable(Page.Example5.name) { Example5Page { navController.popBackStack() } } + horizontallyAnimatedComposable(Page.Example6.name) { Example6Page() } + horizontallyAnimatedComposable(Page.Example7.name) { Example7Page() } + horizontallyAnimatedComposable(Page.Example9.name) { Example9Page() } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private val blueToolbar + @Composable + get() = TopAppBarDefaults.topAppBarColors( + containerColor = Colors.primary, + titleContentColor = Color.White, + ) + +@OptIn(ExperimentalMaterial3Api::class) +private val whiteToolbar + @Composable + get() = TopAppBarDefaults.topAppBarColors( + containerColor = Color.White, + titleContentColor = Color.Black, + ) diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/AppIcons.kt b/compose-multiplatform/sample/src/commonMain/kotlin/AppIcons.kt new file mode 100644 index 00000000..a28ccc71 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/AppIcons.kt @@ -0,0 +1,117 @@ +@file:Suppress("ktlint:standard:backing-property-naming") + +import androidx.compose.foundation.Image +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.materialIcon +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import org.jetbrains.compose.ui.tooling.preview.Preview + +val Icons.AirplaneTakeoff: ImageVector + get() { + if (_airplaneTakeoff != null) { + return _airplaneTakeoff!! + } + _airplaneTakeoff = materialIcon(name = "Plane") { + path( + fill = SolidColor(Color(0xFFDCDCDC)), + fillAlpha = 1.0F, + strokeAlpha = 1.0F, + strokeLineWidth = 0.0F, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Miter, + strokeLineMiter = 4.0F, + pathFillType = PathFillType.NonZero, + ) { + moveTo(2.5F, 19.0F) + horizontalLineTo(21.5F) + verticalLineTo(21.0F) + horizontalLineTo(2.5F) + verticalLineTo(19.0F) + moveTo(22.07F, 9.64F) + curveTo(21.86F, 8.84F, 21.03F, 8.36F, 20.23F, 8.58F) + lineTo(14.92F, 10.0F) + lineTo(8.0F, 3.57F) + lineTo(6.09F, 4.08F) + lineTo(10.23F, 11.25F) + lineTo(5.26F, 12.58F) + lineTo(3.29F, 11.04F) + lineTo(1.84F, 11.43F) + lineTo(3.66F, 14.59F) + lineTo(4.43F, 15.92F) + lineTo(6.03F, 15.5F) + lineTo(11.34F, 14.07F) + lineTo(15.69F, 12.91F) + lineTo(21.0F, 11.5F) + curveTo(21.81F, 11.26F, 22.28F, 10.44F, 22.07F, 9.64F) + close() + } + } + return _airplaneTakeoff!! + } + +private var _airplaneTakeoff: ImageVector? = null + +@Preview +@Composable +@Suppress("UnusedPrivateMember") +private fun IconAirplaneTakeoffPreview() { + Image(imageVector = Icons.AirplaneTakeoff, contentDescription = null) +} + +val Icons.AirplaneLanding: ImageVector + get() { + if (_airplaneLanding != null) { + return _airplaneLanding!! + } + _airplaneLanding = materialIcon(name = "AirplaneLanding") { + path( + fill = SolidColor(Color(0xFFDCDCDC)), + fillAlpha = 1.0F, + strokeAlpha = 1.0F, + strokeLineWidth = 0.0F, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Miter, + strokeLineMiter = 4.0F, + pathFillType = PathFillType.NonZero, + ) { + moveTo(2.5F, 19.0F) + horizontalLineTo(21.5F) + verticalLineTo(21.0F) + horizontalLineTo(2.5F) + verticalLineTo(19.0F) + moveTo(9.68F, 13.27F) + lineTo(14.03F, 14.43F) + lineTo(19.34F, 15.85F) + curveTo(20.14F, 16.06F, 20.96F, 15.59F, 21.18F, 14.79F) + curveTo(21.39F, 14.0F, 20.92F, 13.17F, 20.12F, 12.95F) + lineTo(14.81F, 11.53F) + lineTo(12.05F, 2.5F) + lineTo(10.12F, 2.0F) + verticalLineTo(10.28F) + lineTo(5.15F, 8.95F) + lineTo(4.22F, 6.63F) + lineTo(2.77F, 6.24F) + verticalLineTo(11.41F) + lineTo(4.37F, 11.84F) + lineTo(9.68F, 13.27F) + close() + } + } + return _airplaneLanding!! + } + +private var _airplaneLanding: ImageVector? = null + +@Preview +@Composable +@Suppress("UnusedPrivateMember") +private fun IconAirplaneLandingPreview() { + Image(imageVector = Icons.AirplaneLanding, contentDescription = null) +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Colors.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Colors.kt new file mode 100644 index 00000000..fcb1535a --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Colors.kt @@ -0,0 +1,19 @@ +import androidx.compose.ui.graphics.Color + +object Colors { + val example1Selection = Color(0xFFFCCA3E) + val inactiveText = Color(0xFFBEBEBE) + val example4Gray = Color(0xFF474747) + val example4GrayPast = Color(0xFFBEBEBE) + val example5PageBgColor = Color(0xFF0E0E0E) + val example5ItemViewBgColor = Color(0xFF1B1B1B) + val example5ToolbarColor = Color(0xFF282828) + val example5TextGrey = Color(0xFFDCDCDC) + val example5TextGreyLight = Color(0xFF616161) + val example6MonthBgColor = Color(0xFFB2EBF2) + val example6MonthBgColor2 = Color(0xFFF2C4B2) + val example6MonthBgColor3 = Color(0xFFB2B8F2) + val example7Yellow = Color(0xFFFFEB3B) + val primary = Color(0xFF3F51B5) + val accent = Color(0xFFFF4081) +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/ContinuousSelectionHelper.kt b/compose-multiplatform/sample/src/commonMain/kotlin/ContinuousSelectionHelper.kt new file mode 100644 index 00000000..e8fdaabb --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/ContinuousSelectionHelper.kt @@ -0,0 +1,63 @@ +import com.kizitonwose.calendar.core.atEndOfMonth +import com.kizitonwose.calendar.core.atStartOfMonth +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.datetime.LocalDate +import kotlinx.datetime.daysUntil +import kotlin.LazyThreadSafetyMode.NONE + +data class DateSelection(val startDate: LocalDate? = null, val endDate: LocalDate? = null) { + val daysBetween by lazy(NONE) { + if (startDate == null || endDate == null) { + null + } else { + startDate.daysUntil(endDate) + } + } +} + +private val rangeFormatter = LocalDate.Formats.ISO +fun dateRangeDisplayText(startDate: LocalDate, endDate: LocalDate): String { + return "Selected: ${rangeFormatter.format(startDate)} - ${rangeFormatter.format(endDate)}" +} + +object ContinuousSelectionHelper { + fun getSelection( + clickedDate: LocalDate, + dateSelection: DateSelection, + ): DateSelection { + val (selectionStartDate, selectionEndDate) = dateSelection + return if (selectionStartDate != null) { + if (clickedDate < selectionStartDate || selectionEndDate != null) { + DateSelection(startDate = clickedDate, endDate = null) + } else if (clickedDate != selectionStartDate) { + DateSelection(startDate = selectionStartDate, endDate = clickedDate) + } else { + DateSelection(startDate = clickedDate, endDate = null) + } + } else { + DateSelection(startDate = clickedDate, endDate = null) + } + } + + fun isInDateBetweenSelection( + inDate: LocalDate, + startDate: LocalDate, + endDate: LocalDate, + ): Boolean { + if (startDate.yearMonth == endDate.yearMonth) return false + if (inDate.yearMonth == startDate.yearMonth) return true + val firstDateInThisMonth = inDate.yearMonth.next.atStartOfMonth() + return firstDateInThisMonth in startDate..endDate && startDate != firstDateInThisMonth + } + + fun isOutDateBetweenSelection( + outDate: LocalDate, + startDate: LocalDate, + endDate: LocalDate, + ): Boolean { + if (startDate.yearMonth == endDate.yearMonth) return false + if (outDate.yearMonth == endDate.yearMonth) return true + val lastDateInThisMonth = outDate.yearMonth.previous.atEndOfMonth() + return lastDateInThisMonth in startDate..endDate && endDate != lastDateInThisMonth + } +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Device.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Device.kt new file mode 100644 index 00000000..3fd8c663 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Device.kt @@ -0,0 +1 @@ +expect fun isMobile(): Boolean diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example1Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example1Page.kt new file mode 100644 index 00000000..23a58cb0 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example1Page.kt @@ -0,0 +1,140 @@ +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.daysOfWeek +import kotlinx.coroutines.launch +import kotlinx.datetime.DayOfWeek +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun Example1Page(adjacentMonths: Int = 500) { + val currentMonth = remember { YearMonth.now() } + val startMonth = remember { currentMonth.minusMonths(adjacentMonths) } + val endMonth = remember { currentMonth.plusMonths(adjacentMonths) } + val selections = remember { mutableStateListOf() } + val daysOfWeek = remember { daysOfWeek() } + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = daysOfWeek.first(), + ) + val coroutineScope = rememberCoroutineScope() + val visibleMonth = rememberFirstMostVisibleMonth(state, viewportPercent = 90f) + SimpleCalendarTitle( + modifier = Modifier.padding(vertical = 10.dp, horizontal = 8.dp), + currentMonth = visibleMonth.yearMonth, + goToPrevious = { + coroutineScope.launch { + state.animateScrollToMonth(state.firstVisibleMonth.yearMonth.previous) + } + }, + goToNext = { + coroutineScope.launch { + state.animateScrollToMonth(state.firstVisibleMonth.yearMonth.next) + } + }, + ) + HorizontalCalendar( + modifier = Modifier.testTag("Calendar"), + state = state, + dayContent = { day -> + Day(day, isSelected = selections.contains(day)) { clicked -> + if (selections.contains(clicked)) { + selections.remove(clicked) + } else { + selections.add(clicked) + } + } + }, + monthHeader = { + MonthHeader(daysOfWeek = daysOfWeek) + }, + ) + } +} + +@Composable +private fun MonthHeader(daysOfWeek: List) { + Row( + modifier = Modifier + .fillMaxWidth() + .testTag("MonthHeader"), + ) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + fontSize = 15.sp, + text = dayOfWeek.displayText(), + fontWeight = FontWeight.Medium, + ) + } + } +} + +@Composable +private fun Day(day: CalendarDay, isSelected: Boolean, onClick: (CalendarDay) -> Unit) { + Box( + modifier = Modifier + .aspectRatio(1f) // This is important for square-sizing! + .testTag("MonthDay") + .padding(6.dp) + .clip(CircleShape) + .background(color = if (isSelected) Colors.example1Selection else Color.Transparent) + // Disable clicks on inDates/outDates + .clickable( + enabled = day.position == DayPosition.MonthDate, + showRipple = !isSelected, + onClick = { onClick(day) }, + ), + contentAlignment = Alignment.Center, + ) { + val textColor = when (day.position) { + // Color.Unspecified will use the default text color from the current theme + DayPosition.MonthDate -> if (isSelected) Color.White else Color.Unspecified + DayPosition.InDate, DayPosition.OutDate -> Colors.inactiveText + } + Text( + text = day.date.dayOfMonth.toString(), + color = textColor, + fontSize = 14.sp, + ) + } +} + +@Preview +@Composable +private fun Example1Preview() { + Example1Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example2Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example2Page.kt new file mode 100644 index 00000000..c8356c50 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example2Page.kt @@ -0,0 +1,282 @@ +import ContinuousSelectionHelper.getSelection +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Button +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.VerticalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.daysOfWeek +import com.kizitonwose.calendar.core.now +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import org.jetbrains.compose.ui.tooling.preview.Preview + +private val primaryColor = Color.Black.copy(alpha = 0.9f) +private val selectionColor = primaryColor +private val continuousSelectionColor = Color.LightGray.copy(alpha = 0.3f) + +@Composable +fun Example2Page( + close: () -> Unit = {}, + dateSelected: (startDate: LocalDate, endDate: LocalDate) -> Unit = { _, _ -> }, +) { + val currentMonth = remember { YearMonth.now() } + val startMonth = remember { currentMonth } + val endMonth = remember { currentMonth.plusMonths(12) } + val today = remember { LocalDate.now() } + var selection by remember { mutableStateOf(DateSelection()) } + val daysOfWeek = remember { daysOfWeek() } + MaterialTheme(colorScheme = MaterialTheme.colorScheme.copy(primary = primaryColor)) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + Column { + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = daysOfWeek.first(), + ) + CalendarTop( + daysOfWeek = daysOfWeek, + selection = selection, + close = close, + clearDates = { selection = DateSelection() }, + ) + VerticalCalendar( + state = state, + contentPadding = PaddingValues(bottom = 100.dp), + dayContent = { value -> + Day( + value, + today = today, + selection = selection, + ) { day -> + if (day.position == DayPosition.MonthDate && day.date >= today) { + selection = getSelection( + clickedDate = day.date, + dateSelection = selection, + ) + } + } + }, + monthHeader = { month -> MonthHeader(month) }, + ) + } + CalendarBottom( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .background(Color.White) + .align(Alignment.BottomCenter), + selection = selection, + save = { + val (startDate, endDate) = selection + if (startDate != null && endDate != null) { + dateSelected(startDate, endDate) + } + }, + ) + } + } +} + +@Composable +private fun Day( + day: CalendarDay, + today: LocalDate, + selection: DateSelection, + onClick: (CalendarDay) -> Unit, +) { + var textColor = Color.Transparent + Box( + modifier = Modifier + .aspectRatio(1f) // This is important for square-sizing! + .clickable( + enabled = day.position == DayPosition.MonthDate && day.date >= today, + showRipple = false, + onClick = { onClick(day) }, + ) + .backgroundHighlight( + day = day, + today = today, + selection = selection, + selectionColor = selectionColor, + continuousSelectionColor = continuousSelectionColor, + ) { textColor = it }, + contentAlignment = Alignment.Center, + ) { + Text( + text = day.date.dayOfMonth.toString(), + color = textColor, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + ) + } +} + +@Composable +private fun MonthHeader(calendarMonth: CalendarMonth) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp, bottom = 8.dp, start = 16.dp, end = 16.dp), + ) { + Text( + textAlign = TextAlign.Center, + text = calendarMonth.yearMonth.displayText(), + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + ) + } +} + +@Composable +private fun CalendarTop( + modifier: Modifier = Modifier, + daysOfWeek: List, + selection: DateSelection, + close: () -> Unit, + clearDates: () -> Unit, +) { + Column(modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 6.dp, bottom = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Row( + modifier = Modifier.height(IntrinsicSize.Max), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .fillMaxHeight() + .aspectRatio(1f) + .clip(CircleShape) + .clickable(onClick = close) + .padding(12.dp), + imageVector = Icons.Default.Close, + contentDescription = "Close", + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + modifier = Modifier + .clip(RoundedCornerShape(percent = 50)) + .clickable(onClick = clearDates) + .padding(horizontal = 16.dp, vertical = 8.dp), + text = "Clear", + fontWeight = FontWeight.Medium, + textAlign = TextAlign.End, + ) + } + val daysBetween = selection.daysBetween + val text = if (daysBetween == null) { + "Select dates" + } else { + // Ideally you'd do this using the strings.xml file + "$daysBetween ${if (daysBetween == 1) "night" else "nights"} in Munich" + } + Text( + modifier = Modifier.padding(horizontal = 14.dp), + text = text, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + ) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + color = Color.DarkGray, + text = dayOfWeek.displayText(), + fontSize = 15.sp, + ) + } + } + } + HorizontalDivider() + } +} + +@Composable +private fun CalendarBottom( + modifier: Modifier = Modifier, + selection: DateSelection, + save: () -> Unit, +) { + Column(modifier.fillMaxWidth()) { + HorizontalDivider() + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "€75 night", + fontWeight = FontWeight.Bold, + ) + Spacer(modifier = Modifier.weight(1f)) + Button( + modifier = Modifier + .height(40.dp) + .width(100.dp), + onClick = save, + enabled = selection.daysBetween != null, + ) { + Text(text = "Save") + } + } + } +} + +@Preview +@Composable +private fun Example2Preview() { + Example2Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example2PageHighlight.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example2PageHighlight.kt new file mode 100644 index 00000000..82c1d7cf --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example2PageHighlight.kt @@ -0,0 +1,233 @@ +import ContinuousSelectionHelper.isInDateBetweenSelection +import ContinuousSelectionHelper.isOutDateBetweenSelection +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.DayPosition +import kotlinx.datetime.LocalDate + +private class HalfSizeShape(private val clipStart: Boolean) : Shape { + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ): Outline { + val half = size.width / 2f + val offset = if (layoutDirection == LayoutDirection.Ltr) { + if (clipStart) Offset(half, 0f) else Offset.Zero + } else { + if (clipStart) Offset.Zero else Offset(half, 0f) + } + return Outline.Rectangle(Rect(offset, Size(half, size.height))) + } +} + +/** + * Modern Airbnb highlight style, as seen in the app. + * See also [backgroundHighlightLegacy]. + */ +fun Modifier.backgroundHighlight( + day: CalendarDay, + today: LocalDate, + selection: DateSelection, + selectionColor: Color, + continuousSelectionColor: Color, + textColor: (Color) -> Unit, +): Modifier = composed { + val (startDate, endDate) = selection + val padding = 4.dp + when (day.position) { + DayPosition.MonthDate -> { + when { + day.date < today -> { + textColor(Colors.inactiveText) + this + } + + startDate == day.date && endDate == null -> { + textColor(Color.White) + padding(padding) + .background(color = selectionColor, shape = CircleShape) + } + + day.date == startDate -> { + textColor(Color.White) + padding(vertical = padding) + .background( + color = continuousSelectionColor, + shape = HalfSizeShape(clipStart = true), + ) + .padding(horizontal = padding) + .background(color = selectionColor, shape = CircleShape) + } + + startDate != null && endDate != null && (day.date > startDate && day.date < endDate) -> { + textColor(Colors.example4Gray) + padding(vertical = padding) + .background(color = continuousSelectionColor) + } + + day.date == endDate -> { + textColor(Color.White) + padding(vertical = padding) + .background( + color = continuousSelectionColor, + shape = HalfSizeShape(clipStart = false), + ) + .padding(horizontal = padding) + .background(color = selectionColor, shape = CircleShape) + } + + day.date == today -> { + textColor(Colors.example4Gray) + padding(padding) + .border( + width = 1.dp, + shape = CircleShape, + color = Colors.inactiveText, + ) + } + + else -> { + textColor(Colors.example4Gray) + this + } + } + } + + DayPosition.InDate -> { + textColor(Color.Transparent) + if (startDate != null && endDate != null && + isInDateBetweenSelection(day.date, startDate, endDate) + ) { + padding(vertical = padding) + .background(color = continuousSelectionColor) + } else { + this + } + } + + DayPosition.OutDate -> { + textColor(Color.Transparent) + if (startDate != null && endDate != null && + isOutDateBetweenSelection(day.date, startDate, endDate) + ) { + padding(vertical = padding) + .background(color = continuousSelectionColor) + } else { + this + } + } + } +} + +/** + * Old Airbnb highlight style. + * See also [backgroundHighlight]. + */ +fun Modifier.backgroundHighlightLegacy( + day: CalendarDay, + today: LocalDate, + selection: DateSelection, + selectionColor: Color, + textColor: (Color) -> Unit, +): Modifier = composed { + val (startDate, endDate) = selection + val padding = 4.dp + when (day.position) { + DayPosition.MonthDate -> { + when { + day.date < today -> { + textColor(Colors.inactiveText) + this + } + + startDate == day.date && endDate == null -> { + textColor(Color.White) + padding(padding) + .background(color = selectionColor, shape = CircleShape) + } + + day.date == startDate -> { + textColor(Color.White) + padding(vertical = padding) + .background( + color = selectionColor, + shape = RoundedCornerShape( + topStartPercent = 50, + bottomStartPercent = 50, + ), + ) + } + + startDate != null && endDate != null && (day.date > startDate && day.date < endDate) -> { + textColor(Color.White) + padding(vertical = padding) + .background(color = selectionColor) + } + + day.date == endDate -> { + textColor(Color.White) + padding(vertical = padding) + .background( + color = selectionColor, + shape = RoundedCornerShape(topEndPercent = 50, bottomEndPercent = 50), + ) + } + + day.date == today -> { + textColor(Colors.example4Gray) + padding(padding) + .border( + width = 1.dp, + shape = CircleShape, + color = Colors.inactiveText, + ) + } + + else -> { + textColor(Colors.example4Gray) + this + } + } + } + + DayPosition.InDate -> { + textColor(Color.Transparent) + if (startDate != null && endDate != null && + isInDateBetweenSelection(day.date, startDate, endDate) + ) { + padding(vertical = padding) + .background(color = selectionColor) + } else { + this + } + } + + DayPosition.OutDate -> { + textColor(Color.Transparent) + if (startDate != null && endDate != null && + isOutDateBetweenSelection(day.date, startDate, endDate) + ) { + padding(vertical = padding) + .background(color = selectionColor) + } else { + this + } + } + } +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example3Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example3Page.kt new file mode 100644 index 00000000..492f5685 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example3Page.kt @@ -0,0 +1,319 @@ +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.OutDateStyle +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.daysOfWeek +import kotlinx.coroutines.launch +import kotlinx.datetime.DayOfWeek +import org.jetbrains.compose.ui.tooling.preview.Preview + +private val flights = generateFlights().groupBy { it.time.date } + +private val pageBackgroundColor: Color = Colors.example5PageBgColor +private val itemBackgroundColor: Color = Colors.example5ItemViewBgColor +private val toolbarColor: Color = Colors.example5ToolbarColor +private val selectedItemColor: Color = Colors.example5TextGrey +private val inActiveTextColor: Color = Colors.example5TextGreyLight + +@Composable +fun Example3Page(close: () -> Unit = {}) { + val currentMonth = remember { YearMonth.now() } + val startMonth = remember { currentMonth.minusMonths(500) } + val endMonth = remember { currentMonth.plusMonths(500) } + var selection by remember { mutableStateOf(null) } + val daysOfWeek = remember { daysOfWeek() } + val flightsInSelectedDate = remember { + derivedStateOf { + val date = selection?.date + if (date == null) emptyList() else flights[date].orEmpty() + } + } + Column( + modifier = Modifier + .fillMaxSize() + .background(pageBackgroundColor), + ) { + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = daysOfWeek.first(), + outDateStyle = OutDateStyle.EndOfGrid, + ) + val coroutineScope = rememberCoroutineScope() + val visibleMonth = rememberFirstCompletelyVisibleMonth(state) + LaunchedEffect(visibleMonth) { + // Clear selection if we scroll to a new month. + selection = null + } + + // Draw light content on dark background. + CompositionLocalProvider(LocalContentColor provides darkColorScheme().onSurface) { + SimpleCalendarTitle( + modifier = Modifier + .background(toolbarColor) + .padding(horizontal = 8.dp, vertical = 12.dp), + currentMonth = visibleMonth.yearMonth, + goToPrevious = { + coroutineScope.launch { + state.animateScrollToMonth(state.firstVisibleMonth.yearMonth.previous) + } + }, + goToNext = { + coroutineScope.launch { + state.animateScrollToMonth(state.firstVisibleMonth.yearMonth.next) + } + }, + ) + HorizontalCalendar( + modifier = Modifier.wrapContentWidth(), + state = state, + dayContent = { day -> +// CompositionLocalProvider(LocalRippleConfiguration provides Example3RippleConfiguration) { + val colors = if (day.position == DayPosition.MonthDate) { + flights[day.date].orEmpty().map { it.color } + } else { + emptyList() + } + Day( + day = day, + isSelected = selection == day, + colors = colors, + ) { clicked -> + selection = clicked + } +// } + }, + monthHeader = { + MonthHeader( + modifier = Modifier.padding(vertical = 8.dp), + daysOfWeek = daysOfWeek, + ) + }, + ) + HorizontalDivider(color = pageBackgroundColor) + LazyColumn(modifier = Modifier.fillMaxWidth()) { + items(items = flightsInSelectedDate.value) { flight -> + FlightInformation(flight) + } + } + if (!isMobile()) { + Spacer(Modifier.height(28.dp)) + Button( + onClick = close, + colors = ButtonDefaults.buttonColors().copy(containerColor = toolbarColor), + modifier = Modifier.align(Alignment.CenterHorizontally), + ) { + Text("Close") + } + } + } + } +} + +@Composable +private fun Day( + day: CalendarDay, + isSelected: Boolean = false, + colors: List = emptyList(), + onClick: (CalendarDay) -> Unit = {}, +) { + Box( + modifier = Modifier + .aspectRatio(1f) // This is important for square-sizing! + .border( + width = if (isSelected) 1.dp else 0.dp, + color = if (isSelected) selectedItemColor else Color.Transparent, + ) + .padding(1.dp) + .background(color = itemBackgroundColor) + // Disable clicks on inDates/outDates + .clickable( + enabled = day.position == DayPosition.MonthDate, + onClick = { onClick(day) }, + ), + ) { + val textColor = when (day.position) { + DayPosition.MonthDate -> Color.Unspecified + DayPosition.InDate, DayPosition.OutDate -> inActiveTextColor + } + Text( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 3.dp, end = 4.dp), + text = day.date.dayOfMonth.toString(), + color = textColor, + fontSize = 12.sp, + ) + Column( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(bottom = 8.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + for (color in colors) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(5.dp) + .background(color), + ) + } + } + } +} + +@Composable +private fun MonthHeader( + modifier: Modifier = Modifier, + daysOfWeek: List = emptyList(), +) { + Row(modifier.fillMaxWidth()) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + fontSize = 12.sp, + color = Color.White, + text = dayOfWeek.displayText(uppercase = true), + fontWeight = FontWeight.Light, + ) + } + } +} + +@Composable +private fun LazyItemScope.FlightInformation(flight: Flight) { + Row( + modifier = Modifier + .fillParentMaxWidth() + .height(IntrinsicSize.Max), + horizontalArrangement = Arrangement.spacedBy(2.dp), + ) { + Box( + modifier = Modifier + .background(color = flight.color) + .fillParentMaxWidth(1 / 7f) + .aspectRatio(1f), + contentAlignment = Alignment.Center, + ) { + Text( + text = flightDateTimeFormatter.format(flight.time).uppercase(), + textAlign = TextAlign.Center, + lineHeight = 17.sp, + fontSize = 12.sp, + ) + } + Box( + modifier = Modifier + .background(color = itemBackgroundColor) + .weight(1f) + .fillMaxHeight(), + ) { + AirportInformation(flight.departure, isDeparture = true) + } + Box( + modifier = Modifier + .background(color = itemBackgroundColor) + .weight(1f) + .fillMaxHeight(), + ) { + AirportInformation(flight.destination, isDeparture = false) + } + } + HorizontalDivider(thickness = 2.dp, color = pageBackgroundColor) +} + +@Composable +private fun AirportInformation(airport: Flight.Airport, isDeparture: Boolean) { + Row( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(), + ) { + val vector = if (isDeparture) Icons.AirplaneTakeoff else Icons.AirplaneLanding + Box( + modifier = Modifier + .weight(0.3f) + .fillMaxHeight() + .fillMaxHeight(), + contentAlignment = Alignment.CenterEnd, + ) { + Image(imageVector = vector, contentDescription = null) + } + Column( + modifier = Modifier + .weight(0.7f) + .fillMaxHeight() + .fillMaxWidth(), + verticalArrangement = Arrangement.Center, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = airport.code, + textAlign = TextAlign.Center, + fontSize = 16.sp, + fontWeight = FontWeight.Black, + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = airport.city, + textAlign = TextAlign.Center, + fontSize = 16.sp, + fontWeight = FontWeight.Light, + ) + } + } +} + +@Preview +@Composable +private fun Example3Preview() { + Example3Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example4Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example4Page.kt new file mode 100644 index 00000000..c57b5c97 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example4Page.kt @@ -0,0 +1,149 @@ +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.kizitonwose.calendar.core.now +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun Example4Page() { + val currentMonth = remember { YearMonth.now() } + val startMonth = remember { currentMonth } + val endMonth = remember { currentMonth.plusMonths(500) } + BoxWithConstraints( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = firstDayOfWeekFromLocale(), + ) + HorizontalCalendar( + state = state, + dayContent = { day -> Day(day) }, + monthHeader = { month -> MonthHeader(month) }, + calendarScrollPaged = false, + contentPadding = PaddingValues(4.dp), + monthContainer = { _, container -> + Box( + modifier = Modifier + .width(maxWidth * 0.73f) + .padding(8.dp) + .clip(shape = RoundedCornerShape(8.dp)) + .border( + color = Color.Black, + width = 1.dp, + shape = RoundedCornerShape(8.dp), + ), + ) { + container() + } + }, + monthBody = { _, content -> + Box( + modifier = Modifier.background( + brush = Brush.verticalGradient( + colors = listOf( + Colors.example6MonthBgColor, + Colors.example6MonthBgColor3, + ), + ), + ), + ) { + content() + } + }, + ) + } +} + +@Composable +private fun MonthHeader(calendarMonth: CalendarMonth) { + val daysOfWeek = calendarMonth.weekDays.first().map { it.date.dayOfWeek } + Column( + modifier = Modifier + .wrapContentHeight() + .background(color = Colors.example6MonthBgColor2) + .padding(top = 6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = calendarMonth.yearMonth.displayText(short = true), + fontSize = 18.sp, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + ) + Row(modifier = Modifier.fillMaxWidth()) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + fontSize = 15.sp, + text = dayOfWeek.name.first().toString(), + fontWeight = FontWeight.Medium, + ) + } + } + HorizontalDivider(color = Color.Black) + } +} + +@Composable +private fun Day(day: CalendarDay) { + Box( + Modifier + .fillMaxWidth() + .height(70.dp), + contentAlignment = Alignment.Center, + ) { + if (day.position == DayPosition.MonthDate) { + Text( + modifier = Modifier.align(Alignment.Center), + text = day.date.dayOfMonth.toString(), + color = Color.Black, + fontSize = 14.sp, + ) + } + } +} + +@Preview +@Composable +private fun Example4Preview() { + Example4Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example5Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example5Page.kt new file mode 100644 index 00000000..6298e1a6 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example5Page.kt @@ -0,0 +1,116 @@ +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.WeekCalendar +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.now +import kotlinx.datetime.LocalDate +import kotlinx.datetime.format.Padding +import org.jetbrains.compose.ui.tooling.preview.Preview + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Example5Page(close: () -> Unit = {}) { + val currentDate = remember { LocalDate.now() } + val startDate = remember { currentDate.minusDays(500) } + val endDate = remember { currentDate.plusDays(500) } + var selection by remember { mutableStateOf(currentDate) } + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val state = rememberWeekCalendarState( + startDate = startDate, + endDate = endDate, + firstVisibleWeekDate = currentDate, + ) + val visibleWeek = rememberFirstVisibleWeekAfterScroll(state) + ExampleToolbar( + title = getWeekPageTitle(visibleWeek), + navigationIcon = { NavigationIcon(onBackClick = close) }, + ) + WeekCalendar( + modifier = Modifier.background(color = Colors.primary), + state = state, + dayContent = { day -> + Day(day.date, isSelected = selection == day.date) { clicked -> + if (selection != clicked) { + selection = clicked + } + } + }, + ) + } +} + +private val dateFormatter by lazy { + LocalDate.Format { + dayOfMonth(Padding.ZERO) + } +} + +@Composable +private fun Day(date: LocalDate, isSelected: Boolean, onClick: (LocalDate) -> Unit) { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .clickable { onClick(date) }, + contentAlignment = Alignment.Center, + ) { + Column( + modifier = Modifier.padding(vertical = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = date.dayOfWeek.displayText(), + fontSize = 12.sp, + color = Color.White, + fontWeight = FontWeight.Light, + ) + Text( + text = dateFormatter.format(date), + fontSize = 14.sp, + color = if (isSelected) Colors.example7Yellow else Color.White, + fontWeight = FontWeight.Bold, + ) + } + if (isSelected) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(5.dp) + .background(Colors.example7Yellow) + .align(Alignment.BottomCenter), + ) + } + } +} + +@Preview +@Composable +private fun Example5Preview() { + Example5Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example6Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example6Page.kt new file mode 100644 index 00000000..0350e8aa --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example6Page.kt @@ -0,0 +1,292 @@ +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.CalendarLayoutInfo +import com.kizitonwose.calendar.compose.HeatMapCalendar +import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapCalendarState +import com.kizitonwose.calendar.compose.heatmapcalendar.HeatMapWeek +import com.kizitonwose.calendar.compose.heatmapcalendar.rememberHeatMapCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.kizitonwose.calendar.core.now +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.daysUntil +import kotlinx.datetime.minus +import org.jetbrains.compose.ui.tooling.preview.Preview + +private enum class Level(val color: Color) { + Zero(Color(0xFFEBEDF0)), + One(Color(0xFF9BE9A8)), + Two(Color(0xFF40C463)), + Three(Color(0xFF30A14E)), + Four(Color(0xFF216E3A)), +} + +private fun generateRandomData(startDate: LocalDate, endDate: LocalDate): Map { + val levels = Level.entries + return (0..startDate.daysUntil(endDate)) + .associateTo(hashMapOf()) { count -> + startDate.plusDays(count) to levels.random() + } +} + +@Composable +fun Example6Page() { + var refreshKey by remember { mutableIntStateOf(1) } + val endDate = remember { LocalDate.now() } + // GitHub only shows contributions for the past 12 months + val startDate = remember { endDate.minus(12, DateTimeUnit.MONTH) } + val data = remember { mutableStateOf>(emptyMap()) } + var selection by remember { mutableStateOf?>(null) } + LaunchedEffect(startDate, endDate, refreshKey) { + selection = null + data.value = withContext(Dispatchers.Default) { + generateRandomData(startDate, endDate) + } + } + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val state = rememberHeatMapCalendarState( + startMonth = startDate.yearMonth, + endMonth = endDate.yearMonth, + firstVisibleMonth = endDate.yearMonth, + firstDayOfWeek = firstDayOfWeekFromLocale(), + ) + HeatMapCalendar( + modifier = Modifier.padding(vertical = 10.dp), + state = state, + contentPadding = PaddingValues(end = 6.dp), + dayContent = { day, week -> + Day( + day = day, + startDate = startDate, + endDate = endDate, + week = week, + level = data.value[day.date] ?: Level.Zero, + ) { clicked -> + selection = Pair(clicked, data.value[clicked] ?: Level.Zero) + } + }, + weekHeader = { WeekHeader(it) }, + monthHeader = { MonthHeader(it, endDate, state) }, + ) + CalendarInfo( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 44.dp), + ) + Box(modifier = Modifier.weight(1f)) { + BottomContent( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + .align(Alignment.BottomCenter), + selection = selection, + ) { refreshKey += 1 } + } + } +} + +private val formatter = LocalDate.Formats.ISO + +@Composable +private fun BottomContent( + modifier: Modifier = Modifier, + selection: Pair? = null, + refresh: (() -> Unit), +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + if (selection != null) { + Row( + modifier = Modifier.align(Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text(text = "Clicked: ${formatter.format(selection.first)}") + LevelBox(color = selection.second.color) + } + } + Button( + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + onClick = refresh, + ) { + Text( + text = "Generate random data", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + ) + } + } +} + +@Composable +private fun CalendarInfo(modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.Bottom, + ) { + Text(text = "Less", fontSize = 10.sp) + Level.entries.forEach { level -> + LevelBox(level.color) + } + Text(text = "More", fontSize = 10.sp) + } +} + +private val daySize = 18.dp + +@Composable +private fun Day( + day: CalendarDay, + startDate: LocalDate, + endDate: LocalDate, + week: HeatMapWeek, + level: Level, + onClick: (LocalDate) -> Unit, +) { + // We only want to draw boxes on the days that are in the + // past 12 months. Since the calendar is month-based, we ignore + // the future dates in the current month and those in the start + // month that are older than 12 months from today. + // We draw a transparent box on the empty spaces in the first week + // so the items are laid out properly as the column is top to bottom. + val weekDates = week.days.map { it.date } + if (day.date in startDate..endDate) { + LevelBox(level.color) { onClick(day.date) } + } else if (weekDates.contains(startDate)) { + LevelBox(Color.Transparent) + } +} + +@Composable +private fun LevelBox(color: Color, onClick: (() -> Unit)? = null) { + Box( + modifier = Modifier + .size(daySize) // Must set a size on the day. + .padding(2.dp) + .clip(RoundedCornerShape(2.dp)) + .background(color = color) + .clickable(enabled = onClick != null) { onClick?.invoke() }, + ) +} + +@Composable +private fun WeekHeader(dayOfWeek: DayOfWeek) { + Box( + modifier = Modifier + .height(daySize) // Must set a height on the day of week so it aligns with the day. + .padding(horizontal = 4.dp), + ) { + Text( + text = dayOfWeek.displayText(), + modifier = Modifier.align(Alignment.Center), + fontSize = 10.sp, + ) + } +} + +@Composable +private fun MonthHeader( + calendarMonth: CalendarMonth, + endDate: LocalDate, + state: HeatMapCalendarState, +) { + val density = LocalDensity.current + val firstFullyVisibleMonth by remember { + // Find the first index with at most one box out of bounds. + derivedStateOf { getMonthWithYear(state.layoutInfo, daySize, density) } + } + if (calendarMonth.weekDays.first().first().date <= endDate) { + val month = calendarMonth.yearMonth + val title = if (month == firstFullyVisibleMonth) { + month.displayText(short = true) + } else { + month.month.displayText() + } + Box( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 1.dp, start = 2.dp), + ) { + Text(text = title, fontSize = 10.sp) + } + } +} + +// Find the first index with at most one box out of bounds. +private fun getMonthWithYear( + layoutInfo: CalendarLayoutInfo, + daySize: Dp, + density: Density, +): YearMonth? { + val visibleItemsInfo = layoutInfo.visibleMonthsInfo + return when { + visibleItemsInfo.isEmpty() -> null + visibleItemsInfo.count() == 1 -> visibleItemsInfo.first().month.yearMonth + else -> { + val firstItem = visibleItemsInfo.first() + val daySizePx = with(density) { daySize.toPx() } + if ( + firstItem.size < daySizePx * 3 || // Ensure the Month + Year text can fit. + firstItem.offset < layoutInfo.viewportStartOffset && // Ensure the week row size - 1 is visible. + (layoutInfo.viewportStartOffset - firstItem.offset > daySizePx) + ) { + visibleItemsInfo[1].month.yearMonth + } else { + firstItem.month.yearMonth + } + } + } +} + +@Preview +@Composable +private fun Example6Preview() { + Example6Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example7Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example7Page.kt new file mode 100644 index 00000000..aa93c795 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example7Page.kt @@ -0,0 +1,130 @@ + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.WeekCalendar +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.now +import kotlinx.datetime.LocalDate +import kotlinx.datetime.format.Padding +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun Example7Page() { + val currentDate = remember { LocalDate.now() } + val startDate = remember { currentDate.minusDays(500) } + val endDate = remember { currentDate.plusDays(500) } + var selection by remember { mutableStateOf(null) } + BoxWithConstraints( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val state = rememberWeekCalendarState( + startDate = startDate, + endDate = endDate, + firstVisibleWeekDate = currentDate, + ) + // Draw light content on dark background. + CompositionLocalProvider(LocalContentColor provides darkColorScheme().onSurface) { + WeekCalendar( + modifier = Modifier.padding(vertical = 4.dp), + state = state, + calendarScrollPaged = false, + dayContent = { day -> + Day( + // If paged scrolling is disabled (calendarScrollPaged = false), + // you must set the day width on the WeekCalendar! + modifier = Modifier.width(this@BoxWithConstraints.maxWidth / 9f), + date = day.date, + selected = selection == day.date, + ) { + selection = it + } + }, + ) + } + } +} + +private val dateFormatter by lazy { + LocalDate.Format { + dayOfMonth(Padding.ZERO) + } +} + +@Composable +private fun Day( + date: LocalDate, + selected: Boolean = false, + modifier: Modifier = Modifier, + onClick: (LocalDate) -> Unit = {}, +) { + Box( + modifier = modifier + .padding(2.dp) + .clip(RoundedCornerShape(8.dp)) + .background(color = Colors.example5ToolbarColor) + .border( + shape = RoundedCornerShape(8.dp), + width = if (selected) 2.dp else 0.dp, + color = if (selected) Colors.accent else Color.Transparent, + ) + .wrapContentHeight() + .clickable { onClick(date) }, + contentAlignment = Alignment.Center, + ) { + Column( + modifier = Modifier.padding(vertical = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = date.month.displayText(), + fontSize = 10.sp, + fontWeight = FontWeight.Normal, + ) + Text( + text = dateFormatter.format(date), + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + ) + Text( + text = date.dayOfWeek.displayText(), + fontSize = 10.sp, + fontWeight = FontWeight.Normal, + ) + } + } +} + +@Preview +@Composable +private fun Example7Preview() { + Example7Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example9Page.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example9Page.kt new file mode 100644 index 00000000..8a36a605 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example9Page.kt @@ -0,0 +1,323 @@ +import Example9PageSharedComponents.CalendarHeader +import Example9PageSharedComponents.Day +import Example9PageSharedComponents.MonthAndWeekCalendarTitle +import Example9PageSharedComponents.WeekModeToggle +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.WeekCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.WeekDayPosition +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.atEndOfMonth +import com.kizitonwose.calendar.core.atStartOfMonth +import com.kizitonwose.calendar.core.daysOfWeek +import com.kizitonwose.calendar.core.now +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.coroutines.launch +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import org.jetbrains.compose.ui.tooling.preview.Preview + +/** + * Go to [Example9PageAnimatedVisibility] to see how toggling between week and + * month modes can be done using [AnimatedVisibility] if that interests you. + */ +@Composable +fun Example9Page(adjacentMonths: Int = 500) { + val currentDate = remember { LocalDate.now() } + val currentMonth = remember(currentDate) { currentDate.yearMonth } + val startMonth = remember(currentDate) { currentMonth.minusMonths(adjacentMonths) } + val endMonth = remember(currentDate) { currentMonth.plusMonths(adjacentMonths) } + val selections = remember { mutableStateListOf() } + val daysOfWeek = remember { daysOfWeek() } + + var isWeekMode by remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val monthState = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = daysOfWeek.first(), + ) + val weekState = rememberWeekCalendarState( + startDate = startMonth.atStartOfMonth(), + endDate = endMonth.atEndOfMonth(), + firstVisibleWeekDate = currentDate, + firstDayOfWeek = daysOfWeek.first(), + ) + CalendarTitle( + isWeekMode = isWeekMode, + monthState = monthState, + weekState = weekState, + ) + CalendarHeader(daysOfWeek = daysOfWeek) + val monthCalendarAlpha by animateFloatAsState(if (isWeekMode) 0f else 1f) + val weekCalendarAlpha by animateFloatAsState(if (isWeekMode) 1f else 0f) + var weekCalendarSize by remember { mutableStateOf(DpSize.Zero) } + val visibleMonth = rememberFirstVisibleMonthAfterScroll(monthState) + val weeksInVisibleMonth = visibleMonth.weekDays.count() + val monthCalendarHeight by animateDpAsState( + if (isWeekMode) { + weekCalendarSize.height + } else { + weekCalendarSize.height * weeksInVisibleMonth + }, + tween(durationMillis = 250), + ) + val density = LocalDensity.current + Box { + HorizontalCalendar( + modifier = Modifier + .height(monthCalendarHeight) + .alpha(monthCalendarAlpha) + .zIndex(if (isWeekMode) 0f else 1f), + state = monthState, + dayContent = { day -> + val isSelectable = day.position == DayPosition.MonthDate + Day( + day.date, + isSelected = isSelectable && selections.contains(day.date), + isSelectable = isSelectable, + ) { clicked -> + if (selections.contains(clicked)) { + selections.remove(clicked) + } else { + selections.add(clicked) + } + } + }, + ) + WeekCalendar( + modifier = Modifier + .wrapContentHeight() + .onSizeChanged { + val size = density.run { DpSize(it.width.toDp(), it.height.toDp()) } + if (weekCalendarSize != size) { + weekCalendarSize = size + } + } + .alpha(weekCalendarAlpha) + .zIndex(if (isWeekMode) 1f else 0f), + state = weekState, + dayContent = { day -> + val isSelectable = day.position == WeekDayPosition.RangeDate + Day( + day.date, + isSelected = isSelectable && selections.contains(day.date), + isSelectable = isSelectable, + ) { clicked -> + if (selections.contains(clicked)) { + selections.remove(clicked) + } else { + selections.add(clicked) + } + } + }, + ) + } + Spacer(modifier = Modifier.weight(1f)) + WeekModeToggle( + modifier = Modifier.align(Alignment.CenterHorizontally), + isWeekMode = isWeekMode, + ) { weekMode -> + coroutineScope.launch { + if (weekMode) { + val targetDate = monthState.firstVisibleMonth.weekDays.last().last().date + weekState.scrollToWeek(targetDate) + weekState.animateScrollToWeek(targetDate) // Trigger a layout pass for title update + } else { + val targetMonth = weekState.firstVisibleWeek.days.first().date.yearMonth + monthState.scrollToMonth(targetMonth) + monthState.animateScrollToMonth(targetMonth) // Trigger a layout pass for title update + } + isWeekMode = weekMode + } + } + } +} + +@Composable +private fun CalendarTitle( + isWeekMode: Boolean, + monthState: CalendarState, + weekState: WeekCalendarState, +) { + val visibleMonth = rememberFirstVisibleMonthAfterScroll(monthState) + val visibleWeek = rememberFirstVisibleWeekAfterScroll(weekState) + MonthAndWeekCalendarTitle( + isWeekMode = isWeekMode, + currentMonth = if (isWeekMode) visibleWeek.days.first().date.yearMonth else visibleMonth.yearMonth, + monthState = monthState, + weekState = weekState, + ) +} + +object Example9PageSharedComponents { + @Composable + fun MonthAndWeekCalendarTitle( + isWeekMode: Boolean, + currentMonth: YearMonth, + monthState: CalendarState, + weekState: WeekCalendarState, + ) { + val coroutineScope = rememberCoroutineScope() + SimpleCalendarTitle( + modifier = Modifier.padding(vertical = 10.dp, horizontal = 8.dp), + currentMonth = currentMonth, + goToPrevious = { + coroutineScope.launch { + if (isWeekMode) { + val targetDate = weekState.firstVisibleWeek.days.first().date.minusDays(1) + weekState.animateScrollToWeek(targetDate) + } else { + val targetMonth = monthState.firstVisibleMonth.yearMonth.previous + monthState.animateScrollToMonth(targetMonth) + } + } + }, + goToNext = { + coroutineScope.launch { + if (isWeekMode) { + val targetDate = weekState.firstVisibleWeek.days.last().date.plusDays(1) + weekState.animateScrollToWeek(targetDate) + } else { + val targetMonth = monthState.firstVisibleMonth.yearMonth.next + monthState.animateScrollToMonth(targetMonth) + } + } + }, + ) + } + + @Composable + fun CalendarHeader(daysOfWeek: List) { + Row( + modifier = Modifier + .fillMaxWidth(), + ) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + fontSize = 15.sp, + text = dayOfWeek.displayText(), + fontWeight = FontWeight.Medium, + ) + } + } + } + + @Composable + fun Day( + day: LocalDate, + isSelected: Boolean, + isSelectable: Boolean, + onClick: (LocalDate) -> Unit, + ) { + Box( + modifier = Modifier + .aspectRatio(1f) // This is important for square-sizing! + .padding(6.dp) + .clip(CircleShape) + .background(color = if (isSelected) Colors.example1Selection else Color.Transparent) + .clickable( + enabled = isSelectable, + showRipple = !isSelected, + onClick = { onClick(day) }, + ), + contentAlignment = Alignment.Center, + ) { + val textColor = when { + isSelected -> Color.White + isSelectable -> Color.Unspecified + else -> Colors.inactiveText + } + Text( + text = day.dayOfMonth.toString(), + color = textColor, + fontSize = 14.sp, + ) + } + } + + @Composable + fun WeekModeToggle( + modifier: Modifier, + isWeekMode: Boolean, + weekModeToggled: (isWeekMode: Boolean) -> Unit, + ) { + // We want the entire content to be clickable, not just the checkbox. + Row( + modifier = modifier + .padding(10.dp) + .clip(MaterialTheme.shapes.small) + .clickable { weekModeToggled(!isWeekMode) } + .padding(10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp, Alignment.CenterHorizontally), + ) { + Checkbox( + checked = isWeekMode, + onCheckedChange = null, // Check is handled by parent. + colors = CheckboxDefaults.colors(checkedColor = Colors.example1Selection), + ) + Text(text = "Week mode") + } + } +} + +@Preview +@Composable +private fun Example9Preview() { + Example9Page() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Example9PageAnimatedVisibility.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Example9PageAnimatedVisibility.kt new file mode 100644 index 00000000..078b5b74 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Example9PageAnimatedVisibility.kt @@ -0,0 +1,168 @@ +import Example9PageSharedComponents.CalendarHeader +import Example9PageSharedComponents.Day +import Example9PageSharedComponents.MonthAndWeekCalendarTitle +import Example9PageSharedComponents.WeekModeToggle +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.WeekCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.core.WeekDayPosition +import com.kizitonwose.calendar.core.atEndOfMonth +import com.kizitonwose.calendar.core.atStartOfMonth +import com.kizitonwose.calendar.core.daysOfWeek +import com.kizitonwose.calendar.core.now +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.coroutines.launch +import kotlinx.datetime.LocalDate +import org.jetbrains.compose.ui.tooling.preview.Preview + +/** + * Go to [Example9Page] to see how toggling between week and month + * modes can be done using the [Modifier] if that interests you. + */ +@Composable +fun Example9PageAnimatedVisibility(adjacentMonths: Int = 500) { + val currentDate = remember { LocalDate.now() } + val currentMonth = remember(currentDate) { currentDate.yearMonth } + val startMonth = remember(currentDate) { currentMonth.minusMonths(adjacentMonths) } + val endMonth = remember(currentDate) { currentMonth.plusMonths(adjacentMonths) } + val selections = remember { mutableStateListOf() } + val daysOfWeek = remember { daysOfWeek() } + + var isWeekMode by remember { mutableStateOf(false) } + var isAnimating by remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + val monthState = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = daysOfWeek.first(), + ) + val weekState = rememberWeekCalendarState( + startDate = startMonth.atStartOfMonth(), + endDate = endMonth.atEndOfMonth(), + firstVisibleWeekDate = currentDate, + firstDayOfWeek = daysOfWeek.first(), + ) + CalendarTitle( + isWeekMode = isWeekMode, + monthState = monthState, + weekState = weekState, + isAnimating = isAnimating, + ) + CalendarHeader(daysOfWeek = daysOfWeek) + AnimatedVisibility(visible = !isWeekMode) { + HorizontalCalendar( + state = monthState, + dayContent = { day -> + val isSelectable = day.position == DayPosition.MonthDate + Day( + day.date, + isSelected = isSelectable && selections.contains(day.date), + isSelectable = isSelectable, + ) { clicked -> + if (selections.contains(clicked)) { + selections.remove(clicked) + } else { + selections.add(clicked) + } + } + }, + ) + } + AnimatedVisibility(visible = isWeekMode) { + WeekCalendar( + state = weekState, + dayContent = { day -> + val isSelectable = day.position == WeekDayPosition.RangeDate + Day( + day.date, + isSelected = isSelectable && selections.contains(day.date), + isSelectable = isSelectable, + ) { clicked -> + if (selections.contains(clicked)) { + selections.remove(clicked) + } else { + selections.add(clicked) + } + } + }, + ) + } + Spacer(modifier = Modifier.weight(1f)) + WeekModeToggle( + modifier = Modifier.align(Alignment.CenterHorizontally), + isWeekMode = isWeekMode, + ) { weekMode -> + isAnimating = true + isWeekMode = weekMode + coroutineScope.launch { + if (weekMode) { + val targetDate = monthState.firstVisibleMonth.weekDays.last().last().date + weekState.scrollToWeek(targetDate) + weekState.animateScrollToWeek(targetDate) // Trigger a layout pass for title update + } else { + val targetMonth = weekState.firstVisibleWeek.days.first().date.yearMonth + monthState.scrollToMonth(targetMonth) + monthState.animateScrollToMonth(targetMonth) // Trigger a layout pass for title update + } + isAnimating = false + } + } + } +} + +@Composable +private fun CalendarTitle( + isWeekMode: Boolean, + monthState: CalendarState, + weekState: WeekCalendarState, + isAnimating: Boolean, +) { + val visibleMonth = rememberFirstVisibleMonthAfterScroll(monthState) + val visibleWeek = rememberFirstVisibleWeekAfterScroll(weekState) + val visibleWeekMonth = visibleWeek.days.first().date.yearMonth + // Track animation state to prevent updating the title too early before + // the correct value is available (after the animation). + val currentMonth = if (isWeekMode) { + if (isAnimating) visibleMonth.yearMonth else visibleWeekMonth + } else { + if (isAnimating) visibleWeekMonth else visibleMonth.yearMonth + } + MonthAndWeekCalendarTitle( + isWeekMode = isWeekMode, + currentMonth = currentMonth, + monthState = monthState, + weekState = weekState, + ) +} + +@Preview +@Composable +private fun Example9PageAnimatedVisibilityPreview() { + Example9PageAnimatedVisibility() +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Flight.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Flight.kt new file mode 100644 index 00000000..8e68fafe --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Flight.kt @@ -0,0 +1,129 @@ +import androidx.compose.ui.graphics.Color +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.atDay +import com.kizitonwose.calendar.core.now +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.atTime +import kotlinx.datetime.format.DayOfWeekNames +import kotlinx.datetime.format.MonthNames +import kotlinx.datetime.format.Padding +import kotlinx.datetime.format.char + +private typealias Airport = Flight.Airport + +data class Flight( + val time: LocalDateTime, + val departure: Airport, + val destination: Airport, + val color: Color, +) { + data class Airport(val city: String, val code: String) +} + +fun generateFlights(): List = buildList { + val currentMonth = YearMonth.now() + + currentMonth.atDay(17).also { date -> + add( + Flight( + date.atTime(14, 0), + Airport("Lagos", "LOS"), + Airport("Abuja", "ABV"), + Color(0xFF1565C0), + ), + ) + add( + Flight( + date.atTime(21, 30), + Airport("Enugu", "ENU"), + Airport("Owerri", "QOW"), + Color(0xFFC62828), + ), + ) + } + + currentMonth.atDay(22).also { date -> + add( + Flight( + date.atTime(13, 20), + Airport("Ibadan", "IBA"), + Airport("Benin", "BNI"), + Color(0xFF5D4037), + ), + ) + add( + Flight( + date.atTime(17, 40), + Airport("Sokoto", "SKO"), + Airport("Ilorin", "ILR"), + Color(0xFF455A64), + ), + ) + } + + currentMonth.atDay(3).also { date -> + add( + Flight( + date.atTime(20, 0), + Airport("Makurdi", "MDI"), + Airport("Calabar", "CBQ"), + Color(0xFF00796B), + ), + ) + } + + currentMonth.atDay(12).also { date -> + add( + Flight( + date.atTime(18, 15), + Airport("Kaduna", "KAD"), + Airport("Jos", "JOS"), + Color(0xFF0097A7), + ), + ) + } + + currentMonth.plusMonths(1).atDay(13).also { date -> + add( + Flight( + date.atTime(7, 30), + Airport("Kano", "KAN"), + Airport("Akure", "AKR"), + Color(0xFFC2185B), + ), + ) + add( + Flight( + date.atTime(10, 50), + Airport("Minna", "MXJ"), + Airport("Zaria", "ZAR"), + Color(0xFFEF6C00), + ), + ) + } + + currentMonth.minusMonths(1).atDay(9).also { date -> + add( + Flight( + date.atTime(20, 15), + Airport("Asaba", "ABB"), + Airport("Port Harcourt", "PHC"), + Color(0xFFEF6C00), + ), + ) + } +} + +val flightDateTimeFormatter by lazy { + LocalDateTime.Format { + dayOfWeek(DayOfWeekNames(DayOfWeekNames.ENGLISH_ABBREVIATED.names.map { it.uppercase() })) + char('\n') + dayOfMonth(Padding.ZERO) + char(' ') + monthName(MonthNames(MonthNames.ENGLISH_ABBREVIATED.names.map { it.uppercase() })) + char('\n') + hour() + char(':') + minute() + } +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Format.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Format.kt new file mode 100644 index 00000000..405ebb9d --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Format.kt @@ -0,0 +1,45 @@ +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.toUpperCase +import com.kizitonwose.calendar.core.Week +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.yearMonth +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.Month + +fun YearMonth.displayText(short: Boolean = false): String { + return "${month.displayText(short = short)} $year" +} + +fun Month.displayText(short: Boolean = true): String { + return getDisplayName(short, enLocale) +} + +fun DayOfWeek.displayText(uppercase: Boolean = false): String { + return getShortDisplayName(enLocale).let { value -> + if (uppercase) value.toUpperCase(enLocale) else value + } +} + +expect fun Month.getDisplayName(short: Boolean, locale: Locale): String + +expect fun DayOfWeek.getShortDisplayName(locale: Locale): String + +private val enLocale = Locale("en-US") + +fun getWeekPageTitle(week: Week): String { + val firstDate = week.days.first().date + val lastDate = week.days.last().date + return when { + firstDate.yearMonth == lastDate.yearMonth -> { + firstDate.yearMonth.displayText() + } + + firstDate.year == lastDate.year -> { + "${firstDate.month.displayText(short = false)} - ${lastDate.yearMonth.displayText()}" + } + + else -> { + "${firstDate.yearMonth.displayText()} - ${lastDate.yearMonth.displayText()}" + } + } +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/ListPage.kt b/compose-multiplatform/sample/src/commonMain/kotlin/ListPage.kt new file mode 100644 index 00000000..b88cffc9 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/ListPage.kt @@ -0,0 +1,103 @@ +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +enum class Page(val title: String, val subtitle: String, val showToolBar: Boolean) { + List( + title = "Calendar Library", + subtitle = "", + showToolBar = true, + ), + Example1( + title = "Example 1", + subtitle = "Horizontal Calendar - Month header, paged scroll style, programmatic scrolling, multiple selection.", + showToolBar = true, + ), + Example2( + title = "Example 2", + subtitle = "Vertical Calendar - Sticky header, continuous selection within one month and across multiple months, " + + "dates older than the current day are disabled. Similar to what is in the Airbnb app.", + showToolBar = false, + ), + Example3( + title = "Example 3", + subtitle = "Horizontal Calendar - Single selection, shows the \"EndOfGrid\" implementation of \"OutDateStyle\" property. A flight schedule calendar.", + showToolBar = false, + ), + Example4( + title = "Example 4", + subtitle = "Horizontal Calendar - Custom date width and height, custom month container and content backgrounds, continuous horizontal scroll style.", + showToolBar = true, + ), + Example5( + title = "Example 5", + subtitle = "Week Calendar - Single selection, paged scroll and visible item observation.", + showToolBar = false, + ), + Example6( + title = "Example 6", + subtitle = "HeatMap Calendar - Dynamic month header, continuous scroll. Similar to GitHub's contributions chart.", + showToolBar = true, + ), + Example7( + title = "Example 7", + subtitle = "Week Calendar - Continuous scroll, custom day content width, single selection.", + showToolBar = true, + ), + + // Example8( +// title = "Example 8", +// subtitle = "Fullscreen Horizontal Calendar - Month header and footer, paged horizontal scrolling. Shows the \"Fill\" option of ContentHeightMode property.", +// showToolBar = false, +// ), + Example9( + title = "Example 8", + subtitle = "Month and week calendar toggle with animations.", + showToolBar = true, + ), +} + +@Composable +fun ListPage(click: (Page) -> Unit) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + ) { + items(Page.entries.drop(1)) { item -> + Column( + modifier = Modifier + .fillParentMaxWidth() + .clickable { click(item) } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + val titleStyle = MaterialTheme.typography.titleMedium + Text( + text = item.title, + fontWeight = FontWeight.Medium, + style = titleStyle, + ) + val subtitleStyle = MaterialTheme.typography.bodyMedium + Text( + text = item.subtitle, + style = subtitleStyle, + ) + } + HorizontalDivider() + } + } +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/PageAnimation.kt b/compose-multiplatform/sample/src/commonMain/kotlin/PageAnimation.kt new file mode 100644 index 00000000..91608655 --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/PageAnimation.kt @@ -0,0 +1,73 @@ +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.runtime.Composable +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable + +private const val ANIM_DURATION_MILLIS = 400 + +fun NavGraphBuilder.horizontallyAnimatedComposable( + route: String, + content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit, +) { + composable( + route = route, + content = content, + enterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(ANIM_DURATION_MILLIS), + ) + }, + exitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(ANIM_DURATION_MILLIS), + ) + }, + popEnterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = tween(ANIM_DURATION_MILLIS), + ) + }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = tween(ANIM_DURATION_MILLIS), + ) + }, + ) +} + +fun NavGraphBuilder.verticallyAnimatedComposable( + route: String, + content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit, +) { + composable( + route = route, + content = content, + enterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Up, + animationSpec = tween(ANIM_DURATION_MILLIS), + ) + }, + exitTransition = { + fadeOut(animationSpec = tween(ANIM_DURATION_MILLIS)) + }, + popEnterTransition = { + fadeIn(animationSpec = tween(ANIM_DURATION_MILLIS)) + }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Down, + animationSpec = tween(ANIM_DURATION_MILLIS), + ) + }, + ) +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/SimpleCalendarTitle.kt b/compose-multiplatform/sample/src/commonMain/kotlin/SimpleCalendarTitle.kt new file mode 100644 index 00000000..0fe2edbf --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/SimpleCalendarTitle.kt @@ -0,0 +1,82 @@ + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.core.YearMonth + +@Composable +fun SimpleCalendarTitle( + modifier: Modifier, + currentMonth: YearMonth, + goToPrevious: () -> Unit, + goToNext: () -> Unit, +) { + Row( + modifier = modifier.height(40.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CalendarNavigationIcon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft, + contentDescription = "Previous", + onClick = goToPrevious, + ) + Text( + modifier = Modifier + .weight(1f) + .testTag("MonthTitle"), + text = currentMonth.displayText(), + fontSize = 22.sp, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Medium, + ) + CalendarNavigationIcon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = "Next", + onClick = goToNext, + ) + } +} + +@Composable +private fun CalendarNavigationIcon( + imageVector: ImageVector, + contentDescription: String, + onClick: () -> Unit, +) = Box( + modifier = Modifier + .fillMaxHeight() + .aspectRatio(1f) + .clip(shape = CircleShape) + .clickable(role = Role.Button, onClick = onClick), +) { + Icon( + modifier = Modifier + .fillMaxSize() + .padding(4.dp) + .align(Alignment.Center), + imageVector = imageVector, + contentDescription = contentDescription, + ) +} diff --git a/compose-multiplatform/sample/src/commonMain/kotlin/Utils.kt b/compose-multiplatform/sample/src/commonMain/kotlin/Utils.kt new file mode 100644 index 00000000..45cc3c0b --- /dev/null +++ b/compose-multiplatform/sample/src/commonMain/kotlin/Utils.kt @@ -0,0 +1,191 @@ +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import com.kizitonwose.calendar.compose.CalendarLayoutInfo +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.core.CalendarMonth +import com.kizitonwose.calendar.core.Week +import com.kizitonwose.calendar.core.YearMonth +import com.kizitonwose.calendar.core.minus +import com.kizitonwose.calendar.core.plus +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.minus +import kotlinx.datetime.plus + +fun Modifier.clickable( + enabled: Boolean = true, + showRipple: Boolean = true, + onClickLabel: String? = null, + role: Role? = null, + onClick: () -> Unit, +): Modifier = composed { + clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = if (showRipple) LocalIndication.current else null, + enabled = enabled, + onClickLabel = onClickLabel, + role = role, + onClick = onClick, + ) +} + +@Composable +fun NavigationIcon( + tint: Color = Color.White, + imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack, + onBackClick: () -> Unit, +) { + Box( + modifier = Modifier + .fillMaxHeight() + .aspectRatio(1f) + .padding(8.dp) + .clip(shape = CircleShape) + .clickable(role = Role.Button, onClick = onBackClick), + ) { + Icon( + tint = tint, + modifier = Modifier.align(Alignment.Center), + imageVector = imageVector, + contentDescription = "Back", + ) + } +} + +/** + * Alternative way to find the first fully visible month in the layout. + * + * @see [rememberFirstVisibleMonthAfterScroll] + * @see [rememberFirstMostVisibleMonth] + */ +@Composable +fun rememberFirstCompletelyVisibleMonth(state: CalendarState): CalendarMonth { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) } + // Only take non-null values as null will be produced when the + // list is mid-scroll as no index will be completely visible. + LaunchedEffect(state) { + snapshotFlow { state.layoutInfo.completelyVisibleMonths.firstOrNull() } + .filterNotNull() + .collect { month -> visibleMonth.value = month } + } + return visibleMonth.value +} + +/** + * Returns the first visible month in a paged calendar **after** scrolling stops. + * + * @see [rememberFirstCompletelyVisibleMonth] + * @see [rememberFirstMostVisibleMonth] + */ +@Composable +fun rememberFirstVisibleMonthAfterScroll(state: CalendarState): CalendarMonth { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) } + LaunchedEffect(state) { + snapshotFlow { state.isScrollInProgress } + .filter { scrolling -> !scrolling } + .collect { visibleMonth.value = state.firstVisibleMonth } + } + return visibleMonth.value +} + +/** + * Find first visible week in a paged week calendar **after** scrolling stops. + */ +@Composable +fun rememberFirstVisibleWeekAfterScroll(state: WeekCalendarState): Week { + val visibleWeek = remember(state) { mutableStateOf(state.firstVisibleWeek) } + LaunchedEffect(state) { + snapshotFlow { state.isScrollInProgress } + .filter { scrolling -> !scrolling } + .collect { visibleWeek.value = state.firstVisibleWeek } + } + return visibleWeek.value +} + +/** + * Find the first month on the calendar visible up to the given [viewportPercent] size. + * + * @see [rememberFirstCompletelyVisibleMonth] + * @see [rememberFirstVisibleMonthAfterScroll] + */ +@Composable +fun rememberFirstMostVisibleMonth( + state: CalendarState, + viewportPercent: Float = 50f, +): CalendarMonth { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) } + LaunchedEffect(state) { + snapshotFlow { state.layoutInfo.firstMostVisibleMonth(viewportPercent) } + .filterNotNull() + .collect { month -> visibleMonth.value = month } + } + return visibleMonth.value +} + +private val CalendarLayoutInfo.completelyVisibleMonths: List + get() { + val visibleItemsInfo = this.visibleMonthsInfo.toMutableList() + return if (visibleItemsInfo.isEmpty()) { + emptyList() + } else { + val lastItem = visibleItemsInfo.last() + val viewportSize = this.viewportEndOffset + this.viewportStartOffset + if (lastItem.offset + lastItem.size > viewportSize) { + visibleItemsInfo.removeLast() + } + val firstItem = visibleItemsInfo.firstOrNull() + if (firstItem != null && firstItem.offset < this.viewportStartOffset) { + visibleItemsInfo.removeFirst() + } + visibleItemsInfo.map { it.month } + } + } + +private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50f): CalendarMonth? { + return if (visibleMonthsInfo.isEmpty()) { + null + } else { + val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f + visibleMonthsInfo.firstOrNull { itemInfo -> + if (itemInfo.offset < 0) { + itemInfo.offset + itemInfo.size >= viewportSize + } else { + itemInfo.size - itemInfo.offset >= viewportSize + } + }?.month + } +} + +internal fun LocalDate.plusDays(value: Int): LocalDate = plus(value, DateTimeUnit.DAY) +internal fun LocalDate.minusDays(value: Int): LocalDate = minus(value, DateTimeUnit.DAY) +internal fun YearMonth.plusMonths(value: Int): YearMonth = plus(value, DateTimeUnit.MONTH) +internal fun YearMonth.minusMonths(value: Int): YearMonth = minus(value, DateTimeUnit.MONTH) +internal val YearMonth.next: YearMonth + get() = this.minusMonths(1) +internal val YearMonth.previous: YearMonth + get() = this.minusMonths(1) diff --git a/compose-multiplatform/sample/src/desktopMain/kotlin/Device.desktop.kt b/compose-multiplatform/sample/src/desktopMain/kotlin/Device.desktop.kt new file mode 100644 index 00000000..974565dc --- /dev/null +++ b/compose-multiplatform/sample/src/desktopMain/kotlin/Device.desktop.kt @@ -0,0 +1 @@ +actual fun isMobile(): Boolean = false diff --git a/compose-multiplatform/sample/src/desktopMain/kotlin/Main.kt b/compose-multiplatform/sample/src/desktopMain/kotlin/Main.kt new file mode 100644 index 00000000..263a957c --- /dev/null +++ b/compose-multiplatform/sample/src/desktopMain/kotlin/Main.kt @@ -0,0 +1,11 @@ +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Calendar Sample", + ) { + App() + } +} diff --git a/compose-multiplatform/sample/src/iosMain/kotlin/Device.ios.kt b/compose-multiplatform/sample/src/iosMain/kotlin/Device.ios.kt new file mode 100644 index 00000000..3b8f0b67 --- /dev/null +++ b/compose-multiplatform/sample/src/iosMain/kotlin/Device.ios.kt @@ -0,0 +1 @@ +actual fun isMobile(): Boolean = true diff --git a/compose-multiplatform/sample/src/iosMain/kotlin/Format.ios.kt b/compose-multiplatform/sample/src/iosMain/kotlin/Format.ios.kt new file mode 100644 index 00000000..5291f3fa --- /dev/null +++ b/compose-multiplatform/sample/src/iosMain/kotlin/Format.ios.kt @@ -0,0 +1,20 @@ +import androidx.compose.ui.text.intl.Locale +import com.kizitonwose.calendar.core.daysOfWeek +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.Month +import platform.Foundation.NSCalendar +import platform.Foundation.NSLocale + +actual fun Month.getDisplayName(short: Boolean, locale: Locale): String = + NSCalendar.currentCalendar.let { + it.setLocale(NSLocale(locale.toLanguageTag())) + it.monthSymbols[Month.entries.indexOf(this)] as String + } + +actual fun DayOfWeek.getShortDisplayName(locale: Locale): String = + NSCalendar.currentCalendar.let { + it.setLocale(NSLocale(locale.toLanguageTag())) + it.shortWeekdaySymbols[sundayBasedWeek.indexOf(this)] as String + } + +private val sundayBasedWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.SUNDAY) diff --git a/compose-multiplatform/sample/src/iosMain/kotlin/MainViewController.kt b/compose-multiplatform/sample/src/iosMain/kotlin/MainViewController.kt new file mode 100644 index 00000000..5f9d8e00 --- /dev/null +++ b/compose-multiplatform/sample/src/iosMain/kotlin/MainViewController.kt @@ -0,0 +1,4 @@ +import androidx.compose.ui.window.ComposeUIViewController + +@Suppress("FunctionName") +fun MainViewController() = ComposeUIViewController { App() } diff --git a/compose-multiplatform/sample/src/jvmMain/kotlin/Format.jvm.kt b/compose-multiplatform/sample/src/jvmMain/kotlin/Format.jvm.kt new file mode 100644 index 00000000..13b90094 --- /dev/null +++ b/compose-multiplatform/sample/src/jvmMain/kotlin/Format.jvm.kt @@ -0,0 +1,14 @@ +import androidx.compose.ui.text.intl.Locale +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.Month +import java.time.format.TextStyle +import java.util.Locale as JavaLocale + +actual fun Month.getDisplayName(short: Boolean, locale: Locale): String { + val style = if (short) TextStyle.SHORT else TextStyle.FULL + return getDisplayName(style, JavaLocale.forLanguageTag(locale.toLanguageTag())) +} + +actual fun DayOfWeek.getShortDisplayName(locale: Locale): String { + return getDisplayName(TextStyle.SHORT, JavaLocale.forLanguageTag(locale.toLanguageTag())) +} diff --git a/compose-multiplatform/sample/src/wasmJsMain/kotlin/Device.wasmJs.kt b/compose-multiplatform/sample/src/wasmJsMain/kotlin/Device.wasmJs.kt new file mode 100644 index 00000000..974565dc --- /dev/null +++ b/compose-multiplatform/sample/src/wasmJsMain/kotlin/Device.wasmJs.kt @@ -0,0 +1 @@ +actual fun isMobile(): Boolean = false diff --git a/compose-multiplatform/sample/src/wasmJsMain/kotlin/Format.wasmJs.kt b/compose-multiplatform/sample/src/wasmJsMain/kotlin/Format.wasmJs.kt new file mode 100644 index 00000000..7be940b6 --- /dev/null +++ b/compose-multiplatform/sample/src/wasmJsMain/kotlin/Format.wasmJs.kt @@ -0,0 +1,16 @@ +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.toLowerCase +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.Month + +actual fun Month.getDisplayName(short: Boolean, locale: Locale): String { + val name = name.toLowerCase(enLocale).capitalize(enLocale) + return if (short) name.take(3) else name +} + +actual fun DayOfWeek.getShortDisplayName(locale: Locale): String { + return name.toLowerCase(enLocale).capitalize(enLocale).take(3) +} + +private val enLocale = Locale("en-US") diff --git a/compose-multiplatform/sample/src/wasmJsMain/kotlin/Main.kt b/compose-multiplatform/sample/src/wasmJsMain/kotlin/Main.kt new file mode 100644 index 00000000..48a60ac3 --- /dev/null +++ b/compose-multiplatform/sample/src/wasmJsMain/kotlin/Main.kt @@ -0,0 +1,10 @@ +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.ComposeViewport +import kotlinx.browser.document + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + ComposeViewport(document.body!!) { + App() + } +} diff --git a/compose-multiplatform/sample/src/wasmJsMain/resources/index.html b/compose-multiplatform/sample/src/wasmJsMain/resources/index.html new file mode 100644 index 00000000..ffd7e720 --- /dev/null +++ b/compose-multiplatform/sample/src/wasmJsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + + Calendar Library + + + + + + diff --git a/compose-multiplatform/sample/src/wasmJsMain/resources/styles.css b/compose-multiplatform/sample/src/wasmJsMain/resources/styles.css new file mode 100644 index 00000000..0549b10f --- /dev/null +++ b/compose-multiplatform/sample/src/wasmJsMain/resources/styles.css @@ -0,0 +1,7 @@ +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} \ No newline at end of file diff --git a/compose/api/compose.api b/compose/api/compose.api new file mode 100644 index 00000000..81615668 --- /dev/null +++ b/compose/api/compose.api @@ -0,0 +1,181 @@ +public final class com/kizitonwose/calendar/compose/CalendarItemInfo : androidx/compose/foundation/lazy/LazyListItemInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListItemInfo;Lcom/kizitonwose/calendar/core/CalendarMonth;)V + public fun getContentType ()Ljava/lang/Object; + public fun getIndex ()I + public fun getKey ()Ljava/lang/Object; + public final fun getMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun getOffset ()I + public fun getSize ()I +} + +public final class com/kizitonwose/calendar/compose/CalendarKt { + public static final fun HeatMapCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState;Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition;ZLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + public static final fun HorizontalCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/CalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lcom/kizitonwose/calendar/compose/ContentHeightMode;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun VerticalCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/CalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lcom/kizitonwose/calendar/compose/ContentHeightMode;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Landroidx/compose/runtime/Composer;III)V + public static final fun WeekCalendar (Landroidx/compose/ui/Modifier;Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState;ZZZLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V +} + +public final class com/kizitonwose/calendar/compose/CalendarLayoutInfo : androidx/compose/foundation/lazy/LazyListLayoutInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListLayoutInfo;Lkotlin/jvm/functions/Function1;)V + public fun getAfterContentPadding ()I + public fun getBeforeContentPadding ()I + public fun getMainAxisItemSpacing ()I + public fun getOrientation ()Landroidx/compose/foundation/gestures/Orientation; + public fun getReverseLayout ()Z + public fun getTotalItemsCount ()I + public fun getViewportEndOffset ()I + public fun getViewportSize-YbymL2g ()J + public fun getViewportStartOffset ()I + public fun getVisibleItemsInfo ()Ljava/util/List; + public final fun getVisibleMonthsInfo ()Ljava/util/List; +} + +public final class com/kizitonwose/calendar/compose/CalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/CalendarState$Companion; + public final fun animateScrollToMonth (Ljava/time/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndMonth ()Ljava/time/YearMonth; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/CalendarLayoutInfo; + public final fun getOutDateStyle ()Lcom/kizitonwose/calendar/core/OutDateStyle; + public final fun getStartMonth ()Ljava/time/YearMonth; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToMonth (Ljava/time/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndMonth (Ljava/time/YearMonth;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setOutDateStyle (Lcom/kizitonwose/calendar/core/OutDateStyle;)V + public final fun setStartMonth (Ljava/time/YearMonth;)V +} + +public final class com/kizitonwose/calendar/compose/CalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/CalendarStateKt { + public static final fun rememberCalendarState (Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/DayOfWeek;Lcom/kizitonwose/calendar/core/OutDateStyle;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/CalendarState; +} + +public final class com/kizitonwose/calendar/compose/ComposableSingletons$CalendarMonthsKt { + public static final field INSTANCE Lcom/kizitonwose/calendar/compose/ComposableSingletons$CalendarMonthsKt; + public static field lambda-1 Lkotlin/jvm/functions/Function5; + public static field lambda-2 Lkotlin/jvm/functions/Function5; + public fun ()V + public final fun getLambda-1$compose_release ()Lkotlin/jvm/functions/Function5; + public final fun getLambda-2$compose_release ()Lkotlin/jvm/functions/Function5; +} + +public final class com/kizitonwose/calendar/compose/ContentHeightMode : java/lang/Enum { + public static final field Fill Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static final field Wrap Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/compose/ContentHeightMode; + public static fun values ()[Lcom/kizitonwose/calendar/compose/ContentHeightMode; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState$Companion; + public final fun animateScrollToMonth (Ljava/time/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndMonth ()Ljava/time/YearMonth; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/CalendarLayoutInfo; + public final fun getStartMonth ()Ljava/time/YearMonth; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToMonth (Ljava/time/YearMonth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndMonth (Ljava/time/YearMonth;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setStartMonth (Ljava/time/YearMonth;)V +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarStateKt { + public static final fun rememberHeatMapCalendarState (Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/DayOfWeek;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek : java/io/Serializable { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek; + public fun equals (Ljava/lang/Object;)Z + public final fun getDays ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition : java/lang/Enum { + public static final field End Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static final field Start Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; + public static fun values ()[Lcom/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarItemInfo : androidx/compose/foundation/lazy/LazyListItemInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListItemInfo;Lcom/kizitonwose/calendar/core/Week;)V + public fun getContentType ()Ljava/lang/Object; + public fun getIndex ()I + public fun getKey ()Ljava/lang/Object; + public fun getOffset ()I + public fun getSize ()I + public final fun getWeek ()Lcom/kizitonwose/calendar/core/Week; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo : androidx/compose/foundation/lazy/LazyListLayoutInfo { + public static final field $stable I + public fun (Landroidx/compose/foundation/lazy/LazyListLayoutInfo;Lkotlin/jvm/functions/Function1;)V + public fun getAfterContentPadding ()I + public fun getBeforeContentPadding ()I + public fun getMainAxisItemSpacing ()I + public fun getOrientation ()Landroidx/compose/foundation/gestures/Orientation; + public fun getReverseLayout ()Z + public fun getTotalItemsCount ()I + public fun getViewportEndOffset ()I + public fun getViewportSize-YbymL2g ()J + public fun getViewportStartOffset ()I + public fun getVisibleItemsInfo ()Ljava/util/List; + public final fun getVisibleWeeksInfo ()Ljava/util/List; +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState : androidx/compose/foundation/gestures/ScrollableState { + public static final field $stable I + public static final field Companion Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState$Companion; + public final fun animateScrollToWeek (Ljava/time/LocalDate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispatchRawDelta (F)F + public final fun getEndDate ()Ljava/time/LocalDate; + public final fun getFirstDayOfWeek ()Ljava/time/DayOfWeek; + public final fun getFirstVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getInteractionSource ()Landroidx/compose/foundation/interaction/InteractionSource; + public final fun getLastVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getLayoutInfo ()Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo; + public final fun getStartDate ()Ljava/time/LocalDate; + public fun isScrollInProgress ()Z + public fun scroll (Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun scrollToWeek (Ljava/time/LocalDate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEndDate (Ljava/time/LocalDate;)V + public final fun setFirstDayOfWeek (Ljava/time/DayOfWeek;)V + public final fun setStartDate (Ljava/time/LocalDate;)V +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState$Companion { +} + +public final class com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarStateKt { + public static final fun rememberWeekCalendarState (Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/DayOfWeek;Landroidx/compose/runtime/Composer;II)Lcom/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState; +} + diff --git a/compose/build.gradle.kts b/compose/build.gradle.kts index 29967417..778e1eec 100644 --- a/compose/build.gradle.kts +++ b/compose/build.gradle.kts @@ -1,6 +1,7 @@ import com.kizitonwose.calendar.buildsrc.Android import com.kizitonwose.calendar.buildsrc.Config +import com.kizitonwose.calendar.buildsrc.Versions plugins { alias(libs.plugins.androidLibrary) @@ -35,10 +36,14 @@ dependencies { implementation(project(":data")) implementation(libs.kotlin.stdlib) - implementation(libs.compose.ui) + implementation(libs.compose.ui.ui) implementation(libs.compose.ui.tooling) implementation(libs.compose.foundation) implementation(libs.compose.runtime) testImplementation(libs.test.junit) } + +mavenPublishing { + coordinates(version = Versions.core) +} diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/Calendar.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/Calendar.kt index a9ba05b1..9d5bee28 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/Calendar.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/Calendar.kt @@ -58,7 +58,7 @@ import java.time.DayOfWeek * customisations are rendered. */ @Composable -fun HorizontalCalendar( +public fun HorizontalCalendar( modifier: Modifier = Modifier, state: CalendarState = rememberCalendarState(), calendarScrollPaged: Boolean = true, @@ -71,7 +71,7 @@ fun HorizontalCalendar( monthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit)? = null, monthFooter: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, monthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit)? = null, -) = Calendar( +): Unit = Calendar( modifier = modifier, state = state, calendarScrollPaged = calendarScrollPaged, @@ -121,7 +121,7 @@ fun HorizontalCalendar( * customisations are rendered. */ @Composable -fun VerticalCalendar( +public fun VerticalCalendar( modifier: Modifier = Modifier, state: CalendarState = rememberCalendarState(), calendarScrollPaged: Boolean = false, @@ -134,7 +134,7 @@ fun VerticalCalendar( monthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit)? = null, monthFooter: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, monthContainer: (@Composable LazyItemScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit)? = null, -) = Calendar( +): Unit = Calendar( modifier = modifier, state = state, calendarScrollPaged = calendarScrollPaged, @@ -232,7 +232,7 @@ private fun Calendar( * placed below each week on the calendar. */ @Composable -fun WeekCalendar( +public fun WeekCalendar( modifier: Modifier = Modifier, state: WeekCalendarState = rememberWeekCalendarState(), calendarScrollPaged: Boolean = true, @@ -242,7 +242,7 @@ fun WeekCalendar( dayContent: @Composable BoxScope.(WeekDay) -> Unit, weekHeader: (@Composable ColumnScope.(Week) -> Unit)? = null, weekFooter: (@Composable ColumnScope.(Week) -> Unit)? = null, -) = WeekCalendarImpl( +): Unit = WeekCalendarImpl( modifier = modifier, state = state, calendarScrollPaged = calendarScrollPaged, @@ -274,7 +274,7 @@ fun WeekCalendar( * placed above each month on the calendar. */ @Composable -fun HeatMapCalendar( +public fun HeatMapCalendar( modifier: Modifier = Modifier, state: HeatMapCalendarState = rememberHeatMapCalendarState(), weekHeaderPosition: HeatMapWeekHeaderPosition = HeatMapWeekHeaderPosition.Start, @@ -283,7 +283,7 @@ fun HeatMapCalendar( dayContent: @Composable ColumnScope.(day: CalendarDay, week: HeatMapWeek) -> Unit, weekHeader: (@Composable ColumnScope.(DayOfWeek) -> Unit)? = null, monthHeader: (@Composable ColumnScope.(CalendarMonth) -> Unit)? = null, -) = HeatMapCalendarImpl( +): Unit = HeatMapCalendarImpl( modifier = modifier, state = state, weekHeaderPosition = weekHeaderPosition, diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt index d78948da..6fce652c 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarLayoutInfo.kt @@ -11,12 +11,12 @@ import com.kizitonwose.calendar.core.CalendarMonth * Use [CalendarState.layoutInfo] to retrieve this. * @see LazyListLayoutInfo */ -class CalendarLayoutInfo(info: LazyListLayoutInfo, private val month: (Int) -> CalendarMonth) : +public class CalendarLayoutInfo(info: LazyListLayoutInfo, private val month: (Int) -> CalendarMonth) : LazyListLayoutInfo by info { /** * The list of [CalendarItemInfo] representing all the currently visible months. */ - val visibleMonthsInfo: List + public val visibleMonthsInfo: List get() = visibleItemsInfo.map { CalendarItemInfo(it, month(it.index)) } @@ -30,4 +30,4 @@ class CalendarLayoutInfo(info: LazyListLayoutInfo, private val month: (Int) -> C * @see CalendarLayoutInfo * @see LazyListItemInfo */ -class CalendarItemInfo(info: LazyListItemInfo, val month: CalendarMonth) : LazyListItemInfo by info +public class CalendarItemInfo(info: LazyListItemInfo, public val month: CalendarMonth) : LazyListItemInfo by info diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarState.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarState.kt index 17adf85f..0f5e901b 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarState.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/CalendarState.kt @@ -37,7 +37,7 @@ import java.time.YearMonth * @param outDateStyle the initial value for [CalendarState.outDateStyle] */ @Composable -fun rememberCalendarState( +public fun rememberCalendarState( startMonth: YearMonth = YearMonth.now(), endMonth: YearMonth = startMonth, firstVisibleMonth: YearMonth = startMonth, @@ -77,7 +77,7 @@ fun rememberCalendarState( * @param outDateStyle the preferred style for out date generation. */ @Stable -class CalendarState internal constructor( +public class CalendarState internal constructor( startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek, @@ -89,7 +89,7 @@ class CalendarState internal constructor( private var _startMonth by mutableStateOf(startMonth) /** The first month on the calendar. */ - var startMonth: YearMonth + public var startMonth: YearMonth get() = _startMonth set(value) { if (value != startMonth) { @@ -102,7 +102,7 @@ class CalendarState internal constructor( private var _endMonth by mutableStateOf(endMonth) /** The last month on the calendar. */ - var endMonth: YearMonth + public var endMonth: YearMonth get() = _endMonth set(value) { if (value != endMonth) { @@ -115,7 +115,7 @@ class CalendarState internal constructor( private var _firstDayOfWeek by mutableStateOf(firstDayOfWeek) /** The first day of week on the calendar. */ - var firstDayOfWeek: DayOfWeek + public var firstDayOfWeek: DayOfWeek get() = _firstDayOfWeek set(value) { if (value != firstDayOfWeek) { @@ -128,7 +128,7 @@ class CalendarState internal constructor( private var _outDateStyle by mutableStateOf(outDateStyle) /** The preferred style for out date generation. */ - var outDateStyle: OutDateStyle + public var outDateStyle: OutDateStyle get() = _outDateStyle set(value) { if (value != outDateStyle) { @@ -142,7 +142,7 @@ class CalendarState internal constructor( * * @see [lastVisibleMonth] */ - val firstVisibleMonth: CalendarMonth by derivedStateOf { + public val firstVisibleMonth: CalendarMonth by derivedStateOf { store[listState.firstVisibleItemIndex] } @@ -151,7 +151,7 @@ class CalendarState internal constructor( * * @see [firstVisibleMonth] */ - val lastVisibleMonth: CalendarMonth by derivedStateOf { + public val lastVisibleMonth: CalendarMonth by derivedStateOf { store[listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0] } @@ -173,7 +173,7 @@ class CalendarState internal constructor( * * see [LazyListLayoutInfo] */ - val layoutInfo: CalendarLayoutInfo + public val layoutInfo: CalendarLayoutInfo get() = CalendarLayoutInfo(listState.layoutInfo) { index -> store[index] } /** @@ -181,7 +181,7 @@ class CalendarState internal constructor( * calendar is being dragged. If you want to know whether the fling (or animated scroll) is in * progress, use [isScrollInProgress]. */ - val interactionSource: InteractionSource + public val interactionSource: InteractionSource get() = listState.interactionSource internal val listState = LazyListState( @@ -228,7 +228,7 @@ class CalendarState internal constructor( * * @see [animateScrollToMonth] */ - suspend fun scrollToMonth(month: YearMonth) { + public suspend fun scrollToMonth(month: YearMonth) { listState.scrollToItem(getScrollIndex(month) ?: return) } @@ -238,7 +238,7 @@ class CalendarState internal constructor( * @param month the month to which to scroll. Must be within the * range of [startMonth] and [endMonth]. */ - suspend fun animateScrollToMonth(month: YearMonth) { + public suspend fun animateScrollToMonth(month: YearMonth) { listState.animateScrollToItem(getScrollIndex(month) ?: return) } @@ -261,22 +261,19 @@ class CalendarState internal constructor( override suspend fun scroll( scrollPriority: MutatePriority, block: suspend ScrollScope.() -> Unit, - ) = listState.scroll(scrollPriority, block) + ): Unit = listState.scroll(scrollPriority, block) - companion object { + public companion object { internal val Saver: Saver = listSaver( save = { - val visibleItemState = VisibleItemState( - firstVisibleItemIndex = it.listState.firstVisibleItemIndex, - firstVisibleItemScrollOffset = it.listState.firstVisibleItemScrollOffset, - ) listOf( it.startMonth, it.endMonth, it.firstVisibleMonth.yearMonth, it.firstDayOfWeek, it.outDateStyle, - visibleItemState, + it.listState.firstVisibleItemIndex, + it.listState.firstVisibleItemScrollOffset, ) }, restore = { @@ -286,7 +283,10 @@ class CalendarState internal constructor( firstVisibleMonth = it[2] as YearMonth, firstDayOfWeek = it[3] as DayOfWeek, outDateStyle = it[4] as OutDateStyle, - visibleItemState = it[5] as VisibleItemState, + visibleItemState = VisibleItemState( + firstVisibleItemIndex = it[5] as Int, + firstVisibleItemScrollOffset = it[6] as Int, + ), ) }, ) diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/ContentHeightMode.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/ContentHeightMode.kt index 370751fe..d7611f71 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/ContentHeightMode.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/ContentHeightMode.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier /** * Determines how the height of the day content is calculated. */ -enum class ContentHeightMode { +public enum class ContentHeightMode { /** * The day container will wrap its height. This allows you to * use [Modifier.aspectRatio] if you want square day content diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt index 5cc52091..bb13151e 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapCalendarState.kt @@ -37,7 +37,7 @@ import java.time.YearMonth * @param firstVisibleMonth the initial value for [HeatMapCalendarState.firstVisibleMonth] */ @Composable -fun rememberHeatMapCalendarState( +public fun rememberHeatMapCalendarState( startMonth: YearMonth = YearMonth.now(), endMonth: YearMonth = startMonth, firstVisibleMonth: YearMonth = startMonth, @@ -73,7 +73,7 @@ fun rememberHeatMapCalendarState( * @param firstVisibleMonth the initial value for [HeatMapCalendarState.firstVisibleMonth] */ @Stable -class HeatMapCalendarState internal constructor( +public class HeatMapCalendarState internal constructor( startMonth: YearMonth, endMonth: YearMonth, firstVisibleMonth: YearMonth, @@ -84,7 +84,7 @@ class HeatMapCalendarState internal constructor( private var _startMonth by mutableStateOf(startMonth) /** The first month on the calendar. */ - var startMonth: YearMonth + public var startMonth: YearMonth get() = _startMonth set(value) { if (value != startMonth) { @@ -97,7 +97,7 @@ class HeatMapCalendarState internal constructor( private var _endMonth by mutableStateOf(endMonth) /** The last month on the calendar. */ - var endMonth: YearMonth + public var endMonth: YearMonth get() = _endMonth set(value) { if (value != endMonth) { @@ -110,7 +110,7 @@ class HeatMapCalendarState internal constructor( private var _firstDayOfWeek by mutableStateOf(firstDayOfWeek) /** The first day of week on the calendar. */ - var firstDayOfWeek: DayOfWeek + public var firstDayOfWeek: DayOfWeek get() = _firstDayOfWeek set(value) { if (value != firstDayOfWeek) { @@ -124,7 +124,7 @@ class HeatMapCalendarState internal constructor( * * @see [lastVisibleMonth] */ - val firstVisibleMonth: CalendarMonth by derivedStateOf { + public val firstVisibleMonth: CalendarMonth by derivedStateOf { store[listState.firstVisibleItemIndex] } @@ -133,7 +133,7 @@ class HeatMapCalendarState internal constructor( * * @see [firstVisibleMonth] */ - val lastVisibleMonth: CalendarMonth by derivedStateOf { + public val lastVisibleMonth: CalendarMonth by derivedStateOf { store[listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0] } @@ -153,7 +153,7 @@ class HeatMapCalendarState internal constructor( * If you want to run some side effects like sending an analytics event or updating a state * based on this value consider using "snapshotFlow". */ - val layoutInfo: CalendarLayoutInfo + public val layoutInfo: CalendarLayoutInfo get() = CalendarLayoutInfo(listState.layoutInfo) { index -> store[index] } /** @@ -161,7 +161,7 @@ class HeatMapCalendarState internal constructor( * calendar is being dragged. If you want to know whether the fling (or animated scroll) is in * progress, use [isScrollInProgress]. */ - val interactionSource: InteractionSource + public val interactionSource: InteractionSource get() = listState.interactionSource internal val listState = LazyListState( @@ -201,7 +201,7 @@ class HeatMapCalendarState internal constructor( * * @see [animateScrollToMonth] */ - suspend fun scrollToMonth(month: YearMonth) { + public suspend fun scrollToMonth(month: YearMonth) { listState.scrollToItem(getScrollIndex(month) ?: return) } @@ -211,7 +211,7 @@ class HeatMapCalendarState internal constructor( * @param month the month to which to scroll. Must be within the * range of [startMonth] and [endMonth]. */ - suspend fun animateScrollToMonth(month: YearMonth) { + public suspend fun animateScrollToMonth(month: YearMonth) { listState.animateScrollToItem(getScrollIndex(month) ?: return) } @@ -234,21 +234,18 @@ class HeatMapCalendarState internal constructor( override suspend fun scroll( scrollPriority: MutatePriority, block: suspend ScrollScope.() -> Unit, - ) = listState.scroll(scrollPriority, block) + ): Unit = listState.scroll(scrollPriority, block) - companion object { + public companion object { internal val Saver: Saver = listSaver( save = { - val visibleItemState = VisibleItemState( - firstVisibleItemIndex = it.listState.firstVisibleItemIndex, - firstVisibleItemScrollOffset = it.listState.firstVisibleItemScrollOffset, - ) listOf( it.startMonth, it.endMonth, it.firstVisibleMonth.yearMonth, it.firstDayOfWeek, - visibleItemState, + it.listState.firstVisibleItemIndex, + it.listState.firstVisibleItemScrollOffset, ) }, restore = { @@ -257,7 +254,10 @@ class HeatMapCalendarState internal constructor( endMonth = it[1] as YearMonth, firstVisibleMonth = it[2] as YearMonth, firstDayOfWeek = it[3] as DayOfWeek, - visibleItemState = it[4] as VisibleItemState, + visibleItemState = VisibleItemState( + firstVisibleItemIndex = it[4] as Int, + firstVisibleItemScrollOffset = it[5] as Int, + ), ) }, ) diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt index 355a0e14..ba6b471c 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeek.kt @@ -16,4 +16,4 @@ import java.io.Serializable * @param days the days in this week. */ @Immutable -data class HeatMapWeek(val days: List) : Serializable +public data class HeatMapWeek(val days: List) : Serializable diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt index ef077405..41cebfe0 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/heatmapcalendar/HeatMapWeekHeaderPosition.kt @@ -6,7 +6,7 @@ import com.kizitonwose.calendar.compose.HeatMapCalendar * Determines the position of the week header * composable (Mon, Tue, Wed...) in the [HeatMapCalendar] */ -enum class HeatMapWeekHeaderPosition { +public enum class HeatMapWeekHeaderPosition { /** * The header is positioned at the start of the calendar. */ diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt index b8c7fbac..302e051f 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarLayoutInfo.kt @@ -12,14 +12,14 @@ import com.kizitonwose.calendar.core.Week * * @see LazyListLayoutInfo */ -class WeekCalendarLayoutInfo( +public class WeekCalendarLayoutInfo( info: LazyListLayoutInfo, private val getIndexData: (Int) -> Week, ) : LazyListLayoutInfo by info { /** * The list of [WeekCalendarItemInfo] representing all the currently visible weeks. */ - val visibleWeeksInfo: List + public val visibleWeeksInfo: List get() = visibleItemsInfo.map { info -> WeekCalendarItemInfo(info, getIndexData(info.index)) } @@ -33,5 +33,5 @@ class WeekCalendarLayoutInfo( * @see WeekCalendarLayoutInfo * @see LazyListItemInfo */ -class WeekCalendarItemInfo(info: LazyListItemInfo, val week: Week) : +public class WeekCalendarItemInfo(info: LazyListItemInfo, public val week: Week) : LazyListItemInfo by info diff --git a/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt b/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt index 91b82239..7cc76c4f 100644 --- a/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt +++ b/compose/src/main/java/com/kizitonwose/calendar/compose/weekcalendar/WeekCalendarState.kt @@ -39,7 +39,7 @@ import java.time.YearMonth * @param firstVisibleWeekDate the date which will have its week visible initially. */ @Composable -fun rememberWeekCalendarState( +public fun rememberWeekCalendarState( startDate: LocalDate = YearMonth.now().atStartOfMonth(), endDate: LocalDate = YearMonth.now().atEndOfMonth(), firstVisibleWeekDate: LocalDate = LocalDate.now(), @@ -79,7 +79,7 @@ fun rememberWeekCalendarState( * @param firstVisibleWeekDate the date which will have its week visible initially. */ @Stable -class WeekCalendarState internal constructor( +public class WeekCalendarState internal constructor( startDate: LocalDate, endDate: LocalDate, firstVisibleWeekDate: LocalDate, @@ -106,7 +106,7 @@ class WeekCalendarState internal constructor( * in the week to which this date belongs, depending on the provided [firstDayOfWeek]. * Such days will have their [WeekDayPosition] set to [WeekDayPosition.InDate] */ - var startDate: LocalDate + public var startDate: LocalDate get() = _startDate set(value) { if (value != _startDate) { @@ -123,7 +123,7 @@ class WeekCalendarState internal constructor( * in the week to which this date belongs. Such days will have their [WeekDayPosition] * set to [WeekDayPosition.OutDate] */ - var endDate: LocalDate + public var endDate: LocalDate get() = _endDate set(value) { if (value != _endDate) { @@ -136,7 +136,7 @@ class WeekCalendarState internal constructor( private var _firstDayOfWeek by mutableStateOf(firstDayOfWeek) /** The first day of week on the calendar. */ - var firstDayOfWeek: DayOfWeek + public var firstDayOfWeek: DayOfWeek get() = _firstDayOfWeek set(value) { if (value != _firstDayOfWeek) { @@ -148,14 +148,14 @@ class WeekCalendarState internal constructor( /** * The first week that is visible. */ - val firstVisibleWeek: Week by derivedStateOf { + public val firstVisibleWeek: Week by derivedStateOf { store[listState.firstVisibleItemIndex] } /** * The last week that is visible. */ - val lastVisibleWeek: Week by derivedStateOf { + public val lastVisibleWeek: Week by derivedStateOf { store[listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0] } @@ -175,7 +175,7 @@ class WeekCalendarState internal constructor( * If you want to run some side effects like sending an analytics event or updating a state * based on this value consider using "snapshotFlow". */ - val layoutInfo: WeekCalendarLayoutInfo + public val layoutInfo: WeekCalendarLayoutInfo get() = WeekCalendarLayoutInfo(listState.layoutInfo) { index -> store[index] } internal val store = DataStore { offset -> @@ -218,7 +218,7 @@ class WeekCalendarState internal constructor( * * @see [animateScrollToWeek] */ - suspend fun scrollToWeek(date: LocalDate) { + public suspend fun scrollToWeek(date: LocalDate) { listState.scrollToItem(getScrollIndex(date) ?: return) } @@ -227,7 +227,7 @@ class WeekCalendarState internal constructor( * * @param date the week to which to scroll. */ - suspend fun animateScrollToWeek(date: LocalDate) { + public suspend fun animateScrollToWeek(date: LocalDate) { listState.animateScrollToItem(getScrollIndex(date) ?: return) } @@ -236,7 +236,7 @@ class WeekCalendarState internal constructor( * calendar is being dragged. If you want to know whether the fling (or animated scroll) is in * progress, use [isScrollInProgress]. */ - val interactionSource: InteractionSource + public val interactionSource: InteractionSource get() = listState.interactionSource /** @@ -250,7 +250,7 @@ class WeekCalendarState internal constructor( override suspend fun scroll( scrollPriority: MutatePriority, block: suspend ScrollScope.() -> Unit, - ) = listState.scroll(scrollPriority, block) + ): Unit = listState.scroll(scrollPriority, block) private fun getScrollIndex(date: LocalDate): Int? { if (date !in startDateAdjusted..endDateAdjusted) { @@ -260,19 +260,16 @@ class WeekCalendarState internal constructor( return getWeekIndex(startDateAdjusted, date) } - companion object { - val Saver: Saver = listSaver( + public companion object { + internal val Saver: Saver = listSaver( save = { - val visibleItemState = VisibleItemState( - firstVisibleItemIndex = it.listState.firstVisibleItemIndex, - firstVisibleItemScrollOffset = it.listState.firstVisibleItemScrollOffset, - ) listOf( it.startDate, it.endDate, it.firstVisibleWeek.days.first().date, it.firstDayOfWeek, - visibleItemState, + it.listState.firstVisibleItemIndex, + it.listState.firstVisibleItemScrollOffset, ) }, restore = { @@ -281,7 +278,10 @@ class WeekCalendarState internal constructor( endDate = it[1] as LocalDate, firstVisibleWeekDate = it[2] as LocalDate, firstDayOfWeek = it[3] as DayOfWeek, - visibleItemState = it[4] as VisibleItemState, + visibleItemState = VisibleItemState( + firstVisibleItemIndex = it[4] as Int, + firstVisibleItemScrollOffset = it[5] as Int, + ), ) }, ) diff --git a/core/api/core.api b/core/api/core.api new file mode 100644 index 00000000..dca5da5f --- /dev/null +++ b/core/api/core.api @@ -0,0 +1,89 @@ +public final class com/kizitonwose/calendar/core/CalendarDay : java/io/Serializable { + public fun (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)V + public final fun component1 ()Ljava/time/LocalDate; + public final fun component2 ()Lcom/kizitonwose/calendar/core/DayPosition; + public final fun copy (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)Lcom/kizitonwose/calendar/core/CalendarDay; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/CalendarDay;Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/CalendarDay; + public fun equals (Ljava/lang/Object;)Z + public final fun getDate ()Ljava/time/LocalDate; + public final fun getPosition ()Lcom/kizitonwose/calendar/core/DayPosition; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/CalendarMonth : java/io/Serializable { + public fun (Ljava/time/YearMonth;Ljava/util/List;)V + public final fun component1 ()Ljava/time/YearMonth; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/time/YearMonth;Ljava/util/List;)Lcom/kizitonwose/calendar/core/CalendarMonth; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/CalendarMonth;Ljava/time/YearMonth;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun equals (Ljava/lang/Object;)Z + public final fun getWeekDays ()Ljava/util/List; + public final fun getYearMonth ()Ljava/time/YearMonth; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/DayPosition : java/lang/Enum { + public static final field InDate Lcom/kizitonwose/calendar/core/DayPosition; + public static final field MonthDate Lcom/kizitonwose/calendar/core/DayPosition; + public static final field OutDate Lcom/kizitonwose/calendar/core/DayPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/DayPosition; + public static fun values ()[Lcom/kizitonwose/calendar/core/DayPosition; +} + +public final class com/kizitonwose/calendar/core/ExtensionsKt { + public static final fun atStartOfMonth (Ljava/time/YearMonth;)Ljava/time/LocalDate; + public static final fun daysOfWeek ()Ljava/util/List; + public static final fun daysOfWeek (Ljava/time/DayOfWeek;)Ljava/util/List; + public static synthetic fun daysOfWeek$default (Ljava/time/DayOfWeek;ILjava/lang/Object;)Ljava/util/List; + public static final fun firstDayOfWeekFromLocale ()Ljava/time/DayOfWeek; + public static final fun firstDayOfWeekFromLocale (Ljava/util/Locale;)Ljava/time/DayOfWeek; + public static synthetic fun firstDayOfWeekFromLocale$default (Ljava/util/Locale;ILjava/lang/Object;)Ljava/time/DayOfWeek; + public static final fun getNextMonth (Ljava/time/YearMonth;)Ljava/time/YearMonth; + public static final fun getPreviousMonth (Ljava/time/YearMonth;)Ljava/time/YearMonth; + public static final fun getYearMonth (Ljava/time/LocalDate;)Ljava/time/YearMonth; +} + +public final class com/kizitonwose/calendar/core/OutDateStyle : java/lang/Enum { + public static final field EndOfGrid Lcom/kizitonwose/calendar/core/OutDateStyle; + public static final field EndOfRow Lcom/kizitonwose/calendar/core/OutDateStyle; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/OutDateStyle; + public static fun values ()[Lcom/kizitonwose/calendar/core/OutDateStyle; +} + +public final class com/kizitonwose/calendar/core/Week : java/io/Serializable { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/kizitonwose/calendar/core/Week; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/Week;Ljava/util/List;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/Week; + public fun equals (Ljava/lang/Object;)Z + public final fun getDays ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/WeekDay : java/io/Serializable { + public fun (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;)V + public final fun component1 ()Ljava/time/LocalDate; + public final fun component2 ()Lcom/kizitonwose/calendar/core/WeekDayPosition; + public final fun copy (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;)Lcom/kizitonwose/calendar/core/WeekDay; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/core/WeekDay;Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/WeekDayPosition;ILjava/lang/Object;)Lcom/kizitonwose/calendar/core/WeekDay; + public fun equals (Ljava/lang/Object;)Z + public final fun getDate ()Ljava/time/LocalDate; + public final fun getPosition ()Lcom/kizitonwose/calendar/core/WeekDayPosition; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/core/WeekDayPosition : java/lang/Enum { + public static final field InDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static final field OutDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static final field RangeDate Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/core/WeekDayPosition; + public static fun values ()[Lcom/kizitonwose/calendar/core/WeekDayPosition; +} + diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5be90b00..8d78089f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,4 +1,5 @@ import com.kizitonwose.calendar.buildsrc.Config +import com.kizitonwose.calendar.buildsrc.Versions plugins { alias(libs.plugins.kotlinJvm) @@ -20,3 +21,7 @@ kotlin { dependencies { implementation(libs.compose.runtime) // Only needed for @Immutable annotation. } + +mavenPublishing { + coordinates(version = Versions.core) +} diff --git a/core/src/main/java/com/kizitonwose/calendar/core/CalendarDay.kt b/core/src/main/java/com/kizitonwose/calendar/core/CalendarDay.kt index d5e2a44b..619b610c 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/CalendarDay.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/CalendarDay.kt @@ -11,4 +11,4 @@ import java.time.LocalDate * @param position the [DayPosition] for this day. */ @Immutable -data class CalendarDay(val date: LocalDate, val position: DayPosition) : Serializable +public data class CalendarDay(val date: LocalDate, val position: DayPosition) : Serializable diff --git a/core/src/main/java/com/kizitonwose/calendar/core/CalendarMonth.kt b/core/src/main/java/com/kizitonwose/calendar/core/CalendarMonth.kt index cf5ed175..62511279 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/CalendarMonth.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/CalendarMonth.kt @@ -11,7 +11,7 @@ import java.time.YearMonth * @param weekDays the weeks in this month. */ @Immutable -data class CalendarMonth( +public data class CalendarMonth( val yearMonth: YearMonth, val weekDays: List>, ) : Serializable { diff --git a/core/src/main/java/com/kizitonwose/calendar/core/DayPosition.kt b/core/src/main/java/com/kizitonwose/calendar/core/DayPosition.kt index 116e5458..53dddafd 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/DayPosition.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/DayPosition.kt @@ -3,7 +3,7 @@ package com.kizitonwose.calendar.core /** * Describes the position of a [CalendarDay] in the month. */ -enum class DayPosition { +public enum class DayPosition { /** * The day is positioned at the start of the month to * ensure proper alignment of the first day of the week. diff --git a/core/src/main/java/com/kizitonwose/calendar/core/Extensions.kt b/core/src/main/java/com/kizitonwose/calendar/core/Extensions.kt index 8ee5e512..eb90a58f 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/Extensions.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/Extensions.kt @@ -13,31 +13,31 @@ import java.util.Locale * @see [firstDayOfWeekFromLocale] */ @JvmOverloads -fun daysOfWeek(firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale()): List { +public fun daysOfWeek(firstDayOfWeek: DayOfWeek = firstDayOfWeekFromLocale()): List { val pivot = 7 - firstDayOfWeek.ordinal val daysOfWeek = DayOfWeek.entries // Order `daysOfWeek` array so that firstDayOfWeek is at the start position. - return (daysOfWeek.takeLast(pivot) + daysOfWeek.dropLast(pivot)) + return daysOfWeek.takeLast(pivot) + daysOfWeek.dropLast(pivot) } /** * Returns the first day of the week from the provided locale. */ @JvmOverloads -fun firstDayOfWeekFromLocale(locale: Locale = Locale.getDefault()): DayOfWeek = WeekFields.of(locale).firstDayOfWeek +public fun firstDayOfWeekFromLocale(locale: Locale = Locale.getDefault()): DayOfWeek = WeekFields.of(locale).firstDayOfWeek /** * Returns a [LocalDate] at the start of the month. * * Complements [YearMonth.atEndOfMonth]. */ -fun YearMonth.atStartOfMonth(): LocalDate = this.atDay(1) +public fun YearMonth.atStartOfMonth(): LocalDate = this.atDay(1) -val LocalDate.yearMonth: YearMonth +public val LocalDate.yearMonth: YearMonth get() = YearMonth.of(year, month) -val YearMonth.nextMonth: YearMonth +public val YearMonth.nextMonth: YearMonth get() = this.plusMonths(1) -val YearMonth.previousMonth: YearMonth +public val YearMonth.previousMonth: YearMonth get() = this.minusMonths(1) diff --git a/core/src/main/java/com/kizitonwose/calendar/core/OutDateStyle.kt b/core/src/main/java/com/kizitonwose/calendar/core/OutDateStyle.kt index cd4f688b..560bcb35 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/OutDateStyle.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/OutDateStyle.kt @@ -4,7 +4,7 @@ package com.kizitonwose.calendar.core * Determines how [DayPosition.OutDate] are * generated for each month on the calendar. */ -enum class OutDateStyle { +public enum class OutDateStyle { /** * The calendar will generate outDates until it reaches * the end of the month row. This means that if a month diff --git a/core/src/main/java/com/kizitonwose/calendar/core/Week.kt b/core/src/main/java/com/kizitonwose/calendar/core/Week.kt index af60c150..d19665ff 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/Week.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/Week.kt @@ -9,7 +9,7 @@ import java.io.Serializable * @param days the days in this week. */ @Immutable -data class Week(val days: List) : Serializable { +public data class Week(val days: List) : Serializable { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/core/src/main/java/com/kizitonwose/calendar/core/WeekDay.kt b/core/src/main/java/com/kizitonwose/calendar/core/WeekDay.kt index c7e367e1..4ff93452 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/WeekDay.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/WeekDay.kt @@ -11,4 +11,4 @@ import java.time.LocalDate * @param position the [WeekDayPosition] for this day. */ @Immutable -data class WeekDay(val date: LocalDate, val position: WeekDayPosition) : Serializable +public data class WeekDay(val date: LocalDate, val position: WeekDayPosition) : Serializable diff --git a/core/src/main/java/com/kizitonwose/calendar/core/WeekDayPosition.kt b/core/src/main/java/com/kizitonwose/calendar/core/WeekDayPosition.kt index 8dfc6809..6c3c5e47 100644 --- a/core/src/main/java/com/kizitonwose/calendar/core/WeekDayPosition.kt +++ b/core/src/main/java/com/kizitonwose/calendar/core/WeekDayPosition.kt @@ -3,7 +3,7 @@ package com.kizitonwose.calendar.core /** * Describes the position of a [WeekDay] on the calendar. */ -enum class WeekDayPosition { +public enum class WeekDayPosition { /** * The day is positioned at the start of the calendar to * ensure proper alignment of the first day of the week. diff --git a/data/api/data.api b/data/api/data.api new file mode 100644 index 00000000..8c53e103 --- /dev/null +++ b/data/api/data.api @@ -0,0 +1,79 @@ +public final class com/kizitonwose/calendar/data/DataStore : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { + public fun (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun clear ()V + public fun containsKey (I)Z + public final fun containsKey (Ljava/lang/Object;)Z + public fun containsValue (Ljava/lang/Object;)Z + public final fun entrySet ()Ljava/util/Set; + public fun get (I)Ljava/lang/Object; + public final fun get (Ljava/lang/Object;)Ljava/lang/Object; + public fun getEntries ()Ljava/util/Set; + public fun getKeys ()Ljava/util/Set; + public fun getSize ()I + public fun getValues ()Ljava/util/Collection; + public fun isEmpty ()Z + public final fun keySet ()Ljava/util/Set; + public fun put (ILjava/lang/Object;)Ljava/lang/Object; + public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public fun putAll (Ljava/util/Map;)V + public fun remove (I)Ljava/lang/Object; + public final fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public final fun size ()I + public final fun values ()Ljava/util/Collection; +} + +public final class com/kizitonwose/calendar/data/ExtensionsKt { + public static final fun daysUntil (Ljava/time/DayOfWeek;Ljava/time/DayOfWeek;)I +} + +public final class com/kizitonwose/calendar/data/MonthData { + public final fun copy (Ljava/time/YearMonth;II)Lcom/kizitonwose/calendar/data/MonthData; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/MonthData;Ljava/time/YearMonth;IIILjava/lang/Object;)Lcom/kizitonwose/calendar/data/MonthData; + public fun equals (Ljava/lang/Object;)Z + public final fun getCalendarMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/data/MonthDataKt { + public static final fun getCalendarMonthData (Ljava/time/YearMonth;ILjava/time/DayOfWeek;Lcom/kizitonwose/calendar/core/OutDateStyle;)Lcom/kizitonwose/calendar/data/MonthData; + public static final fun getHeatMapCalendarMonthData (Ljava/time/YearMonth;ILjava/time/DayOfWeek;)Lcom/kizitonwose/calendar/data/MonthData; + public static final fun getMonthIndex (Ljava/time/YearMonth;Ljava/time/YearMonth;)I + public static final fun getMonthIndicesCount (Ljava/time/YearMonth;Ljava/time/YearMonth;)I +} + +public final class com/kizitonwose/calendar/data/UtilsKt { + public static final fun checkDateRange (Ljava/time/LocalDate;Ljava/time/LocalDate;)V + public static final fun checkDateRange (Ljava/time/YearMonth;Ljava/time/YearMonth;)V +} + +public final class com/kizitonwose/calendar/data/WeekData { + public final fun copy (Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/LocalDate;)Lcom/kizitonwose/calendar/data/WeekData; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/WeekData;Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/LocalDate;ILjava/lang/Object;)Lcom/kizitonwose/calendar/data/WeekData; + public fun equals (Ljava/lang/Object;)Z + public final fun getWeek ()Lcom/kizitonwose/calendar/core/Week; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/kizitonwose/calendar/data/WeekDataKt { + public static final fun getWeekCalendarAdjustedRange (Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/DayOfWeek;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public static final fun getWeekCalendarData (Ljava/time/LocalDate;ILjava/time/LocalDate;Ljava/time/LocalDate;)Lcom/kizitonwose/calendar/data/WeekData; + public static final fun getWeekIndex (Ljava/time/LocalDate;Ljava/time/LocalDate;)I + public static final fun getWeekIndicesCount (Ljava/time/LocalDate;Ljava/time/LocalDate;)I +} + +public final class com/kizitonwose/calendar/data/WeekDateRange { + public fun (Ljava/time/LocalDate;Ljava/time/LocalDate;)V + public final fun component1 ()Ljava/time/LocalDate; + public final fun component2 ()Ljava/time/LocalDate; + public final fun copy (Ljava/time/LocalDate;Ljava/time/LocalDate;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/data/WeekDateRange;Ljava/time/LocalDate;Ljava/time/LocalDate;ILjava/lang/Object;)Lcom/kizitonwose/calendar/data/WeekDateRange; + public fun equals (Ljava/lang/Object;)Z + public final fun getEndDateAdjusted ()Ljava/time/LocalDate; + public final fun getStartDateAdjusted ()Ljava/time/LocalDate; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/data/build.gradle.kts b/data/build.gradle.kts index e612955e..7e73d147 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,5 +1,6 @@ import com.kizitonwose.calendar.buildsrc.Config +import com.kizitonwose.calendar.buildsrc.Versions plugins { alias(libs.plugins.kotlinJvm) @@ -24,3 +25,7 @@ dependencies { testImplementation(libs.test.junit) } + +mavenPublishing { + coordinates(version = Versions.core) +} diff --git a/data/src/main/java/com/kizitonwose/calendar/data/DataStore.kt b/data/src/main/java/com/kizitonwose/calendar/data/DataStore.kt index 7445bd89..13b08dcf 100644 --- a/data/src/main/java/com/kizitonwose/calendar/data/DataStore.kt +++ b/data/src/main/java/com/kizitonwose/calendar/data/DataStore.kt @@ -4,7 +4,7 @@ package com.kizitonwose.calendar.data * Basically [MutableMap.getOrPut] but allows us read the map * in multiple places without calling `getOrPut` everywhere. */ -class DataStore( +public class DataStore( private val store: MutableMap = HashMap(), private val create: (offset: Int) -> V, ) : MutableMap by store { diff --git a/data/src/main/java/com/kizitonwose/calendar/data/Extensions.kt b/data/src/main/java/com/kizitonwose/calendar/data/Extensions.kt index 69303a3b..eb53661f 100644 --- a/data/src/main/java/com/kizitonwose/calendar/data/Extensions.kt +++ b/data/src/main/java/com/kizitonwose/calendar/data/Extensions.kt @@ -3,4 +3,4 @@ package com.kizitonwose.calendar.data import java.time.DayOfWeek // E.g DayOfWeek.SATURDAY.daysUntil(DayOfWeek.TUESDAY) = 3 -fun DayOfWeek.daysUntil(other: DayOfWeek) = (7 + (other.value - value)) % 7 +public fun DayOfWeek.daysUntil(other: DayOfWeek): Int = (7 + (other.value - value)) % 7 diff --git a/data/src/main/java/com/kizitonwose/calendar/data/MonthData.kt b/data/src/main/java/com/kizitonwose/calendar/data/MonthData.kt index 5b38d842..1eab49ca 100644 --- a/data/src/main/java/com/kizitonwose/calendar/data/MonthData.kt +++ b/data/src/main/java/com/kizitonwose/calendar/data/MonthData.kt @@ -12,7 +12,7 @@ import java.time.DayOfWeek import java.time.YearMonth import java.time.temporal.ChronoUnit -data class MonthData internal constructor( +public data class MonthData internal constructor( private val month: YearMonth, private val inDays: Int, private val outDays: Int, @@ -27,7 +27,7 @@ data class MonthData internal constructor( private val nextMonth = month.nextMonth - val calendarMonth = + val calendarMonth: CalendarMonth = CalendarMonth(month, rows.map { week -> week.map { dayOffset -> getDay(dayOffset) } }) private fun getDay(dayOffset: Int): CalendarDay { @@ -42,7 +42,7 @@ data class MonthData internal constructor( } } -fun getCalendarMonthData( +public fun getCalendarMonthData( startMonth: YearMonth, offset: Int, firstDayOfWeek: DayOfWeek, @@ -64,7 +64,7 @@ fun getCalendarMonthData( return MonthData(month, inDays, outDays) } -fun getHeatMapCalendarMonthData( +public fun getHeatMapCalendarMonthData( startMonth: YearMonth, offset: Int, firstDayOfWeek: DayOfWeek, @@ -82,11 +82,11 @@ fun getHeatMapCalendarMonthData( return MonthData(month, inDays, outDays) } -fun getMonthIndex(startMonth: YearMonth, targetMonth: YearMonth): Int { +public fun getMonthIndex(startMonth: YearMonth, targetMonth: YearMonth): Int { return ChronoUnit.MONTHS.between(startMonth, targetMonth).toInt() } -fun getMonthIndicesCount(startMonth: YearMonth, endMonth: YearMonth): Int { +public fun getMonthIndicesCount(startMonth: YearMonth, endMonth: YearMonth): Int { // Add one to include the start month itself! return getMonthIndex(startMonth, endMonth) + 1 } diff --git a/data/src/main/java/com/kizitonwose/calendar/data/Utils.kt b/data/src/main/java/com/kizitonwose/calendar/data/Utils.kt index 33ff3376..ab9cfc8f 100644 --- a/data/src/main/java/com/kizitonwose/calendar/data/Utils.kt +++ b/data/src/main/java/com/kizitonwose/calendar/data/Utils.kt @@ -3,13 +3,13 @@ package com.kizitonwose.calendar.data import java.time.LocalDate import java.time.YearMonth -fun checkDateRange(startMonth: YearMonth, endMonth: YearMonth) { +public fun checkDateRange(startMonth: YearMonth, endMonth: YearMonth) { check(endMonth >= startMonth) { "startMonth: $startMonth is greater than endMonth: $endMonth" } } -fun checkDateRange(startDate: LocalDate, endDate: LocalDate) { +public fun checkDateRange(startDate: LocalDate, endDate: LocalDate) { check(endDate >= startDate) { "startDate: $startDate is greater than endDate: $endDate" } diff --git a/data/src/main/java/com/kizitonwose/calendar/data/WeekData.kt b/data/src/main/java/com/kizitonwose/calendar/data/WeekData.kt index 8652bb7e..006383cd 100644 --- a/data/src/main/java/com/kizitonwose/calendar/data/WeekData.kt +++ b/data/src/main/java/com/kizitonwose/calendar/data/WeekData.kt @@ -7,12 +7,12 @@ import java.time.DayOfWeek import java.time.LocalDate import java.time.temporal.ChronoUnit -data class WeekDateRange( +public data class WeekDateRange( val startDateAdjusted: LocalDate, val endDateAdjusted: LocalDate, ) -fun getWeekCalendarAdjustedRange( +public fun getWeekCalendarAdjustedRange( startDate: LocalDate, endDate: LocalDate, firstDayOfWeek: DayOfWeek, @@ -25,7 +25,7 @@ fun getWeekCalendarAdjustedRange( return WeekDateRange(startDateAdjusted = startDateAdjusted, endDateAdjusted = endDateAdjusted) } -fun getWeekCalendarData( +public fun getWeekCalendarData( startDateAdjusted: LocalDate, offset: Int, desiredStartDate: LocalDate, @@ -35,12 +35,12 @@ fun getWeekCalendarData( return WeekData(firstDayInWeek, desiredStartDate, desiredEndDate) } -data class WeekData internal constructor( +public data class WeekData internal constructor( private val firstDayInWeek: LocalDate, private val desiredStartDate: LocalDate, private val desiredEndDate: LocalDate, ) { - val week = Week((0 until 7).map { dayOffset -> getDay(dayOffset) }) + val week: Week = Week((0 until 7).map { dayOffset -> getDay(dayOffset) }) private fun getDay(dayOffset: Int): WeekDay { val date = firstDayInWeek.plusDays(dayOffset.toLong()) @@ -53,11 +53,11 @@ data class WeekData internal constructor( } } -fun getWeekIndex(startDateAdjusted: LocalDate, date: LocalDate): Int { +public fun getWeekIndex(startDateAdjusted: LocalDate, date: LocalDate): Int { return ChronoUnit.WEEKS.between(startDateAdjusted, date).toInt() } -fun getWeekIndicesCount(startDateAdjusted: LocalDate, endDateAdjusted: LocalDate): Int { +public fun getWeekIndicesCount(startDateAdjusted: LocalDate, endDateAdjusted: LocalDate): Int { // Add one to include the start week itself! return getWeekIndex(startDateAdjusted, endDateAdjusted) + 1 } diff --git a/docs/Compose.md b/docs/Compose.md index 8310306a..05b1fb68 100644 --- a/docs/Compose.md +++ b/docs/Compose.md @@ -3,6 +3,7 @@ ## Table of Contents - [Quick links](#quick-links) +- [Compose Multiplatform Information](#compose-multipletform-information) - [Compose versions](#compose-versions) - [Calendar Composables](#calendar-composables) - [Usage](#usage) @@ -23,25 +24,36 @@ Check out the sample app if you have not yet done so. Most techniques that you would want to implement are already done in the examples. -Download the sample app [here](https://github.com/kizitonwose/Calendar/releases/download/2.0.0/sample.apk) +Download the Android sample app [here](https://github.com/kizitonwose/Calendar/releases/download/2.0.0/sample.apk) -Read the sample app's source code [here](https://github.com/kizitonwose/Calendar/tree/main/sample) +Read the Android sample app's source code [here](https://github.com/kizitonwose/Calendar/tree/main/sample) + +View the multiplatform sample project online at https://calendar.kizitonwose.dev + +Read the multiplatform sample project's source code [here](https://github.com/kizitonwose/Calendar/tree/main/compose-multiplatform/sample) Add the library to your project [here](https://github.com/kizitonwose/Calendar#setup) **If you are looking for the view-based documentation, you can find it [here](https://github.com/kizitonwose/Calendar/blob/main/docs/View.md)** -## Compose versions +## Compose Multiplatform Information + +The APIs for the compose libraries for Android and Multiplatform projects have been designed such that you can copy examples across both projects and they would work without code changes as the classes have the same names and package declarations. The only difference in some cases would be that the code for the Android calendar library needs to import classes such as `LocalDate` and `YearMonth` from the `java.time` package while the multiplaform calendar library needs to import such classes from the [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) library. + +Note that the `YearMonth` class does not yet exist in the `kotlinx-datetime` library, therefore the multiplatfrom calendar library includes a minimal `YearMonth` class implementation to bridge this gap until the class [is hopefully added](https://github.com/Kotlin/kotlinx-datetime/issues/168) to the `kotlinx-datetime` library. + +The functions `plusMonths`, `minusMonths`, `plusDays` and `minusDays` used in the examples are provided out of the box by the `java.time` library, but can be easily added for multiplatform projects using the `kotlinx-datetime` library if needed: + +```kotlin +fun YearMonth.plusMonths(value: Int): YearMonth = plus(value, DateTimeUnit.MONTH) +fun YearMonth.minusMonths(value: Int): YearMonth = minus(value, DateTimeUnit.MONTH) +fun LocalDate.plusDays(value: Int): LocalDate = plus(value, DateTimeUnit.DAY) +fun LocalDate.minusDays(value: Int): LocalDate = minus(value, DateTimeUnit.DAY) +``` -Ensure that you are using the library version that matches with the Compose UI version in your project. If you use a version of the library that has a higher version of Compose UI than the one in your project, gradle will upgrade the Compose UI version in your project via transitive dependency. +## Compose UI versions -| Compose UI | Calendar Library | -|:----------:|:----------------:| -| 1.2.x | 2.0.x | -| 1.3.x | 2.1.x - 2.2.x | -| 1.4.x | 2.3.x | -| 1.5.x | 2.4.x | -| 1.6.x | 2.5.x | +Ensure that you are using the library version that matches the Compose UI version in your project. If you use a version of the library that has a higher version of Compose UI than the one in your project, gradle will upgrade the Compose UI version in your project via transitive dependency. See the compatibility table [here](https://github.com/kizitonwose/Calendar#compose-ui-version-compatibility). ## Calendar Composables diff --git a/gradle.properties b/gradle.properties index 422f821b..76002a3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,6 @@ SONATYPE_HOST=S01 RELEASE_SIGNING_ENABLED=true GROUP=com.kizitonwose.calendar -VERSION_NAME=2.6.0-SNAPSHOT POM_NAME=Calendar POM_INCEPTION_YEAR=2019 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 302f2cfb..75d3da56 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ espresso = "3.5.1" [libraries] kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.0" } desugar = { module = "com.android.tools:desugar_jdk_libs", version = "2.0.4" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } @@ -22,7 +23,7 @@ androidx-test-rules = { module = "androidx.test:rules", version = "1.5.0" } androidx-test-junit = { module = "androidx.test.ext:junit", version = "1.1.5" } test-junit = { module = "junit:junit", version = "4.13.2" } -compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } +compose-ui-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } @@ -33,6 +34,8 @@ compose-navigation = { module = "androidx.navigation:navigation-compose", versio compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" } +jetbrains-compose-navigation = { module = "org.jetbrains.androidx.navigation:navigation-compose", version = "2.8.0-alpha02" } + [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } @@ -42,3 +45,8 @@ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinter = { id = "org.jmailen.kotlinter", version = "4.3.0" } mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.28.0" } versionCheck = { id = "com.github.ben-manes.versions", version = "0.51.0" } +bcv = "org.jetbrains.kotlinx.binary-compatibility-validator:0.15.1" + +# KMM +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +jetbrainsCompose = { id = "org.jetbrains.compose", version = "1.7.0-alpha01" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock new file mode 100644 index 00000000..014de6a7 --- /dev/null +++ b/kotlin-js-store/yarn.lock @@ -0,0 +1,3050 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + +"@jsonjoy.com/base64@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" + integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== + +"@jsonjoy.com/json-pack@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz#ab59c642a2e5368e8bcfd815d817143d4f3035d0" + integrity sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg== + dependencies: + "@jsonjoy.com/base64" "^1.1.1" + "@jsonjoy.com/util" "^1.1.2" + hyperdyperid "^1.2.0" + thingies "^1.20.0" + +"@jsonjoy.com/util@^1.1.2": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.2.0.tgz#0fe9a92de72308c566ebcebe8b5a3f01d3149df2" + integrity sha512-4B8B+3vFsY4eo33DMKyJPlQ3sBMpPFUZK2dr3O3rXrOGKKbYG44J0XSFkDo1VOQiri5HFEhIeVvItjR2xcazmg== + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.5.4": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + +"@types/cors@^2.8.12": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.56.10" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d" + integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/http-proxy@^1.17.8": + version "1.17.14" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" + integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== + dependencies: + "@types/node" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@>=10.0.0": + version "20.14.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" + integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== + dependencies: + undici-types "~5.26.4" + +"@types/qs@*": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/retry@0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" + integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.15.5": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/sockjs@^0.3.36": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.10": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" + integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== + +"@webpack-cli/info@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" + integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== + +"@webpack-cli/serve@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" + integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn@^8.7.1, acorn@^8.8.2: + version "8.12.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" + integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.9.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.16.0.tgz#22e2a92b94f005f7e0f9c9d39652ef0b8f6f0cb4" + integrity sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw== + dependencies: + fast-deep-equal "^3.1.3" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.4.1" + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body-parser@1.20.2, body-parser@^1.19.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" + integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== + dependencies: + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.21.10: + version "4.23.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.1.tgz#ce4af0534b3d37db5c1a4ca98b9080f985041e96" + integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw== + dependencies: + caniuse-lite "^1.0.30001629" + electron-to-chromium "^1.4.796" + node-releases "^2.0.14" + update-browserslist-db "^1.0.16" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bundle-name@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" + integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== + dependencies: + run-applescript "^7.0.0" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001629: + version "1.0.30001638" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz#598e1f0c2ac36f37ebc3f5b8887a32ca558e5d56" + integrity sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^3.5.1, chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.10, colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +connect@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cross-spawn@^7.0.0, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +custom-event@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" + integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== + +date-format@^4.0.14: + version "4.0.14" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" + integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +default-browser-id@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" + integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + +default-browser@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" + integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + dependencies: + bundle-name "^4.1.0" + default-browser-id "^5.0.0" + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +di@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" + integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +dom-serialize@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" + integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== + dependencies: + custom-event "~1.0.0" + ent "~2.2.0" + extend "^3.0.0" + void-elements "^2.0.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.796: + version "1.4.814" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.814.tgz#176535a0b899c9c473464502ab77576aa8bb1cbe" + integrity sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +engine.io-parser@~5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49" + integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw== + +engine.io@~6.5.2: + version "6.5.5" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.5.tgz#430b80d8840caab91a50e9e23cb551455195fc93" + integrity sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA== + dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + +enhanced-resolve@^5.16.0: + version "5.17.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz#d037603789dd9555b89aaec7eb78845c49089bc5" + integrity sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +ent@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.1.tgz#68dc99a002f115792c26239baedaaea9e70c0ca2" + integrity sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A== + dependencies: + punycode "^1.4.1" + +envinfo@^7.7.3: + version "7.13.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" + integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + +escalade@^3.1.1, escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.3: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.2.7: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +follow-redirects@^1.0.0: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +foreground-child@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +format-util@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +glob@^10.3.7: + version "10.4.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" + integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.7: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.4.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" + integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +hyperdyperid@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" + integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + dependencies: + hasown "^2.0.2" + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + +is-network-error@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" + integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isbinaryfile@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" + integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jackspeak@^3.1.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.0.tgz#a75763ff36ad778ede6a156d8ee8b124de445b4a" + integrity sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +karma-chrome-launcher@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" + integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== + dependencies: + which "^1.2.1" + +karma-mocha@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" + integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== + dependencies: + minimist "^1.2.3" + +karma-sourcemap-loader@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488" + integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA== + dependencies: + graceful-fs "^4.2.10" + +karma-webpack@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.1.tgz#4eafd31bbe684a747a6e8f3e4ad373e53979ced4" + integrity sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ== + dependencies: + glob "^7.1.3" + minimatch "^9.0.3" + webpack-merge "^4.1.5" + +karma@6.4.3: + version "6.4.3" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.3.tgz#763e500f99597218bbb536de1a14acc4ceea7ce8" + integrity sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q== + dependencies: + "@colors/colors" "1.5.0" + body-parser "^1.19.0" + braces "^3.0.2" + chokidar "^3.5.1" + connect "^3.7.0" + di "^0.0.1" + dom-serialize "^2.2.1" + glob "^7.1.7" + graceful-fs "^4.2.6" + http-proxy "^1.18.1" + isbinaryfile "^4.0.8" + lodash "^4.17.21" + log4js "^6.4.1" + mime "^2.5.2" + minimatch "^3.0.4" + mkdirp "^0.5.5" + qjobs "^1.2.0" + range-parser "^1.2.1" + rimraf "^3.0.2" + socket.io "^4.7.2" + source-map "^0.6.1" + tmp "^0.2.1" + ua-parser-js "^0.7.30" + yargs "^16.1.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +launch-editor@^2.6.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.0.tgz#7255d90bdba414448e2138faa770a74f28451305" + integrity sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.8.1" + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log4js@^6.4.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" + integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== + dependencies: + date-format "^4.0.14" + debug "^4.3.4" + flatted "^3.2.7" + rfdc "^1.3.0" + streamroller "^3.1.5" + +lru-cache@^10.2.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.3.0.tgz#4a4aaf10c84658ab70f79a85a9a3f1e1fb11196b" + integrity sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^4.6.0: + version "4.9.3" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.9.3.tgz#41a3218065fe3911d9eba836250c8f4e43f816bc" + integrity sha512-bsYSSnirtYTWi1+OPMFb0M048evMKyUYe0EbtuGQgq6BVQM1g1W8/KIUJCCvjgI/El0j6Q4WsmMiBwLUBSw8LA== + dependencies: + "@jsonjoy.com/json-pack" "^1.0.3" + "@jsonjoy.com/util" "^1.1.2" + tree-dump "^1.0.1" + tslib "^2.0.0" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.2: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.3, minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.3, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mkdirp@^0.5.5: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mocha@10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" + integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "8.1.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1, on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^10.0.3: + version "10.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1" + integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw== + dependencies: + default-browser "^5.2.1" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^3.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-retry@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd" + integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA== + dependencies: + "@types/retry" "0.12.2" + is-network-error "^1.0.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qjobs@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" + integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +rfdc@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rimraf@^5.0.5: + version "5.0.7" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.7.tgz#27bddf202e7d89cb2e0381656380d1734a854a74" + integrity sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg== + dependencies: + glob "^10.3.7" + +run-applescript@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" + integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0, schema-utils@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +socket.io-adapter@~2.5.2: + version "2.5.5" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" + integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== + dependencies: + debug "~4.3.4" + ws "~8.17.1" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@^4.7.2: + version "4.7.5" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.5.tgz#56eb2d976aef9d1445f373a62d781a41c7add8f8" + integrity sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +source-map-js@^1.0.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map-loader@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-5.0.0.tgz#f593a916e1cc54471cfc8851b905c8a845fc7e38" + integrity sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA== + dependencies: + iconv-lite "^0.6.3" + source-map-js "^1.0.2" + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +streamroller@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" + integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== + dependencies: + date-format "^4.0.14" + debug "^4.3.4" + fs-extra "^8.1.0" + +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1, supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@^5.26.0: + version "5.31.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.1.tgz#735de3c987dd671e95190e6b98cfe2f07f3cf0d4" + integrity sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +thingies@^1.20.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" + integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tmp@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tree-dump@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac" + integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ== + +tslib@^2.0.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@5.4.3: + version "5.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" + integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== + +ua-parser-js@^0.7.30: + version "0.7.38" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.38.tgz#f497d8a4dc1fec6e854e5caa4b2f9913422ef054" + integrity sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" + integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2, uri-js@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +void-elements@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" + integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== + +watchpack@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +webpack-cli@5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" + integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^2.1.1" + "@webpack-cli/info" "^2.0.2" + "@webpack-cli/serve" "^2.0.5" + colorette "^2.0.14" + commander "^10.0.1" + cross-spawn "^7.0.3" + envinfo "^7.7.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^5.7.3" + +webpack-dev-middleware@^7.1.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz#2af00538b6e4eda05f5afdd5d711dbebc05958f7" + integrity sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA== + dependencies: + colorette "^2.0.10" + memfs "^4.6.0" + mime-types "^2.1.31" + on-finished "^2.4.1" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8" + integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA== + dependencies: + "@types/bonjour" "^3.5.13" + "@types/connect-history-api-fallback" "^1.5.4" + "@types/express" "^4.17.21" + "@types/serve-index" "^1.9.4" + "@types/serve-static" "^1.15.5" + "@types/sockjs" "^0.3.36" + "@types/ws" "^8.5.10" + ansi-html-community "^0.0.8" + bonjour-service "^1.2.1" + chokidar "^3.6.0" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.4.0" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.1.0" + launch-editor "^2.6.1" + open "^10.0.3" + p-retry "^6.2.0" + rimraf "^5.0.5" + schema-utils "^4.2.0" + selfsigned "^2.4.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^7.1.0" + ws "^8.16.0" + +webpack-merge@^4.1.5: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== + dependencies: + lodash "^4.17.15" + +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@5.91.0: + version "5.91.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" + integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-assertions "^1.9.0" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.16.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.16.0, ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0, yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index c2f2c8e2..ee357143 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation(libs.androidx.cardview) implementation(libs.material.view) - implementation(libs.compose.ui) + implementation(libs.compose.ui.ui) implementation(libs.compose.ui.tooling) implementation(libs.compose.foundation) implementation(libs.compose.runtime) diff --git a/sample/src/main/java/com/kizitonwose/calendar/sample/compose/SimpleCalendarTitle.kt b/sample/src/main/java/com/kizitonwose/calendar/sample/compose/SimpleCalendarTitle.kt index 5442f3fe..173a2e21 100644 --- a/sample/src/main/java/com/kizitonwose/calendar/sample/compose/SimpleCalendarTitle.kt +++ b/sample/src/main/java/com/kizitonwose/calendar/sample/compose/SimpleCalendarTitle.kt @@ -11,19 +11,20 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.kizitonwose.calendar.sample.R import com.kizitonwose.calendar.sample.shared.displayText import java.time.YearMonth @@ -39,7 +40,7 @@ fun SimpleCalendarTitle( verticalAlignment = Alignment.CenterVertically, ) { CalendarNavigationIcon( - icon = painterResource(id = R.drawable.ic_chevron_left), + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft, contentDescription = "Previous", onClick = goToPrevious, ) @@ -53,7 +54,7 @@ fun SimpleCalendarTitle( fontWeight = FontWeight.Medium, ) CalendarNavigationIcon( - icon = painterResource(id = R.drawable.ic_chevron_right), + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Next", onClick = goToNext, ) @@ -62,7 +63,7 @@ fun SimpleCalendarTitle( @Composable private fun CalendarNavigationIcon( - icon: Painter, + imageVector: ImageVector, contentDescription: String, onClick: () -> Unit, ) = Box( @@ -77,7 +78,7 @@ private fun CalendarNavigationIcon( .fillMaxSize() .padding(4.dp) .align(Alignment.Center), - painter = icon, + imageVector = imageVector, contentDescription = contentDescription, ) } diff --git a/sample/src/main/res/drawable/ic_airplane_takeoff.xml b/sample/src/main/res/drawable/ic_airplane_takeoff.xml index c0f531a1..10ffb020 100644 --- a/sample/src/main/res/drawable/ic_airplane_takeoff.xml +++ b/sample/src/main/res/drawable/ic_airplane_takeoff.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml index a012e7f6..19ec2b53 100644 --- a/sample/src/main/res/values/colors.xml +++ b/sample/src/main/res/values/colors.xml @@ -28,7 +28,7 @@ #282828 #DCDCDC #616161 - #b2ebf2 + #B2EBF2 #F2C4B2 #B2B8F2 #5D4037 @@ -37,7 +37,7 @@ #388E3C #00796B #C2185B - #c62828 + #C62828 #1565C0 #0097A7 #BF000000 diff --git a/settings.gradle.kts b/settings.gradle.kts index eb300931..5514c86a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ pluginManagement { } @Suppress("UnstableApiUsage") dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) +// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() @@ -18,3 +18,5 @@ include(":data") include(":view") include(":compose") include(":sample") +include(":compose-multiplatform:library") +include(":compose-multiplatform:sample") diff --git a/view/api/view.api b/view/api/view.api new file mode 100644 index 00000000..fdc70dcb --- /dev/null +++ b/view/api/view.api @@ -0,0 +1,161 @@ +public abstract interface class com/kizitonwose/calendar/view/Binder { + public abstract fun bind (Lcom/kizitonwose/calendar/view/ViewContainer;Ljava/lang/Object;)V + public abstract fun create (Landroid/view/View;)Lcom/kizitonwose/calendar/view/ViewContainer; +} + +public class com/kizitonwose/calendar/view/CalendarView : androidx/recyclerview/widget/RecyclerView { + public fun (Landroid/content/Context;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V + public final fun findFirstVisibleDay ()Lcom/kizitonwose/calendar/core/CalendarDay; + public final fun findFirstVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun findLastVisibleDay ()Lcom/kizitonwose/calendar/core/CalendarDay; + public final fun findLastVisibleMonth ()Lcom/kizitonwose/calendar/core/CalendarMonth; + public final fun getDayBinder ()Lcom/kizitonwose/calendar/view/MonthDayBinder; + public final fun getDaySize ()Lcom/kizitonwose/calendar/view/DaySize; + public final fun getDayViewResource ()I + public final fun getMonthFooterBinder ()Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder; + public final fun getMonthFooterResource ()I + public final fun getMonthHeaderBinder ()Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder; + public final fun getMonthHeaderResource ()I + public final fun getMonthMargins ()Lcom/kizitonwose/calendar/view/MarginValues; + public final fun getMonthScrollListener ()Lkotlin/jvm/functions/Function1; + public final fun getMonthViewClass ()Ljava/lang/String; + public final fun getOrientation ()I + public final fun getOutDateStyle ()Lcom/kizitonwose/calendar/core/OutDateStyle; + public final fun getScrollPaged ()Z + public final fun notifyCalendarChanged ()V + public final fun notifyDateChanged (Ljava/time/LocalDate;)V + public final fun notifyDateChanged (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)V + public final fun notifyDateChanged (Ljava/time/LocalDate;[Lcom/kizitonwose/calendar/core/DayPosition;)V + public static synthetic fun notifyDateChanged$default (Lcom/kizitonwose/calendar/view/CalendarView;Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;ILjava/lang/Object;)V + public final fun notifyDayChanged (Lcom/kizitonwose/calendar/core/CalendarDay;)V + public final fun notifyMonthChanged (Ljava/time/YearMonth;)V + public final fun scrollToDate (Ljava/time/LocalDate;)V + public final fun scrollToDate (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)V + public static synthetic fun scrollToDate$default (Lcom/kizitonwose/calendar/view/CalendarView;Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;ILjava/lang/Object;)V + public final fun scrollToDay (Lcom/kizitonwose/calendar/core/CalendarDay;)V + public final fun scrollToMonth (Ljava/time/YearMonth;)V + public final fun setDayBinder (Lcom/kizitonwose/calendar/view/MonthDayBinder;)V + public final fun setDaySize (Lcom/kizitonwose/calendar/view/DaySize;)V + public final fun setDayViewResource (I)V + public final fun setMonthFooterBinder (Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder;)V + public final fun setMonthFooterResource (I)V + public final fun setMonthHeaderBinder (Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder;)V + public final fun setMonthHeaderResource (I)V + public final fun setMonthMargins (Lcom/kizitonwose/calendar/view/MarginValues;)V + public final fun setMonthScrollListener (Lkotlin/jvm/functions/Function1;)V + public final fun setMonthViewClass (Ljava/lang/String;)V + public final fun setOrientation (I)V + public final fun setOutDateStyle (Lcom/kizitonwose/calendar/core/OutDateStyle;)V + public final fun setScrollPaged (Z)V + public final fun setup (Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/DayOfWeek;)V + public final fun smoothScrollToDate (Ljava/time/LocalDate;)V + public final fun smoothScrollToDate (Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;)V + public static synthetic fun smoothScrollToDate$default (Lcom/kizitonwose/calendar/view/CalendarView;Ljava/time/LocalDate;Lcom/kizitonwose/calendar/core/DayPosition;ILjava/lang/Object;)V + public final fun smoothScrollToDay (Lcom/kizitonwose/calendar/core/CalendarDay;)V + public final fun smoothScrollToMonth (Ljava/time/YearMonth;)V + public final fun updateMonthData ()V + public final fun updateMonthData (Ljava/time/YearMonth;)V + public final fun updateMonthData (Ljava/time/YearMonth;Ljava/time/YearMonth;)V + public final fun updateMonthData (Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/DayOfWeek;)V + public static synthetic fun updateMonthData$default (Lcom/kizitonwose/calendar/view/CalendarView;Ljava/time/YearMonth;Ljava/time/YearMonth;Ljava/time/DayOfWeek;ILjava/lang/Object;)V +} + +public final class com/kizitonwose/calendar/view/DaySize : java/lang/Enum { + public static final field FreeForm Lcom/kizitonwose/calendar/view/DaySize; + public static final field Rectangle Lcom/kizitonwose/calendar/view/DaySize; + public static final field SeventhWidth Lcom/kizitonwose/calendar/view/DaySize; + public static final field Square Lcom/kizitonwose/calendar/view/DaySize; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/kizitonwose/calendar/view/DaySize; + public static fun values ()[Lcom/kizitonwose/calendar/view/DaySize; +} + +public final class com/kizitonwose/calendar/view/MarginValues { + public fun ()V + public fun (IIII)V + public synthetic fun (IIIIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()I + public final fun component4 ()I + public final fun copy (IIII)Lcom/kizitonwose/calendar/view/MarginValues; + public static synthetic fun copy$default (Lcom/kizitonwose/calendar/view/MarginValues;IIIIILjava/lang/Object;)Lcom/kizitonwose/calendar/view/MarginValues; + public fun equals (Ljava/lang/Object;)Z + public final fun getBottom ()I + public final fun getEnd ()I + public final fun getStart ()I + public final fun getTop ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/kizitonwose/calendar/view/MonthDayBinder : com/kizitonwose/calendar/view/Binder { +} + +public abstract interface class com/kizitonwose/calendar/view/MonthHeaderFooterBinder : com/kizitonwose/calendar/view/Binder { +} + +public class com/kizitonwose/calendar/view/ViewContainer { + public fun (Landroid/view/View;)V + public final fun getView ()Landroid/view/View; +} + +public class com/kizitonwose/calendar/view/WeekCalendarView : androidx/recyclerview/widget/RecyclerView { + public fun (Landroid/content/Context;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V + public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V + public final fun findFirstVisibleDay ()Lcom/kizitonwose/calendar/core/WeekDay; + public final fun findFirstVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun findLastVisibleDay ()Lcom/kizitonwose/calendar/core/WeekDay; + public final fun findLastVisibleWeek ()Lcom/kizitonwose/calendar/core/Week; + public final fun getDayBinder ()Lcom/kizitonwose/calendar/view/WeekDayBinder; + public final fun getDaySize ()Lcom/kizitonwose/calendar/view/DaySize; + public final fun getDayViewResource ()I + public final fun getScrollPaged ()Z + public final fun getWeekFooterBinder ()Lcom/kizitonwose/calendar/view/WeekHeaderFooterBinder; + public final fun getWeekFooterResource ()I + public final fun getWeekHeaderBinder ()Lcom/kizitonwose/calendar/view/WeekHeaderFooterBinder; + public final fun getWeekHeaderResource ()I + public final fun getWeekMargins ()Lcom/kizitonwose/calendar/view/MarginValues; + public final fun getWeekScrollListener ()Lkotlin/jvm/functions/Function1; + public final fun getWeekViewClass ()Ljava/lang/String; + public final fun notifyCalendarChanged ()V + public final fun notifyDateChanged (Ljava/time/LocalDate;)V + public final fun notifyDayChanged (Lcom/kizitonwose/calendar/core/WeekDay;)V + public final fun notifyWeekChanged (Lcom/kizitonwose/calendar/core/WeekDay;)V + public final fun notifyWeekChanged (Ljava/time/LocalDate;)V + public final fun scrollToDate (Ljava/time/LocalDate;)V + public final fun scrollToDay (Lcom/kizitonwose/calendar/core/WeekDay;)V + public final fun scrollToWeek (Lcom/kizitonwose/calendar/core/WeekDay;)V + public final fun scrollToWeek (Ljava/time/LocalDate;)V + public final fun setDayBinder (Lcom/kizitonwose/calendar/view/WeekDayBinder;)V + public final fun setDaySize (Lcom/kizitonwose/calendar/view/DaySize;)V + public final fun setDayViewResource (I)V + public final fun setScrollPaged (Z)V + public final fun setWeekFooterBinder (Lcom/kizitonwose/calendar/view/WeekHeaderFooterBinder;)V + public final fun setWeekFooterResource (I)V + public final fun setWeekHeaderBinder (Lcom/kizitonwose/calendar/view/WeekHeaderFooterBinder;)V + public final fun setWeekHeaderResource (I)V + public final fun setWeekMargins (Lcom/kizitonwose/calendar/view/MarginValues;)V + public final fun setWeekScrollListener (Lkotlin/jvm/functions/Function1;)V + public final fun setWeekViewClass (Ljava/lang/String;)V + public final fun setup (Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/DayOfWeek;)V + public final fun smoothScrollToDate (Ljava/time/LocalDate;)V + public final fun smoothScrollToDay (Lcom/kizitonwose/calendar/core/WeekDay;)V + public final fun smoothScrollToWeek (Lcom/kizitonwose/calendar/core/WeekDay;)V + public final fun smoothScrollToWeek (Ljava/time/LocalDate;)V + public final fun updateWeekData ()V + public final fun updateWeekData (Ljava/time/LocalDate;)V + public final fun updateWeekData (Ljava/time/LocalDate;Ljava/time/LocalDate;)V + public final fun updateWeekData (Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/DayOfWeek;)V + public static synthetic fun updateWeekData$default (Lcom/kizitonwose/calendar/view/WeekCalendarView;Ljava/time/LocalDate;Ljava/time/LocalDate;Ljava/time/DayOfWeek;ILjava/lang/Object;)V +} + +public abstract interface class com/kizitonwose/calendar/view/WeekDayBinder : com/kizitonwose/calendar/view/Binder { +} + +public abstract interface class com/kizitonwose/calendar/view/WeekHeaderFooterBinder : com/kizitonwose/calendar/view/Binder { +} + diff --git a/view/build.gradle.kts b/view/build.gradle.kts index 79012081..d0528fae 100644 --- a/view/build.gradle.kts +++ b/view/build.gradle.kts @@ -1,5 +1,6 @@ import com.kizitonwose.calendar.buildsrc.Android import com.kizitonwose.calendar.buildsrc.Config +import com.kizitonwose.calendar.buildsrc.Versions plugins { alias(libs.plugins.androidLibrary) @@ -34,3 +35,7 @@ dependencies { // Expose RecyclerView which is CalendarView"s superclass. api(libs.androidx.recyclerview) } + +mavenPublishing { + coordinates(version = Versions.core) +} diff --git a/view/src/main/java/com/kizitonwose/calendar/view/Binder.kt b/view/src/main/java/com/kizitonwose/calendar/view/Binder.kt index 68912bbf..f47504be 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/Binder.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/Binder.kt @@ -6,7 +6,7 @@ import com.kizitonwose.calendar.core.CalendarMonth import com.kizitonwose.calendar.core.Week import com.kizitonwose.calendar.core.WeekDay -open class ViewContainer(val view: View) +public open class ViewContainer(public val view: View) /** * Responsible for managing a view for a given [Data]. @@ -14,19 +14,19 @@ open class ViewContainer(val view: View) * be called whenever the view needs to be used to * bind an instance of the associated [Data]. */ -interface Binder { - fun create(view: View): Container - fun bind(container: Container, data: Data) +public interface Binder { + public fun create(view: View): Container + public fun bind(container: Container, data: Data) } -interface WeekDayBinder : Binder +public interface WeekDayBinder : Binder -interface WeekHeaderFooterBinder : Binder +public interface WeekHeaderFooterBinder : Binder -interface MonthDayBinder : Binder +public interface MonthDayBinder : Binder -interface MonthHeaderFooterBinder : Binder +public interface MonthHeaderFooterBinder : Binder -typealias MonthScrollListener = (CalendarMonth) -> Unit +public typealias MonthScrollListener = (CalendarMonth) -> Unit -typealias WeekScrollListener = (Week) -> Unit +public typealias WeekScrollListener = (Week) -> Unit diff --git a/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt b/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt index 7e877edf..ec6f77de 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt @@ -19,12 +19,12 @@ import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth -open class CalendarView : RecyclerView { +public open class CalendarView : RecyclerView { /** * The [MonthDayBinder] instance used for managing day * cell view creation and reuse on the calendar. */ - var dayBinder: MonthDayBinder<*>? = null + public var dayBinder: MonthDayBinder<*>? = null set(value) { field = value invalidateViewHolders() @@ -34,7 +34,7 @@ open class CalendarView : RecyclerView { * The [MonthHeaderFooterBinder] instance used for managing header views. * The header view is shown above each month on the Calendar. */ - var monthHeaderBinder: MonthHeaderFooterBinder<*>? = null + public var monthHeaderBinder: MonthHeaderFooterBinder<*>? = null set(value) { field = value invalidateViewHolders() @@ -44,7 +44,7 @@ open class CalendarView : RecyclerView { * The [MonthHeaderFooterBinder] instance used for managing footer views. * The footer view is shown below each month on the Calendar. */ - var monthFooterBinder: MonthHeaderFooterBinder<*>? = null + public var monthFooterBinder: MonthHeaderFooterBinder<*>? = null set(value) { field = value invalidateViewHolders() @@ -54,13 +54,13 @@ open class CalendarView : RecyclerView { * Called when the calendar scrolls to a new month. * Mostly beneficial if [scrollPaged] is `true`. */ - var monthScrollListener: MonthScrollListener? = null + public var monthScrollListener: MonthScrollListener? = null /** * The xml resource that is inflated and used as the day cell view. * This must be provided. */ - var dayViewResource = 0 + public var dayViewResource: Int = 0 set(value) { if (field != value) { check(value != 0) { "Invalid 'dayViewResource' value." } @@ -73,7 +73,7 @@ open class CalendarView : RecyclerView { * The xml resource that is inflated and used as a header for every month. * Set zero to disable. */ - var monthHeaderResource = 0 + public var monthHeaderResource: Int = 0 set(value) { if (field != value) { field = value @@ -85,7 +85,7 @@ open class CalendarView : RecyclerView { * The xml resource that is inflated and used as a footer for every month. * Set zero to disable. */ - var monthFooterResource = 0 + public var monthFooterResource: Int = 0 set(value) { if (field != value) { field = value @@ -100,7 +100,7 @@ open class CalendarView : RecyclerView { * **You should exclude the name and constructor of this class from code * obfuscation if enabled**. */ - var monthViewClass: String? = null + public var monthViewClass: String? = null set(value) { if (field != value) { field = value @@ -113,7 +113,7 @@ open class CalendarView : RecyclerView { * This determines the scroll direction of the calendar. */ @Orientation - var orientation: Int = HORIZONTAL + public var orientation: Int = HORIZONTAL set(value) { if (field != value) { field = value @@ -127,7 +127,7 @@ open class CalendarView : RecyclerView { * snap to the nearest month after a scroll or swipe action. * If `false`, the calendar scrolls normally. */ - var scrollPaged = false + public var scrollPaged: Boolean = false set(value) { if (field != value) { field = value @@ -141,7 +141,7 @@ open class CalendarView : RecyclerView { * * @see [DayPosition] */ - var outDateStyle = OutDateStyle.EndOfRow + public var outDateStyle: OutDateStyle = OutDateStyle.EndOfRow set(value) { if (field != value) { field = value @@ -153,7 +153,7 @@ open class CalendarView : RecyclerView { * Determines how the size of each day on the calendar is calculated. * Can be [DaySize.Square], [DaySize.SeventhWidth] or [DaySize.FreeForm]. */ - var daySize: DaySize = DaySize.Square + public var daySize: DaySize = DaySize.Square set(value) { if (field != value) { field = value @@ -165,7 +165,7 @@ open class CalendarView : RecyclerView { * The margins, in pixels to be applied on each month view. * this can be used to add a space between two months. */ - var monthMargins = MarginValues() + public var monthMargins: MarginValues = MarginValues() set(value) { if (field != value) { field = value @@ -190,13 +190,13 @@ open class CalendarView : RecyclerView { private var endMonth: YearMonth? = null private var firstDayOfWeek: DayOfWeek? = null - constructor(context: Context) : super(context) + public constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + public constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init(attrs, 0, 0) } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : + public constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init(attrs, defStyleAttr, defStyleAttr) } @@ -282,7 +282,7 @@ open class CalendarView : RecyclerView { * shows the view for the month without any animations. * For a smooth scrolling effect, use [smoothScrollToMonth] */ - fun scrollToMonth(month: YearMonth) { + public fun scrollToMonth(month: YearMonth) { calendarLayoutManager.scrollToIndex(month) } @@ -290,7 +290,7 @@ open class CalendarView : RecyclerView { * Scroll to a specific month on the calendar using a smooth scrolling animation. * Just like [scrollToMonth], but with a smooth scrolling animation. */ - fun smoothScrollToMonth(month: YearMonth) { + public fun smoothScrollToMonth(month: YearMonth) { calendarLayoutManager.smoothScrollToIndex(month) } @@ -301,7 +301,7 @@ open class CalendarView : RecyclerView { * in horizontal mode. No animation is performed. * For a smooth scrolling effect, use [smoothScrollToDay]. */ - fun scrollToDay(day: CalendarDay) { + public fun scrollToDay(day: CalendarDay) { calendarLayoutManager.scrollToDay(day) } @@ -309,7 +309,7 @@ open class CalendarView : RecyclerView { * Shortcut for [scrollToDay] with a [LocalDate] instance. */ @JvmOverloads - fun scrollToDate(date: LocalDate, position: DayPosition = DayPosition.MonthDate) { + public fun scrollToDate(date: LocalDate, position: DayPosition = DayPosition.MonthDate) { scrollToDay(CalendarDay(date, position)) } @@ -317,7 +317,7 @@ open class CalendarView : RecyclerView { * Scroll to a specific [CalendarDay] using a smooth scrolling animation. * Just like [scrollToDay], but with a smooth scrolling animation. */ - fun smoothScrollToDay(day: CalendarDay) { + public fun smoothScrollToDay(day: CalendarDay) { calendarLayoutManager.smoothScrollToDay(day) } @@ -325,7 +325,7 @@ open class CalendarView : RecyclerView { * Shortcut for [smoothScrollToDay] with a [LocalDate] instance. */ @JvmOverloads - fun smoothScrollToDate(date: LocalDate, position: DayPosition = DayPosition.MonthDate) { + public fun smoothScrollToDate(date: LocalDate, position: DayPosition = DayPosition.MonthDate) { smoothScrollToDay(CalendarDay(date, position)) } @@ -334,7 +334,7 @@ open class CalendarView : RecyclerView { * This causes [MonthDayBinder.bind] to be called with the [ViewContainer] * at this position. Use this to reload a date cell on the Calendar. */ - fun notifyDayChanged(day: CalendarDay) { + public fun notifyDayChanged(day: CalendarDay) { calendarAdapter.reloadDay(day) } @@ -342,7 +342,7 @@ open class CalendarView : RecyclerView { * Shortcut for [notifyDayChanged] with a [LocalDate] instance. */ @JvmOverloads - fun notifyDateChanged(date: LocalDate, position: DayPosition = DayPosition.MonthDate) { + public fun notifyDateChanged(date: LocalDate, position: DayPosition = DayPosition.MonthDate) { notifyDayChanged(CalendarDay(date, position)) } @@ -357,7 +357,7 @@ open class CalendarView : RecyclerView { * specified day positions. This causes [MonthDayBinder.bind] to be called * with the [ViewContainer] at the relevant [DayPosition] values. */ - fun notifyDateChanged( + public fun notifyDateChanged( date: LocalDate, vararg position: DayPosition, ) { @@ -375,7 +375,7 @@ open class CalendarView : RecyclerView { * [MonthHeaderFooterBinder.bind] will be called for this month's header view if available. * [MonthHeaderFooterBinder.bind] will be called for this month's footer view if available. */ - fun notifyMonthChanged(month: YearMonth) { + public fun notifyMonthChanged(month: YearMonth) { calendarAdapter.reloadMonth(month) } @@ -383,7 +383,7 @@ open class CalendarView : RecyclerView { * Notify the CalendarView to reload all months. * @see [notifyMonthChanged]. */ - fun notifyCalendarChanged() { + public fun notifyCalendarChanged() { calendarAdapter.reloadCalendar() } @@ -392,7 +392,7 @@ open class CalendarView : RecyclerView { * * @return The first visible month or null if not found. */ - fun findFirstVisibleMonth(): CalendarMonth? { + public fun findFirstVisibleMonth(): CalendarMonth? { return calendarAdapter.findFirstVisibleMonth() } @@ -401,7 +401,7 @@ open class CalendarView : RecyclerView { * * @return The last visible month or null if not found. */ - fun findLastVisibleMonth(): CalendarMonth? { + public fun findLastVisibleMonth(): CalendarMonth? { return calendarAdapter.findLastVisibleMonth() } @@ -411,7 +411,7 @@ open class CalendarView : RecyclerView { * * @return The first visible day or null if not found. */ - fun findFirstVisibleDay(): CalendarDay? { + public fun findFirstVisibleDay(): CalendarDay? { return calendarAdapter.findFirstVisibleDay() } @@ -421,7 +421,7 @@ open class CalendarView : RecyclerView { * * @return The last visible day or null if not found. */ - fun findLastVisibleDay(): CalendarDay? { + public fun findLastVisibleDay(): CalendarDay? { return calendarAdapter.findLastVisibleDay() } @@ -433,7 +433,7 @@ open class CalendarView : RecyclerView { * @param endMonth The last month on the calendar. * @param firstDayOfWeek A [DayOfWeek] to be the first day of week. */ - fun setup(startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek) { + public fun setup(startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek) { checkDateRange(startMonth = startMonth, endMonth = endMonth) this.startMonth = startMonth this.endMonth = endMonth @@ -459,7 +459,7 @@ open class CalendarView : RecyclerView { * the calendar with a large date range instead of updating the range frequently. */ @JvmOverloads - fun updateMonthData( + public fun updateMonthData( startMonth: YearMonth = requireStartMonth(), endMonth: YearMonth = requireEndMonth(), firstDayOfWeek: DayOfWeek = requireFirstDayOfWeek(), diff --git a/view/src/main/java/com/kizitonwose/calendar/view/DaySize.kt b/view/src/main/java/com/kizitonwose/calendar/view/DaySize.kt index a9e9acab..11746da8 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/DaySize.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/DaySize.kt @@ -5,7 +5,7 @@ import android.view.ViewGroup /** * Determines how the size of each day on the calendar is calculated. */ -enum class DaySize { +public enum class DaySize { /** * Each day will have both width and height matching * the width of the calendar divided by 7. diff --git a/view/src/main/java/com/kizitonwose/calendar/view/MarginValues.kt b/view/src/main/java/com/kizitonwose/calendar/view/MarginValues.kt index 5c5f8dfc..fc1d7d5d 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/MarginValues.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/MarginValues.kt @@ -2,7 +2,7 @@ package com.kizitonwose.calendar.view import androidx.annotation.Px -data class MarginValues( +public data class MarginValues( @Px val start: Int = 0, @Px val top: Int = 0, @Px val end: Int = 0, diff --git a/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt b/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt index dfc6a935..e4fc63f4 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt @@ -14,12 +14,12 @@ import com.kizitonwose.calendar.view.internal.weekcalendar.WeekCalendarLayoutMan import java.time.DayOfWeek import java.time.LocalDate -open class WeekCalendarView : RecyclerView { +public open class WeekCalendarView : RecyclerView { /** * The [WeekDayBinder] instance used for managing day * cell view creation and reuse. */ - var dayBinder: WeekDayBinder<*>? = null + public var dayBinder: WeekDayBinder<*>? = null set(value) { field = value invalidateViewHolders() @@ -29,7 +29,7 @@ open class WeekCalendarView : RecyclerView { * The [WeekHeaderFooterBinder] instance used for managing header views. * The header view is shown above each week on the Calendar. */ - var weekHeaderBinder: WeekHeaderFooterBinder<*>? = null + public var weekHeaderBinder: WeekHeaderFooterBinder<*>? = null set(value) { field = value invalidateViewHolders() @@ -39,7 +39,7 @@ open class WeekCalendarView : RecyclerView { * The [WeekHeaderFooterBinder] instance used for managing footer views. * The footer view is shown below each week on the Calendar. */ - var weekFooterBinder: WeekHeaderFooterBinder<*>? = null + public var weekFooterBinder: WeekHeaderFooterBinder<*>? = null set(value) { field = value invalidateViewHolders() @@ -49,13 +49,13 @@ open class WeekCalendarView : RecyclerView { * Called when the calendar scrolls to a new week. * Mostly beneficial if [scrollPaged] is `true`. */ - var weekScrollListener: WeekScrollListener? = null + public var weekScrollListener: WeekScrollListener? = null /** * The xml resource that is inflated and used as the day cell view. * This must be provided. */ - var dayViewResource = 0 + public var dayViewResource: Int = 0 set(value) { if (field != value) { check(value != 0) { "Invalid 'dayViewResource' value." } @@ -68,7 +68,7 @@ open class WeekCalendarView : RecyclerView { * The xml resource that is inflated and used as a header for every week. * Set zero to disable. */ - var weekHeaderResource = 0 + public var weekHeaderResource: Int = 0 set(value) { if (field != value) { field = value @@ -80,7 +80,7 @@ open class WeekCalendarView : RecyclerView { * The xml resource that is inflated and used as a footer for every week. * Set zero to disable. */ - var weekFooterResource = 0 + public var weekFooterResource: Int = 0 set(value) { if (field != value) { field = value @@ -95,7 +95,7 @@ open class WeekCalendarView : RecyclerView { * **You should exclude the name and constructor of this class from code * obfuscation if enabled**. */ - var weekViewClass: String? = null + public var weekViewClass: String? = null set(value) { if (field != value) { field = value @@ -108,7 +108,7 @@ open class WeekCalendarView : RecyclerView { * snap to the nearest week after a scroll or swipe action. * If `false`, the calendar scrolls normally. */ - var scrollPaged = true + public var scrollPaged: Boolean = true set(value) { if (field != value) { field = value @@ -120,7 +120,7 @@ open class WeekCalendarView : RecyclerView { * Determines how the size of each day on the calendar is calculated. * Can be [DaySize.Square], [DaySize.SeventhWidth] or [DaySize.FreeForm]. */ - var daySize: DaySize = DaySize.Square + public var daySize: DaySize = DaySize.Square set(value) { if (field != value) { field = value @@ -132,7 +132,7 @@ open class WeekCalendarView : RecyclerView { * The margins, in pixels to be applied each week view. * this can be used to add a space between two weeks. */ - var weekMargins = MarginValues() + public var weekMargins: MarginValues = MarginValues() set(value) { if (field != value) { field = value @@ -155,13 +155,13 @@ open class WeekCalendarView : RecyclerView { private var endDate: LocalDate? = null private var firstDayOfWeek: DayOfWeek? = null - constructor(context: Context) : super(context) + public constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + public constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init(attrs, 0, 0) } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : + public constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init(attrs, defStyleAttr, defStyleAttr) } @@ -224,7 +224,7 @@ open class WeekCalendarView : RecyclerView { * This instantly shows the view for the week without any animations. * For a smooth scrolling effect, use [smoothScrollToWeek] */ - fun scrollToWeek(date: LocalDate) { + public fun scrollToWeek(date: LocalDate) { calendarLayoutManager.scrollToIndex(date) } @@ -233,7 +233,7 @@ open class WeekCalendarView : RecyclerView { * using a smooth scrolling animation. * Just like [scrollToWeek], but with a smooth scrolling animation. */ - fun smoothScrollToWeek(date: LocalDate) { + public fun smoothScrollToWeek(date: LocalDate) { calendarLayoutManager.smoothScrollToIndex(date) } @@ -243,7 +243,7 @@ open class WeekCalendarView : RecyclerView { * No animation is performed. * For a smooth scrolling effect, use [smoothScrollToDate]. */ - fun scrollToDate(date: LocalDate) { + public fun scrollToDate(date: LocalDate) { calendarLayoutManager.scrollToDay(date) } @@ -251,7 +251,7 @@ open class WeekCalendarView : RecyclerView { * Scroll to a specific date using a smooth scrolling animation. * Just like [scrollToDate], but with a smooth scrolling animation. */ - fun smoothScrollToDate(date: LocalDate) { + public fun smoothScrollToDate(date: LocalDate) { calendarLayoutManager.smoothScrollToDay(date) } @@ -260,7 +260,7 @@ open class WeekCalendarView : RecyclerView { * This instantly shows the view for the week without any animations. * For a smooth scrolling effect, use [smoothScrollToWeek] */ - fun scrollToWeek(day: WeekDay) { + public fun scrollToWeek(day: WeekDay) { scrollToWeek(day.date) } @@ -269,7 +269,7 @@ open class WeekCalendarView : RecyclerView { * using a smooth scrolling animation. * Just like [scrollToWeek], but with a smooth scrolling animation. */ - fun smoothScrollToWeek(day: WeekDay) { + public fun smoothScrollToWeek(day: WeekDay) { smoothScrollToWeek(day.date) } @@ -279,7 +279,7 @@ open class WeekCalendarView : RecyclerView { * No animation is performed. * For a smooth scrolling effect, use [smoothScrollToDay]. */ - fun scrollToDay(day: WeekDay) { + public fun scrollToDay(day: WeekDay) { scrollToDate(day.date) } @@ -287,7 +287,7 @@ open class WeekCalendarView : RecyclerView { * Scroll to a specific [WeekDay] using a smooth scrolling animation. * Just like [scrollToDay], but with a smooth scrolling animation. */ - fun smoothScrollToDay(day: WeekDay) { + public fun smoothScrollToDay(day: WeekDay) { smoothScrollToDate(day.date) } @@ -296,7 +296,7 @@ open class WeekCalendarView : RecyclerView { * This causes [WeekDayBinder.bind] to be called with the [ViewContainer] * at this position. Use this to reload a date cell on the Calendar. */ - fun notifyDateChanged(date: LocalDate) { + public fun notifyDateChanged(date: LocalDate) { calendarAdapter.reloadDay(date) } @@ -305,7 +305,7 @@ open class WeekCalendarView : RecyclerView { * This causes [WeekDayBinder.bind] to be called with the [ViewContainer] * at this position. Use this to reload a date cell on the Calendar. */ - fun notifyDayChanged(day: WeekDay) { + public fun notifyDayChanged(day: WeekDay) { notifyDateChanged(day.date) } @@ -316,7 +316,7 @@ open class WeekCalendarView : RecyclerView { * [WeekHeaderFooterBinder.bind] will be called for this week's header view if available. * [WeekHeaderFooterBinder.bind] will be called for this week's footer view if available. */ - fun notifyWeekChanged(date: LocalDate) { + public fun notifyWeekChanged(date: LocalDate) { calendarAdapter.reloadWeek(date) } @@ -327,7 +327,7 @@ open class WeekCalendarView : RecyclerView { * [WeekHeaderFooterBinder.bind] will be called for this week's header view if available. * [WeekHeaderFooterBinder.bind] will be called for this week's footer view if available. */ - fun notifyWeekChanged(day: WeekDay) { + public fun notifyWeekChanged(day: WeekDay) { notifyWeekChanged(day.date) } @@ -336,7 +336,7 @@ open class WeekCalendarView : RecyclerView { * * @see [notifyWeekChanged] */ - fun notifyCalendarChanged() { + public fun notifyCalendarChanged() { calendarAdapter.reloadCalendar() } @@ -345,7 +345,7 @@ open class WeekCalendarView : RecyclerView { * * @return The first visible week or null if not found. */ - fun findFirstVisibleWeek(): Week? { + public fun findFirstVisibleWeek(): Week? { return calendarAdapter.findFirstVisibleWeek() } @@ -354,7 +354,7 @@ open class WeekCalendarView : RecyclerView { * * @return The last visible week or null if not found. */ - fun findLastVisibleWeek(): Week? { + public fun findLastVisibleWeek(): Week? { return calendarAdapter.findLastVisibleWeek() } @@ -364,7 +364,7 @@ open class WeekCalendarView : RecyclerView { * * @return The first visible day or null if not found. */ - fun findFirstVisibleDay(): WeekDay? { + public fun findFirstVisibleDay(): WeekDay? { return calendarAdapter.findFirstVisibleDay() } @@ -374,7 +374,7 @@ open class WeekCalendarView : RecyclerView { * * @return The last visible day or null if not found. */ - fun findLastVisibleDay(): WeekDay? { + public fun findLastVisibleDay(): WeekDay? { return calendarAdapter.findLastVisibleDay() } @@ -386,7 +386,7 @@ open class WeekCalendarView : RecyclerView { * @param endDate A date in the last week on the calendar. * @param firstDayOfWeek A [DayOfWeek] to be the first day of week. */ - fun setup(startDate: LocalDate, endDate: LocalDate, firstDayOfWeek: DayOfWeek) { + public fun setup(startDate: LocalDate, endDate: LocalDate, firstDayOfWeek: DayOfWeek) { checkDateRange(startDate = startDate, endDate = endDate) this.startDate = startDate this.endDate = endDate @@ -411,7 +411,7 @@ open class WeekCalendarView : RecyclerView { * the calendar with a large date range instead of updating the range frequently. */ @JvmOverloads - fun updateWeekData( + public fun updateWeekData( startDate: LocalDate = requireStartDate(), endDate: LocalDate = requireEndDate(), firstDayOfWeek: DayOfWeek = requireFirstDayOfWeek(),