diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a717384 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gradle" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + labels: + - "[Deps]" + assignees: + - hi-manshu diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..eb59994 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,16 @@ +name: Pull Request Workflow + +on: pull_request +jobs: + code-style-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 11 + - name: ktlint check + uses: gradle/gradle-build-action@v2 + with: + arguments: ktlintCheck + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..39ab0d8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: Release + +on: + release: + types: [created] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build APK ⚙️🛠 + run: ./gradlew app:assemble + + - name: Get release + id: get_release + uses: bruceadams/get-release@v1.2.3 + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Upload Kalendar Sample App 🚀 + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/debug/app-debug.apk + asset_name: kalendar-sample.apk + asset_content_type: application/apk + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84f7752 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +.idea +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/build +/buildSrc/build/* +/buildSrc/build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1821ac --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +### Kalendar - An Elementary Compose Calendar. + +![Kalendar](art/banner.png) + +This is a calendar to integrate Calendar with Custom design in your jetpack compose project.You can +also add list of events for your days. +_Made with ❤️ for Android Developers by Himanshu_ + +[![Kalendar](https://img.shields.io/maven-central/v/com.himanshoe/kalendar)](https://search.maven.org/artifact/com.himanshoe/kalendar) +[![Github Followers](https://img.shields.io/github/followers/hi-manshu?label=Follow&style=social)](https://github.com/hi-manshu) +[![Twitter Follow](https://img.shields.io/twitter/follow/hi_man_shoe?label=Follow&style=social)](https://twitter.com/hi_man_shoe) +[![Sample App](https://img.shields.io/github/v/release/hi-manshu/Kalendar?color=7885FF&label=Sample%20App&logo=android&style=for-the-badge)](https://github.com/hi-manshu/Kalendar/releases/latest/download/kalendar-sample.apk) + +## Introduction + +With Compose getting the attention, it was about time to have its own Calendar. Kalendar is all +about it with the customization and design. + +## Setup + +To add Kalendar, add this dependency, + +```gradle +dependencies { + ..... + implementation("com.himanshoe:kalendar:1.1.0") +} +``` + +or to use Kalendar Endlos, add this dependency, + +```gradle +dependencies { + ..... + implementation("com.himanshoe:kalendar-endlos:1.0.0") + } + ``` + +## To See More Detail Readme + +- [Kalendar](docs/Kalendar.md) +- [KalendarEndlos](docs/KalendarEndlos.md) + +Please drop a star if you like it <3 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..458a3b6 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,68 @@ +plugins { + id(Plugins.application) + kotlin(Plugins.android) +} + +android { + compileSdk = ModuleExtension.compileSdkVersion + + defaultConfig { + applicationId = ModuleExtension.App.applicationId + minSdk = ModuleExtension.DefaultConfigs.minSdkVersion + targetSdk = ModuleExtension.DefaultConfigs.targetSdkVersion + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = ModuleExtension.DefaultConfigs.testInstrumentationRunner + vectorDrawables { + useSupportLibrary = true + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile(ModuleExtension.DefaultConfigs.defaultProguardOptimizeFileName), + ModuleExtension.DefaultConfigs.proGuardRules + ) + } + create("staging") { + initWith(getByName("debug")) + matchingFallbacks.add("debug") + } + } + + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = ModuleExtension.jvmTarget + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } +} + +dependencies { + implementation(project(":kalendar")) + implementation(Deps.DateTime.date) + implementation(project(mapOf("path" to ":kalendar-endlos"))) + implementation("androidx.compose.material3:material3:1.0.0-alpha15") + implementation("androidx.navigation:navigation-compose:2.5.1") + // jetpack compose + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") // <- this dependency is required + implementation(Deps.Compose.ui) + implementation(Deps.Compose.material) + implementation(Deps.Compose.activity) + implementation(Deps.Jetpack.Core.ktx) + implementation(Deps.Android.materialDesign) + testImplementation(Deps.Test.jUnit) + androidTestImplementation(Deps.AndroidTest.jUnitExtensions) + androidTestImplementation(Deps.AndroidTest.espressoCore) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..5b13b11 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts.kts.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/himanshoe/sample/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/himanshoe/sample/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..50d6487 --- /dev/null +++ b/app/src/androidTest/java/com/himanshoe/sample/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.himanshoe.sample + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.himanshoe.sample", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..21a772c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/himanshoe/sample/MainActivity.kt b/app/src/main/java/com/himanshoe/sample/MainActivity.kt new file mode 100644 index 0000000..49c101c --- /dev/null +++ b/app/src/main/java/com/himanshoe/sample/MainActivity.kt @@ -0,0 +1,62 @@ +package com.himanshoe.sample + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExtendedFloatingActionButton +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.unit.dp +import androidx.navigation.compose.rememberNavController + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + val navigator = rememberNavController() + RegisterNavigation( + navigator = navigator, + onEndlossClicked = { + navigator.navigate("endloss") + }, onKalendarClicked = { + navigator.navigate("kalendar") + } + ) + } + } +} + +@Composable +fun KalendarApp(onEndlossClicked: () -> Unit, onKalendarClicked: () -> Unit) { + val list: List Unit>> = + listOf( + "Kalendar" to onKalendarClicked, + "Kalendar Endloss" to onEndlossClicked, + ) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .padding(16.dp) + ) { + + items(list) { item -> + ExtendedFloatingActionButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 5.dp), + onClick = item.second + ) { + Text(item.first) + } + } + } +} diff --git a/app/src/main/java/com/himanshoe/sample/Navigator.kt b/app/src/main/java/com/himanshoe/sample/Navigator.kt new file mode 100644 index 0000000..939daba --- /dev/null +++ b/app/src/main/java/com/himanshoe/sample/Navigator.kt @@ -0,0 +1,26 @@ +package com.himanshoe.sample + +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.himanshoe.sample.demo.KalendarDemo +import com.himanshoe.sample.demo.KalendarEndlossDemo + +@Composable +fun RegisterNavigation( + navigator: NavHostController, + onEndlossClicked: () -> Unit, + onKalendarClicked: () -> Unit, +) { + NavHost(navController = navigator, startDestination = "main") { + composable("endloss") { KalendarEndlossDemo() } + composable("main") { + KalendarApp( + onEndlossClicked, + onKalendarClicked, + ) + } + composable("kalendar") { KalendarDemo() } + } +} diff --git a/app/src/main/java/com/himanshoe/sample/demo/KalendarDemo.kt b/app/src/main/java/com/himanshoe/sample/demo/KalendarDemo.kt new file mode 100644 index 0000000..9f8bd63 --- /dev/null +++ b/app/src/main/java/com/himanshoe/sample/demo/KalendarDemo.kt @@ -0,0 +1,50 @@ +package com.himanshoe.sample.demo + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.himanshoe.kalendar.Kalendar +import com.himanshoe.kalendar.model.KalendarType + +@Composable +fun KalendarDemo() { + + Column( + Modifier + .fillMaxSize() + .background(Color.White) + ) { + Kalendar( + kalendarType = KalendarType.Oceanic, + ) + Text( + text = "Kalendar Oceanic Type", + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + Kalendar( + kalendarType = KalendarType.Firey, + modifier = Modifier.padding(16.dp) + ) + Text( + text = "Kalendar Firey Type", + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } +} diff --git a/app/src/main/java/com/himanshoe/sample/demo/KalendarEndlossDemo.kt b/app/src/main/java/com/himanshoe/sample/demo/KalendarEndlossDemo.kt new file mode 100644 index 0000000..d79e81d --- /dev/null +++ b/app/src/main/java/com/himanshoe/sample/demo/KalendarEndlossDemo.kt @@ -0,0 +1,28 @@ +package com.himanshoe.sample.demo + +import androidx.compose.foundation.background +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.himanshoe.kalendar.endlos.Kalendar +import com.himanshoe.kalendar.endlos.model.KalendarEvent +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn + +@Composable +fun KalendarEndlossDemo() { + val oneWeekFromNow = + Clock.System.todayIn(TimeZone.currentSystemDefault()).plus(1, DateTimeUnit.WEEK) + + Kalendar( + Modifier.background(Color.White), + kalendarEvents = listOf( + KalendarEvent(oneWeekFromNow, "Himanshu"), + KalendarEvent(oneWeekFromNow, "Himanshu"), + KalendarEvent(oneWeekFromNow, "Himanshu") + ) + ) +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..7706ab9 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6b78462 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6b78462 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..41653ce --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..157a838 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #9E94B4 + #CFC4E5 + #FEF7FE + #FF000000 + #FFFFFFFF + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..76f21d5 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + sample-app + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..4e406e9 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/test/java/com/himanshoe/sample/ExampleUnitTest.kt b/app/src/test/java/com/himanshoe/sample/ExampleUnitTest.kt new file mode 100644 index 0000000..5b9c893 --- /dev/null +++ b/app/src/test/java/com/himanshoe/sample/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.himanshoe.sample + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/art/banner.png b/art/banner.png new file mode 100644 index 0000000..1287337 Binary files /dev/null and b/art/banner.png differ diff --git a/art/endlos.png b/art/endlos.png new file mode 100644 index 0000000..437ce40 Binary files /dev/null and b/art/endlos.png differ diff --git a/art/kalendar-firey.png b/art/kalendar-firey.png new file mode 100644 index 0000000..154e5f1 Binary files /dev/null and b/art/kalendar-firey.png differ diff --git a/art/oceanic.png b/art/oceanic.png new file mode 100644 index 0000000..cf7337d Binary files /dev/null and b/art/oceanic.png differ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..40241ed --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,36 @@ +buildscript { + repositories { + google() + mavenCentral() + maven("https://plugins.gradle.org/m2/") + + } + dependencies { + classpath(Deps.Gradle.androidGradlePlugin) + classpath(Deps.Gradle.kotlinGradlePlugin) + classpath(Deps.Gradle.vanniktechGradlePlugin) + classpath(Deps.Gradle.detektGradlePlugin) + classpath(Deps.Gradle.ktlintGradlePlugin) + } +} + +tasks.register("clean", Delete::class) { + delete(rootProject.buildDir) +} +subprojects { + apply(plugin = Plugins.ktlint) + apply(plugin = Plugins.detekt) + +// detekt { +// config = files("$rootDir/${ModuleExtension.FilePath.detekt}") +// buildUponDefaultConfig = true +// } +} +allprojects { + pluginManager.withPlugin(Plugins.vanniktechPublish) { + extensions.getByType(com.vanniktech.maven.publish.MavenPublishPluginExtension::class.java) + .apply { + sonatypeHost = com.vanniktech.maven.publish.SonatypeHost.S01 + } + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..1c0ea9a --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `kotlin-dsl` + kotlin("jvm") version "1.6.10" +} + +repositories { + mavenCentral() +} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt new file mode 100644 index 0000000..148e0ba --- /dev/null +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -0,0 +1,62 @@ +object Plugins { + val application by lazy { "com.android.application" } + val library by lazy { "com.android.library" } + val android by lazy { "android" } + val kotlinAndroid by lazy { "kotlin-android" } + val vanniktechPublish by lazy { "com.vanniktech.maven.publish" } + val ktlint by lazy { "org.jlleitschuh.gradle.ktlint" } + val detekt by lazy { "io.gitlab.arturbosch.detekt" } +} + +object Deps { + + object Compose { + val ui by lazy { "androidx.compose.ui:ui:${Versions.compose}" } + val uiTooling by lazy { "androidx.compose.ui:ui-tooling:${Versions.compose}" } + val uiToolingPreview by lazy { "androidx.compose.ui:ui-tooling-preview:${Versions.compose}" } + val material by lazy { "androidx.compose.material:material:${Versions.compose}" } + val activity by lazy { "androidx.activity:activity-compose:${Versions.activity}" } + val viewmodel by lazy { "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1" } + val pagination by lazy { "androidx.paging:paging-compose:1.0.0-alpha14"} + } + + object Android { + val materialDesign by lazy { "com.google.android.material:material:${Versions.material}" } + } + + object Test { + val jUnit by lazy { "junit:junit:${Versions.jUnit}" } + } + + object AndroidTest { + val jUnitExtensions by lazy { "androidx.test.ext:junit:${Versions.jUnitExtensions}" } + val espressoCore by lazy { "androidx.test.espresso:espresso-core:${Versions.espresso}" } + val uiTestJunit by lazy { "androidx.compose.ui:ui-test-junit4:${Versions.compose}" } + } + + object DateTime { + val date by lazy { "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" } + } + + object Jetpack { + object Core { + val ktx by lazy { "androidx.core:core-ktx:${Versions.core}" } + } + } + + object Desugar { + val jdk by lazy { "com.android.tools:desugar_jdk_libs:1.1.5" } + } + + object Gradle { + val kotlinGradlePlugin by lazy { "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" } + val androidGradlePlugin by lazy { "com.android.tools.build:gradle:${Versions.androidGradlePlugin}" } + val vanniktechGradlePlugin by lazy { "com.vanniktech:gradle-maven-publish-plugin:${Versions.vanniktechGradlePlugin}" } + val ktlintGradlePlugin by lazy { "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktlint}" } + val detektGradlePlugin by lazy { "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${Versions.detekt}" } + } +} + +object ProjectPath { + const val kalendar = ":kalendar" +} diff --git a/buildSrc/src/main/kotlin/ModuleExtension.kt b/buildSrc/src/main/kotlin/ModuleExtension.kt new file mode 100644 index 0000000..1f30169 --- /dev/null +++ b/buildSrc/src/main/kotlin/ModuleExtension.kt @@ -0,0 +1,22 @@ +object ModuleExtension { + const val compileSdkVersion = 32 + const val jvmTarget = "11" + + object DefaultConfigs { + const val minSdkVersion = 21 + const val targetSdkVersion = 32 + const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + const val defaultConsumerProguardFiles = "consumer-rules.pro" + const val proGuardRules = "proguard-rules.pro" + const val defaultProguardOptimizeFileName = "proguard-android-optimize.txt" + } + + object App { + const val applicationId = "com.himanshoe.sample" + } + + object FilePath { + const val gitHooks = "gradle/scripts/git-hooks.gradle.kts" + const val detekt = "gradle/config/detekt.yml" + } +} diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt new file mode 100644 index 0000000..72331b1 --- /dev/null +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -0,0 +1,14 @@ +object Versions { + const val compose = "1.2.0" + const val androidGradlePlugin = "7.2.0" + const val vanniktechGradlePlugin = "0.18.0" + const val kotlin = "1.7.0" + const val activity = "1.3.1" + const val material = "1.4.0" + const val jUnit = "4.13.2" + const val jUnitExtensions = "1.1.3" + const val espresso = "3.4.0" + const val core = "1.7.0" + const val ktlint = "10.2.0" + const val detekt = "1.19.0" +} diff --git a/docs/Kalendar.md b/docs/Kalendar.md new file mode 100644 index 0000000..b7478d3 --- /dev/null +++ b/docs/Kalendar.md @@ -0,0 +1,106 @@ +## Kalendar + +This is a calendar library that gives integration for either Week/Month view. + +## Introduction + +With Compose getting the attention, it was about time to have its own Calendar. Kalendar is all +about it with the customization and design. + +## Implementation + +### Step: 01 + +To add Kalendar, add this dependency, + +```gradle +dependencies { + ..... + implementation("com.himanshoe:kalendar:1.1.0") +} +``` + +### Step: 02 (Usage) +To integrate Kalendar use the composable, + +```kotlin + Kalendar(kalendarType = KalendarType.Oceanic) +``` + +Preview: + + +where `Oceanic` gives the WeekView of the current week or you can use, + +```kotlin + Kalendar(kalendarType = KalendarType.Firey) +``` +that will give you the Month view of the current month + +Preview: + + + +### Step: 03 (Customisation) +With Kalendar at your disposal you have customization at your tip to make it suited based on your need. + +`Kalendar` composable has lot of optional parameters that you can use. Like, +> Setting up Events +```kotlin +kalendarEvents: List = emptyList(), +``` +Here, `KalendarEvent` can be used to pass events to mark specific days. `KalendarEvent` looks like, +```kotlin +data class KalendarEvent( + val date: LocalDate, + val eventName: String, + val eventDescription: String? = null, +) +``` + + +> Setting up KalendarThemeColors +```kotlin +kalendarThemeColors: List = KalendarColors.defaultColors(), +``` +Here, `KalendarThemeColor` list can be used to pass the basic theming colors that will be common for all the Views. +PS: I would say check the `defaultColors` once as well! + +`KalendarThemeColor` looks like, +```kotlin +data class KalendarThemeColor( + val backgroundColor: Color, + val dayBackgroundColor: Color, + val headerTextColor: Color, +) +``` + +Alternatively, +if you dont want each month having its own color set, you can use, +```kotlin +kalendarThemeColor: KalendarThemeColor = //Your color, +``` + +> Individual Date Click Listener +```kotlin +onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, +``` +You can use this lambda to get values of the specific day that is in format of, +```kotlin +class KalendarDay(val localDate: LocalDate) +``` +and the list of events for that particular day. + +> Setting up Colors for Individual Day +```kotlin +kalendarDayColors: KalendarDayColors = KalendarDayDefaultColors.defaultColors() +``` +This will have list of two colors, +* First one for default color +* Second one for the color it will come when it is selected. + +> Navigate to Date +``` kotlin + takeMeToDate: LocalDate? + ``` +It will make the date the first day to be displayed in the view. diff --git a/docs/KalendarEndlos.md b/docs/KalendarEndlos.md new file mode 100644 index 0000000..825d259 --- /dev/null +++ b/docs/KalendarEndlos.md @@ -0,0 +1,88 @@ + +## Kalendar + +This is a calendar library that gives integration for the year View starting with Current month. It uses pagination3 under the hood. + +## Introduction + +With Compose getting the attention, it was about time to have its own Calendar. Kalendar is all +about it with the customization and design. + +## Implementation + +### Step: 01 + +To add Kalendar, add this dependency, + +```gradle +dependencies { + ..... + implementation("com.himanshoe:kalendar-endlos:1.0.0") + } + ``` + +### Step: 02 (Usage) +To integrate Kalendar use the composable, + +```kotlin +Kalendar() +``` +Preview: + + +### Step: 03 (Customisation) +With Kalendar at your disposal you have customization at your tip to make it suited based on your need. + +`Kalendar` composable has lot of optional parameters that you can use. Like, +> Setting up Events +```kotlin +kalendarEvents: List = emptyList(), +``` +Here, `KalendarEvent` can be used to pass events to mark specific days. `KalendarEvent` looks like, +```kotlin +data class KalendarEvent( + val date: LocalDate, + val eventName: String, + val eventDescription: String? = null, ) +``` + + +> Setting up KalendarThemeColors +```kotlin +kalendarThemeColors: List = KalendarColors.defaultColors(), +``` +Here, `KalendarThemeColor` list can be used to pass the basic theming colors that will be common for all the Views. +PS: I would say check the `defaultColors` once as well! + +`KalendarThemeColor` looks like, +```kotlin +data class KalendarThemeColor( + val backgroundColor: Color, + val dayBackgroundColor: Color, + val headerTextColor: Color, ) +``` + +Alternatively, +if you dont want each month having its own color set, you can use, +```kotlin +kalendarThemeColor: KalendarThemeColor = //Your color, +``` + +> Individual Date Click Listener +```kotlin +onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, +``` +You can use this lambda to get values of the specific day that is in format of, +```kotlin +class KalendarDay(val localDate: LocalDate) +``` +and the list of events for that particular day. + +> Setting up Colors for Individual Day +```kotlin +kalendarDayColors: KalendarDayColors = KalendarDayDefaultColors.defaultColors() +``` +This will have list of two colors, +* First one for default color +* Second one for the color it will come when it is selected. + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..cac7c68 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/gradle/config/detekt.yml b/gradle/config/detekt.yml new file mode 100644 index 0000000..5e2bc63 --- /dev/null +++ b/gradle/config/detekt.yml @@ -0,0 +1,21 @@ +naming: + TopLevelPropertyNaming: + constantPattern: '[A-Z][A-Za-z0-9]*' + FunctionNaming: + functionPattern: '([A-Za-z][a-zA-Z0-9]*)|(`.*`)' + MatchingDeclarationName: + active: false + +style: + MagicNumber: + ignorePropertyDeclaration: true + UnusedPrivateMember: + ignoreAnnotated: [ 'Preview' ] + +complexity: + LongParameterList: + functionThreshold: 25 + constructorThreshold: 25 + ignoreDefaultParameters: true + TooManyFunctions: + thresholdInFiles: 20 diff --git a/gradle/scripts/git-hooks.gradle.kts b/gradle/scripts/git-hooks.gradle.kts new file mode 100644 index 0000000..2c2df72 --- /dev/null +++ b/gradle/scripts/git-hooks.gradle.kts @@ -0,0 +1,33 @@ +tasks { + register("copyGitHooks") { + description = "Copies the git hooks from scripts/git-hooks to the .git folder." + group = "plugins.git-hook" + from("$rootDir/scripts/git-hooks/") { + include("**/*.sh") + rename("(.*).sh", "$1") + } + into("$rootDir/.git/hooks") + } + + register("installGitHooks") { + description = "Installs the scripts git hooks from scripts/git-hooks." + group = "plugins.git-hook" + workingDir(rootDir) + commandLine("chmod") + args("-R", "+x", ".git/hooks/") + dependsOn(named("copyGitHooks")) + doLast { + print("Git hooks installed successfully.") + } + } + + register("deleteGitHooks") { + description = "Delete the scripts git hooks." + group = "plugins.git-hook" + delete(fileTree(".git/hooks/")) + } + + afterEvaluate { + tasks["clean"].dependsOn(tasks.named("installGitHooks")) + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..412326f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jul 29 13:34:16 IST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/kalendar-endlos/.gitignore b/kalendar-endlos/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/kalendar-endlos/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/kalendar-endlos/build.gradle.kts b/kalendar-endlos/build.gradle.kts new file mode 100644 index 0000000..246c3a0 --- /dev/null +++ b/kalendar-endlos/build.gradle.kts @@ -0,0 +1,60 @@ +plugins { + id(Plugins.library) + id(Plugins.kotlinAndroid) +} + +android { + compileSdk = ModuleExtension.compileSdkVersion + + defaultConfig { + minSdk = ModuleExtension.DefaultConfigs.minSdkVersion + targetSdk = ModuleExtension.DefaultConfigs.targetSdkVersion + + testInstrumentationRunner = ModuleExtension.DefaultConfigs.testInstrumentationRunner + consumerProguardFiles(ModuleExtension.DefaultConfigs.defaultConsumerProguardFiles) + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile(ModuleExtension.DefaultConfigs.defaultProguardOptimizeFileName), + ModuleExtension.DefaultConfigs.proGuardRules + ) + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = ModuleExtension.jvmTarget + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } +} + +dependencies { + implementation(Deps.Compose.ui) + implementation(Deps.Compose.material) + implementation(Deps.Compose.uiToolingPreview) + implementation(Deps.Compose.activity) + + debugApi(Deps.Compose.uiTooling) + implementation(Deps.Compose.viewmodel) + implementation(Deps.Compose.pagination) + + testImplementation(Deps.Test.jUnit) + androidTestImplementation(Deps.AndroidTest.jUnitExtensions) + androidTestImplementation(Deps.AndroidTest.espressoCore) + androidTestApi(Deps.AndroidTest.uiTestJunit) + + api(Deps.DateTime.date) +} + +plugins.apply(Plugins.vanniktechPublish) diff --git a/kalendar-endlos/consumer-rules.pro b/kalendar-endlos/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/kalendar-endlos/gradle.properties b/kalendar-endlos/gradle.properties new file mode 100644 index 0000000..2d267f1 --- /dev/null +++ b/kalendar-endlos/gradle.properties @@ -0,0 +1,17 @@ +# Maven +POM_ARTIFACT_ID=kalendar-endlos +POM_NAME=Kalendar Endlos +POM_DESCRIPTION=An Elementary Compose Calendar. +POM_PACKAGING=aar +POM_INCEPTION_YEAR=2022 +GROUP=com.himanshoe +VERSION_NAME=1.0.0-RC5 +VERSION_CODE=1 +POM_URL=https://github.com/hi-manshu +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo +POM_SCM_URL=https://github.com/hi-manshu +POM_DEVELOPER_ID=hi-manshu +POM_DEVELOPER_NAME=Himanshu Singh +POM_DEVELOPER_URL=https://github.com/hi-manshu diff --git a/kalendar-endlos/proguard-rules.pro b/kalendar-endlos/proguard-rules.pro new file mode 100644 index 0000000..2f9dc5a --- /dev/null +++ b/kalendar-endlos/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/kalendar-endlos/src/androidTest/java/com/himanshoe/kalendar/endlos/ExampleInstrumentedTest.kt b/kalendar-endlos/src/androidTest/java/com/himanshoe/kalendar/endlos/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..866adba --- /dev/null +++ b/kalendar-endlos/src/androidTest/java/com/himanshoe/kalendar/endlos/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.himanshoe.kalendar.endlos + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.himanshoe.kalendar.endlos.test", appContext.packageName) + } +} diff --git a/kalendar-endlos/src/main/AndroidManifest.xml b/kalendar-endlos/src/main/AndroidManifest.xml new file mode 100644 index 0000000..59fd09c --- /dev/null +++ b/kalendar-endlos/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/Kalendar.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/Kalendar.kt new file mode 100644 index 0000000..66803ec --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/Kalendar.kt @@ -0,0 +1,101 @@ +package com.himanshoe.kalendar.endlos + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.items +import com.himanshoe.kalendar.endlos.color.KalendarColors +import com.himanshoe.kalendar.endlos.color.KalendarThemeColor +import com.himanshoe.kalendar.endlos.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.endlos.component.day.config.KalendarDayDefaultColors +import com.himanshoe.kalendar.endlos.model.KalendarDay +import com.himanshoe.kalendar.endlos.model.KalendarEvent +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn + +@Composable +fun Kalendar( + modifier: Modifier = Modifier, + kalendarEvents: List = emptyList(), + kalendarThemeColors: List = KalendarColors.defaultColors(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + kalendarDayColors: KalendarDayColors = KalendarDayDefaultColors.defaultColors(), +) { + val kalendarViewModel: KalendarViewModel = viewModel() + KalendarEndloss( + modifier = modifier, + kalendarItems = kalendarViewModel.getDates().collectAsLazyPagingItems(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + kalendarThemeColors = kalendarThemeColors, + ) +} + +@Composable +fun Kalendar( + modifier: Modifier = Modifier, + kalendarThemeColor: KalendarThemeColor, + kalendarEvents: List = emptyList(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + kalendarDayColors: KalendarDayColors = KalendarDayDefaultColors.defaultColors(), +) { + val kalendarViewModel: KalendarViewModel = viewModel() + KalendarEndloss( + modifier = modifier, + kalendarItems = kalendarViewModel.getDates().collectAsLazyPagingItems(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + kalendarThemeColors = listOf(kalendarThemeColor), + ) +} + +@Composable +private fun KalendarEndloss( + modifier: Modifier, + kalendarItems: LazyPagingItems, + kalendarEvents: List, + onCurrentDayClick: (KalendarDay, List) -> Unit, + kalendarDayColors: KalendarDayColors, + kalendarThemeColors: List +) { + val today = Clock.System.todayIn(TimeZone.currentSystemDefault()) + val clickedDay = remember { mutableStateOf(today) } + + LazyColumn(modifier = modifier.fillMaxWidth()) { + items(kalendarItems) { date: LocalDate? -> + if (date != null) { + val kalendarThemeColor = if (kalendarThemeColors.count() == 1) { + kalendarThemeColors.first() + } else kalendarThemeColors[date.monthNumber.minus(1)] + + KalendarMonth( + date = date, + modifier = modifier + .padding(horizontal = 12.dp, vertical = 6.dp) + .background(kalendarThemeColor.backgroundColor), + kalendarEvents = kalendarEvents, + onCurrentDayClick = { day, events -> + clickedDay.value = day.localDate + onCurrentDayClick(day, events) + }, + kalendarDayColors = kalendarDayColors, + selectedDay = clickedDay.value, + kalendarThemeColors = kalendarThemeColor + ) + } + } + } +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarMonth.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarMonth.kt new file mode 100644 index 0000000..3da86c4 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarMonth.kt @@ -0,0 +1,111 @@ +package com.himanshoe.kalendar.endlos + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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 +import com.himanshoe.kalendar.endlos.color.KalendarThemeColor +import com.himanshoe.kalendar.endlos.component.KalendarHeader +import com.himanshoe.kalendar.endlos.component.day.EmptyKalendarDay +import com.himanshoe.kalendar.endlos.component.day.KalendarDay +import com.himanshoe.kalendar.endlos.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.endlos.component.text.KalendarNormalText +import com.himanshoe.kalendar.endlos.model.KalendarDay +import com.himanshoe.kalendar.endlos.model.KalendarEvent +import com.himanshoe.kalendar.endlos.model.toKalendarDay +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.Month +import kotlinx.datetime.toLocalDate + +private const val DaysInWeek = 7 +private val WeekDays = listOf("M", "T", "W", "T", "F", "S", "S") + +@Composable +internal fun KalendarMonth( + date: LocalDate, + modifier: Modifier = Modifier, + kalendarEvents: List, + onCurrentDayClick: (KalendarDay, List) -> Unit, + selectedDay: LocalDate, + kalendarDayColors: KalendarDayColors, + kalendarThemeColors: KalendarThemeColor, +) { + + Column( + modifier = modifier + .fillMaxWidth() + .padding(bottom = 16.dp, start = 4.dp, top = 4.dp, end = 4.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + KalendarHeader( + modifier = Modifier.padding(vertical = 28.dp, horizontal = 16.dp), + month = date.month, + year = date.year, + textColor = kalendarDayColors.textColor + ) + val start = getKalendarStartDay(date.dayOfWeek) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceAround + ) { + WeekDays.forEach { + KalendarNormalText( + text = it, + fontWeight = FontWeight.Normal, + color = kalendarDayColors.textColor + ) + } + } + (start..date.month.minLength()).map { + if (it > 0) { + getGeneratedDay(it, date.month, date.year) + } else -1 + }.toList().chunked(DaysInWeek).forEach { weekDays -> + BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { + val size = (maxWidth.div(DaysInWeek)) + + Row(horizontalArrangement = Arrangement.spacedBy(0.dp)) { + weekDays.forEach { localDate -> + if (localDate is LocalDate) { + KalendarDay( + modifier = Modifier, + size = size, + kalendarDay = localDate.toKalendarDay(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + selectedKalendarDay = selectedDay, + dotColor = kalendarThemeColors.headerTextColor, + dayBackgroundColor = kalendarThemeColors.dayBackgroundColor + ) + } else { + EmptyKalendarDay( + background = Color.Transparent, + modifier = Modifier + ) + } + } + } + } + } + } +} + +private fun getKalendarStartDay(dayOfWeek: DayOfWeek): Int { + return 1.minus(dayOfWeek.value) +} + +private fun getGeneratedDay(day: Int, currentMonth: Month, currentYear: Int): LocalDate { + val monthValue = + if (currentMonth.value.toString().length == 1) "0${currentMonth.value}" else currentMonth.value.toString() + val newDay = if (day.toString().length == 1) "0$day" else day + return "$currentYear-$monthValue-$newDay".toLocalDate() +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarPagingSource.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarPagingSource.kt new file mode 100644 index 0000000..e8a57c5 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarPagingSource.kt @@ -0,0 +1,39 @@ +package com.himanshoe.kalendar.endlos + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn + +class KalendarPagingSource : PagingSource() { + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val position = params.key ?: 0 + val calendarDateList = mutableListOf() + val currentDate = Clock.System + .todayIn(TimeZone.currentSystemDefault()) + .plus(position.toLong(), DateTimeUnit.MONTH) + + val currentMonth = currentDate.month + val currentYear = currentDate.year + val monthStartDate = LocalDate(currentYear, currentMonth, 1) + + calendarDateList.add(monthStartDate) + return LoadResult.Page( + data = calendarDateList, + prevKey = null, + nextKey = position.plus(1) + ) + } +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarViewModel.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarViewModel.kt new file mode 100644 index 0000000..a3a3b0b --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/KalendarViewModel.kt @@ -0,0 +1,21 @@ +package com.himanshoe.kalendar.endlos + +import androidx.lifecycle.ViewModel +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import kotlinx.datetime.LocalDate + +internal class KalendarViewModel : ViewModel() { + + fun getDates(): Flow> = Pager( + config = PagingConfig( + 12, + enablePlaceholders = true, + initialLoadSize = 12, + prefetchDistance = 5 + ), + pagingSourceFactory = { KalendarPagingSource() } + ).flow +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/color/KalendarColors.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/color/KalendarColors.kt new file mode 100644 index 0000000..2df5466 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/color/KalendarColors.kt @@ -0,0 +1,73 @@ +package com.himanshoe.kalendar.endlos.color + +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color + +internal object KalendarColors { + + fun defaultColors(): List = buildList { + repeat(12) { index -> + add( + KalendarThemeColor( + kalendarBackgroundColor[index], + backgroundColor[index], + headerColors[index] + ) + ) + } + } + + @Stable + private val backgroundColor = listOf( + Color(0xffF7CFD3), + Color(0xffEFBDCF), + Color(0xffDBBFE4), + Color(0xffCFC4E5), + Color(0xffC6CAE6), + Color(0xffC1DEF9), + Color(0xffBDE3F9), + Color(0xffBEE8F1), + Color(0xffBBDEDB), + Color(0xffCEE5CB), + Color(0xffDEEBCB), + Color(0xffF1F4C8), + ) + + @Stable + private val kalendarBackgroundColor = listOf( + Color.White, + Color(0xFFFCEFFE), + Color(0xFFFDF2FE), + Color(0xFFFEF7FE), + Color(0xFFF9FDFE), + Color(0xFFF1FEFF), + Color(0xFFEBFEFF), + Color(0xFFE9FEFF), + Color(0xFFEBFEFF), + Color(0xFFFCFFFC), + Color(0xFFFFFFFB), + Color(0xFFFFFFF7), + ) + + @Stable + private val headerColors = listOf( + Color(0xFFC39EA1), + Color(0xFFBB8D9E), + Color(0xFFAA8FB1), + Color(0xFF9E94B4), + Color(0xFF9599B4), + Color(0xFF91ABC5), + Color(0xFF8CB2C6), + Color(0xFF8CB7BE), + Color(0xFF8BACA9), + Color(0xFF9DB39A), + Color(0xFFADBA9A), + Color(0xFFBEC196), + ) +} + +data class KalendarThemeColor( + val backgroundColor: Color, + val dayBackgroundColor: Color, + val headerTextColor: Color, +) diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/KalendarHeader.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/KalendarHeader.kt new file mode 100644 index 0000000..ca64494 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/KalendarHeader.kt @@ -0,0 +1,36 @@ +package com.himanshoe.kalendar.endlos.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.himanshoe.kalendar.endlos.component.text.KalendarTitle +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextColor +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextConfig +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextSize +import kotlinx.datetime.Month +import java.util.Locale + +@Composable +internal fun KalendarHeader( + modifier: Modifier, + month: Month, + year: Int, + textColor: Color, + textSize: KalendarTextSize = KalendarTextSize.Title + +) { + KalendarTitle( + modifier = modifier.fillMaxWidth(), + text = getTitleText(month.name, year), + kalendarTextConfig = KalendarTextConfig(KalendarTextColor(textColor), textSize) + ) +} + +internal fun getTitleText(monthName: String, year: Int): String { + return monthName.lowercase().replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.getDefault() + ) else it.toString() + } + " " + year +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/KalendarDay.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/KalendarDay.kt new file mode 100644 index 0000000..f48fe18 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/KalendarDay.kt @@ -0,0 +1,181 @@ +package com.himanshoe.kalendar.endlos.component.day + +import androidx.compose.foundation.BorderStroke +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.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.himanshoe.kalendar.endlos.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.endlos.component.day.config.KalendarDayState +import com.himanshoe.kalendar.endlos.component.text.KalendarNormalText +import com.himanshoe.kalendar.endlos.model.KalendarDay +import com.himanshoe.kalendar.endlos.model.KalendarEvent +import kotlinx.datetime.LocalDate + +@Composable +fun KalendarDay( + kalendarDay: KalendarDay, + modifier: Modifier = Modifier, + size: Dp = 56.dp, + textSize: TextUnit = 16.sp, + kalendarEvents: List = emptyList(), + isCurrentDay: Boolean = false, + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + selectedKalendarDay: LocalDate, + kalendarDayColors: KalendarDayColors, + dotColor: Color, + dayBackgroundColor: Color, +) { + val kalendarDayState = getKalendarDayState(selectedKalendarDay, kalendarDay.localDate) + val bgColor = getBackgroundColor(kalendarDayState, dayBackgroundColor) + val textColor = getTextColor(kalendarDayState, kalendarDayColors) + val shape = getTextSelectionShape(kalendarDayState) + val weight = getTextWeight(kalendarDayState) + val border = getBorder(isCurrentDay) + + Column( + modifier = modifier + .border(border = border, shape = CircleShape) + .clip(shape = shape) + .size(size = size) + .background(color = bgColor) + .clickable { onCurrentDayClick(kalendarDay, kalendarEvents) }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + KalendarNormalText( + text = kalendarDay.localDate.dayOfMonth.toString(), + modifier = Modifier, + fontWeight = weight, + color = textColor, + textSize = textSize, + ) + Row( + modifier = Modifier + .padding(top = 4.dp) + .fillMaxWidth() + .align(Alignment.CenterHorizontally), + horizontalArrangement = Arrangement.Center + ) { + if (kalendarEvents.isNotEmpty()) { + kalendarEvents.take(3).forEachIndexed { index, _ -> + KalendarDots( + modifier = Modifier, + index = index, + size = size, + color = dotColor + ) + } + } + } + } +} +@Composable +fun EmptyKalendarDay( + modifier: Modifier = Modifier, + size: Dp = 56.dp, + background: Color, +) { + + Box( + modifier = modifier + .clip(shape = RectangleShape) + .size(size = size) + .background( + color = background + ), + ) +} + +@Composable +fun KalendarDots( + modifier: Modifier = Modifier, + index: Int, + size: Dp, + color: Color +) { + Box( + modifier = modifier + .padding(horizontal = 1.dp) + .clip(shape = CircleShape) + .background( + color = color + .copy(alpha = index.plus(1) * 0.3F) + ) + .size(size = size.div(12)) + ) +} + +private fun getKalendarDayState(selectedDate: LocalDate, currentDay: LocalDate) = + when (selectedDate) { + currentDay -> KalendarDayState.KalendarDaySelected + else -> KalendarDayState.KalendarDayDefault + } + +private fun getBorder(isCurrentDay: Boolean) = + BorderStroke( + width = if (isCurrentDay) 1.dp else 0.dp, + color = if (isCurrentDay) Color.Black else Color.Transparent, + ) + +private fun getTextWeight(kalendarDayState: KalendarDayState) = + if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + FontWeight.Bold + } else { + FontWeight.SemiBold + } + +private fun getBackgroundColor( + kalendarDayState: KalendarDayState, + backgroundColor: Color, +) = if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + backgroundColor +} else { + Color.Transparent +} + +private fun getTextSelectionShape( + kalendarDayState: KalendarDayState, +) = if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + CircleShape +} else { + RectangleShape +} + +private fun getTextColor( + kalendarDayState: KalendarDayState, + kalendarDayColors: KalendarDayColors, +): Color = if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + kalendarDayColors.selectedTextColor +} else { + kalendarDayColors.textColor +} + +@Preview +@Composable +private fun KalendarDayPreview() { +// KalendarDay( +// kalendarDay = KalendarDay(localDate = Clock.System.todayIn(TimeZone.currentSystemDefault())), +// kalendarDayConfig = KalendarDayDefaults.kalendarDayConfig(), +// selectedKalendarDate = mutableStateOf() +// ) +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/config/KalendarDayColors.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/config/KalendarDayColors.kt new file mode 100644 index 0000000..f85ce42 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/config/KalendarDayColors.kt @@ -0,0 +1,13 @@ +package com.himanshoe.kalendar.endlos.component.day.config + +import androidx.compose.ui.graphics.Color + +data class KalendarDayColors( + val textColor: Color, // Default Text Color + val selectedTextColor: Color, // Selected Text Color +) + +object KalendarDayDefaultColors { + + fun defaultColors() = KalendarDayColors(Color.Black, Color.White) +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/config/KalendarDayState.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/config/KalendarDayState.kt new file mode 100644 index 0000000..54dc275 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/day/config/KalendarDayState.kt @@ -0,0 +1,6 @@ +package com.himanshoe.kalendar.endlos.component.day.config + +sealed interface KalendarDayState { + object KalendarDaySelected : KalendarDayState + object KalendarDayDefault : KalendarDayState +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarNormalText.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarNormalText.kt new file mode 100644 index 0000000..038e90d --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarNormalText.kt @@ -0,0 +1,43 @@ +package com.himanshoe.kalendar.endlos.component.text + +import androidx.compose.material.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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextSize + +@Composable +fun KalendarNormalText( + text: String, + modifier: Modifier = Modifier, + fontWeight: FontWeight, + color: Color, + textSize: TextUnit = KalendarTextSize.Normal.size + +) { + Text( + modifier = modifier, + color = color, + fontSize = textSize, + text = text, + fontWeight = fontWeight, + textAlign = TextAlign.Center + ) +} + +@Preview +@Composable +private fun KalendarNormalTextPreview() { + KalendarNormalText( + text = "Hye Himanshu", + modifier = Modifier, + fontWeight = FontWeight.SemiBold, + color = Color.Black, + textSize = 26.sp + ) +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarSubTitle.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarSubTitle.kt new file mode 100644 index 0000000..84b12cf --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarSubTitle.kt @@ -0,0 +1,34 @@ +package com.himanshoe.kalendar.endlos.component.text + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextConfig +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextDefaults + +@Composable +fun KalendarSubTitle( + text: String, + modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Start, + fontWeight: FontWeight = FontWeight.SemiBold, + kalendarTextConfig: KalendarTextConfig = KalendarTextDefaults.kalendarSubTitleTextConfig() +) { + Text( + modifier = modifier, + color = kalendarTextConfig.kalendarTextColor.textColor, + fontSize = kalendarTextConfig.kalendarTextSize.size, + text = text, + fontWeight = fontWeight, + textAlign = textAlign + ) +} + +@Preview +@Composable +private fun KalendarSubTitlePreview() { + KalendarSubTitle(modifier = Modifier, text = "Hye Himanshu") +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarTitle.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarTitle.kt new file mode 100644 index 0000000..a075ce5 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/KalendarTitle.kt @@ -0,0 +1,34 @@ +package com.himanshoe.kalendar.endlos.component.text + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextConfig +import com.himanshoe.kalendar.endlos.component.text.config.KalendarTextDefaults + +@Composable +fun KalendarTitle( + text: String, + modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Start, + fontWeight: FontWeight = FontWeight.SemiBold, + kalendarTextConfig: KalendarTextConfig = KalendarTextDefaults.kalendarTitleTextConfig() +) { + Text( + modifier = modifier, + color = kalendarTextConfig.kalendarTextColor.textColor, + fontSize = kalendarTextConfig.kalendarTextSize.size, + text = text, + fontWeight = fontWeight, + textAlign = textAlign + ) +} + +@Preview +@Composable +private fun KalendarTitlePreview() { + KalendarTitle(modifier = Modifier, text = "Hye Himanshu") +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextColor.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextColor.kt new file mode 100644 index 0000000..d253b5a --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextColor.kt @@ -0,0 +1,19 @@ +package com.himanshoe.kalendar.endlos.component.text.config + +import androidx.compose.ui.graphics.Color + +private val TitleTextColor = Color(0xFFD2827A) + +data class KalendarTextColor( + val textColor: Color +) + +internal object KalendarTextColorDefaults { + + fun kalendarTitleTextColor() = KalendarTextColor( + textColor = TitleTextColor + ) + fun kalendarNormalTextColor() = KalendarTextColor( + textColor = TitleTextColor + ) +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextConfig.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextConfig.kt new file mode 100644 index 0000000..363aefb --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextConfig.kt @@ -0,0 +1,27 @@ +package com.himanshoe.kalendar.endlos.component.text.config + +data class KalendarTextConfig( + val kalendarTextColor: KalendarTextColor = KalendarTextColorDefaults.kalendarTitleTextColor(), + val kalendarTextSize: KalendarTextSize = KalendarTextSize.Title +) + +internal object KalendarTextDefaults { + + fun kalendarTitleTextConfig() = + KalendarTextConfig( + kalendarTextColor = KalendarTextColorDefaults.kalendarTitleTextColor(), + kalendarTextSize = KalendarTextSize.Title + ) + + fun kalendarSubTitleTextConfig() = + KalendarTextConfig( + kalendarTextColor = KalendarTextColorDefaults.kalendarTitleTextColor(), + kalendarTextSize = KalendarTextSize.SubTitle + ) + + fun kalendarNormalTextConfig() = + KalendarTextConfig( + kalendarTextColor = KalendarTextColorDefaults.kalendarNormalTextColor(), + kalendarTextSize = KalendarTextSize.Normal + ) +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextSize.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextSize.kt new file mode 100644 index 0000000..755ccfd --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/component/text/config/KalendarTextSize.kt @@ -0,0 +1,11 @@ +package com.himanshoe.kalendar.endlos.component.text.config + +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + +sealed class KalendarTextSize(val size: TextUnit) { + object Title : KalendarTextSize(32.sp) + object SubTitle : KalendarTextSize(24.sp) + object Normal : KalendarTextSize(16.sp) + data class Custom(val textUnit: TextUnit) : KalendarTextSize(textUnit) +} diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/model/KalendarDay.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/model/KalendarDay.kt new file mode 100644 index 0000000..d8dd476 --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/model/KalendarDay.kt @@ -0,0 +1,8 @@ +package com.himanshoe.kalendar.endlos.model + +import kotlinx.datetime.LocalDate + +@JvmInline +value class KalendarDay(val localDate: LocalDate) + +fun LocalDate.toKalendarDay() = KalendarDay(this) diff --git a/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/model/KalendarEvent.kt b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/model/KalendarEvent.kt new file mode 100644 index 0000000..8562cbd --- /dev/null +++ b/kalendar-endlos/src/main/java/com/himanshoe/kalendar/endlos/model/KalendarEvent.kt @@ -0,0 +1,9 @@ +package com.himanshoe.kalendar.endlos.model + +import kotlinx.datetime.LocalDate + +data class KalendarEvent( + val date: LocalDate, + val eventName: String, + val eventDescription: String? = null, +) diff --git a/kalendar-endlos/src/test/java/com/himanshoe/kalendar/endlos/ExampleUnitTest.kt b/kalendar-endlos/src/test/java/com/himanshoe/kalendar/endlos/ExampleUnitTest.kt new file mode 100644 index 0000000..8c5786c --- /dev/null +++ b/kalendar-endlos/src/test/java/com/himanshoe/kalendar/endlos/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.himanshoe.kalendar.endlos + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/kalendar/.gitignore b/kalendar/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/kalendar/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/kalendar/build.gradle.kts b/kalendar/build.gradle.kts new file mode 100644 index 0000000..6775800 --- /dev/null +++ b/kalendar/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + id(Plugins.library) + id(Plugins.kotlinAndroid) +} + +android { + compileSdk = ModuleExtension.compileSdkVersion + + defaultConfig { + minSdk = ModuleExtension.DefaultConfigs.minSdkVersion + targetSdk = ModuleExtension.DefaultConfigs.targetSdkVersion + + testInstrumentationRunner = ModuleExtension.DefaultConfigs.testInstrumentationRunner + consumerProguardFiles(ModuleExtension.DefaultConfigs.defaultConsumerProguardFiles) + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile(ModuleExtension.DefaultConfigs.defaultProguardOptimizeFileName), + ModuleExtension.DefaultConfigs.proGuardRules + ) + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = ModuleExtension.jvmTarget + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } +} + +dependencies { + implementation(Deps.Compose.ui) + implementation(Deps.Compose.material) + implementation(Deps.Compose.uiToolingPreview) + implementation(Deps.Compose.activity) + api(Deps.DateTime.date) + + debugApi(Deps.Compose.uiTooling) + + testImplementation(Deps.Test.jUnit) + androidTestImplementation(Deps.AndroidTest.jUnitExtensions) + androidTestImplementation(Deps.AndroidTest.espressoCore) + androidTestApi(Deps.AndroidTest.uiTestJunit) +} + +plugins.apply(Plugins.vanniktechPublish) diff --git a/kalendar/consumer-rules.pro b/kalendar/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/kalendar/gradle.properties b/kalendar/gradle.properties new file mode 100644 index 0000000..b660294 --- /dev/null +++ b/kalendar/gradle.properties @@ -0,0 +1,17 @@ +# Maven +POM_ARTIFACT_ID=kalendar +POM_NAME=kalendar +POM_DESCRIPTION=An Elementary Compose Calendar. +POM_PACKAGING=aar +POM_INCEPTION_YEAR=2022 +GROUP=com.himanshoe +VERSION_NAME=1.0.0 +VERSION_CODE=1 +POM_URL=https://github.com/hi-manshu +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo +POM_SCM_URL=https://github.com/hi-manshu +POM_DEVELOPER_ID=hi-manshu +POM_DEVELOPER_NAME=Himanshu Singh +POM_DEVELOPER_URL=https://github.com/hi-manshu diff --git a/kalendar/proguard-rules.pro b/kalendar/proguard-rules.pro new file mode 100644 index 0000000..84c21e8 --- /dev/null +++ b/kalendar/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/kalendar/src/androidTest/java/com/himanshoe/kalendar/ExampleInstrumentedTest.kt b/kalendar/src/androidTest/java/com/himanshoe/kalendar/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..f30f4f3 --- /dev/null +++ b/kalendar/src/androidTest/java/com/himanshoe/kalendar/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.himanshoe.kalendar + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.himanshoe.kalendar.test", appContext.packageName) + } +} diff --git a/kalendar/src/main/AndroidManifest.xml b/kalendar/src/main/AndroidManifest.xml new file mode 100644 index 0000000..961020f --- /dev/null +++ b/kalendar/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/Kalendar.kt b/kalendar/src/main/java/com/himanshoe/kalendar/Kalendar.kt new file mode 100644 index 0000000..9e1eb40 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/Kalendar.kt @@ -0,0 +1,87 @@ +package com.himanshoe.kalendar + +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.himanshoe.kalendar.color.KalendarColors +import com.himanshoe.kalendar.color.KalendarThemeColor +import com.himanshoe.kalendar.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.component.day.config.KalendarDayDefaultColors +import com.himanshoe.kalendar.model.KalendarDay +import com.himanshoe.kalendar.model.KalendarEvent +import com.himanshoe.kalendar.model.KalendarType +import com.himanshoe.kalendar.ui.firey.KalendarFirey +import com.himanshoe.kalendar.ui.oceanic.KalendarOceanic +import kotlinx.datetime.LocalDate + +@Composable +fun Kalendar( + modifier: Modifier = Modifier, + kalendarType: KalendarType = KalendarType.Oceanic, + kalendarEvents: List = emptyList(), + kalendarThemeColors: List = KalendarColors.defaultColors(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + kalendarDayColors: KalendarDayColors = KalendarDayDefaultColors.defaultColors(), + takeMeToDate: LocalDate? = null, +) { + if (kalendarThemeColors.isEmpty() || kalendarThemeColors.count() < 12) throw Exception("KalendarThemeColor cannot be null or less than 12, If you want to use same color accors months pass kalendarThemeColor = KalendarThemeColor(values)") + + when (kalendarType) { + KalendarType.Oceanic -> KalendarOceanic( + modifier = modifier.wrapContentHeight(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + kalendarThemeColors = kalendarThemeColors, + takeMeToDate = takeMeToDate + ) + KalendarType.Firey -> { + KalendarFirey( + modifier = modifier.wrapContentHeight(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + kalendarThemeColors = kalendarThemeColors, + takeMeToDate = takeMeToDate + ) + } + } +} + +@Composable +fun Kalendar( + modifier: Modifier = Modifier, + kalendarThemeColor: KalendarThemeColor, + kalendarType: KalendarType = KalendarType.Oceanic, + kalendarEvents: List = emptyList(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + kalendarDayColors: KalendarDayColors = KalendarDayDefaultColors.defaultColors(), + takeMeToDate: LocalDate? = null, +) { + when (kalendarType) { + KalendarType.Oceanic -> KalendarOceanic( + modifier = modifier.wrapContentHeight(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + kalendarThemeColor = kalendarThemeColor, + takeMeToDate = takeMeToDate + ) + KalendarType.Firey -> { + KalendarFirey( + modifier = modifier.wrapContentHeight(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = onCurrentDayClick, + kalendarDayColors = kalendarDayColors, + kalendarThemeColor = kalendarThemeColor, + takeMeToDate = takeMeToDate + ) + } + } +} + +@Composable +@Preview +private fun KalendarPreview() { +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/color/KalendarColors.kt b/kalendar/src/main/java/com/himanshoe/kalendar/color/KalendarColors.kt new file mode 100644 index 0000000..3b0f7a8 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/color/KalendarColors.kt @@ -0,0 +1,73 @@ +package com.himanshoe.kalendar.color + +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color + +internal object KalendarColors { + + fun defaultColors(): List = buildList { + repeat(12) { index -> + add( + KalendarThemeColor( + kalendarBackgroundColor[index], + backgroundColor[index], + headerColors[index] + ) + ) + } + } + + @Stable + private val backgroundColor = listOf( + Color(0xffF7CFD3), + Color(0xffEFBDCF), + Color(0xffDBBFE4), + Color(0xffCFC4E5), + Color(0xffC6CAE6), + Color(0xffC1DEF9), + Color(0xffBDE3F9), + Color(0xffBEE8F1), + Color(0xffBBDEDB), + Color(0xffCEE5CB), + Color(0xffDEEBCB), + Color(0xffF1F4C8), + ) + + @Stable + private val kalendarBackgroundColor = listOf( + Color.White, + Color(0xFFFCEFFE), + Color(0xFFFDF2FE), + Color(0xFFFEF7FE), + Color(0xFFF9FDFE), + Color(0xFFF1FEFF), + Color(0xFFEBFEFF), + Color(0xFFE9FEFF), + Color(0xFFEBFEFF), + Color(0xFFFCFFFC), + Color(0xFFFFFFFB), + Color(0xFFFFFFF7), + ) + + @Stable + private val headerColors = listOf( + Color(0xFFC39EA1), + Color(0xFFBB8D9E), + Color(0xFFAA8FB1), + Color(0xFF9E94B4), + Color(0xFF9599B4), + Color(0xFF91ABC5), + Color(0xFF8CB2C6), + Color(0xFF8CB7BE), + Color(0xFF8BACA9), + Color(0xFF9DB39A), + Color(0xFFADBA9A), + Color(0xFFBEC196), + ) +} + +data class KalendarThemeColor( + val backgroundColor: Color, + val dayBackgroundColor: Color, + val headerTextColor: Color, +) diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/KalendarHeader.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/KalendarHeader.kt new file mode 100644 index 0000000..549aee0 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/KalendarHeader.kt @@ -0,0 +1,124 @@ +package com.himanshoe.kalendar.component + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ContentTransform +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.with +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowLeft +import androidx.compose.material.icons.filled.KeyboardArrowRight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.himanshoe.kalendar.component.button.KalendarIconButton +import com.himanshoe.kalendar.component.text.KalendarSubTitle +import com.himanshoe.kalendar.component.text.config.KalendarTextColor +import com.himanshoe.kalendar.component.text.config.KalendarTextConfig +import com.himanshoe.kalendar.component.text.config.KalendarTextSize +import kotlinx.datetime.Month +import java.util.Locale + +@OptIn(ExperimentalAnimationApi::class) +@Composable +fun KalendarHeader( + modifier: Modifier, + month: Month, + year: Int, + onPreviousClick: () -> Unit = {}, + onNextClick: () -> Unit = {}, + arrowShown: Boolean = true, + textColor: Color, + textSize: KalendarTextSize = KalendarTextSize.SubTitle +) { + val isNext = remember { mutableStateOf(true) } + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 8.dp, bottom = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + AnimatedContent( + modifier = Modifier + .wrapContentHeight() + .wrapContentWidth() + .align(Alignment.CenterVertically), + targetState = getTitleText(month.name, year), + transitionSpec = { + addAnimation(isNext = isNext.value).using( + SizeTransform(clip = false) + ) + } + ) { + KalendarSubTitle( + text = it, + modifier = Modifier, + kalendarTextConfig = KalendarTextConfig( + kalendarTextColor = KalendarTextColor(textColor), kalendarTextSize = textSize + ) + ) + } + if (arrowShown) { + Row( + modifier = Modifier + .wrapContentWidth() + .align(Alignment.CenterVertically), + horizontalArrangement = Arrangement.End, + ) { + KalendarIconButton( + modifier = Modifier.wrapContentSize(), + imageVector = Icons.Default.KeyboardArrowLeft, + contentDescription = "Previous Week", + onClick = { + isNext.value = false + onPreviousClick() + } + + ) + KalendarIconButton( + modifier = Modifier.wrapContentSize(), + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = "Next Month", + onClick = { + isNext.value = true + onNextClick() + } + ) + } + } + } +} + +@OptIn(ExperimentalAnimationApi::class) +internal fun addAnimation(duration: Int = 500, isNext: Boolean): ContentTransform { + return slideInVertically(animationSpec = tween(durationMillis = duration)) { height -> if (isNext) height else -height } + fadeIn( + animationSpec = tween(durationMillis = duration) + ) with slideOutVertically(animationSpec = tween(durationMillis = duration)) { height -> if (isNext) -height else height } + fadeOut( + animationSpec = tween(durationMillis = duration) + ) +} + +internal fun getTitleText(monthName: String, year: Int): String { + return monthName.lowercase().replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.getDefault() + ) else it.toString() + } + " " + year +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/button/KalendarIconButton.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/button/KalendarIconButton.kt new file mode 100644 index 0000000..d508c2c --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/button/KalendarIconButton.kt @@ -0,0 +1,33 @@ +package com.himanshoe.kalendar.component.button + +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector + +@Composable +fun KalendarIconButton( + imageVector: ImageVector, + modifier: Modifier = Modifier, + contentDescription: String? = null, + onClick: () -> Unit = {} +) { + IconButton( + onClick = onClick, + modifier = modifier + .wrapContentSize() + .clip(CircleShape) + ) { + Icon( + modifier = Modifier, + tint = Color(0xFF413D4B), + imageVector = imageVector, + contentDescription = contentDescription + ) + } +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/day/KalendarDay.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/day/KalendarDay.kt new file mode 100644 index 0000000..e0d9bc7 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/day/KalendarDay.kt @@ -0,0 +1,165 @@ +package com.himanshoe.kalendar.component.day + +import androidx.compose.foundation.BorderStroke +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.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.himanshoe.kalendar.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.component.day.config.KalendarDayState +import com.himanshoe.kalendar.component.text.KalendarNormalText +import com.himanshoe.kalendar.model.KalendarDay +import com.himanshoe.kalendar.model.KalendarEvent +import kotlinx.datetime.LocalDate + +@Composable +fun KalendarDay( + kalendarDay: KalendarDay, + modifier: Modifier = Modifier, + size: Dp = 56.dp, + textSize: TextUnit = 16.sp, + kalendarEvents: List = emptyList(), + isCurrentDay: Boolean = false, + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + selectedKalendarDay: LocalDate, + kalendarDayColors: KalendarDayColors, + dotColor: Color, + dayBackgroundColor: Color, +) { + val kalendarDayState = getKalendarDayState(selectedKalendarDay, kalendarDay.localDate) + val bgColor = getBackgroundColor(kalendarDayState, dayBackgroundColor) + val textColor = getTextColor(kalendarDayState, kalendarDayColors) + val shape = getTextSelectionShape(kalendarDayState) + val weight = getTextWeight(kalendarDayState) + val border = getBorder(isCurrentDay) + + Column( + modifier = modifier + .border(border = border, shape = CircleShape) + .clip(shape = shape) + .size(size = size) + .background(color = bgColor) + .clickable { onCurrentDayClick(kalendarDay, kalendarEvents) }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + KalendarNormalText( + text = kalendarDay.localDate.dayOfMonth.toString(), + modifier = Modifier, + fontWeight = weight, + color = textColor, + textSize = textSize, + ) + Row( + modifier = Modifier + .padding(top = 4.dp) + .fillMaxWidth() + .align(Alignment.CenterHorizontally), + horizontalArrangement = Arrangement.Center + ) { + if (kalendarEvents.isNotEmpty()) { + kalendarEvents.take(3).forEachIndexed { index, _ -> + KalendarDots( + modifier = Modifier, + index = index, + size = size, + color = dotColor + ) + } + } + } + } +} + +@Composable +fun KalendarDots( + modifier: Modifier = Modifier, + index: Int, + size: Dp, + color: Color +) { + Box( + modifier = modifier + .padding(horizontal = 1.dp) + .clip(shape = CircleShape) + .background( + color = color + .copy(alpha = index.plus(1) * 0.3F) + ) + .size(size = size.div(12)) + ) +} + +private fun getKalendarDayState(selectedDate: LocalDate, currentDay: LocalDate) = + when (selectedDate) { + currentDay -> KalendarDayState.KalendarDaySelected + else -> KalendarDayState.KalendarDayDefault + } + +private fun getBorder(isCurrentDay: Boolean) = + BorderStroke( + width = if (isCurrentDay) 1.dp else 0.dp, + color = if (isCurrentDay) Color.Black else Color.Transparent, + ) + +private fun getTextWeight(kalendarDayState: KalendarDayState) = + if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + FontWeight.Bold + } else { + FontWeight.SemiBold + } + +private fun getBackgroundColor( + kalendarDayState: KalendarDayState, + backgroundColor: Color, +) = if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + backgroundColor +} else { + Color.Transparent +} + +private fun getTextSelectionShape( + kalendarDayState: KalendarDayState, +) = if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + CircleShape +} else { + RectangleShape +} + +private fun getTextColor( + kalendarDayState: KalendarDayState, + kalendarDayColors: KalendarDayColors, +): Color = if (kalendarDayState is KalendarDayState.KalendarDaySelected) { + kalendarDayColors.selectedTextColor +} else { + kalendarDayColors.textColor +} + +@Preview +@Composable +private fun KalendarDayPreview() { +// KalendarDay( +// kalendarDay = KalendarDay(localDate = Clock.System.todayIn(TimeZone.currentSystemDefault())), +// kalendarDayConfig = KalendarDayDefaults.kalendarDayConfig(), +// selectedKalendarDate = mutableStateOf() +// ) +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/day/config/KalendarDayColors.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/day/config/KalendarDayColors.kt new file mode 100644 index 0000000..1d5c1ff --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/day/config/KalendarDayColors.kt @@ -0,0 +1,13 @@ +package com.himanshoe.kalendar.component.day.config + +import androidx.compose.ui.graphics.Color + +data class KalendarDayColors( + val textColor: Color, // Default Text Color + val selectedTextColor: Color, // Selected Text Color +) + +object KalendarDayDefaultColors { + + fun defaultColors() = KalendarDayColors(Color.Black, Color.White) +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/day/config/KalendarDayState.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/day/config/KalendarDayState.kt new file mode 100644 index 0000000..bb2a13a --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/day/config/KalendarDayState.kt @@ -0,0 +1,6 @@ +package com.himanshoe.kalendar.component.day.config + +sealed interface KalendarDayState { + object KalendarDaySelected : KalendarDayState + object KalendarDayDefault : KalendarDayState +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarNormalText.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarNormalText.kt new file mode 100644 index 0000000..011852a --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarNormalText.kt @@ -0,0 +1,43 @@ +package com.himanshoe.kalendar.component.text + +import androidx.compose.material.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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +import com.himanshoe.kalendar.component.text.config.KalendarTextSize + +@Composable +fun KalendarNormalText( + text: String, + modifier: Modifier = Modifier, + fontWeight: FontWeight, + color: Color, + textSize: TextUnit = KalendarTextSize.Normal.size + +) { + Text( + modifier = modifier, + color = color, + fontSize = textSize, + text = text, + fontWeight = fontWeight, + textAlign = TextAlign.Center + ) +} + +@Preview +@Composable +private fun KalendarNormalTextPreview() { + KalendarNormalText( + text = "Hye Himanshu", + modifier = Modifier, + fontWeight = FontWeight.SemiBold, + color = Color.Black, + textSize = 26.sp + ) +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarSubTitle.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarSubTitle.kt new file mode 100644 index 0000000..88d01be --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarSubTitle.kt @@ -0,0 +1,34 @@ +package com.himanshoe.kalendar.component.text + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.himanshoe.kalendar.component.text.config.KalendarTextConfig +import com.himanshoe.kalendar.component.text.config.KalendarTextDefaults + +@Composable +fun KalendarSubTitle( + text: String, + modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Start, + fontWeight: FontWeight = FontWeight.SemiBold, + kalendarTextConfig: KalendarTextConfig = KalendarTextDefaults.kalendarSubTitleTextConfig() +) { + Text( + modifier = modifier, + color = kalendarTextConfig.kalendarTextColor.textColor, + fontSize = kalendarTextConfig.kalendarTextSize.size, + text = text, + fontWeight = fontWeight, + textAlign = textAlign + ) +} + +@Preview +@Composable +private fun KalendarSubTitlePreview() { + KalendarSubTitle(modifier = Modifier, text = "Hye Himanshu") +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarTitle.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarTitle.kt new file mode 100644 index 0000000..2843fb5 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/KalendarTitle.kt @@ -0,0 +1,34 @@ +package com.himanshoe.kalendar.component.text + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.himanshoe.kalendar.component.text.config.KalendarTextConfig +import com.himanshoe.kalendar.component.text.config.KalendarTextDefaults + +@Composable +fun KalendarTitle( + text: String, + modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Start, + fontWeight: FontWeight = FontWeight.SemiBold, + kalendarTextConfig: KalendarTextConfig = KalendarTextDefaults.kalendarTitleTextConfig() +) { + Text( + modifier = modifier, + color = kalendarTextConfig.kalendarTextColor.textColor, + fontSize = kalendarTextConfig.kalendarTextSize.size, + text = text, + fontWeight = fontWeight, + textAlign = textAlign + ) +} + +@Preview +@Composable +private fun KalendarTitlePreview() { + KalendarTitle(modifier = Modifier, text = "Hye Himanshu") +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextColor.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextColor.kt new file mode 100644 index 0000000..628daf9 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextColor.kt @@ -0,0 +1,19 @@ +package com.himanshoe.kalendar.component.text.config + +import androidx.compose.ui.graphics.Color + +private val TitleTextColor = Color(0xFFD2827A) + +data class KalendarTextColor( + val textColor: Color +) + +internal object KalendarTextColorDefaults { + + fun kalendarTitleTextColor() = KalendarTextColor( + textColor = TitleTextColor + ) + fun kalendarNormalTextColor() = KalendarTextColor( + textColor = TitleTextColor + ) +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextConfig.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextConfig.kt new file mode 100644 index 0000000..83e8153 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextConfig.kt @@ -0,0 +1,27 @@ +package com.himanshoe.kalendar.component.text.config + +data class KalendarTextConfig( + val kalendarTextColor: KalendarTextColor = KalendarTextColorDefaults.kalendarTitleTextColor(), + val kalendarTextSize: KalendarTextSize = KalendarTextSize.Title +) + +internal object KalendarTextDefaults { + + fun kalendarTitleTextConfig() = + KalendarTextConfig( + kalendarTextColor = KalendarTextColorDefaults.kalendarTitleTextColor(), + kalendarTextSize = KalendarTextSize.Title + ) + + fun kalendarSubTitleTextConfig() = + KalendarTextConfig( + kalendarTextColor = KalendarTextColorDefaults.kalendarTitleTextColor(), + kalendarTextSize = KalendarTextSize.SubTitle + ) + + fun kalendarNormalTextConfig() = + KalendarTextConfig( + kalendarTextColor = KalendarTextColorDefaults.kalendarNormalTextColor(), + kalendarTextSize = KalendarTextSize.Normal + ) +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextSize.kt b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextSize.kt new file mode 100644 index 0000000..717203d --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/component/text/config/KalendarTextSize.kt @@ -0,0 +1,11 @@ +package com.himanshoe.kalendar.component.text.config + +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + +sealed class KalendarTextSize(val size: TextUnit) { + object Title : KalendarTextSize(32.sp) + object SubTitle : KalendarTextSize(24.sp) + object Normal : KalendarTextSize(16.sp) + data class Custom(val textUnit: TextUnit) : KalendarTextSize(textUnit) +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarDay.kt b/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarDay.kt new file mode 100644 index 0000000..94f1550 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarDay.kt @@ -0,0 +1,8 @@ +package com.himanshoe.kalendar.model + +import kotlinx.datetime.LocalDate + +@JvmInline +value class KalendarDay(val localDate: LocalDate) + +fun LocalDate.toKalendarDay() = KalendarDay(this) diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarEvent.kt b/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarEvent.kt new file mode 100644 index 0000000..b371481 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarEvent.kt @@ -0,0 +1,9 @@ +package com.himanshoe.kalendar.model + +import kotlinx.datetime.LocalDate + +data class KalendarEvent( + val date: LocalDate, + val eventName: String, + val eventDescription: String? = null, +) diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarType.kt b/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarType.kt new file mode 100644 index 0000000..50c05fb --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/model/KalendarType.kt @@ -0,0 +1,6 @@ +package com.himanshoe.kalendar.model + +sealed interface KalendarType { + object Firey : KalendarType + object Oceanic : KalendarType +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/ui/firey/KalendarFirey.kt b/kalendar/src/main/java/com/himanshoe/kalendar/ui/firey/KalendarFirey.kt new file mode 100644 index 0000000..adfec66 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/ui/firey/KalendarFirey.kt @@ -0,0 +1,198 @@ +package com.himanshoe.kalendar.ui.firey + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.himanshoe.kalendar.color.KalendarThemeColor +import com.himanshoe.kalendar.component.KalendarHeader +import com.himanshoe.kalendar.component.day.KalendarDay +import com.himanshoe.kalendar.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.component.text.KalendarNormalText +import com.himanshoe.kalendar.model.KalendarDay +import com.himanshoe.kalendar.model.KalendarEvent +import com.himanshoe.kalendar.model.toKalendarDay +import kotlinx.datetime.Clock +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.Month +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDate +import kotlinx.datetime.todayIn + +val WeekDays = listOf("M", "T", "W", "T", "F", "S", "S") + +@Composable +fun KalendarFirey( + modifier: Modifier = Modifier, + kalendarEvents: List = emptyList(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + takeMeToDate: LocalDate?, + kalendarDayColors: KalendarDayColors, + kalendarThemeColors: List +) { + val currentDay = takeMeToDate ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) + val displayedMonth = remember { + mutableStateOf(currentDay.month) + } + val (currentMonth, currentYear) = displayedMonth.value to currentDay.year + val daysInMonth = currentMonth.minLength() + val year = currentDay.year + val monthValue = if (currentMonth.value.toString().length == 1) "0" + currentMonth.value.toString() else currentMonth.value.toString() + val startDayOfMonth = "${currentDay.year}-$monthValue-01".toLocalDate() + val firstDayOfMonth = startDayOfMonth.dayOfWeek + val selectedKalendarDate = remember { mutableStateOf(currentDay) } + + Column( + modifier = modifier + .background( + color = kalendarThemeColors[currentMonth.value.minus(1)].backgroundColor + ) + .wrapContentHeight() + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp) + ) { + KalendarHeader( + modifier = Modifier.padding(vertical = 12.dp), + month = displayedMonth.value, + onPreviousClick = { + displayedMonth.value = displayedMonth.value.minus(1) + }, + onNextClick = { + displayedMonth.value = displayedMonth.value.plus(1) + }, + year = year, + textColor = kalendarThemeColors[currentMonth.value.minus(1)].headerTextColor, + ) + LazyVerticalGrid( + modifier = Modifier.fillMaxWidth(), + columns = GridCells.Fixed(7), + content = { + items(WeekDays) { + KalendarNormalText( + text = it, + fontWeight = FontWeight.Normal, + color = kalendarDayColors.textColor, + ) + } + items((getInitialDayOfMonth(firstDayOfMonth)..daysInMonth).toList()) { + if (it > 0) { + val day = getGeneratedDay(it, currentMonth, currentYear) + val isCurrentDay = day == currentDay + KalendarDay( + kalendarDay = day.toKalendarDay(), + modifier = Modifier, + kalendarEvents = kalendarEvents, + isCurrentDay = isCurrentDay, + onCurrentDayClick = { kalendarDay, events -> + selectedKalendarDate.value = kalendarDay.localDate + onCurrentDayClick(kalendarDay, events) + }, + selectedKalendarDay = selectedKalendarDate.value, + kalendarDayColors = kalendarDayColors, + dotColor = kalendarThemeColors[currentMonth.value.minus(1)].headerTextColor, + dayBackgroundColor = kalendarThemeColors[currentMonth.value.minus(1)].dayBackgroundColor, + ) + } + } + } + ) + } +} + +@Composable +fun KalendarFirey( + modifier: Modifier = Modifier, + kalendarEvents: List = emptyList(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + takeMeToDate: LocalDate?, + kalendarDayColors: KalendarDayColors, + kalendarThemeColor: KalendarThemeColor +) { + val currentDay = takeMeToDate ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) + val displayedMonth = remember { + mutableStateOf(currentDay.month) + } + val (currentMonth, currentYear) = displayedMonth.value to currentDay.year + val daysInMonth = currentMonth.minLength() + val year = currentDay.year + val monthValue = if (currentMonth.value.toString().length == 1) "0" + currentMonth.value.toString() else currentMonth.value.toString() + val startDayOfMonth = "${currentDay.year}-$monthValue-01".toLocalDate() + val firstDayOfMonth = startDayOfMonth.dayOfWeek + val selectedKalendarDate = remember { mutableStateOf(currentDay) } + + Column( + modifier = modifier + .background( + color = kalendarThemeColor.backgroundColor + ) + .wrapContentHeight() + .fillMaxWidth() + .padding(vertical = 16.dp, horizontal = 8.dp) + ) { + KalendarHeader( + modifier = Modifier.padding(vertical = 12.dp), + month = displayedMonth.value, + onPreviousClick = { + displayedMonth.value = displayedMonth.value.minus(1) + }, + onNextClick = { + displayedMonth.value = displayedMonth.value.plus(1) + }, + year = year, + textColor = kalendarThemeColor.headerTextColor, + ) + LazyVerticalGrid( + modifier = Modifier.fillMaxWidth(), + columns = GridCells.Fixed(7), + content = { + items(WeekDays) { + KalendarNormalText( + text = it, + fontWeight = FontWeight.Normal, + color = kalendarDayColors.textColor, + ) + } + items((getInitialDayOfMonth(firstDayOfMonth)..daysInMonth).toList()) { + if (it > 0) { + val day = getGeneratedDay(it, currentMonth, currentYear) + val isCurrentDay = day == currentDay + KalendarDay( + kalendarDay = day.toKalendarDay(), + modifier = Modifier, + kalendarEvents = kalendarEvents, + isCurrentDay = isCurrentDay, + onCurrentDayClick = { kalendarDay, events -> + selectedKalendarDate.value = kalendarDay.localDate + onCurrentDayClick(kalendarDay, events) + }, + selectedKalendarDay = selectedKalendarDate.value, + kalendarDayColors = kalendarDayColors, + dotColor = kalendarThemeColor.headerTextColor, + dayBackgroundColor = kalendarThemeColor.dayBackgroundColor, + ) + } + } + } + ) + } +} + +private fun getInitialDayOfMonth(firstDayOfMonth: DayOfWeek) = -(firstDayOfMonth.value).minus(2) + +private fun getGeneratedDay(day: Int, currentMonth: Month, currentYear: Int): LocalDate { + val monthValue = + if (currentMonth.value.toString().length == 1) "0${currentMonth.value}" else currentMonth.value.toString() + val newDay = if (day.toString().length == 1) "0$day" else day + return "$currentYear-$monthValue-$newDay".toLocalDate() +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/ui/oceanic/KalendarOceanic.kt b/kalendar/src/main/java/com/himanshoe/kalendar/ui/oceanic/KalendarOceanic.kt new file mode 100644 index 0000000..28d33d3 --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/ui/oceanic/KalendarOceanic.kt @@ -0,0 +1,147 @@ +package com.himanshoe.kalendar.ui.oceanic + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.himanshoe.kalendar.color.KalendarThemeColor +import com.himanshoe.kalendar.component.KalendarHeader +import com.himanshoe.kalendar.component.day.KalendarDay +import com.himanshoe.kalendar.component.day.config.KalendarDayColors +import com.himanshoe.kalendar.model.KalendarDay +import com.himanshoe.kalendar.model.KalendarEvent +import com.himanshoe.kalendar.model.toKalendarDay +import com.himanshoe.kalendar.ui.oceanic.data.getNext7Dates +import com.himanshoe.kalendar.ui.oceanic.data.getPrevious7Dates +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn + +@Composable +fun KalendarOceanic( + modifier: Modifier = Modifier, + kalendarEvents: List = emptyList(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + takeMeToDate: LocalDate?, + kalendarDayColors: KalendarDayColors, + kalendarThemeColors: List, +) { + val currentDay = takeMeToDate ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) + val weekValue = remember { mutableStateOf(currentDay.getNext7Dates()) } + val month = weekValue.value.first().month + val year = weekValue.value.first().year + val selectedKalendarDate = remember { mutableStateOf(currentDay) } + + Column( + modifier = modifier + .background(color = kalendarThemeColors[month.value.minus(1)].backgroundColor) + .wrapContentHeight() + .fillMaxWidth() + .padding(all = 8.dp) + ) { + KalendarHeader( + modifier = Modifier.fillMaxWidth(), + month = month, + year = year, + onPreviousClick = { + val firstDayOfDisplayedWeek = weekValue.value.first() + weekValue.value = firstDayOfDisplayedWeek.getPrevious7Dates() + }, + onNextClick = { + val lastDayOfDisplayedWeek = weekValue.value.last().plus(1, DateTimeUnit.DAY) + weekValue.value = lastDayOfDisplayedWeek.getNext7Dates() + }, + textColor = kalendarThemeColors[month.value.minus(1)].headerTextColor, + ) + Row(modifier = Modifier.wrapContentWidth()) { + weekValue.value.forEach { localDate -> + val isCurrentDay = localDate == currentDay + + KalendarDay( + modifier = Modifier, + isCurrentDay = isCurrentDay, + kalendarDay = localDate.toKalendarDay(), + kalendarEvents = kalendarEvents, + onCurrentDayClick = { kalendarDay, events -> + selectedKalendarDate.value = kalendarDay.localDate + onCurrentDayClick(kalendarDay, events) + }, + kalendarDayColors = kalendarDayColors, + selectedKalendarDay = selectedKalendarDate.value, + dotColor = kalendarThemeColors[month.value.minus(1)].headerTextColor, + dayBackgroundColor = kalendarThemeColors[month.value.minus(1)].dayBackgroundColor + ) + } + } + } +} + +@Composable +fun KalendarOceanic( + modifier: Modifier = Modifier, + kalendarEvents: List = emptyList(), + onCurrentDayClick: (KalendarDay, List) -> Unit = { _, _ -> }, + takeMeToDate: LocalDate?, + kalendarDayColors: KalendarDayColors, + kalendarThemeColor: KalendarThemeColor, +) { + val currentDay = takeMeToDate ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) + val weekValue = remember { mutableStateOf(currentDay.getNext7Dates()) } + val month = weekValue.value.first().month + val year = weekValue.value.first().year + val selectedKalendarDate = remember { mutableStateOf(currentDay) } + + Column( + modifier = modifier + .background(color = kalendarThemeColor.backgroundColor) + .wrapContentHeight() + .fillMaxWidth() + .padding(all = 8.dp) + ) { + KalendarHeader( + modifier = Modifier.fillMaxWidth(), + month = month, + year = year, + onPreviousClick = { + val firstDayOfDisplayedWeek = weekValue.value.first() + weekValue.value = firstDayOfDisplayedWeek.getPrevious7Dates() + }, + onNextClick = { + val lastDayOfDisplayedWeek = weekValue.value.last().plus(1, DateTimeUnit.DAY) + weekValue.value = lastDayOfDisplayedWeek.getNext7Dates() + }, + textColor = kalendarThemeColor.headerTextColor, + ) + Row(modifier = Modifier.wrapContentWidth()) { + weekValue.value.forEach { localDate -> + val isCurrentDay = localDate == currentDay + + KalendarDay( + kalendarDay = localDate.toKalendarDay(), + modifier = Modifier, + kalendarEvents = kalendarEvents, + isCurrentDay = isCurrentDay, + onCurrentDayClick = { kalendarDay, events -> + selectedKalendarDate.value = kalendarDay.localDate + onCurrentDayClick(kalendarDay, events) + }, + selectedKalendarDay = selectedKalendarDate.value, + kalendarDayColors = kalendarDayColors, + dotColor = kalendarThemeColor.headerTextColor, + dayBackgroundColor = kalendarThemeColor.dayBackgroundColor, + ) + } + } + } +} diff --git a/kalendar/src/main/java/com/himanshoe/kalendar/ui/oceanic/data/WeekDates.kt b/kalendar/src/main/java/com/himanshoe/kalendar/ui/oceanic/data/WeekDates.kt new file mode 100644 index 0000000..25af7eb --- /dev/null +++ b/kalendar/src/main/java/com/himanshoe/kalendar/ui/oceanic/data/WeekDates.kt @@ -0,0 +1,22 @@ +package com.himanshoe.kalendar.ui.oceanic.data + +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.minus +import kotlinx.datetime.plus + +internal fun LocalDate.getNext7Dates(): List { + val dates = mutableListOf() + repeat(7) { day -> + dates.add(this.plus(day, DateTimeUnit.DAY)) + } + return dates +} + +internal fun LocalDate.getPrevious7Dates(): List { + val dates = mutableListOf() + repeat(7) { day -> + dates.add(this.minus(day.plus(1), DateTimeUnit.DAY)) + } + return dates.reversed() +} diff --git a/kalendar/src/test/java/com/himanshoe/kalendar/ExampleUnitTest.kt b/kalendar/src/test/java/com/himanshoe/kalendar/ExampleUnitTest.kt new file mode 100644 index 0000000..b649d9c --- /dev/null +++ b/kalendar/src/test/java/com/himanshoe/kalendar/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.himanshoe.kalendar + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c5e26cb --- /dev/null +++ b/settings.gradle @@ -0,0 +1,12 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() // Warning: this repository is going to shut down soon + } +} +rootProject.name = "sample-app" +include ':app' +include ':kalendar' +include ':kalendar-endlos'