├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── ci-gradle.properties └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENCE.md ├── README.md ├── app ├── .gitignore ├── android │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── vanpra │ │ │ └── composematerialdialogdemos │ │ │ └── android │ │ │ └── MainActivity.kt │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── tick.png │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml ├── common │ ├── build.gradle.kts │ └── src │ │ └── commonMain │ │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogdemos │ │ ├── Strings.kt │ │ ├── Util.kt │ │ ├── demos │ │ ├── BasicDialog.kt │ │ ├── ColorDialog.kt │ │ ├── DateTimeDialog.kt │ │ └── ListDialog.kt │ │ └── ui │ │ ├── Color.kt │ │ ├── Shape.kt │ │ ├── Theme.kt │ │ └── Type.kt ├── desktop │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogdemos │ │ └── desktop │ │ ├── Main.kt │ │ └── demos │ │ ├── BasicDialog.kt │ │ ├── ColorDialog.kt │ │ ├── DateTimeDialog.kt │ │ └── ListDialog.kt ├── ios │ ├── build.gradle.kts │ └── src │ │ └── uikitMain │ │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── ios │ │ ├── Main.kt │ │ └── demos │ │ ├── BasicDialog.kt │ │ ├── ColorDialog.kt │ │ ├── DateTimeDialog.kt │ │ └── ListDialog.kt └── iosApp │ ├── Configuration │ └── Config.xcconfig │ ├── iosApp.xcodeproj │ └── project.pbxproj │ └── iosApp │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── app-icon-1024.png │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── iOSApp.swift ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── CommonModulePlugin.kt │ ├── Dependencies.kt │ └── update_deps.py ├── color ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties ├── proguard-rules.pro ├── screenshots │ └── debug │ │ ├── com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_argbColorPicker.png │ │ ├── com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_mainColorPicker.png │ │ ├── com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_mainColorPickerWithInitialSelection.png │ │ ├── com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_rgbColorPicker.png │ │ └── com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_subColorPicker.png └── src │ ├── androidTest │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vanpra │ │ │ └── composematerialdialogs │ │ │ └── color │ │ │ └── test │ │ │ ├── functional │ │ │ └── ColorPickerDialogTests.kt │ │ │ └── screenshot │ │ │ └── ColorPickerDialogTests.kt │ └── res │ │ └── values │ │ └── strings.xml │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── color │ │ ├── ColorPalette.kt │ │ ├── ColorPicker.kt │ │ └── Util.kt │ ├── iosMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── color │ │ └── IosUtil.kt │ └── jvmCommonMain │ └── kotlin │ └── com │ └── vanpra │ └── composematerialdialogs │ └── color │ └── JvmUtil.kt ├── core ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties ├── proguard-rules.pro ├── screenshots │ └── debug │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithButtons.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithButtonsAndIconTitle.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithInput.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithStackedButtons.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithoutButtons.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.InputDialogTest_dialogWithFilledInput.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.InputDialogTest_dialogWithOutlinedInput.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.ListDialog_customListSelectionDialog.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.ListDialog_multiSelectionDialog.png │ │ ├── com.vanpra.composematerialdialogs.test.screenshot.ListDialog_simpleListSelectionDialog.png │ │ └── com.vanpra.composematerialdialogs.test.screenshot.ListDialog_singleSelectionDialog.png └── src │ ├── androidMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── AndroidUtils.kt │ ├── androidTest │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vanpra │ │ │ └── composematerialdialogs │ │ │ └── test │ │ │ ├── functional │ │ │ ├── DialogButtonsTest.kt │ │ │ ├── InputDialogTest.kt │ │ │ └── ListDialogTest.kt │ │ │ └── screenshot │ │ │ ├── BasicDialogTest.kt │ │ │ ├── InputDialogTest.kt │ │ │ └── ListDialogTest.kt │ └── res │ │ └── values │ │ └── strings.xml │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ ├── MaterialDialog.kt │ │ ├── MaterialDialogButtons.kt │ │ ├── MaterialDialogCore.kt │ │ ├── MaterialDialogInput.kt │ │ ├── MaterialDialogLists.kt │ │ └── Utils.kt │ ├── iosMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── IosUtils.kt │ ├── jvmCommonMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── JvmUtils.kt │ └── jvmMain │ └── kotlin │ └── com │ └── vanpra │ └── composematerialdialogs │ └── DesktopUtils.kt ├── datetime ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── gradle.properties ├── proguard-rules.pro ├── screenshots │ └── debug │ │ ├── com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerBasic.png │ │ ├── com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerWithCustomTitle.png │ │ ├── com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerWithRestrictedDates.png │ │ ├── com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePicker24Hour.png │ │ ├── com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePickerBasic.png │ │ └── com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePickerWithCustomTitle.png └── src │ ├── androidMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── datetime │ │ └── util │ │ └── AndroidUtils.kt │ ├── androidTest │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vanpra │ │ │ └── composematerialdialogs │ │ │ └── datetime │ │ │ └── test │ │ │ ├── functional │ │ │ ├── DatePickerTest.kt │ │ │ └── TimePickerTest.kt │ │ │ └── screenshot │ │ │ └── DateTimePickerTest.kt │ └── res │ │ └── values │ │ └── strings.xml │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── datetime │ │ ├── date │ │ ├── DatePicker.kt │ │ ├── DatePickerColors.kt │ │ ├── DatePickerDefaults.kt │ │ └── DatePickerState.kt │ │ ├── time │ │ ├── TimePicker.kt │ │ ├── TimePickerColors.kt │ │ ├── TimePickerDefaults.kt │ │ └── TimePickerState.kt │ │ └── util │ │ ├── Composables.kt │ │ ├── Extensions.kt │ │ ├── Utils.kt │ │ └── WeekFields.kt │ ├── iosMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── datetime │ │ └── util │ │ ├── IosUIUtils.kt │ │ ├── IosUtils.kt │ │ └── IosWeekFields.kt │ ├── jvmCommonMain │ └── kotlin │ │ └── com │ │ └── vanpra │ │ └── composematerialdialogs │ │ └── datetime │ │ └── util │ │ ├── JvmExtensions.kt │ │ └── JvmWeekFields.kt │ └── jvmMain │ └── kotlin │ └── com │ └── vanpra │ └── composematerialdialogs │ └── datetime │ └── util │ └── DesktopUtils.kt ├── docs ├── ColorPicker.md ├── Core.md ├── DateTimePicker.md ├── PULL_REQUEST_TEMPLATE.md └── index.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imgs ├── basic_core.png ├── basic_list.jpg ├── color_picker.png ├── date.png ├── date_and_time.png ├── full_core.png ├── input.jpg ├── multi_selection.png ├── single_selection.png └── time.png ├── mkdocs.yml ├── release ├── secring.gpg.aes ├── signing-cleanup.sh ├── signing-setup.sh └── signing.properties.aes ├── renovate.json ├── settings.gradle.kts └── test-utils ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src └── main └── java └── com └── vanpra └── composematerialdialogs └── test └── utils ├── DialogTestUtil.kt ├── ScreenshotTestFilter.kt └── extensions ├── ColorComposeTestRule.kt ├── CoreComposeTestRule.kt └── DatetimeComposeTestRule.kt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: vanpra 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behaviour (including any applicable code): 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behaviour** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Please complete the following information about your device:** 27 | - Name: [e.g. Pixel 6 Pro] 28 | - Android Version: [e.g. Android 12] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Turn Gradle daemon off due to https://github.com/Kotlin/dokka/issues/1405 18 | org.gradle.daemon=false 19 | 20 | org.gradle.parallel=true 21 | org.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryError 22 | org.gradle.workers.max=2 23 | 24 | kotlin.compiler.execution.strategy=in-process 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | build 7 | /buildSrc/build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | secret.properties 13 | debug.keystore 14 | /docs/api 15 | .kotlin 16 | 17 | # Do not commit plain-text signing info 18 | release/*.properties 19 | release/*.gpg -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Compose Material Dialogs 2 | 3 | :confetti_ball: Firstly, thanks for taking time to contribute to the library!​ :confetti_ball: 4 | 5 | Below are all the details required to get started with the code base (including structure, testing and styling) 6 | 7 | ## Getting Started 8 | 9 | ### Codebase 10 | 11 | The code base is split up into 3 main modules: `core` , `color` and `datetime`. The core module contains the majority of the code to handle the creation of the base dialog and provides the basic dialog components such a titles, text and inputs. This module also handles the creation of buttons and their layout. The other 2 modules are quite self explanatory with the `color` module providing a color selection dialog and the `datetime` module providing separate date and time dialogs. 12 | 13 | ### Styleguides 14 | 15 | #### Git Commit Messages 16 | 17 | The following are just suggestions but would be good to follow to keep the commit history consistent and readable: 18 | 19 | - Use present tense ("Fix bug" not "Fixed bug") 20 | - Use imperative mood ("Change variable name" not "Changes variable name") 21 | - Limit the first line to 75 characters or less 22 | - Make the commit message specific ("Refactor MaterialDialog class methods" not "Refactor classes") 23 | 24 | #### Kotlin Styleguide 25 | 26 | All the code is linted using Ktlint through the [spotless](https://github.com/diffplug/spotless) gradle plugin. In order to check if your code matches Ktlint's styling you can use the command: 27 | 28 | ```bash 29 | ./gradlew spotlessCheck 30 | ``` 31 | 32 | You can also fix the styling of your code using the following command: 33 | 34 | ``` 35 | ./gradlew spotlessApply 36 | ``` 37 | 38 | NOTE: The `spotlessApply` task does not fix wildcard imports which might be automatically formatted by Android Studio. You can avoid this by 39 | 40 | changing Android Studio's import settings in `Editor -> Code Style -> Kotlin -> Imports tab` and then change it to `Use single name imports`. 41 | 42 | ### Testing 43 | 44 | The project consists of 3 types of tests: 45 | 46 | - Unit tests - Used for any function which can be tested independently of UI 47 | - Functionality tests - Used to test functionality of UI 48 | - Screenshot tests - Used to test the appearance of UI 49 | 50 | The first 2 of these tests can be run on any type of device however the screenshot tests require you to use an emulator with a specific device and version so that they are consistent and can be compared. 51 | 52 | You will need to create a new AVD with the following properties, this can be done either through Android Studio or command line: 53 | 54 | ``` 55 | Device: Nexus 6P 56 | API Level: 28 57 | ``` 58 | 59 | To ensure consistant reporducable screenshot testing the emulator has to be started using the command line with some additional parameters (repace [AVD Name] with the one you created in the previous step): 60 | 61 | ``` 62 | emulator -avd [AVD NAME] -no-window -gpu swiftshader_indirect 63 | ``` 64 | 65 | To run the Unit and Functionality tests locally you can run the following command: 66 | 67 | ``` 68 | ./gradlew connectedCheck -Pandroid.testInstrumentationRunnerArguments.filter=com.vanpra.composematerialdialogs.test.utils.NotScreenshotTestFilter 69 | ``` 70 | 71 | To run the screenshot tests you can use: 72 | 73 | ``` 74 | ./gradlew executeScreenshotTests -Pandroid.testInstrumentationRunnerArguments.filter=com.vanpra.composematerialdialogs.test.utils.ScreenshotTestFilter 75 | ``` 76 | 77 | You can then view the screenshot test report at in the directory `[module]/build/reports/shot/verification/index.htm` 78 | 79 | If you have added a new screenshot test or changed the UI such that it affects an existing screenshot test you will have to recapture the screenshots using an emulator. Before doing this run the screenshot tests to verify that all the tests which are not affected by the change are still passing. After doing so you can run the following command which will capture on the emulator which should be setup as mentioned above: 80 | 81 | ``` 82 | ./gradlew executeScreenshotTests -Precord -Pandroid.testInstrumentationRunnerArguments.filter=com.vanpra.composematerialdialogs.test.utils.ScreenshotTestFilter 83 | ``` 84 | 85 | ## Pull Requests 86 | 87 | All PR's will have to pass the CI pipeline before they can be reviewed. The CI consists of a `lint and build` stage which makes sure the styling complies with Ktlint and the code compiles. After this the CI runs the `testing ` stage which will run the screenshot and functional tests mentioned above. 88 | 89 | When creating a pull request you will be given a template you can fill out and once the CI is passing someone will review your changes. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compose Material Dialogs 2 | 3 | :rocket: Easy to use library to help you build complex dialogs using Compose Multiplatform :rocket: 4 | 5 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ae8d455118164f43a24732761a970cc8)](https://www.codacy.com/gh/vanpra/compose-material-dialogs/dashboard?utm_source=github.com&utm_medium=referral&utm_content=vanpra/compose-material-dialogs&utm_campaign=Badge_Grade)![Build & Test](https://github.com/vanpra/compose-material-dialogs/actions/workflows/main.yml/badge.svg) 6 | 7 | **Current Library Compose Multiplatform Version: 1.7.0** 8 | 9 | ### [See Releases and Changelog](https://github.com/syer10/compose-material-dialogs/blob/main/CHANGELOG.md) 10 | 11 | ## Core 12 | 13 | #### [Core Documentation](https://vanpra.github.io/compose-material-dialogs/Core) 14 | 15 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/full_core.png) 16 | 17 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ca.gosyer/compose-material-dialogs-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ca.gosyer/compose-material-dialogs-core) 18 | 19 | ```gradle 20 | dependencies { 21 | ... 22 | implementation "ca.gosyer:compose-material-dialogs-core:0.9.6" 23 | ... 24 | } 25 | ``` 26 | 27 | ## Date and Time Picker 28 | 29 | #### [Date and Time Picker Documentation](https://vanpra.github.io/compose-material-dialogs/DateTimePicker) 30 | 31 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/date_and_time.png) 32 | 33 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ca.gosyer/compose-material-dialogs-datetime/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ca.gosyer/compose-material-dialogs-datetime) 34 | 35 | ```gradle 36 | dependencies { 37 | ... 38 | implementation "ca.gosyer:compose-material-dialogs-datetime:0.9.6" 39 | ... 40 | } 41 | ``` 42 | 43 | ## Color Picker 44 | 45 | #### [Color Picker Documentation](https://vanpra.github.io/compose-material-dialogs/ColorPicker) 46 | 47 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/color_picker.png) 48 | 49 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ca.gosyer/compose-material-dialogs-color/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ca.gosyer/compose-material-dialogs-color) 50 | 51 | ```gradle 52 | dependencies { 53 | ... 54 | implementation "ca.gosyer:compose-material-dialogs-color:0.9.6" 55 | ... 56 | } 57 | ``` 58 | 59 | ## For Developers 60 | 61 | If you would like to help by contributing to the library have a look at our [Contribution Guide](https://github.com/vanpra/compose-material-dialogs/blob/main/CONTRIBUTING.md) to get started 62 | 63 | ## Credits 64 | 65 | This library's design is heavily inspired by https://github.com/afollestad/material-dialogs 66 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 3 | 4 | plugins { 5 | id("com.android.application") 6 | kotlin("android") 7 | id("org.jetbrains.compose") 8 | id("org.jetbrains.kotlin.plugin.compose") 9 | } 10 | 11 | android { 12 | namespace = "com.vanpra.composematerialdialogdemos" 13 | 14 | defaultConfig { 15 | applicationId = "com.vanpra.composematerialdialogs.app.android" 16 | 17 | versionName = "1.0" 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | packagingOptions.setExcludes( 22 | setOf( 23 | "META-INF/LICENSE", 24 | "META-INF/AL2.0", 25 | "META-INF/**", 26 | ) 27 | ) 28 | 29 | buildFeatures { 30 | viewBinding = true 31 | compose = true 32 | } 33 | 34 | compileOptions { 35 | isCoreLibraryDesugaringEnabled = true 36 | sourceCompatibility = JavaVersion.VERSION_1_8 37 | targetCompatibility = JavaVersion.VERSION_1_8 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation(projects.app.common) 43 | 44 | implementation(projects.composeMaterialDialogsCore) 45 | implementation(projects.composeMaterialDialogsColor) 46 | implementation(projects.composeMaterialDialogsDatetime) 47 | 48 | // implementation(Dependencies.ComposeMaterialDialogs.core) 49 | // implementation(Dependencies.ComposeMaterialDialogs.datetime) 50 | // implementation(Dependencies.ComposeMaterialDialogs.color) 51 | 52 | implementation(Dependencies.Google.material) 53 | implementation(Dependencies.AndroidX.coreKtx) 54 | 55 | implementation(Dependencies.DateTime.dateTime) 56 | 57 | implementation(compose.ui) 58 | implementation(compose.material) 59 | implementation(compose.materialIconsExtended) 60 | implementation(compose.animation) 61 | implementation(compose.foundation) 62 | 63 | implementation(Dependencies.AndroidX.Compose.activity) 64 | implementation(Dependencies.AndroidX.Compose.navigation) 65 | 66 | coreLibraryDesugaring(Dependencies.desugar) 67 | } 68 | 69 | tasks.withType { 70 | compilerOptions { 71 | jvmTarget = JvmTarget.JVM_1_8 72 | } 73 | } -------------------------------------------------------------------------------- /app/android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/android/src/main/java/com/vanpra/composematerialdialogdemos/android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.android 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.wrapContentSize 11 | import androidx.compose.foundation.lazy.LazyColumn 12 | import androidx.compose.foundation.lazy.items 13 | import androidx.compose.foundation.rememberScrollState 14 | import androidx.compose.foundation.verticalScroll 15 | import androidx.compose.material.MaterialTheme 16 | import androidx.compose.material.Text 17 | import androidx.compose.material.TextButton 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.unit.dp 22 | import androidx.navigation.compose.NavHost 23 | import androidx.navigation.compose.composable 24 | import androidx.navigation.compose.rememberNavController 25 | import com.vanpra.composematerialdialogdemos.demos.BasicDialogDemo 26 | import com.vanpra.composematerialdialogdemos.demos.BasicListDialogDemo 27 | import com.vanpra.composematerialdialogdemos.demos.ColorDialogDemo 28 | import com.vanpra.composematerialdialogdemos.demos.DateTimeDialogDemo 29 | import com.vanpra.composematerialdialogdemos.demos.MultiSelectionDemo 30 | import com.vanpra.composematerialdialogdemos.demos.SingleSelectionDemo 31 | import com.vanpra.composematerialdialogdemos.ui.ComposeMaterialDialogsTheme 32 | 33 | /** 34 | * @brief MainActivity with material dialog samples 35 | */ 36 | class MainActivity : ComponentActivity() { 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | setContent { 40 | ComposeMaterialDialogsTheme { 41 | DialogDemos() 42 | } 43 | } 44 | } 45 | } 46 | 47 | data class DialogSectionData(val title: String, val content: @Composable () -> Unit) 48 | 49 | val sections = listOf( 50 | DialogSectionData("Basic Dialogs") { BasicDialogDemo() }, 51 | DialogSectionData("Basic List Dialogs") { BasicListDialogDemo() }, 52 | DialogSectionData("Single Selection List Dialogs") { SingleSelectionDemo() }, 53 | DialogSectionData("Multi-Selection List Dialogs") { MultiSelectionDemo() }, 54 | DialogSectionData("Date and Time Picker Dialogs") { DateTimeDialogDemo() }, 55 | DialogSectionData("Color Picker Dialogs") { ColorDialogDemo() } 56 | ) 57 | 58 | /** 59 | * @brief Collection of Material Dialog Demos 60 | */ 61 | @Composable 62 | fun DialogDemos() { 63 | val navController = rememberNavController() 64 | 65 | NavHost(navController = navController, "Home") { 66 | composable("Home") { 67 | LazyColumn { 68 | items(sections) { 69 | TextButton( 70 | onClick = { navController.navigate(it.title) }, 71 | modifier = Modifier 72 | .fillMaxWidth() 73 | .padding(8.dp) 74 | .background(MaterialTheme.colors.primaryVariant), 75 | ) { 76 | Text( 77 | it.title, 78 | modifier = Modifier 79 | .fillMaxWidth() 80 | .wrapContentSize(Alignment.Center), 81 | color = MaterialTheme.colors.onPrimary 82 | ) 83 | } 84 | } 85 | } 86 | } 87 | 88 | sections.forEach { dialogSection -> 89 | composable(dialogSection.title) { 90 | Column(Modifier.verticalScroll(rememberScrollState())) { 91 | dialogSection.content() 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/android/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/android/src/main/res/drawable/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/drawable/tick.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | compose-material-dialogs 3 | Let us help apps determine location. This means sending 4 | anonymous location data to us, even when no apps are running 5 | Use Location Services? 6 | Phone Ringtone 7 | Label as: 8 | Set backup account 9 | Enter you name: 10 | -------------------------------------------------------------------------------- /app/android/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | plugins { 5 | kotlin("multiplatform") 6 | id("com.android.library") 7 | id("org.jetbrains.compose") 8 | id("org.jetbrains.kotlin.plugin.compose") 9 | } 10 | 11 | 12 | kotlin { 13 | androidTarget { 14 | publishAllLibraryVariants() 15 | compilations.all { 16 | compileTaskProvider.configure { 17 | compilerOptions { 18 | jvmTarget = JvmTarget.JVM_1_8 19 | } 20 | } 21 | } 22 | } 23 | jvm { 24 | compilations.all { 25 | compileTaskProvider.configure { 26 | compilerOptions { 27 | jvmTarget = JvmTarget.JVM_11 28 | } 29 | } 30 | } 31 | } 32 | iosX64() 33 | iosArm64() 34 | iosSimulatorArm64() 35 | 36 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 37 | applyDefaultHierarchyTemplate { 38 | common { 39 | group("jvmCommon") { 40 | withAndroidTarget() 41 | withJvm() 42 | } 43 | } 44 | } 45 | 46 | sourceSets { 47 | commonMain { 48 | dependencies { 49 | api(kotlin("stdlib-common")) 50 | api(compose.ui) 51 | api(compose.foundation) 52 | api(compose.material) 53 | api(compose.animation) 54 | api(Dependencies.DateTime.dateTime) 55 | api(projects.composeMaterialDialogsCore) 56 | api(projects.composeMaterialDialogsColor) 57 | api(projects.composeMaterialDialogsDatetime) 58 | } 59 | } 60 | commonTest { 61 | dependencies { 62 | implementation(kotlin("test-common")) 63 | implementation(kotlin("test-annotations-common")) 64 | } 65 | } 66 | } 67 | } 68 | 69 | android { 70 | namespace = "com.vanpra.composematerialdialogs.app.common" 71 | } -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/Strings.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos 2 | 3 | object Strings { 4 | const val location_dialog_message = "Let us help apps determine location. This means sending\n" + 5 | " anonymous location data to us, even when no apps are running" 6 | const val location_dialog_title = "Use Location Services?" 7 | const val ringtone_dialog_title = "Phone Ringtone" 8 | const val labels_dialog_title = "Label as:" 9 | const val backup_dialog_title = "Set backup account" 10 | const val input_dialog_title = "Enter you name:" 11 | } -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/Util.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.wrapContentSize 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.material.TextButton 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.DpSize 14 | import androidx.compose.ui.unit.dp 15 | import com.vanpra.composematerialdialogs.MaterialDialog 16 | import com.vanpra.composematerialdialogs.MaterialDialogButtons 17 | import com.vanpra.composematerialdialogs.MaterialDialogProperties 18 | import com.vanpra.composematerialdialogs.MaterialDialogScope 19 | import com.vanpra.composematerialdialogs.rememberMaterialDialogState 20 | 21 | /** 22 | * @brief Builds a dialog and adds button to the layout which shows the dialog on click 23 | */ 24 | @Composable 25 | fun DialogAndShowButton( 26 | buttonText: String, 27 | size: DpSize = DpSize(400.dp, 300.dp), 28 | buttons: @Composable MaterialDialogButtons.() -> Unit = {}, 29 | content: @Composable MaterialDialogScope.() -> Unit 30 | ) { 31 | val dialogState = rememberMaterialDialogState() 32 | 33 | MaterialDialog( 34 | dialogState = dialogState, 35 | buttons = buttons, 36 | properties = MaterialDialogProperties( 37 | windowSize = size, 38 | isWindowDialog = false, 39 | ) 40 | ) { 41 | content() 42 | } 43 | 44 | TextButton( 45 | onClick = { dialogState.show() }, 46 | modifier = Modifier 47 | .fillMaxWidth() 48 | .padding(8.dp) 49 | .background(MaterialTheme.colors.primaryVariant), 50 | ) { 51 | Text( 52 | buttonText, 53 | modifier = Modifier 54 | .fillMaxWidth() 55 | .wrapContentSize(Alignment.Center), 56 | color = MaterialTheme.colors.onPrimary 57 | ) 58 | } 59 | } 60 | 61 | /** 62 | * @brief Add title to top of layout 63 | */ 64 | @Composable 65 | fun DialogSection(title: String, content: @Composable () -> Unit) { 66 | Text( 67 | title, 68 | color = MaterialTheme.colors.onSurface, 69 | style = MaterialTheme.typography.subtitle1, 70 | modifier = Modifier.padding(start = 8.dp, top = 8.dp) 71 | ) 72 | 73 | content() 74 | } 75 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/demos/BasicDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.demos 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.text.KeyboardActions 5 | import androidx.compose.foundation.text.KeyboardOptions 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.LocationOn 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.focus.FocusRequester 12 | import androidx.compose.ui.graphics.ColorFilter 13 | import androidx.compose.ui.text.input.ImeAction 14 | import com.vanpra.composematerialdialogdemos.DialogAndShowButton 15 | import com.vanpra.composematerialdialogdemos.Strings 16 | import com.vanpra.composematerialdialogs.TextFieldStyle 17 | import com.vanpra.composematerialdialogs.iconTitle 18 | import com.vanpra.composematerialdialogs.input 19 | import com.vanpra.composematerialdialogs.message 20 | import com.vanpra.composematerialdialogs.title 21 | 22 | /** 23 | * @brief Basic Dialog Demos 24 | */ 25 | @Composable 26 | fun BasicDialogDemo() { 27 | DialogAndShowButton(buttonText = "Basic Dialog") { 28 | title(text = Strings.location_dialog_title) 29 | message(text = Strings.location_dialog_message) 30 | } 31 | 32 | DialogAndShowButton( 33 | buttonText = "Basic Dialog With Buttons", 34 | buttons = { 35 | negativeButton("Disagree") 36 | positiveButton("Agree") 37 | } 38 | ) { 39 | title(text = Strings.location_dialog_title) 40 | message(text = Strings.location_dialog_message) 41 | } 42 | 43 | DialogAndShowButton( 44 | buttonText = "Basic Dialog With Buttons and Icon Title", 45 | buttons = { 46 | negativeButton("Disagree") 47 | positiveButton("Agree") 48 | } 49 | ) { 50 | iconTitle( 51 | icon = { 52 | Image( 53 | Icons.Default.LocationOn, 54 | contentDescription = "Location Icon", 55 | colorFilter = ColorFilter.tint(MaterialTheme.colors.onBackground) 56 | ) 57 | }, 58 | text = Strings.location_dialog_title 59 | ) 60 | message(text = Strings.location_dialog_message) 61 | } 62 | 63 | DialogAndShowButton( 64 | buttonText = "Basic Dialog With Stacked Buttons", 65 | buttons = { 66 | negativeButton("No Thanks") 67 | positiveButton("Turn On Speed Boost") 68 | } 69 | ) { 70 | title(text = Strings.location_dialog_title) 71 | message(text = Strings.location_dialog_message) 72 | } 73 | 74 | DialogAndShowButton( 75 | buttonText = "Basic Input Dialog", 76 | buttons = { 77 | negativeButton("Cancel") 78 | positiveButton("Ok") 79 | } 80 | ) { 81 | title(text = Strings.input_dialog_title) 82 | input(label = "Name", placeholder = "Jon Smith") { 83 | println("SELECTION:$it") 84 | } 85 | } 86 | 87 | DialogAndShowButton( 88 | buttonText = "Outlined Input Dialog", 89 | buttons = { 90 | negativeButton("Cancel") 91 | positiveButton("Ok") 92 | } 93 | ) { 94 | title(text = Strings.input_dialog_title) 95 | input(label = "Name", placeholder = "Jon Smith", textFieldStyle = TextFieldStyle.Outlined) { 96 | println("SELECTION:$it") 97 | } 98 | } 99 | 100 | DialogAndShowButton( 101 | buttonText = "Basic Input Dialog With Immediate Focus", 102 | buttons = { 103 | negativeButton("Cancel") 104 | positiveButton("Ok") 105 | } 106 | ) { 107 | val focusRequester = remember { FocusRequester() } 108 | title(text = Strings.input_dialog_title) 109 | input( 110 | label = "Name", 111 | placeholder = "Jon Smith", 112 | focusRequester = focusRequester, 113 | focusOnShow = true 114 | ) { 115 | println("SELECTION:$it") 116 | } 117 | } 118 | 119 | DialogAndShowButton( 120 | buttonText = "Input Dialog with submit on IME Action", 121 | buttons = { 122 | negativeButton("Cancel") 123 | positiveButton("Ok") 124 | } 125 | ) { 126 | title(text = Strings.input_dialog_title) 127 | input( 128 | label = "Name", placeholder = "Jon Smith", 129 | keyboardActions = KeyboardActions( 130 | onDone = { submit() } 131 | ), 132 | keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) 133 | ) { 134 | println("SELECTION:$it") 135 | } 136 | } 137 | 138 | DialogAndShowButton( 139 | buttonText = "Input Dialog with input validation", 140 | buttons = { 141 | negativeButton("Cancel") 142 | positiveButton("Ok") 143 | } 144 | ) { 145 | title("Please enter your email") 146 | input( 147 | label = "Email", 148 | placeholder = "hello@example.com", 149 | errorMessage = "Invalid email", 150 | isTextValid = { 151 | it.contains('@') 152 | } 153 | ) { 154 | println("SELECTION:$it") 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/demos/ColorDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.demos 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.layout.width 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Switch 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.runtime.mutableStateOf 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.runtime.setValue 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.unit.dp 18 | import com.vanpra.composematerialdialogdemos.DialogAndShowButton 19 | import com.vanpra.composematerialdialogs.MaterialDialogButtons 20 | import com.vanpra.composematerialdialogs.color.ARGBPickerState 21 | import com.vanpra.composematerialdialogs.color.ColorPalette 22 | import com.vanpra.composematerialdialogs.color.colorChooser 23 | import com.vanpra.composematerialdialogs.title 24 | 25 | /** 26 | * @brief Color Picker Demos 27 | */ 28 | @OptIn(ExperimentalFoundationApi::class) 29 | @Composable 30 | fun ColorDialogDemo() { 31 | var waitForPositiveButton by remember { mutableStateOf(false) } 32 | 33 | Row(Modifier.padding(8.dp)) { 34 | Switch( 35 | checked = waitForPositiveButton, 36 | onCheckedChange = { waitForPositiveButton = it } 37 | ) 38 | 39 | Spacer(Modifier.width(8.dp)) 40 | 41 | Text( 42 | text = "Wait for positive button", 43 | style = MaterialTheme.typography.body1, 44 | color = MaterialTheme.colors.onBackground 45 | ) 46 | } 47 | 48 | DialogAndShowButton( 49 | buttonText = "Color Picker Dialog", 50 | buttons = { defaultColorDialogButtons() } 51 | ) { 52 | title("Select a Color") 53 | colorChooser(colors = ColorPalette.Primary, waitForPositiveButton = waitForPositiveButton) { 54 | println(it) 55 | } 56 | } 57 | 58 | DialogAndShowButton( 59 | buttonText = "Color Picker Dialog With Sub Colors", 60 | buttons = { defaultColorDialogButtons() } 61 | ) { 62 | title("Select a Sub Color") 63 | colorChooser( 64 | colors = ColorPalette.Primary, 65 | subColors = ColorPalette.PrimarySub, 66 | waitForPositiveButton = waitForPositiveButton 67 | ) { 68 | println(it) 69 | } 70 | } 71 | 72 | DialogAndShowButton( 73 | buttonText = "Color Picker Dialog With Initial Selection", 74 | buttons = { defaultColorDialogButtons() } 75 | ) { 76 | title("Select a Sub Color") 77 | colorChooser( 78 | colors = ColorPalette.Primary, 79 | subColors = ColorPalette.PrimarySub, 80 | waitForPositiveButton = waitForPositiveButton, 81 | initialSelection = 5 82 | ) { 83 | println(it) 84 | } 85 | } 86 | 87 | DialogAndShowButton( 88 | buttonText = "Color Picker Dialog With RGB Selector", 89 | buttons = { defaultColorDialogButtons() } 90 | ) { 91 | title("Custom RGB") 92 | colorChooser( 93 | colors = ColorPalette.Primary, 94 | subColors = ColorPalette.PrimarySub, 95 | argbPickerState = ARGBPickerState.WithoutAlphaSelector, 96 | waitForPositiveButton = waitForPositiveButton 97 | ) { 98 | println(it) 99 | } 100 | } 101 | 102 | DialogAndShowButton( 103 | buttonText = "Color Picker Dialog With ARGB Selector", 104 | buttons = { defaultColorDialogButtons() } 105 | ) { 106 | title("Custom ARGB") 107 | colorChooser( 108 | colors = ColorPalette.Primary, 109 | subColors = ColorPalette.PrimarySub, 110 | argbPickerState = ARGBPickerState.WithAlphaSelector, 111 | waitForPositiveButton = waitForPositiveButton 112 | ) { 113 | println(it) 114 | } 115 | } 116 | } 117 | 118 | @Composable 119 | private fun MaterialDialogButtons.defaultColorDialogButtons() { 120 | positiveButton("Select") 121 | negativeButton("Cancel") 122 | } 123 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/demos/DateTimeDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.demos 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.ui.graphics.Color 7 | import com.vanpra.composematerialdialogdemos.DialogAndShowButton 8 | import com.vanpra.composematerialdialogs.MaterialDialogButtons 9 | import com.vanpra.composematerialdialogs.datetime.date.datepicker 10 | import com.vanpra.composematerialdialogs.datetime.time.TimePickerColors 11 | import com.vanpra.composematerialdialogs.datetime.time.TimePickerDefaults 12 | import com.vanpra.composematerialdialogs.datetime.time.timepicker 13 | import kotlinx.datetime.DayOfWeek 14 | import kotlinx.datetime.LocalTime 15 | 16 | /** 17 | * @brief Date and Time Picker Demos 18 | */ 19 | @Composable 20 | fun DateTimeDialogDemo() { 21 | val purple = remember { Color(0xFF3700B3) } 22 | 23 | val colors: TimePickerColors = if (isSystemInDarkTheme()) { 24 | TimePickerDefaults.colors( 25 | activeBackgroundColor = purple.copy(0.3f), 26 | activeTextColor = Color.White, 27 | selectorColor = purple, 28 | inactiveBackgroundColor = Color(0xFF292929), 29 | ) 30 | } else { 31 | TimePickerDefaults.colors( 32 | inactiveBackgroundColor = Color.LightGray, 33 | activeBackgroundColor = purple.copy(0.1f), 34 | activeTextColor = purple, 35 | selectorColor = purple 36 | ) 37 | } 38 | 39 | //val context = LocalContext.current 40 | 41 | DialogAndShowButton( 42 | buttonText = "Time Picker Dialog", 43 | buttons = { defaultDateTimeDialogButtons() } 44 | ) { 45 | timepicker(colors = colors) { 46 | println(it.toString()) 47 | //Toast.makeText(context, it.toString(), Toast.LENGTH_LONG).show() 48 | } 49 | } 50 | 51 | DialogAndShowButton( 52 | buttonText = "Time Picker Dialog With Min/Max", 53 | buttons = { defaultDateTimeDialogButtons() } 54 | ) { 55 | timepicker( 56 | colors = colors, 57 | timeRange = LocalTime(9, 35)..LocalTime(21, 13), 58 | is24HourClock = false 59 | ) { 60 | println(it.toString()) 61 | //Toast.makeText(context, it.toString(), Toast.LENGTH_LONG).show() 62 | } 63 | } 64 | 65 | DialogAndShowButton( 66 | buttonText = "Time Picker Dialog 24H", 67 | buttons = { defaultDateTimeDialogButtons() } 68 | ) { 69 | timepicker(colors = colors, is24HourClock = true) { 70 | println(it.toString()) 71 | //Toast.makeText(context, it.toString(), Toast.LENGTH_LONG).show() 72 | } 73 | } 74 | 75 | DialogAndShowButton( 76 | buttonText = "Time Picker Dialog 24H With Min/Max", 77 | buttons = { defaultDateTimeDialogButtons() } 78 | ) { 79 | timepicker( 80 | colors = colors, 81 | timeRange = LocalTime(9, 35)..LocalTime(21, 13), 82 | is24HourClock = true 83 | ) { 84 | println(it.toString()) 85 | //Toast.makeText(context, it.toString(), Toast.LENGTH_LONG).show() 86 | } 87 | } 88 | 89 | DialogAndShowButton( 90 | buttonText = "Date Picker Dialog", 91 | buttons = { defaultDateTimeDialogButtons() } 92 | ) { 93 | datepicker { 94 | println(it.toString()) 95 | } 96 | } 97 | 98 | DialogAndShowButton( 99 | buttonText = "Date Picker Dialog with date restrictions", 100 | buttons = { defaultDateTimeDialogButtons() } 101 | ) { 102 | datepicker(allowedDateValidator = { 103 | it.dayOfWeek !== DayOfWeek.SATURDAY && it.dayOfWeek !== DayOfWeek.SUNDAY 104 | }) { 105 | println(it.toString()) 106 | } 107 | } 108 | } 109 | 110 | @Composable 111 | private fun MaterialDialogButtons.defaultDateTimeDialogButtons() { 112 | positiveButton("Ok") 113 | negativeButton("Cancel") 114 | } 115 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/ui/Color.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ui 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val purple200 = Color(0xFFBB86FC) 6 | val purple500 = Color(0xFF6200EE) 7 | val purple700 = Color(0xFF3700B3) 8 | 9 | val blue200 = Color(0xFF90CAF9) 10 | val blue500 = Color(0xFF2196F3) 11 | val blue700 = Color(0xFF1976D2) 12 | 13 | val teal200 = Color(0xFF03DAC5) 14 | 15 | val dialogBackground = Color(0xFF252525) 16 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/ui/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ui 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) 12 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ui 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | 10 | private val DarkColorPalette = darkColors( 11 | primary = blue500, 12 | primaryVariant = blue700, 13 | onPrimary = Color.White, 14 | secondary = teal200 15 | ) 16 | 17 | private val LightColorPalette = lightColors( 18 | primary = blue500, 19 | primaryVariant = blue700, 20 | onPrimary = Color.White, 21 | secondary = teal200 22 | ) 23 | 24 | @Composable 25 | fun ComposeMaterialDialogsTheme( 26 | darkTheme: Boolean = isSystemInDarkTheme(), 27 | content: @Composable () -> Unit 28 | ) { 29 | val colors = if (darkTheme) { 30 | DarkColorPalette 31 | } else { 32 | LightColorPalette 33 | } 34 | 35 | MaterialTheme( 36 | colors = colors, 37 | typography = typography, 38 | shapes = shapes, 39 | content = content 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /app/common/src/commonMain/kotlin/com/vanpra/composematerialdialogdemos/ui/Type.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ui 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) 29 | -------------------------------------------------------------------------------- /app/desktop/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | id("org.jetbrains.compose") 4 | id("org.jetbrains.kotlin.plugin.compose") 5 | } 6 | 7 | dependencies { 8 | implementation(compose.desktop.currentOs) 9 | 10 | implementation(projects.app.common) 11 | 12 | implementation(projects.composeMaterialDialogsCore) 13 | implementation(projects.composeMaterialDialogsColor) 14 | implementation(projects.composeMaterialDialogsDatetime) 15 | 16 | /*implementation(Dependencies.ComposeMaterialDialogs.core) 17 | implementation(Dependencies.ComposeMaterialDialogs.datetime) 18 | implementation(Dependencies.ComposeMaterialDialogs.color)*/ 19 | 20 | implementation("cafe.adriel.voyager:voyager-navigator:1.0.1") 21 | 22 | implementation(Dependencies.DateTime.dateTime) 23 | } 24 | 25 | compose.desktop { 26 | application { 27 | mainClass = "com.vanpra.composematerialdialogdemos.desktop.MainKt" 28 | } 29 | } -------------------------------------------------------------------------------- /app/desktop/src/main/kotlin/com/vanpra/composematerialdialogdemos/desktop/Main.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.desktop 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.wrapContentSize 9 | import androidx.compose.foundation.lazy.LazyColumn 10 | import androidx.compose.foundation.lazy.items 11 | import androidx.compose.material.Icon 12 | import androidx.compose.material.IconButton 13 | import androidx.compose.material.MaterialTheme 14 | import androidx.compose.material.Scaffold 15 | import androidx.compose.material.Text 16 | import androidx.compose.material.TextButton 17 | import androidx.compose.material.TopAppBar 18 | import androidx.compose.material.icons.Icons 19 | import androidx.compose.material.icons.filled.Home 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.remember 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.window.singleWindowApplication 26 | import cafe.adriel.voyager.core.screen.Screen 27 | import cafe.adriel.voyager.navigator.CurrentScreen 28 | import cafe.adriel.voyager.navigator.LocalNavigator 29 | import cafe.adriel.voyager.navigator.Navigator 30 | import cafe.adriel.voyager.navigator.currentOrThrow 31 | import com.vanpra.composematerialdialogdemos.desktop.demos.BasicDialogDemoScreen 32 | import com.vanpra.composematerialdialogdemos.desktop.demos.BasicListDialogDemoScreen 33 | import com.vanpra.composematerialdialogdemos.desktop.demos.ColorDialogDemoScreen 34 | import com.vanpra.composematerialdialogdemos.desktop.demos.DateTimeDialogDemoScreen 35 | import com.vanpra.composematerialdialogdemos.desktop.demos.MultiSelectionDemoScreen 36 | import com.vanpra.composematerialdialogdemos.desktop.demos.SingleSelectionDemoScreen 37 | import com.vanpra.composematerialdialogdemos.ui.ComposeMaterialDialogsTheme 38 | 39 | fun main() = singleWindowApplication { 40 | ComposeMaterialDialogsTheme { 41 | DialogDemos() 42 | } 43 | } 44 | 45 | data class DialogSectionData(val title: String, val screen: () -> Screen) 46 | 47 | val sections = listOf( 48 | DialogSectionData("Basic Dialogs") { BasicDialogDemoScreen() }, 49 | DialogSectionData("Basic List Dialogs") { BasicListDialogDemoScreen() }, 50 | DialogSectionData("Single Selection List Dialogs") { SingleSelectionDemoScreen() }, 51 | DialogSectionData("Multi-Selection List Dialogs") { MultiSelectionDemoScreen() }, 52 | DialogSectionData("Date and Time Picker Dialogs") { DateTimeDialogDemoScreen() }, 53 | DialogSectionData("Color Picker Dialogs") { ColorDialogDemoScreen() } 54 | ) 55 | 56 | class HomeScreen : Screen { 57 | @Composable 58 | override fun Content() { 59 | val navigator = LocalNavigator.currentOrThrow 60 | LazyColumn { 61 | items(sections) { 62 | TextButton( 63 | onClick = { navigator push it.screen() }, 64 | modifier = Modifier 65 | .fillMaxWidth() 66 | .padding(8.dp) 67 | .background(MaterialTheme.colors.primaryVariant), 68 | ) { 69 | Text( 70 | it.title, 71 | modifier = Modifier 72 | .fillMaxWidth() 73 | .wrapContentSize(Alignment.Center), 74 | color = MaterialTheme.colors.onPrimary 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * @brief Collection of Material Dialog Demos 84 | */ 85 | @Composable 86 | fun DialogDemos() { 87 | Navigator(remember { HomeScreen() }) { 88 | Scaffold( 89 | topBar = { 90 | TopAppBar( 91 | navigationIcon = { 92 | IconButton(it::popUntilRoot) { 93 | Icon(Icons.Default.Home, null) 94 | } 95 | }, 96 | title = { 97 | Text("Demo") 98 | } 99 | ) 100 | } 101 | ) { 102 | Box(Modifier.padding(it)) { 103 | CurrentScreen() 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/desktop/src/main/kotlin/com/vanpra/composematerialdialogdemos/desktop/demos/BasicDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.desktop.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.BasicDialogDemo 12 | 13 | class BasicDialogDemoScreen : Screen { 14 | override val key: ScreenKey = uniqueScreenKey 15 | @Composable 16 | override fun Content() { 17 | Column(Modifier.verticalScroll(rememberScrollState())) { 18 | BasicDialogDemo() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/desktop/src/main/kotlin/com/vanpra/composematerialdialogdemos/desktop/demos/ColorDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.desktop.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.ColorDialogDemo 12 | 13 | class ColorDialogDemoScreen : Screen { 14 | override val key: ScreenKey = uniqueScreenKey 15 | @Composable 16 | override fun Content() { 17 | Column(Modifier.verticalScroll(rememberScrollState())) { 18 | ColorDialogDemo() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/desktop/src/main/kotlin/com/vanpra/composematerialdialogdemos/desktop/demos/DateTimeDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.desktop.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.DateTimeDialogDemo 12 | 13 | class DateTimeDialogDemoScreen : Screen { 14 | override val key: ScreenKey = uniqueScreenKey 15 | @Composable 16 | override fun Content() { 17 | Column(Modifier.verticalScroll(rememberScrollState())) { 18 | DateTimeDialogDemo() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/desktop/src/main/kotlin/com/vanpra/composematerialdialogdemos/desktop/demos/ListDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.desktop.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.BasicListDialogDemo 12 | import com.vanpra.composematerialdialogdemos.demos.MultiSelectionDemo 13 | import com.vanpra.composematerialdialogdemos.demos.SingleSelectionDemo 14 | 15 | class BasicListDialogDemoScreen : Screen { 16 | override val key: ScreenKey = uniqueScreenKey 17 | @Composable 18 | override fun Content() { 19 | Column(Modifier.verticalScroll(rememberScrollState())) { 20 | BasicListDialogDemo() 21 | } 22 | } 23 | } 24 | 25 | class MultiSelectionDemoScreen : Screen { 26 | override val key: ScreenKey = uniqueScreenKey 27 | @Composable 28 | override fun Content() { 29 | Column(Modifier.verticalScroll(rememberScrollState())) { 30 | MultiSelectionDemo() 31 | } 32 | } 33 | } 34 | 35 | class SingleSelectionDemoScreen : Screen { 36 | override val key: ScreenKey = uniqueScreenKey 37 | @Composable 38 | override fun Content() { 39 | Column(Modifier.verticalScroll(rememberScrollState())) { 40 | SingleSelectionDemo() 41 | } 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/ios/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 2 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 3 | 4 | plugins { 5 | kotlin("multiplatform") 6 | id("org.jetbrains.compose") 7 | id("org.jetbrains.kotlin.plugin.compose") 8 | } 9 | 10 | kotlin { 11 | val configuration: KotlinNativeTarget.() -> Unit = { 12 | binaries.framework { 13 | baseName = "ComposeApp" 14 | isStatic = true 15 | } 16 | } 17 | iosX64("uikitX64", configuration) 18 | iosArm64("uikitArm64", configuration) 19 | iosSimulatorArm64("uikitSimulatorArm64", configuration) 20 | 21 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 22 | applyHierarchyTemplate { 23 | common { 24 | group("uikit") { 25 | withIosArm64() 26 | withIosX64() 27 | withIosSimulatorArm64() 28 | } 29 | } 30 | } 31 | 32 | sourceSets { 33 | val commonMain by getting 34 | val commonTest by getting 35 | getByName("uikitMain") { 36 | dependsOn(commonMain) 37 | dependencies { 38 | implementation(projects.app.common) 39 | 40 | implementation(projects.composeMaterialDialogsCore) 41 | implementation(projects.composeMaterialDialogsColor) 42 | implementation(projects.composeMaterialDialogsDatetime) 43 | 44 | /*implementation(Dependencies.ComposeMaterialDialogs.core) 45 | implementation(Dependencies.ComposeMaterialDialogs.datetime) 46 | implementation(Dependencies.ComposeMaterialDialogs.color)*/ 47 | 48 | implementation(Dependencies.DateTime.dateTime) 49 | 50 | implementation("io.github.aakira:napier:2.7.1") 51 | 52 | implementation("cafe.adriel.voyager:voyager-navigator:1.0.1") 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/ios/src/uikitMain/kotlin/com/vanpra/composematerialdialogs/ios/Main.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.ios 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.WindowInsets 6 | import androidx.compose.foundation.layout.displayCutout 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.windowInsetsPadding 10 | import androidx.compose.foundation.layout.wrapContentSize 11 | import androidx.compose.foundation.lazy.LazyColumn 12 | import androidx.compose.foundation.lazy.items 13 | import androidx.compose.material.Icon 14 | import androidx.compose.material.IconButton 15 | import androidx.compose.material.MaterialTheme 16 | import androidx.compose.material.Scaffold 17 | import androidx.compose.material.Text 18 | import androidx.compose.material.TextButton 19 | import androidx.compose.material.TopAppBar 20 | import androidx.compose.material.icons.Icons 21 | import androidx.compose.material.icons.filled.Home 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.runtime.remember 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.unit.dp 27 | import androidx.compose.ui.window.ComposeUIViewController 28 | import cafe.adriel.voyager.core.screen.Screen 29 | import cafe.adriel.voyager.navigator.CurrentScreen 30 | import cafe.adriel.voyager.navigator.LocalNavigator 31 | import cafe.adriel.voyager.navigator.Navigator 32 | import cafe.adriel.voyager.navigator.currentOrThrow 33 | import com.vanpra.composematerialdialogdemos.ios.demos.BasicDialogDemoScreen 34 | import com.vanpra.composematerialdialogdemos.ios.demos.BasicListDialogDemoScreen 35 | import com.vanpra.composematerialdialogdemos.ios.demos.ColorDialogDemoScreen 36 | import com.vanpra.composematerialdialogdemos.ios.demos.DateTimeDialogDemoScreen 37 | import com.vanpra.composematerialdialogdemos.ios.demos.MultiSelectionDemoScreen 38 | import com.vanpra.composematerialdialogdemos.ios.demos.SingleSelectionDemoScreen 39 | import com.vanpra.composematerialdialogdemos.ui.ComposeMaterialDialogsTheme 40 | import io.github.aakira.napier.DebugAntilog 41 | import io.github.aakira.napier.Napier 42 | 43 | fun MainViewController() = run { 44 | Napier.base(DebugAntilog()) 45 | ComposeUIViewController { 46 | ComposeMaterialDialogsTheme { 47 | DialogDemos() 48 | } 49 | } 50 | } 51 | 52 | data class DialogSectionData(val title: String, val screen: () -> Screen) 53 | 54 | val sections = listOf( 55 | DialogSectionData("Basic Dialogs") { BasicDialogDemoScreen() }, 56 | DialogSectionData("Basic List Dialogs") { BasicListDialogDemoScreen() }, 57 | DialogSectionData("Single Selection List Dialogs") { SingleSelectionDemoScreen() }, 58 | DialogSectionData("Multi-Selection List Dialogs") { MultiSelectionDemoScreen() }, 59 | DialogSectionData("Date and Time Picker Dialogs") { DateTimeDialogDemoScreen() }, 60 | DialogSectionData("Color Picker Dialogs") { ColorDialogDemoScreen() } 61 | ) 62 | 63 | class HomeScreen : Screen { 64 | @Composable 65 | override fun Content() { 66 | val navigator = LocalNavigator.currentOrThrow 67 | LazyColumn { 68 | items(sections) { 69 | TextButton( 70 | onClick = { navigator push it.screen() }, 71 | modifier = Modifier 72 | .fillMaxWidth() 73 | .padding(8.dp) 74 | .background(MaterialTheme.colors.primaryVariant), 75 | ) { 76 | Text( 77 | it.title, 78 | modifier = Modifier 79 | .fillMaxWidth() 80 | .wrapContentSize(Alignment.Center), 81 | color = MaterialTheme.colors.onPrimary 82 | ) 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * @brief Collection of Material Dialog Demos 91 | */ 92 | @Composable 93 | fun DialogDemos() { 94 | Navigator(remember { HomeScreen() }) { 95 | Scaffold( 96 | topBar = { 97 | TopAppBar( 98 | navigationIcon = { 99 | IconButton(it::popUntilRoot) { 100 | Icon(Icons.Default.Home, null) 101 | } 102 | }, 103 | title = { 104 | Text("Demo") 105 | }, 106 | modifier = Modifier.windowInsetsPadding(WindowInsets.displayCutout) 107 | ) 108 | }, 109 | ) { 110 | Box(Modifier.padding(it)) { 111 | CurrentScreen() 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/ios/src/uikitMain/kotlin/com/vanpra/composematerialdialogs/ios/demos/BasicDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ios.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.BasicDialogDemo 12 | 13 | class BasicDialogDemoScreen : Screen { 14 | override val key: ScreenKey = uniqueScreenKey 15 | @Composable 16 | override fun Content() { 17 | Column(Modifier.verticalScroll(rememberScrollState())) { 18 | BasicDialogDemo() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/ios/src/uikitMain/kotlin/com/vanpra/composematerialdialogs/ios/demos/ColorDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ios.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.ColorDialogDemo 12 | 13 | class ColorDialogDemoScreen : Screen { 14 | override val key: ScreenKey = uniqueScreenKey 15 | @Composable 16 | override fun Content() { 17 | Column(Modifier.verticalScroll(rememberScrollState())) { 18 | ColorDialogDemo() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/ios/src/uikitMain/kotlin/com/vanpra/composematerialdialogs/ios/demos/DateTimeDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ios.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.DateTimeDialogDemo 12 | 13 | class DateTimeDialogDemoScreen : Screen { 14 | override val key: ScreenKey = uniqueScreenKey 15 | @Composable 16 | override fun Content() { 17 | Column(Modifier.verticalScroll(rememberScrollState())) { 18 | DateTimeDialogDemo() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/ios/src/uikitMain/kotlin/com/vanpra/composematerialdialogs/ios/demos/ListDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogdemos.ios.demos 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import cafe.adriel.voyager.core.screen.Screen 9 | import cafe.adriel.voyager.core.screen.ScreenKey 10 | import cafe.adriel.voyager.core.screen.uniqueScreenKey 11 | import com.vanpra.composematerialdialogdemos.demos.BasicListDialogDemo 12 | import com.vanpra.composematerialdialogdemos.demos.MultiSelectionDemo 13 | import com.vanpra.composematerialdialogdemos.demos.SingleSelectionDemo 14 | 15 | class BasicListDialogDemoScreen : Screen { 16 | override val key: ScreenKey = uniqueScreenKey 17 | @Composable 18 | override fun Content() { 19 | Column(Modifier.verticalScroll(rememberScrollState())) { 20 | BasicListDialogDemo() 21 | } 22 | } 23 | } 24 | 25 | class MultiSelectionDemoScreen : Screen { 26 | override val key: ScreenKey = uniqueScreenKey 27 | @Composable 28 | override fun Content() { 29 | Column(Modifier.verticalScroll(rememberScrollState())) { 30 | MultiSelectionDemo() 31 | } 32 | } 33 | } 34 | 35 | class SingleSelectionDemoScreen : Screen { 36 | override val key: ScreenKey = uniqueScreenKey 37 | @Composable 38 | override fun Content() { 39 | Column(Modifier.verticalScroll(rememberScrollState())) { 40 | SingleSelectionDemo() 41 | } 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/iosApp/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=com.vanpra.composematerialdialogs.ComposeMaterialDialogs 3 | APP_NAME=Compose Material Dialogs -------------------------------------------------------------------------------- /app/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png -------------------------------------------------------------------------------- /app/iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /app/iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var body: some View { 15 | ComposeView() 16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler 17 | } 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | CADisableMinimumFrameDurationOnPhone 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /app/iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 3 | 4 | plugins { 5 | id("org.jetbrains.compose") version "1.7.0" apply false 6 | //id("com.diffplug.spotless") version "6.0.4" 7 | id("org.jetbrains.dokka") version "2.0.0" 8 | id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" 9 | } 10 | 11 | buildscript { 12 | repositories { 13 | google() 14 | mavenCentral() 15 | maven("https://plugins.gradle.org/m2/") 16 | } 17 | 18 | dependencies { 19 | classpath(Dependencies.Kotlin.gradlePlugin) 20 | classpath("com.android.tools.build:gradle:8.8.0") 21 | classpath("com.vanniktech:gradle-maven-publish-plugin:0.30.0") 22 | classpath(Dependencies.Shot.core) 23 | } 24 | } 25 | 26 | allprojects { 27 | repositories { 28 | google() 29 | mavenCentral() 30 | gradlePluginPortal() 31 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 32 | } 33 | } 34 | 35 | tasks.dokkaHtmlMultiModule.configure { 36 | outputDirectory.set(projectDir.resolve("docs/api")) 37 | } 38 | 39 | subprojects { 40 | //plugins.apply("com.diffplug.spotless") 41 | /*spotless { 42 | kotlin { 43 | target("*") 44 | ktlint(Dependencies.Ktlint.version) 45 | } 46 | }*/ 47 | 48 | tasks.withType { 49 | compilerOptions { 50 | if (name.contains("android", true)) { 51 | jvmTarget = JvmTarget.JVM_1_8 52 | } 53 | } 54 | } 55 | 56 | tasks.withType { 57 | testLogging { 58 | showStandardStreams = true 59 | } 60 | } 61 | 62 | plugins.withType { 63 | configure { 64 | compileSdkVersion(35) 65 | defaultConfig { 66 | minSdk = 21 67 | targetSdk = 35 68 | 69 | testInstrumentationRunner = "com.karumi.shot.ShotTestRunner" 70 | testApplicationId = "com.vanpra.composematerialdialogs.test" 71 | } 72 | compileOptions { 73 | sourceCompatibility(JavaVersion.VERSION_1_8) 74 | targetCompatibility(JavaVersion.VERSION_1_8) 75 | } 76 | } 77 | } 78 | 79 | plugins.withType { 80 | configure { 81 | tolerance = 1.0 82 | } 83 | } 84 | 85 | 86 | // Read in the signing.properties file if it is exists 87 | val signingPropsFile = rootProject.file("release/signing.properties") 88 | if (signingPropsFile.exists()) { 89 | java.util.Properties().apply { 90 | signingPropsFile.inputStream().use { 91 | load(it) 92 | } 93 | }.forEach { key1, value1 -> 94 | val key = key1.toString() 95 | val value = value1.toString() 96 | if (key == "signing.secretKeyRingFile") { 97 | // If this is the key ring, treat it as a relative path 98 | project.ext.set(key, rootProject.file(value).absolutePath) 99 | } else { 100 | project.ext.set(key, value) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | google() 3 | mavenCentral() 4 | maven("https://dl.bintray.com/kotlin/kotlin-eap") 5 | } 6 | 7 | plugins { 8 | `kotlin-dsl` 9 | } 10 | 11 | dependencies { 12 | modules { 13 | module("org.jetbrains.trove4j:trove4j") { 14 | replacedBy("org.jetbrains.intellij.deps:trove4j") 15 | } 16 | } 17 | } 18 | 19 | gradlePlugin { 20 | plugins { 21 | register("common-library") { 22 | id = "common-library" 23 | implementationClass = "CommonModulePlugin" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/CommonModulePlugin.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | import org.gradle.api.artifacts.dsl.DependencyHandler 4 | 5 | class CommonModulePlugin: Plugin { 6 | override fun apply(project: Project) { 7 | with(project) { 8 | applyPlugins() 9 | } 10 | } 11 | 12 | private fun Project.applyPlugins() { 13 | plugins.run { 14 | apply("com.android.library") 15 | apply("kotlin-multiplatform") 16 | apply("com.vanniktech.maven.publish") 17 | apply("shot") 18 | apply("org.jetbrains.dokka") 19 | apply("org.jetbrains.compose") 20 | apply("org.jetbrains.kotlin.plugin.compose") 21 | } 22 | } 23 | 24 | private fun DependencyHandler.implementation(dependency: String) { 25 | add("implementation", dependency) 26 | } 27 | 28 | private fun DependencyHandler.androidTestImplementation(dependency: String) { 29 | add("androidTestImplementation", dependency) 30 | } 31 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Dependencies.kt: -------------------------------------------------------------------------------- 1 | object Dependencies { 2 | const val desugar = "com.android.tools:desugar_jdk_libs:2.1.4" 3 | 4 | object ComposeMaterialDialogs { 5 | const val version = "0.9.6" 6 | 7 | const val core = "ca.gosyer:compose-material-dialogs-core:$version" 8 | const val datetime = "ca.gosyer:compose-material-dialogs-datetime:$version" 9 | const val color = "ca.gosyer:compose-material-dialogs-color:$version" 10 | } 11 | 12 | object Ktlint { 13 | const val version = "0.45.2" 14 | } 15 | 16 | object Kotlin { 17 | private const val version = "2.0.21" 18 | const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version" 19 | } 20 | 21 | object DateTime { 22 | private const val version = "0.6.1" 23 | const val dateTime = "org.jetbrains.kotlinx:kotlinx-datetime:$version" 24 | } 25 | 26 | object Shot { 27 | private const val version = "6.1.0" 28 | const val core = "com.karumi:shot:$version" 29 | const val android = "com.karumi:shot-android:$version" 30 | } 31 | 32 | object Google { 33 | const val material = "com.google.android.material:material:1.12.0" 34 | } 35 | 36 | object AndroidX { 37 | const val coreKtx = "androidx.core:core-ktx:1.15.0" 38 | 39 | object Testing { 40 | const val version = "1.6.2" 41 | const val core = "androidx.test:core:$version" 42 | const val rules = "androidx.test:rules:$version" 43 | const val runner = "androidx.test:runner:$version" 44 | } 45 | 46 | object Compose { 47 | const val activity = "androidx.activity:activity-compose:1.10.0" 48 | const val navigation = "androidx.navigation:navigation-compose:2.8.5" 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/update_deps.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List 3 | import bs4 as beautifulsoup 4 | import requests 5 | from tqdm import tqdm 6 | 7 | from selenium import webdriver 8 | from selenium.webdriver.chrome.options import Options 9 | from selenium.webdriver.common.by import By 10 | from selenium.webdriver.support.ui import WebDriverWait 11 | from selenium.webdriver.support import expected_conditions as ec 12 | 13 | main_search_url = 'https://search.maven.org/solrsearch/select?q=g:"{}"+AND+a:"{}"&core=gav&rows=1&wt=json' 14 | google_search_url = "https://maven.google.com/web/index.html" 15 | 16 | 17 | chrome_options = Options() 18 | # chrome_options.add_argument("--headless") 19 | chrome_options.add_argument("--window-size=1920x1080") 20 | driver = webdriver.Chrome(options=chrome_options) 21 | 22 | dep_file = "Dependencies.kt" 23 | 24 | with open(dep_file, "r") as readfile: 25 | current_deps = readfile.read().splitlines() 26 | 27 | version_patten = re.compile(r"version = \"(.*)\"") 28 | dep_pattern = re.compile(r"const val ([^=]*) = \"(.*)\"") 29 | current_version = ("", -1, "") 30 | 31 | updated_deps: List[str] = [] 32 | 33 | for index, item in enumerate(tqdm(current_deps)): 34 | version_match = version_patten.search(item) 35 | dep_match = dep_pattern.search(item) 36 | 37 | if version_match: 38 | if current_version[1] != -1: 39 | updated_deps[current_version[1]] = re.sub(version_patten, f'version = "{current_version[0]}"', current_version[2]) 40 | current_version = (version_match.group(1), index, item) 41 | updated_deps.append(item) 42 | elif dep_match: 43 | (dep, version) = dep_match.group(2).rsplit(":", 1) 44 | (group, artifact) = dep.split(":") 45 | main_dep_url = main_search_url.format(group, artifact) 46 | google_dep_url = f"{google_search_url}#{group}:{artifact}" 47 | 48 | res = requests.get(main_dep_url).json() 49 | latest_version = version 50 | 51 | if res["response"]["docs"]: 52 | latest_version = res["response"]["docs"][0]["v"] 53 | else: 54 | print(google_dep_url) 55 | driver.get(google_dep_url) 56 | elems = WebDriverWait(driver, 10).until( 57 | ec.visibility_of_element_located( 58 | (By.XPATH, "//div[@class='content-header ng-binding ng-scope']") 59 | ) 60 | ) 61 | 62 | soup = beautifulsoup.BeautifulSoup(driver.page_source, "html.parser") 63 | latest_version = soup.find("span", {"class": "ng-binding"}).text 64 | 65 | if version == "$version": 66 | current_version = (latest_version, current_version[1], current_version[2]) 67 | updated_deps.append(item) 68 | else: 69 | updated_deps.append( 70 | f"const val {dep_match.group(1)} = \"{group}:{artifact}:{latest_version}\"" 71 | ) 72 | else: 73 | updated_deps.append(item) 74 | 75 | 76 | if current_version[1] != -1: 77 | updated_deps[current_version[1]] = re.sub(version_patten, f'version = "{current_version[0]}"', current_version[2]) 78 | 79 | with open(dep_file, "w") as outfile: 80 | outfile.write("\n".join(updated_deps)) -------------------------------------------------------------------------------- /color/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /color/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | plugins { 5 | id("common-library") 6 | } 7 | 8 | 9 | kotlin { 10 | androidTarget { 11 | publishAllLibraryVariants() 12 | compilations.all { 13 | compileTaskProvider.configure { 14 | compilerOptions { 15 | jvmTarget = JvmTarget.JVM_1_8 16 | } 17 | } 18 | } 19 | } 20 | jvm { 21 | compilations.all { 22 | compileTaskProvider.configure { 23 | compilerOptions { 24 | jvmTarget = JvmTarget.JVM_11 25 | } 26 | } 27 | } 28 | } 29 | iosX64() 30 | iosArm64() 31 | iosSimulatorArm64() 32 | 33 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 34 | applyDefaultHierarchyTemplate { 35 | common { 36 | group("jvmCommon") { 37 | withAndroidTarget() 38 | withJvm() 39 | } 40 | } 41 | } 42 | 43 | sourceSets { 44 | commonMain { 45 | dependencies { 46 | api(projects.composeMaterialDialogsCore) 47 | compileOnly(compose.ui) 48 | compileOnly(compose.foundation) 49 | compileOnly(compose.material) 50 | compileOnly(compose.animation) 51 | } 52 | } 53 | commonTest { 54 | dependencies { 55 | implementation(kotlin("test-common")) 56 | implementation(kotlin("test-annotations-common")) 57 | } 58 | } 59 | } 60 | } 61 | 62 | shot { 63 | tolerance = 1.0 // Tolerance needed for CI 64 | } 65 | 66 | android { 67 | namespace = "com.vanpra.composematerialdialogs.color" 68 | } -------------------------------------------------------------------------------- /color/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/color/consumer-rules.pro -------------------------------------------------------------------------------- /color/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=compose-material-dialogs-color 2 | POM_NAME=Compose Material Dialog Color Picker 3 | -------------------------------------------------------------------------------- /color/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_argbColorPicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_argbColorPicker.png -------------------------------------------------------------------------------- /color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_mainColorPicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_mainColorPicker.png -------------------------------------------------------------------------------- /color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_mainColorPickerWithInitialSelection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_mainColorPickerWithInitialSelection.png -------------------------------------------------------------------------------- /color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_rgbColorPicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_rgbColorPicker.png -------------------------------------------------------------------------------- /color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_subColorPicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/color/screenshots/debug/com.vanpra.composematerialdialogs.color.test.screenshot.ColorPickerDialogTests_subColorPicker.png -------------------------------------------------------------------------------- /color/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /color/src/androidTest/java/com/vanpra/composematerialdialogs/color/test/screenshot/ColorPickerDialogTests.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.color.test.screenshot 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.test.junit4.createComposeRule 5 | import androidx.compose.ui.test.performClick 6 | import androidx.compose.ui.test.performTouchInput 7 | import androidx.compose.ui.test.swipeLeft 8 | import com.karumi.shot.ScreenshotTest 9 | import com.vanpra.composematerialdialogs.color.ARGBPickerState 10 | import com.vanpra.composematerialdialogs.color.ColorPalette 11 | import com.vanpra.composematerialdialogs.color.colorChooser 12 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 13 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialog 14 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialogColorPicker 15 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialogColorSelector 16 | import com.vanpra.composematerialdialogs.test.utils.extensions.setContentAndWaitForIdle 17 | import org.junit.Rule 18 | import org.junit.Test 19 | 20 | class ColorPickerDialogTests : ScreenshotTest { 21 | @get:Rule 22 | val composeTestRule = createComposeRule() 23 | 24 | @Test 25 | fun mainColorPicker() { 26 | composeTestRule.setContentAndWaitForIdle { 27 | DialogWithContent { 28 | colorChooser(colors = ColorPalette.Primary) 29 | } 30 | } 31 | compareScreenshot(composeTestRule.onDialog()) 32 | } 33 | 34 | @Test 35 | fun mainColorPickerWithInitialSelection() { 36 | composeTestRule.setContentAndWaitForIdle { 37 | DialogWithContent { 38 | colorChooser(colors = ColorPalette.Primary, initialSelection = 4) 39 | } 40 | } 41 | compareScreenshot(composeTestRule.onDialog()) 42 | } 43 | 44 | @Test 45 | fun subColorPicker() { 46 | composeTestRule.setContentAndWaitForIdle { 47 | DialogWithContent { 48 | colorChooser(colors = ColorPalette.Primary, subColors = ColorPalette.PrimarySub) 49 | } 50 | } 51 | 52 | composeTestRule.onDialogColorSelector(0).performClick() 53 | compareScreenshot(composeTestRule.onDialog()) 54 | } 55 | 56 | @Test 57 | fun rgbColorPicker() { 58 | /* Using list with custom color to ensure the box background uses this color */ 59 | composeTestRule.setContentAndWaitForIdle { 60 | DialogWithContent { 61 | colorChooser( 62 | colors = listOf(Color(100, 100, 200)), 63 | argbPickerState = ARGBPickerState.WithoutAlphaSelector, 64 | subColors = ColorPalette.PrimarySub 65 | ) 66 | } 67 | } 68 | 69 | composeTestRule.onDialogColorPicker().performTouchInput { swipeLeft() } 70 | composeTestRule.waitForIdle() 71 | compareScreenshot(composeTestRule.onDialog()) 72 | } 73 | 74 | @Test 75 | fun argbColorPicker() { 76 | /* Using color with 0 alpha to test squares background */ 77 | composeTestRule.setContentAndWaitForIdle { 78 | DialogWithContent { 79 | colorChooser( 80 | colors = listOf(Color(0, 0, 0, 0)), 81 | argbPickerState = ARGBPickerState.WithAlphaSelector, 82 | subColors = ColorPalette.PrimarySub 83 | ) 84 | } 85 | } 86 | 87 | composeTestRule.onDialogColorPicker().performTouchInput { swipeLeft() } 88 | composeTestRule.waitForIdle() 89 | compareScreenshot(composeTestRule.onDialog()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /color/src/androidTest/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Let us help apps determine location. This means sending 3 | anonymous location data to us, even when no apps are running 4 | Use Location Services? 5 | Phone Ringtone 6 | Label as: 7 | Set backup account 8 | Enter you name: 9 | -------------------------------------------------------------------------------- /color/src/commonMain/kotlin/com/vanpra/composematerialdialogs/color/Util.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.color 2 | 3 | internal expect fun Int.toHexString(): String -------------------------------------------------------------------------------- /color/src/iosMain/kotlin/com/vanpra/composematerialdialogs/color/IosUtil.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.color 2 | 3 | import platform.Foundation.NSString 4 | import platform.Foundation.stringWithFormat 5 | 6 | internal actual fun Int.toHexString(): String { 7 | return NSString.stringWithFormat(format = "%X", this) 8 | } -------------------------------------------------------------------------------- /color/src/jvmCommonMain/kotlin/com/vanpra/composematerialdialogs/color/JvmUtil.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.color 2 | 3 | import java.util.Locale 4 | 5 | internal actual fun Int.toHexString(): String { 6 | return Integer.toHexString(this) 7 | .uppercase(Locale.ROOT) 8 | } -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | plugins { 5 | id("common-library") 6 | } 7 | 8 | 9 | kotlin { 10 | androidTarget { 11 | publishAllLibraryVariants() 12 | compilations.all { 13 | compileTaskProvider.configure { 14 | compilerOptions { 15 | jvmTarget = JvmTarget.JVM_1_8 16 | } 17 | } 18 | } 19 | } 20 | jvm { 21 | compilations.all { 22 | compileTaskProvider.configure { 23 | compilerOptions { 24 | jvmTarget = JvmTarget.JVM_11 25 | } 26 | } 27 | } 28 | } 29 | iosX64() 30 | iosArm64() 31 | iosSimulatorArm64() 32 | 33 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 34 | applyDefaultHierarchyTemplate { 35 | common { 36 | group("jvmCommon") { 37 | withAndroidTarget() 38 | withJvm() 39 | } 40 | } 41 | } 42 | 43 | sourceSets { 44 | commonMain { 45 | dependencies { 46 | compileOnly(compose.ui) 47 | compileOnly(compose.foundation) 48 | compileOnly(compose.material) 49 | compileOnly(compose.animation) 50 | } 51 | } 52 | commonTest { 53 | dependencies { 54 | implementation(kotlin("test-common")) 55 | implementation(kotlin("test-annotations-common")) 56 | } 57 | } 58 | } 59 | } 60 | 61 | android { 62 | namespace = "com.vanpra.composematerialdialogs.core" 63 | } -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/consumer-rules.pro -------------------------------------------------------------------------------- /core/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=compose-material-dialogs-core 2 | POM_NAME=Compose Material Dialog Core 3 | -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithButtons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithButtons.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithButtonsAndIconTitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithButtonsAndIconTitle.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithInput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithInput.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithStackedButtons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithStackedButtons.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithoutButtons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.BasicDialogTest_dialogWithoutButtons.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.InputDialogTest_dialogWithFilledInput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.InputDialogTest_dialogWithFilledInput.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.InputDialogTest_dialogWithOutlinedInput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.InputDialogTest_dialogWithOutlinedInput.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_customListSelectionDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_customListSelectionDialog.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_multiSelectionDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_multiSelectionDialog.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_simpleListSelectionDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_simpleListSelectionDialog.png -------------------------------------------------------------------------------- /core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_singleSelectionDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/core/screenshots/debug/com.vanpra.composematerialdialogs.test.screenshot.ListDialog_singleSelectionDialog.png -------------------------------------------------------------------------------- /core/src/androidMain/kotlin/com/vanpra/composematerialdialogs/AndroidUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs 2 | 3 | import androidx.compose.foundation.layout.sizeIn 4 | import androidx.compose.foundation.layout.wrapContentHeight 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Shape 9 | import androidx.compose.ui.platform.LocalConfiguration 10 | import androidx.compose.ui.unit.Dp 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.window.Dialog 13 | import androidx.compose.ui.window.DialogProperties 14 | import androidx.compose.ui.window.SecureFlagPolicy 15 | import kotlin.math.min 16 | 17 | @Composable 18 | internal actual fun rememberScreenConfiguration(): ScreenConfiguration { 19 | val config = LocalConfiguration.current 20 | return remember(config.screenWidthDp, config.screenHeightDp) { 21 | ScreenConfiguration( 22 | screenWidthDp = config.screenWidthDp, 23 | screenHeightDp = config.screenHeightDp 24 | ) 25 | } 26 | } 27 | 28 | @Composable 29 | internal actual fun isSmallDevice(): Boolean { 30 | return LocalConfiguration.current.screenWidthDp <= 360 31 | } 32 | 33 | @Composable 34 | internal actual fun isLargeDevice(): Boolean { 35 | return LocalConfiguration.current.screenWidthDp <= 600 36 | } 37 | 38 | @Composable 39 | internal actual fun DialogBox( 40 | onDismissRequest: () -> Unit, 41 | properties: MaterialDialogProperties, 42 | content: @Composable () -> Unit 43 | ) = Dialog( 44 | onDismissRequest = onDismissRequest, 45 | properties = DialogProperties( 46 | dismissOnBackPress = properties.dismissOnBackPress, 47 | dismissOnClickOutside = properties.dismissOnClickOutside, 48 | securePolicy = properties.securePolicy.toSecureFlagPolicy(), 49 | usePlatformDefaultWidth = properties.usePlatformDefaultWidth, 50 | decorFitsSystemWindows = properties.decorFitsSystemWindows 51 | ), 52 | content = content 53 | ) 54 | 55 | private fun SecurePolicy.toSecureFlagPolicy(): SecureFlagPolicy { 56 | return when (this) { 57 | SecurePolicy.Inherit -> SecureFlagPolicy.Inherit 58 | SecurePolicy.SecureOn -> SecureFlagPolicy.SecureOn 59 | SecurePolicy.SecureOff -> SecureFlagPolicy.SecureOff 60 | } 61 | } 62 | 63 | @Composable 64 | internal actual fun getDialogShape(isWindowDialog: Boolean, shape: Shape): Shape = shape 65 | 66 | @Composable 67 | internal actual fun ScreenConfiguration.getMaxHeight(isWindowDialog: Boolean): Dp { 68 | return if (isLargeDevice()) { 69 | screenHeightDp.dp - 96.dp 70 | } else { 71 | 560.dp 72 | } 73 | } 74 | 75 | @Composable 76 | internal actual fun ScreenConfiguration.getPadding(isWindowDialog: Boolean): Dp { 77 | val isDialogFullWidth = screenWidthDp == LocalConfiguration.current.screenWidthDp 78 | return if (isDialogFullWidth) 16.dp else 0.dp 79 | } 80 | 81 | internal actual fun Modifier.dialogHeight(isWindowDialog: Boolean): Modifier = wrapContentHeight() 82 | 83 | internal actual fun Modifier.dialogMaxSize(isWindowDialog: Boolean, maxHeight: Dp): Modifier = 84 | this.sizeIn(maxHeight = maxHeight, maxWidth = 560.dp) 85 | 86 | internal actual fun getLayoutHeight(isWindowDialog: Boolean, maxHeightPx: Int, layoutHeight: Int): Int { 87 | return min(maxHeightPx, layoutHeight) 88 | } 89 | -------------------------------------------------------------------------------- /core/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/androidTest/java/com/vanpra/composematerialdialogs/test/functional/DialogButtonsTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.functional 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.test.junit4.createComposeRule 5 | import androidx.compose.ui.test.performClick 6 | import androidx.test.ext.junit.runners.AndroidJUnit4 7 | import com.vanpra.composematerialdialogs.MaterialDialogButtons 8 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 9 | import com.vanpra.composematerialdialogs.test.utils.extensions.assertDialogDoesNotExist 10 | import com.vanpra.composematerialdialogs.test.utils.extensions.assertDialogExists 11 | import com.vanpra.composematerialdialogs.test.utils.extensions.onNegativeButton 12 | import com.vanpra.composematerialdialogs.test.utils.extensions.onPositiveButton 13 | import org.junit.Rule 14 | import org.junit.Test 15 | import org.junit.runner.RunWith 16 | 17 | @RunWith(AndroidJUnit4::class) 18 | class DialogButtonsTest { 19 | @get:Rule 20 | val composeTestRule = createComposeRule() 21 | 22 | @Composable 23 | private fun MaterialDialogButtons.defaultButtons() { 24 | negativeButton("Disagree") 25 | positiveButton("Agree") 26 | } 27 | 28 | @Test 29 | fun dialogDismissedOnPositiveButton() { 30 | composeTestRule.setContent { DialogWithContent(buttons = { defaultButtons() }) } 31 | composeTestRule.onPositiveButton().performClick() 32 | composeTestRule.assertDialogDoesNotExist() 33 | } 34 | 35 | @Test 36 | fun dialogDismissedOnNegativeButton() { 37 | composeTestRule.setContent { DialogWithContent(buttons = { defaultButtons() }) } 38 | composeTestRule.onNegativeButton().performClick() 39 | composeTestRule.assertDialogDoesNotExist() 40 | } 41 | 42 | @Test 43 | fun dialogNotDismissedOnPositiveButton() { 44 | composeTestRule.setContent { DialogWithContent(false, buttons = { defaultButtons() }) } 45 | composeTestRule.onPositiveButton().performClick() 46 | composeTestRule.assertDialogExists() 47 | } 48 | 49 | @Test 50 | fun dialogNotDismissedOnNegativeButton() { 51 | composeTestRule.setContent { DialogWithContent(false, buttons = { defaultButtons() }) } 52 | composeTestRule.onNegativeButton().performClick() 53 | composeTestRule.assertDialogExists() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/androidTest/java/com/vanpra/composematerialdialogs/test/screenshot/BasicDialogTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.screenshot 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.filled.LocationOn 7 | import androidx.compose.ui.graphics.ColorFilter 8 | import androidx.compose.ui.res.stringResource 9 | import androidx.compose.ui.test.junit4.createComposeRule 10 | import androidx.test.ext.junit.runners.AndroidJUnit4 11 | import com.karumi.shot.ScreenshotTest 12 | import com.vanpra.composematerialdialogs.iconTitle 13 | import com.vanpra.composematerialdialogs.input 14 | import com.vanpra.composematerialdialogs.message 15 | import com.vanpra.composematerialdialogs.test.R 16 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 17 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialog 18 | import com.vanpra.composematerialdialogs.test.utils.extensions.setContentAndWaitForIdle 19 | import com.vanpra.composematerialdialogs.title 20 | import org.junit.Rule 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | 24 | @RunWith(AndroidJUnit4::class) 25 | class BasicDialogTest : ScreenshotTest { 26 | @get:Rule 27 | val composeTestRule = createComposeRule() 28 | 29 | @Test 30 | fun dialogWithoutButtons() { 31 | composeTestRule.setContentAndWaitForIdle { 32 | DialogWithContent { 33 | title(text = stringResource(R.string.location_dialog_title)) 34 | message(text = stringResource(R.string.location_dialog_message)) 35 | } 36 | } 37 | compareScreenshot(composeTestRule.onDialog()) 38 | } 39 | 40 | @Test 41 | fun dialogWithButtons() { 42 | composeTestRule.setContentAndWaitForIdle { 43 | DialogWithContent( 44 | buttons = { 45 | negativeButton("Disagree") 46 | positiveButton("Agree") 47 | } 48 | ) { 49 | title(text = stringResource(R.string.location_dialog_title)) 50 | message(text = stringResource(R.string.location_dialog_message)) 51 | } 52 | } 53 | compareScreenshot(composeTestRule.onDialog()) 54 | } 55 | 56 | @Test 57 | fun dialogWithButtonsAndIconTitle() { 58 | composeTestRule.setContentAndWaitForIdle { 59 | DialogWithContent( 60 | buttons = { 61 | negativeButton("Disagree") 62 | positiveButton("Agree") 63 | } 64 | ) { 65 | iconTitle( 66 | icon = { 67 | Image( 68 | Icons.Default.LocationOn, 69 | contentDescription = "Location Icon", 70 | colorFilter = ColorFilter.tint(MaterialTheme.colors.onBackground) 71 | ) 72 | }, 73 | textRes = R.string.location_dialog_title 74 | ) 75 | message(text = stringResource(R.string.location_dialog_message)) 76 | } 77 | } 78 | compareScreenshot(composeTestRule.onDialog()) 79 | } 80 | 81 | @Test 82 | fun dialogWithStackedButtons() { 83 | composeTestRule.setContentAndWaitForIdle { 84 | DialogWithContent( 85 | buttons = { 86 | negativeButton("No Thanks") 87 | positiveButton("Turn On Speed Boost") 88 | } 89 | ) { 90 | title(text = stringResource(R.string.location_dialog_title)) 91 | message(text = stringResource(R.string.location_dialog_message)) 92 | } 93 | } 94 | compareScreenshot(composeTestRule.onDialog()) 95 | } 96 | 97 | @Test 98 | fun dialogWithInput() { 99 | composeTestRule.setContentAndWaitForIdle { 100 | DialogWithContent( 101 | buttons = { 102 | negativeButton("Cancel") 103 | positiveButton("Ok") 104 | } 105 | ) { 106 | title(text = stringResource(R.string.input_dialog_title)) 107 | input(label = "Name", hint = "Jon Smith") 108 | } 109 | } 110 | compareScreenshot(composeTestRule.onDialog()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/src/androidTest/java/com/vanpra/composematerialdialogs/test/screenshot/InputDialogTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.screenshot 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import com.karumi.shot.ScreenshotTest 6 | import com.vanpra.composematerialdialogs.test.R 7 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 8 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialog 9 | import com.vanpra.composematerialdialogs.test.utils.extensions.setContentAndWaitForIdle 10 | import com.vanpra.composematerialdialogs.title 11 | import input 12 | import org.junit.Rule 13 | import org.junit.Test 14 | import org.junit.runner.RunWith 15 | 16 | @RunWith(AndroidJUnit4::class) 17 | class InputDialogTest : ScreenshotTest { 18 | @get:Rule 19 | val composeTestRule = createComposeRule() 20 | 21 | @Test 22 | fun dialogWithFilledInput() { 23 | composeTestRule.setContentAndWaitForIdle { 24 | DialogWithContent( 25 | buttons = { 26 | negativeButton("Cancel") 27 | positiveButton("Ok") 28 | } 29 | ) { 30 | title(res = R.string.input_dialog_title) 31 | input(label = "Name", placeholder = "Jon Smith") 32 | } 33 | } 34 | compareScreenshot(composeTestRule.onDialog()) 35 | } 36 | 37 | @Test 38 | fun dialogWithOutlinedInput() { 39 | composeTestRule.setContentAndWaitForIdle { 40 | DialogWithContent( 41 | buttons = { 42 | negativeButton("Cancel") 43 | positiveButton("Ok") 44 | } 45 | ) { 46 | title(res = R.string.input_dialog_title) 47 | input( 48 | label = "Name", 49 | placeholder = "Jon Smith", 50 | textFieldStyle = TextFieldStyle.Outlined 51 | ) 52 | } 53 | } 54 | compareScreenshot(composeTestRule.onDialog()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/androidTest/java/com/vanpra/composematerialdialogs/test/screenshot/ListDialogTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.screenshot 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.filled.AccountCircle 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.ColorFilter 15 | import androidx.compose.ui.layout.ContentScale 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.test.junit4.createComposeRule 18 | import androidx.compose.ui.unit.dp 19 | import androidx.test.ext.junit.runners.AndroidJUnit4 20 | import com.karumi.shot.ScreenshotTest 21 | import com.vanpra.composematerialdialogs.listItems 22 | import com.vanpra.composematerialdialogs.listItemsMultiChoice 23 | import com.vanpra.composematerialdialogs.listItemsSingleChoice 24 | import com.vanpra.composematerialdialogs.test.R 25 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 26 | import com.vanpra.composematerialdialogs.test.utils.defaultButtons 27 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialog 28 | import com.vanpra.composematerialdialogs.test.utils.extensions.setContentAndWaitForIdle 29 | import com.vanpra.composematerialdialogs.title 30 | import org.junit.Rule 31 | import org.junit.Test 32 | import org.junit.runner.RunWith 33 | 34 | private val ringtones = 35 | listOf( 36 | "None", 37 | "Callisto", 38 | "Ganymede", 39 | "Luna", 40 | "Rrrring", 41 | "Beats", 42 | "Dance Party", 43 | "Zen Too", 44 | "None", 45 | "Callisto", 46 | "Ganymede", 47 | "Luna", 48 | "Rrrring", 49 | "Beats", 50 | "Dance Party", 51 | "Zen Too" 52 | ) 53 | private val labels = listOf("None", "Forums", "Social", "Updates", "Promotions", "Spam", "Bin") 54 | private val emails = listOf( 55 | "joe@material-dialog.com", 56 | "jane@material-dialog.com", 57 | "dan@material-dialog.com", 58 | "karen@material-dialog.com" 59 | ) 60 | 61 | @RunWith(AndroidJUnit4::class) 62 | class ListDialog : ScreenshotTest { 63 | @get:Rule 64 | val composeTestRule = createComposeRule() 65 | 66 | @Test 67 | fun simpleListSelectionDialog() { 68 | composeTestRule.setContentAndWaitForIdle { 69 | DialogWithContent { 70 | title(text = stringResource(R.string.backup_dialog_title)) 71 | listItems(emails) 72 | } 73 | } 74 | compareScreenshot(composeTestRule.onDialog()) 75 | } 76 | 77 | @Test 78 | fun customListSelectionDialog() { 79 | composeTestRule.setContentAndWaitForIdle { 80 | DialogWithContent { 81 | title(text = stringResource(R.string.backup_dialog_title)) 82 | listItems( 83 | list = emails, 84 | item = { _, email -> 85 | Row(Modifier.fillMaxWidth()) { 86 | Image( 87 | Icons.Default.AccountCircle, 88 | contentDescription = "Account icon", 89 | modifier = Modifier 90 | .padding(vertical = 8.dp) 91 | .size(30.dp), 92 | contentScale = ContentScale.FillHeight, 93 | colorFilter = ColorFilter.tint(MaterialTheme.colors.onBackground) 94 | ) 95 | Text( 96 | email, 97 | modifier = Modifier 98 | .padding(start = 16.dp) 99 | .align(Alignment.CenterVertically), 100 | color = MaterialTheme.colors.onBackground, 101 | style = MaterialTheme.typography.body1 102 | ) 103 | } 104 | } 105 | ) 106 | } 107 | } 108 | compareScreenshot(composeTestRule.onDialog()) 109 | } 110 | 111 | @Test 112 | fun multiSelectionDialog() { 113 | composeTestRule.setContentAndWaitForIdle { 114 | DialogWithContent(buttons = { defaultButtons() }) { 115 | title(text = stringResource(R.string.labels_dialog_title)) 116 | listItemsMultiChoice(labels) 117 | } 118 | } 119 | compareScreenshot(composeTestRule.onDialog()) 120 | } 121 | 122 | @Test 123 | fun singleSelectionDialog() { 124 | composeTestRule.setContentAndWaitForIdle { 125 | DialogWithContent(buttons = { defaultButtons() }) { 126 | title(text = stringResource(R.string.ringtone_dialog_title)) 127 | listItemsSingleChoice(ringtones) 128 | } 129 | } 130 | compareScreenshot(composeTestRule.onDialog()) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /core/src/androidTest/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Let us help apps determine location. This means sending 3 | anonymous location data to us, even when no apps are running 4 | Use Location Services? 5 | Phone Ringtone 6 | Label as: 7 | Set backup account 8 | Enter you name: 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/com/vanpra/composematerialdialogs/MaterialDialogCore.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.width 10 | import androidx.compose.foundation.layout.wrapContentHeight 11 | import androidx.compose.foundation.layout.wrapContentWidth 12 | import androidx.compose.material.MaterialTheme 13 | import androidx.compose.material.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.text.TextStyle 19 | import androidx.compose.ui.unit.dp 20 | 21 | /** 22 | * Adds a title with the given text to the dialog 23 | * @param text title text from a string literal 24 | * @param res title text from a string resource 25 | * @param center text is aligned to center when true 26 | */ 27 | @Composable 28 | fun MaterialDialogScope.title( 29 | text: String, 30 | color: Color = MaterialTheme.colors.onSurface, 31 | style: TextStyle = MaterialTheme.typography.h6, 32 | center: Boolean = false 33 | ) { 34 | var modifier = Modifier 35 | .fillMaxWidth() 36 | .padding(start = 24.dp, end = 24.dp) 37 | .height(64.dp) 38 | .wrapContentHeight(Alignment.CenterVertically) 39 | 40 | modifier = modifier.then( 41 | Modifier.wrapContentWidth( 42 | if (center) { 43 | Alignment.CenterHorizontally 44 | } else { 45 | Alignment.Start 46 | } 47 | ) 48 | ) 49 | 50 | Text( 51 | text = text, 52 | color = color, 53 | style = style, 54 | modifier = modifier 55 | ) 56 | } 57 | 58 | /** 59 | * Adds a title with the given text and icon to the dialog 60 | * @param text title text from a string literal 61 | * @param textRes title text from a string resource 62 | * @param icon optional icon displayed at the start of the title 63 | */ 64 | @Composable 65 | fun MaterialDialogScope.iconTitle( 66 | text: String, 67 | color: Color = MaterialTheme.colors.onSurface, 68 | style: TextStyle = MaterialTheme.typography.h6, 69 | icon: @Composable () -> Unit = {}, 70 | ) { 71 | Row( 72 | modifier = Modifier 73 | .padding(start = 24.dp, end = 24.dp) 74 | .height(64.dp), 75 | verticalAlignment = Alignment.CenterVertically 76 | ) { 77 | icon() 78 | Spacer(Modifier.width(14.dp)) 79 | Text( 80 | text = text, 81 | color = color, 82 | style = style 83 | ) 84 | } 85 | } 86 | 87 | /** 88 | * Adds paragraph of text to the dialog 89 | * @param text message text from a string literal 90 | * @param res message text from a string resource 91 | */ 92 | @Composable 93 | fun MaterialDialogScope.message( 94 | text: String, 95 | color: Color = MaterialTheme.colors.onSurface, 96 | style: TextStyle = MaterialTheme.typography.body1, 97 | ) { 98 | Text( 99 | text = text, 100 | color = color, 101 | style = style, 102 | modifier = Modifier 103 | .padding(bottom = 28.dp, start = 24.dp, end = 24.dp) 104 | ) 105 | } 106 | 107 | /** 108 | * Create an view in the dialog with the given content and appropriate padding 109 | * @param content the content of the custom view 110 | */ 111 | @Composable 112 | fun MaterialDialogScope.customView(content: @Composable () -> Unit) { 113 | Box(modifier = Modifier.padding(bottom = 28.dp, start = 24.dp, end = 24.dp)) { 114 | content() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/com/vanpra/composematerialdialogs/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Immutable 5 | import androidx.compose.runtime.Stable 6 | import androidx.compose.ui.Alignment 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Shape 9 | import androidx.compose.ui.graphics.painter.Painter 10 | import androidx.compose.ui.layout.Placeable 11 | import androidx.compose.ui.unit.Dp 12 | import androidx.compose.ui.unit.DpSize 13 | import androidx.compose.ui.unit.dp 14 | 15 | internal fun List>.buttons(type: MaterialDialogButtonTypes) = 16 | this.filter { it.first == type }.map { it.second } 17 | 18 | 19 | // Screen Configuration 20 | 21 | internal class ScreenConfiguration(val screenWidthDp: Int, val screenHeightDp: Int) 22 | 23 | @Composable 24 | internal expect fun rememberScreenConfiguration(): ScreenConfiguration 25 | 26 | @Composable 27 | internal expect fun isSmallDevice(): Boolean 28 | 29 | @Composable 30 | internal expect fun isLargeDevice(): Boolean 31 | 32 | // Dialog 33 | @Composable 34 | internal expect fun DialogBox( 35 | onDismissRequest: () -> Unit, 36 | properties: MaterialDialogProperties, 37 | content: @Composable () -> Unit, 38 | ) 39 | 40 | 41 | // SecureFlagPolicy For Android Dialogs 42 | @Stable 43 | enum class SecurePolicy { 44 | Inherit, //Forces [WindowManager.LayoutParams.FLAG_SECURE] 45 | SecureOn, // Sets [WindowManager.LayoutParams.FLAG_SECURE] on the window 46 | SecureOff // No [WindowManager.LayoutParams.FLAG_SECURE] will be set on the window 47 | } 48 | 49 | // Desktop Window Position 50 | 51 | fun DesktopWindowPosition(x: Dp, y: Dp): DesktopWindowPosition = DesktopWindowPosition.Absolute(x = x, y = y) 52 | fun DesktopWindowPosition(alignment: Alignment): DesktopWindowPosition = DesktopWindowPosition.Aligned(alignment) 53 | 54 | @Stable 55 | sealed class DesktopWindowPosition { 56 | abstract val x: Dp 57 | 58 | abstract val y: Dp 59 | 60 | abstract val isSpecified: Boolean 61 | 62 | @Immutable 63 | object PlatformDefault : DesktopWindowPosition() { 64 | override val x: Dp get() = Dp.Unspecified 65 | override val y: Dp get() = Dp.Unspecified 66 | override val isSpecified: Boolean get() = false 67 | } 68 | 69 | @Immutable 70 | class Absolute(override val x: Dp, override val y: Dp) : DesktopWindowPosition() { 71 | override val isSpecified: Boolean = true 72 | } 73 | 74 | @Immutable 75 | class Aligned(val alignment: Alignment) : DesktopWindowPosition() { 76 | override val x: Dp get() = Dp.Unspecified 77 | override val y: Dp get() = Dp.Unspecified 78 | override val isSpecified: Boolean get() = false 79 | } 80 | } 81 | 82 | @Immutable 83 | data class MaterialDialogProperties( 84 | val dismissOnBackPress: Boolean = true, 85 | val dismissOnClickOutside: Boolean = true, 86 | val securePolicy: SecurePolicy = SecurePolicy.Inherit, 87 | val usePlatformDefaultWidth : Boolean = false, 88 | val decorFitsSystemWindows: Boolean = true, 89 | val isWindowDialog: Boolean = true, 90 | val windowPosition: DesktopWindowPosition = DesktopWindowPosition(Alignment.Center), 91 | val windowSize: DpSize = DpSize(400.dp, 300.dp), 92 | val windowTitle: String = "Untitled", 93 | val windowIcon: Painter? = null, 94 | val windowIsResizable: Boolean = true 95 | ) 96 | 97 | @Composable 98 | internal expect fun getDialogShape(isWindowDialog: Boolean, shape: Shape): Shape 99 | 100 | @Composable 101 | internal expect fun ScreenConfiguration.getMaxHeight(isWindowDialog: Boolean): Dp 102 | 103 | @Composable 104 | internal expect fun ScreenConfiguration.getPadding(isWindowDialog: Boolean): Dp 105 | 106 | internal expect fun Modifier.dialogHeight(isWindowDialog: Boolean): Modifier 107 | 108 | internal expect fun Modifier.dialogMaxSize(isWindowDialog: Boolean, maxHeight: Dp): Modifier 109 | 110 | internal expect fun getLayoutHeight(isWindowDialog: Boolean, maxHeightPx: Int, layoutHeight: Int): Int 111 | 112 | expect class AtomicInt() { 113 | constructor(initialValue: Int) 114 | fun set(newValue: Int) 115 | fun getAndIncrement(): Int 116 | } -------------------------------------------------------------------------------- /core/src/iosMain/kotlin/com/vanpra/composematerialdialogs/IosUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs 2 | 3 | import androidx.compose.foundation.layout.sizeIn 4 | import androidx.compose.foundation.layout.wrapContentHeight 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.CompositionLocalProvider 7 | import androidx.compose.runtime.compositionLocalOf 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.ExperimentalComposeUiApi 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Shape 12 | import androidx.compose.ui.platform.LocalDensity 13 | import androidx.compose.ui.platform.LocalWindowInfo 14 | import androidx.compose.ui.unit.Dp 15 | import androidx.compose.ui.unit.DpSize 16 | import androidx.compose.ui.unit.dp 17 | import androidx.compose.ui.window.Dialog 18 | import androidx.compose.ui.window.DialogProperties 19 | import kotlinx.atomicfu.atomic 20 | import kotlin.math.min 21 | 22 | @RequiresOptIn( 23 | level = RequiresOptIn.Level.ERROR, 24 | message = "This is internal API for Compose Material Dialogs that shouldn't be used" 25 | ) 26 | @Target( 27 | AnnotationTarget.CLASS, 28 | AnnotationTarget.FUNCTION, 29 | AnnotationTarget.PROPERTY 30 | ) 31 | annotation class InternalComposeMaterialDialogsApi 32 | 33 | actual class AtomicInt actual constructor(initialValue: Int) { 34 | private val value = atomic(initialValue) 35 | actual constructor() : this(0) 36 | 37 | actual fun set(newValue: Int) { 38 | value.value = newValue 39 | } 40 | actual fun getAndIncrement(): Int = value.getAndIncrement() 41 | } 42 | 43 | 44 | private fun DpSize.toScreenConfiguration(): ScreenConfiguration { 45 | return ScreenConfiguration( 46 | width.value.toInt(), 47 | height.value.toInt() 48 | ) 49 | } 50 | 51 | @Composable 52 | internal actual fun isSmallDevice(): Boolean { 53 | return LocalScreenConfiguration.current.screenWidthDp <= 360 54 | } 55 | 56 | @Composable 57 | internal actual fun isLargeDevice(): Boolean { 58 | return LocalScreenConfiguration.current.screenWidthDp <= 600 59 | } 60 | 61 | @Composable 62 | internal actual fun rememberScreenConfiguration(): ScreenConfiguration { 63 | return LocalScreenConfiguration.current 64 | } 65 | 66 | @InternalComposeMaterialDialogsApi 67 | @Composable 68 | fun getDialogScreenWidthDp(): Int { 69 | return LocalScreenConfiguration.current.screenWidthDp 70 | } 71 | 72 | internal val LocalScreenConfiguration = compositionLocalOf{ throw IllegalStateException("Unused") } 73 | 74 | @OptIn(ExperimentalComposeUiApi::class) 75 | @Composable 76 | internal actual fun DialogBox( 77 | onDismissRequest: () -> Unit, 78 | properties: MaterialDialogProperties, 79 | content: @Composable () -> Unit, 80 | ) { 81 | val size = LocalWindowInfo.current.containerSize 82 | CompositionLocalProvider( 83 | LocalScreenConfiguration provides with(LocalDensity.current) { 84 | remember(this, size) { 85 | ScreenConfiguration( 86 | size.width.toDp().value.toInt(), 87 | size.height.toDp().value.toInt() 88 | ) 89 | } 90 | } 91 | ) { 92 | Dialog( 93 | onDismissRequest = onDismissRequest, 94 | properties = DialogProperties( 95 | dismissOnBackPress = properties.dismissOnBackPress, 96 | dismissOnClickOutside = properties.dismissOnClickOutside, 97 | usePlatformDefaultWidth = properties.usePlatformDefaultWidth, 98 | ), 99 | ) { 100 | content() 101 | } 102 | } 103 | } 104 | 105 | @Composable 106 | internal actual fun getDialogShape(isWindowDialog: Boolean, shape: Shape): Shape = shape 107 | 108 | 109 | @Composable 110 | internal actual fun ScreenConfiguration.getMaxHeight(isWindowDialog: Boolean): Dp { 111 | return if (isLargeDevice()) { 112 | screenHeightDp.dp - 96.dp 113 | } else { 114 | 560.dp 115 | } 116 | } 117 | 118 | @OptIn(ExperimentalComposeUiApi::class) 119 | @Composable 120 | internal actual fun ScreenConfiguration.getPadding(isWindowDialog: Boolean): Dp { 121 | val isDialogFullWidth = screenWidthDp == with(LocalDensity.current) { 122 | LocalWindowInfo.current.containerSize.width.toDp() 123 | }.value.toInt() 124 | return if (isDialogFullWidth) 16.dp else 0.dp 125 | } 126 | 127 | internal actual fun Modifier.dialogHeight(isWindowDialog: Boolean): Modifier = wrapContentHeight() 128 | 129 | internal actual fun Modifier.dialogMaxSize(isWindowDialog: Boolean, maxHeight: Dp): Modifier = sizeIn(maxHeight = maxHeight, maxWidth = 560.dp) 130 | 131 | internal actual fun getLayoutHeight(isWindowDialog: Boolean, maxHeightPx: Int, layoutHeight: Int): Int { 132 | return min(maxHeightPx, layoutHeight) 133 | } 134 | -------------------------------------------------------------------------------- /core/src/jvmCommonMain/kotlin/com/vanpra/composematerialdialogs/JvmUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | actual class AtomicInt(private val value: AtomicInteger) { 6 | actual constructor(initialValue: Int) : this(AtomicInteger(initialValue)) 7 | actual constructor() : this(AtomicInteger()) 8 | actual fun set(newValue: Int) { 9 | value.set(newValue) 10 | } 11 | actual fun getAndIncrement(): Int = value.getAndIncrement() 12 | } -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/com/vanpra/composematerialdialogs/DesktopUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs 2 | 3 | import androidx.compose.foundation.layout.BoxWithConstraints 4 | import androidx.compose.foundation.layout.fillMaxHeight 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.sizeIn 7 | import androidx.compose.foundation.layout.wrapContentHeight 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.CompositionLocalProvider 10 | import androidx.compose.runtime.compositionLocalOf 11 | import androidx.compose.ui.ExperimentalComposeUiApi 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.RectangleShape 14 | import androidx.compose.ui.graphics.Shape 15 | import androidx.compose.ui.platform.LocalDensity 16 | import androidx.compose.ui.platform.LocalWindowInfo 17 | import androidx.compose.ui.unit.Dp 18 | import androidx.compose.ui.unit.DpSize 19 | import androidx.compose.ui.unit.dp 20 | import androidx.compose.ui.window.Dialog 21 | import androidx.compose.ui.window.DialogProperties 22 | import androidx.compose.ui.window.DialogWindow 23 | import androidx.compose.ui.window.WindowPosition 24 | import androidx.compose.ui.window.rememberDialogState 25 | import kotlin.math.min 26 | 27 | private fun DpSize.toScreenConfiguration(): ScreenConfiguration { 28 | return ScreenConfiguration( 29 | width.value.toInt(), 30 | height.value.toInt() 31 | ) 32 | } 33 | 34 | @Composable 35 | internal actual fun rememberScreenConfiguration(): ScreenConfiguration { 36 | return LocalScreenConfiguration.current 37 | } 38 | 39 | @Composable 40 | internal actual fun isSmallDevice(): Boolean { 41 | return false 42 | } 43 | 44 | @Composable 45 | internal actual fun isLargeDevice(): Boolean { 46 | return true 47 | } 48 | 49 | internal val LocalScreenConfiguration = compositionLocalOf{ throw IllegalStateException("Unused") } 50 | 51 | @Composable 52 | internal actual fun DialogBox( 53 | onDismissRequest: () -> Unit, 54 | properties: MaterialDialogProperties, 55 | content: @Composable () -> Unit 56 | ) = if (properties.isWindowDialog) { 57 | DialogWindow( 58 | onCloseRequest = onDismissRequest, 59 | state = rememberDialogState( 60 | position = properties.windowPosition.toWindowPosition(), 61 | size = properties.windowSize 62 | ), 63 | title = properties.windowTitle, 64 | icon = properties.windowIcon, 65 | resizable = properties.windowIsResizable, 66 | content = { 67 | BoxWithConstraints { 68 | CompositionLocalProvider( 69 | LocalScreenConfiguration provides ScreenConfiguration( 70 | maxWidth.value.toInt(), 71 | maxHeight.value.toInt() 72 | ), 73 | content = content 74 | ) 75 | } 76 | } 77 | ) 78 | } else { 79 | Dialog( 80 | onDismissRequest = onDismissRequest, 81 | properties = DialogProperties( 82 | dismissOnBackPress = properties.dismissOnBackPress, 83 | dismissOnClickOutside = properties.dismissOnClickOutside, 84 | usePlatformDefaultWidth = properties.usePlatformDefaultWidth, 85 | ), 86 | ) { 87 | BoxWithConstraints { 88 | CompositionLocalProvider( 89 | LocalScreenConfiguration provides ScreenConfiguration( 90 | maxWidth.value.toInt(), 91 | maxHeight.value.toInt() 92 | ), 93 | content = content 94 | ) 95 | } 96 | } 97 | } 98 | 99 | private fun DesktopWindowPosition.toWindowPosition(): WindowPosition { 100 | return when (this) { 101 | DesktopWindowPosition.PlatformDefault -> WindowPosition.PlatformDefault 102 | is DesktopWindowPosition.Absolute -> WindowPosition(x = x, y = y) 103 | is DesktopWindowPosition.Aligned -> WindowPosition(alignment) 104 | } 105 | } 106 | 107 | @Composable 108 | internal actual fun getDialogShape(isWindowDialog: Boolean, shape: Shape) = if (isWindowDialog) { 109 | RectangleShape 110 | } else { 111 | shape 112 | } 113 | 114 | @Composable 115 | internal actual fun ScreenConfiguration.getMaxHeight(isWindowDialog: Boolean): Dp { 116 | return if (isWindowDialog) { 117 | screenHeightDp.dp 118 | } else { 119 | if (isLargeDevice()) { 120 | screenHeightDp.dp - 96.dp 121 | } else { 122 | 560.dp 123 | } 124 | } 125 | } 126 | 127 | @OptIn(ExperimentalComposeUiApi::class) 128 | @Composable 129 | internal actual fun ScreenConfiguration.getPadding(isWindowDialog: Boolean): Dp { 130 | return if (isWindowDialog) { 131 | 0.dp 132 | } else { 133 | val isDialogFullWidth = screenWidthDp == with(LocalDensity.current) { 134 | LocalWindowInfo.current.containerSize.width.toDp() 135 | }.value.toInt() 136 | if (isDialogFullWidth) 16.dp else 0.dp 137 | } 138 | } 139 | 140 | internal actual fun Modifier.dialogHeight(isWindowDialog: Boolean): Modifier = if (isWindowDialog) { 141 | fillMaxHeight() 142 | } else { 143 | wrapContentHeight() 144 | } 145 | 146 | internal actual fun Modifier.dialogMaxSize(isWindowDialog: Boolean, maxHeight: Dp): Modifier = if (isWindowDialog) { 147 | fillMaxWidth() 148 | } else { 149 | sizeIn(maxHeight = maxHeight, maxWidth = 560.dp) 150 | } 151 | 152 | internal actual fun getLayoutHeight(isWindowDialog: Boolean, maxHeightPx: Int, layoutHeight: Int): Int { 153 | return if (isWindowDialog) { 154 | maxHeightPx 155 | } else { 156 | min(maxHeightPx, layoutHeight) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /datetime/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /datetime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | plugins { 5 | id("common-library") 6 | } 7 | 8 | kotlin { 9 | androidTarget { 10 | publishAllLibraryVariants() 11 | compilations.all { 12 | compileTaskProvider.configure { 13 | compilerOptions { 14 | jvmTarget = JvmTarget.JVM_1_8 15 | } 16 | } 17 | } 18 | } 19 | jvm { 20 | compilations.all { 21 | compileTaskProvider.configure { 22 | compilerOptions { 23 | jvmTarget = JvmTarget.JVM_11 24 | } 25 | } 26 | } 27 | } 28 | iosX64() 29 | iosArm64() 30 | iosSimulatorArm64() 31 | 32 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 33 | applyDefaultHierarchyTemplate { 34 | common { 35 | group("jvmCommon") { 36 | withAndroidTarget() 37 | withJvm() 38 | } 39 | } 40 | } 41 | 42 | sourceSets { 43 | commonMain { 44 | dependencies { 45 | api(projects.composeMaterialDialogsCore) 46 | compileOnly(compose.ui) 47 | compileOnly(compose.foundation) 48 | compileOnly(compose.material) 49 | compileOnly(compose.animation) 50 | api(Dependencies.DateTime.dateTime) 51 | } 52 | } 53 | commonTest { 54 | dependencies { 55 | implementation(kotlin("test-common")) 56 | implementation(kotlin("test-annotations-common")) 57 | } 58 | } 59 | } 60 | } 61 | 62 | android { 63 | namespace = "com.vanpra.composematerialdialogs.datetime" 64 | compileOptions { 65 | isCoreLibraryDesugaringEnabled = true 66 | } 67 | 68 | dependencies { 69 | add("coreLibraryDesugaring", Dependencies.desugar) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /datetime/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/consumer-rules.pro -------------------------------------------------------------------------------- /datetime/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=compose-material-dialogs-datetime 2 | POM_NAME=Compose Material Dialog Date and Time Pickers -------------------------------------------------------------------------------- /datetime/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerBasic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerBasic.png -------------------------------------------------------------------------------- /datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerWithCustomTitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerWithCustomTitle.png -------------------------------------------------------------------------------- /datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerWithRestrictedDates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_datePickerWithRestrictedDates.png -------------------------------------------------------------------------------- /datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePicker24Hour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePicker24Hour.png -------------------------------------------------------------------------------- /datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePickerBasic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePickerBasic.png -------------------------------------------------------------------------------- /datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePickerWithCustomTitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/datetime/screenshots/debug/com.vanpra.composematerialdialogs.datetime.test.screenshot.DateTimePickerTest_timePickerWithCustomTitle.png -------------------------------------------------------------------------------- /datetime/src/androidMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/AndroidUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import android.graphics.Paint 4 | import android.graphics.Rect 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Canvas 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.graphics.nativeCanvas 9 | import androidx.compose.ui.graphics.toArgb 10 | import androidx.compose.ui.platform.LocalConfiguration 11 | import kotlin.math.abs 12 | import kotlin.math.cos 13 | import kotlin.math.sin 14 | 15 | @Composable 16 | internal actual fun isSmallDevice(): Boolean { 17 | return LocalConfiguration.current.screenWidthDp <= 360 18 | } 19 | 20 | @Composable 21 | internal actual fun isLargeDevice(): Boolean { 22 | return LocalConfiguration.current.screenWidthDp <= 600 23 | } 24 | 25 | internal actual fun Canvas.drawText( 26 | text: String, 27 | x: Float, 28 | y: Float, 29 | color: Color, 30 | textSize: Float, 31 | angle: Float, 32 | radius: Float, 33 | isCenter: Boolean?, 34 | alpha: Int, 35 | ) { 36 | val outerText = Paint() 37 | outerText.color = color.toArgb() 38 | outerText.textSize = textSize 39 | outerText.textAlign = 40 | if (isCenter == true) Paint.Align.CENTER else if (isCenter == false) Paint.Align.LEFT else Paint.Align.RIGHT 41 | outerText.alpha = maxOf(0, minOf(alpha * 255, 255)) 42 | 43 | val r = Rect() 44 | outerText.getTextBounds(text, 0, text.length, r) 45 | 46 | nativeCanvas.drawText( 47 | text, 48 | x + (radius * cos(angle)), 49 | y + (radius * sin(angle)) + (abs(r.height())) / 2, 50 | outerText 51 | ) 52 | } -------------------------------------------------------------------------------- /datetime/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /datetime/src/androidTest/java/com/vanpra/composematerialdialogs/datetime/test/functional/DatePickerTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.test.functional 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.compose.ui.test.onNodeWithText 5 | import androidx.compose.ui.test.performClick 6 | import androidx.test.ext.junit.runners.AndroidJUnit4 7 | import com.vanpra.composematerialdialogs.datetime.date.datepicker 8 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 9 | import com.vanpra.composematerialdialogs.test.utils.defaultButtons 10 | import com.vanpra.composematerialdialogs.test.utils.extensions.assertDialogDoesNotExist 11 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialogDateSelector 12 | import com.vanpra.composematerialdialogs.test.utils.extensions.onPositiveButton 13 | import org.junit.Assert.assertEquals 14 | import org.junit.Rule 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | import java.time.LocalDate 18 | 19 | @RunWith(AndroidJUnit4::class) 20 | class DatePickerTest { 21 | @get:Rule 22 | val composeTestRule = createComposeRule() 23 | 24 | private val testDate = LocalDate.of(2021, 1, 1) 25 | 26 | @Test 27 | fun datePickerDialogWaitForPositiveButton() { 28 | var selectedDate: LocalDate? = null 29 | 30 | composeTestRule.setContent { 31 | DialogWithContent(buttons = { defaultButtons() }) { 32 | datepicker(initialDate = testDate, waitForPositiveButton = true) { 33 | selectedDate = it 34 | } 35 | } 36 | } 37 | 38 | composeTestRule.onDialogDateSelector(20).performClick() 39 | assertEquals(null, selectedDate) 40 | composeTestRule.onPositiveButton().performClick() 41 | /* Need this line or else tests don't wait for dialog to close */ 42 | composeTestRule.assertDialogDoesNotExist() 43 | assertEquals(LocalDate.of(2021, 1, 20), selectedDate) 44 | } 45 | 46 | @Test 47 | fun datePickerDialogDontWaitForPositiveButton() { 48 | var selectedDate: LocalDate? = null 49 | 50 | composeTestRule.setContent { 51 | DialogWithContent(buttons = { defaultButtons() }) { 52 | datepicker(initialDate = testDate, waitForPositiveButton = false) { 53 | selectedDate = it 54 | } 55 | } 56 | } 57 | 58 | composeTestRule.onDialogDateSelector(20).performClick() 59 | composeTestRule.waitForIdle() 60 | assertEquals(LocalDate.of(2021, 1, 20), selectedDate) 61 | selectedDate = null 62 | composeTestRule.onPositiveButton().performClick() 63 | /* Need this line or else tests don't wait for dialog to close */ 64 | composeTestRule.assertDialogDoesNotExist() 65 | assertEquals(null, selectedDate) 66 | } 67 | 68 | @Test 69 | fun datePickerCustomTitle() { 70 | val title = "Custom Title" 71 | composeTestRule.setContent { 72 | DialogWithContent(buttons = { defaultButtons() }) { 73 | datepicker(title = title) 74 | } 75 | } 76 | 77 | composeTestRule.onNodeWithText(title).assertExists() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /datetime/src/androidTest/java/com/vanpra/composematerialdialogs/datetime/test/functional/TimePickerTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.test.functional 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.compose.ui.test.onNodeWithText 5 | import com.vanpra.composematerialdialogs.datetime.time.timepicker 6 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 7 | import com.vanpra.composematerialdialogs.test.utils.defaultButtons 8 | import org.junit.Rule 9 | import org.junit.Test 10 | 11 | class TimePickerTest { 12 | @get:Rule 13 | val composeTestRule = createComposeRule() 14 | 15 | @Test 16 | fun timePickerCustomTitle() { 17 | val title = "Custom Title" 18 | composeTestRule.setContent { 19 | DialogWithContent(buttons = { defaultButtons() }) { 20 | timepicker(title = title) 21 | } 22 | } 23 | 24 | composeTestRule.onNodeWithText(title).assertExists() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /datetime/src/androidTest/java/com/vanpra/composematerialdialogs/datetime/test/screenshot/DateTimePickerTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.test.screenshot 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import com.karumi.shot.ScreenshotTest 5 | import com.vanpra.composematerialdialogs.datetime.date.datepicker 6 | import com.vanpra.composematerialdialogs.datetime.time.timepicker 7 | import com.vanpra.composematerialdialogs.test.utils.DialogWithContent 8 | import com.vanpra.composematerialdialogs.test.utils.extensions.onDialog 9 | import com.vanpra.composematerialdialogs.test.utils.extensions.setContentAndWaitForIdle 10 | import org.junit.Rule 11 | import org.junit.Test 12 | import java.time.DayOfWeek 13 | import java.time.LocalDate 14 | import java.time.LocalTime 15 | 16 | class DateTimePickerTest : ScreenshotTest { 17 | @get:Rule 18 | val composeTestRule = createComposeRule() 19 | 20 | private val testTitle = "Custom Title" 21 | 22 | @Test 23 | fun datePickerBasic() { 24 | composeTestRule.setContentAndWaitForIdle { 25 | DialogWithContent { 26 | datepicker(initialDate = LocalDate.of(2021, 1, 1)) 27 | } 28 | } 29 | compareScreenshot(composeTestRule.onDialog()) 30 | } 31 | 32 | @Test 33 | fun timePickerBasic() { 34 | composeTestRule.setContentAndWaitForIdle { 35 | DialogWithContent { 36 | timepicker(initialTime = LocalTime.of(12, 0)) 37 | } 38 | } 39 | compareScreenshot(composeTestRule.onDialog()) 40 | } 41 | 42 | @Test 43 | fun timePicker24Hour() { 44 | composeTestRule.setContentAndWaitForIdle { 45 | DialogWithContent { 46 | timepicker( 47 | initialTime = LocalTime.of(12, 0), 48 | is24HourClock = true 49 | ) 50 | } 51 | } 52 | compareScreenshot(composeTestRule.onDialog()) 53 | } 54 | 55 | @Test 56 | fun datePickerWithCustomTitle() { 57 | composeTestRule.setContentAndWaitForIdle { 58 | DialogWithContent { 59 | datepicker(title = testTitle, initialDate = LocalDate.of(2021, 7, 27)) 60 | } 61 | } 62 | 63 | compareScreenshot(composeTestRule.onDialog()) 64 | } 65 | 66 | @Test 67 | fun datePickerWithRestrictedDates() { 68 | composeTestRule.setContentAndWaitForIdle { 69 | DialogWithContent { 70 | datepicker(title = testTitle, initialDate = LocalDate.of(2021, 7, 27), allowedDateValidator = { 71 | it.dayOfWeek != DayOfWeek.SATURDAY && it.dayOfWeek != DayOfWeek.SUNDAY 72 | }) 73 | } 74 | } 75 | 76 | compareScreenshot(composeTestRule.onDialog()) 77 | } 78 | 79 | @Test 80 | fun timePickerWithCustomTitle() { 81 | composeTestRule.setContentAndWaitForIdle { 82 | DialogWithContent { 83 | timepicker(title = testTitle, initialTime = LocalTime.of(19, 0)) 84 | } 85 | } 86 | 87 | compareScreenshot(composeTestRule.onDialog()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /datetime/src/androidTest/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Let us help apps determine location. This means sending 3 | anonymous location data to us, even when no apps are running 4 | Use Location Services? 5 | Phone Ringtone 6 | Label as: 7 | Set backup account 8 | Enter you name: 9 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/date/DatePickerColors.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.date 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.State 5 | import androidx.compose.runtime.rememberUpdatedState 6 | import androidx.compose.ui.graphics.Color 7 | 8 | /** 9 | * Represents the colors used by a [timepicker] and its parts in different states 10 | * 11 | * See [DatePickerDefaults.colors] for the default implementation 12 | */ 13 | interface DatePickerColors { 14 | val headerBackgroundColor: Color 15 | val headerTextColor: Color 16 | val calendarHeaderTextColor: Color 17 | 18 | /** 19 | * Gets the background color dependant on if the item is active or not 20 | * 21 | * @param active true if the component/item is selected and false otherwise 22 | * @return background color as a State 23 | */ 24 | @Composable 25 | fun dateBackgroundColor(active: Boolean): State 26 | 27 | /** 28 | * Gets the text color dependant on if the item is active or not 29 | * 30 | * @param active true if the component/item is selected and false otherwise 31 | * @return text color as a State 32 | */ 33 | @Composable 34 | fun dateTextColor(active: Boolean): State 35 | } 36 | 37 | internal class DefaultDatePickerColors( 38 | override val headerBackgroundColor: Color, 39 | override val headerTextColor: Color, 40 | override val calendarHeaderTextColor: Color, 41 | private val dateActiveBackgroundColor: Color, 42 | private val dateInactiveBackgroundColor: Color, 43 | private val dateActiveTextColor: Color, 44 | private val dateInactiveTextColor: Color 45 | ) : DatePickerColors { 46 | @Composable 47 | override fun dateBackgroundColor(active: Boolean): State { 48 | return rememberUpdatedState(if (active) dateActiveBackgroundColor else dateInactiveBackgroundColor) 49 | } 50 | 51 | @Composable 52 | override fun dateTextColor(active: Boolean): State { 53 | return rememberUpdatedState(if (active) dateActiveTextColor else dateInactiveTextColor) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/date/DatePickerDefaults.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.date 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.graphics.Color 6 | 7 | /** 8 | * Object to hold default values used by [datepicker] 9 | */ 10 | object DatePickerDefaults { 11 | /** 12 | * Initialises a [DatePickerColors] object which represents the different colors used by 13 | * the [datepicker] composable 14 | * @param headerBackgroundColor background color of header 15 | * @param headerTextColor color of text on the header 16 | * @param calendarHeaderTextColor color of text on the calendar header (year selector 17 | * and days of week) 18 | * @param dateActiveBackgroundColor background color of date when selected 19 | * @param dateActiveTextColor color of date text when selected 20 | * @param dateInactiveBackgroundColor background color of date when not selected 21 | * @param dateInactiveTextColor color of date text when not selected 22 | */ 23 | @Composable 24 | fun colors( 25 | headerBackgroundColor: Color = MaterialTheme.colors.primary, 26 | headerTextColor: Color = MaterialTheme.colors.onPrimary, 27 | calendarHeaderTextColor: Color = MaterialTheme.colors.onBackground, 28 | dateActiveBackgroundColor: Color = MaterialTheme.colors.primary, 29 | dateInactiveBackgroundColor: Color = Color.Transparent, 30 | dateActiveTextColor: Color = MaterialTheme.colors.onPrimary, 31 | dateInactiveTextColor: Color = MaterialTheme.colors.onBackground 32 | ): DatePickerColors { 33 | return DefaultDatePickerColors( 34 | headerBackgroundColor = headerBackgroundColor, 35 | headerTextColor = headerTextColor, 36 | calendarHeaderTextColor = calendarHeaderTextColor, 37 | dateActiveBackgroundColor = dateActiveBackgroundColor, 38 | dateInactiveBackgroundColor = dateInactiveBackgroundColor, 39 | dateActiveTextColor = dateActiveTextColor, 40 | dateInactiveTextColor = dateInactiveTextColor, 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/date/DatePickerState.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.date 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import androidx.compose.ui.graphics.Color 7 | import kotlinx.datetime.LocalDate 8 | 9 | internal class DatePickerState( 10 | initialDate: LocalDate, 11 | val colors: DatePickerColors, 12 | val yearRange: IntRange, 13 | val dialogBackground: Color 14 | ) { 15 | var selected by mutableStateOf(initialDate) 16 | var yearPickerShowing by mutableStateOf(false) 17 | } 18 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/time/TimePickerColors.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.time 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.State 6 | import androidx.compose.runtime.rememberUpdatedState 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.unit.dp 9 | 10 | /** 11 | * Represents the colors used by a [timepicker] and its parts in different states 12 | * 13 | * See [TimePickerDefaults.colors] for the default implementation 14 | */ 15 | interface TimePickerColors { 16 | val border: BorderStroke 17 | 18 | /** 19 | * Gets the background color dependant on if the item is active or not 20 | * 21 | * @param active true if the component/item is selected and false otherwise 22 | * @return background color as a State 23 | */ 24 | @Composable 25 | fun backgroundColor(active: Boolean): State 26 | 27 | /** 28 | * Gets the text color dependant on if the item is active or not 29 | * 30 | * @param active true if the component/item is selected and false otherwise 31 | * @return text color as a State 32 | */ 33 | @Composable 34 | fun textColor(active: Boolean): State 35 | 36 | /** 37 | * Get the color of clock hand and color of text in clock hand 38 | */ 39 | fun selectorColor(): Color 40 | fun selectorTextColor(): Color 41 | 42 | /** 43 | * Get color of title text 44 | */ 45 | fun headerTextColor(): Color 46 | 47 | @Composable 48 | fun periodBackgroundColor(active: Boolean): State 49 | } 50 | 51 | internal class DefaultTimePickerColors( 52 | private val activeBackgroundColor: Color, 53 | private val inactiveBackgroundColor: Color, 54 | private val activeTextColor: Color, 55 | private val inactiveTextColor: Color, 56 | private val inactivePeriodBackground: Color, 57 | private val selectorColor: Color, 58 | private val selectorTextColor: Color, 59 | private val headerTextColor: Color, 60 | borderColor: Color 61 | ) : TimePickerColors { 62 | override val border = BorderStroke(1.dp, borderColor) 63 | 64 | @Composable 65 | override fun backgroundColor(active: Boolean): State { 66 | return rememberUpdatedState(if (active) activeBackgroundColor else inactiveBackgroundColor) 67 | } 68 | 69 | @Composable 70 | override fun textColor(active: Boolean): State { 71 | return rememberUpdatedState(if (active) activeTextColor else inactiveTextColor) 72 | } 73 | 74 | override fun selectorColor(): Color { 75 | return selectorColor 76 | } 77 | 78 | override fun selectorTextColor(): Color { 79 | return selectorTextColor 80 | } 81 | 82 | override fun headerTextColor(): Color { 83 | return headerTextColor 84 | } 85 | 86 | @Composable 87 | override fun periodBackgroundColor(active: Boolean): State { 88 | return rememberUpdatedState(if (active) activeBackgroundColor else inactivePeriodBackground) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/time/TimePickerDefaults.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.time 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.graphics.Color 6 | 7 | /** 8 | * Object to hold default values used by [timepicker] 9 | */ 10 | object TimePickerDefaults { 11 | /** 12 | * Initialises a [TimePickerColors] object which represents the different colors used by 13 | * the [timepicker] composable 14 | * 15 | * @param activeBackgroundColor background color of selected time unit or period (AM/PM) 16 | * @param inactiveBackgroundColor background color of inactive items in the dialog including 17 | * the clock face 18 | * @param activeTextColor color of text on the activeBackgroundColor 19 | * @param inactiveTextColor color of text on the inactiveBackgroundColor 20 | * @param inactivePeriodBackground background color of the inactive period (AM/PM) selector 21 | * @param selectorColor color of clock hand/selector 22 | * @param selectorTextColor color of text on selectedColor 23 | * @param headerTextColor Get color of title text 24 | * @param borderColor border color of the period (AM/PM) selector 25 | */ 26 | @Composable 27 | fun colors( 28 | activeBackgroundColor: Color = MaterialTheme.colors.primary.copy(0.3f), 29 | inactiveBackgroundColor: Color = MaterialTheme.colors.onBackground.copy(0.3f), 30 | activeTextColor: Color = MaterialTheme.colors.onPrimary, 31 | inactiveTextColor: Color = MaterialTheme.colors.onBackground, 32 | inactivePeriodBackground: Color = Color.Transparent, 33 | selectorColor: Color = MaterialTheme.colors.primary, 34 | selectorTextColor: Color = MaterialTheme.colors.onPrimary, 35 | headerTextColor: Color = MaterialTheme.colors.onBackground, 36 | borderColor: Color = MaterialTheme.colors.onBackground, 37 | ): TimePickerColors { 38 | return DefaultTimePickerColors( 39 | activeBackgroundColor = activeBackgroundColor, 40 | inactiveBackgroundColor = inactiveBackgroundColor, 41 | activeTextColor = activeTextColor, 42 | inactiveTextColor = inactiveTextColor, 43 | inactivePeriodBackground = inactivePeriodBackground, 44 | selectorColor = selectorColor, 45 | selectorTextColor = selectorTextColor, 46 | headerTextColor = headerTextColor, 47 | borderColor = borderColor 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/time/TimePickerState.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.time 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import com.vanpra.composematerialdialogs.datetime.util.isAM 7 | import kotlinx.datetime.LocalTime 8 | 9 | internal enum class ClockScreen { 10 | Hour, 11 | Minute; 12 | 13 | fun isHour() = this == Hour 14 | fun isMinute() = this == Minute 15 | } 16 | 17 | internal class TimePickerState( 18 | val colors: TimePickerColors, 19 | selectedTime: LocalTime, 20 | currentScreen: ClockScreen = ClockScreen.Hour, 21 | clockInput: Boolean = true, 22 | timeRange: ClosedRange, 23 | is24Hour: Boolean, 24 | ) { 25 | var selectedTime by mutableStateOf(selectedTime) 26 | var timeRange by mutableStateOf(timeRange) 27 | var is24Hour by mutableStateOf(is24Hour) 28 | var currentScreen by mutableStateOf(currentScreen) 29 | var clockInput by mutableStateOf(clockInput) 30 | 31 | private fun minimumMinute(isAM: Boolean, hour: Int): Int { 32 | return when { 33 | isAM == timeRange.start.isAM -> 34 | if (timeRange.start.hour == hour) { 35 | timeRange.start.minute 36 | } else { 37 | 0 38 | } 39 | isAM -> 61 40 | else -> 0 41 | } 42 | } 43 | 44 | private fun maximumMinute(isAM: Boolean, hour: Int): Int { 45 | return when { 46 | isAM == timeRange.endInclusive.isAM -> 47 | if (timeRange.endInclusive.hour == hour) { 48 | timeRange.endInclusive.minute 49 | } else { 50 | 60 51 | } 52 | isAM -> 60 53 | else -> 0 54 | } 55 | } 56 | 57 | fun hourRange() = timeRange.start.hour..timeRange.endInclusive.hour 58 | 59 | fun minuteRange(isAM: Boolean, hour: Int) = minimumMinute(isAM, hour)..maximumMinute(isAM, hour) 60 | } 61 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/Composables.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.foundation.layout.wrapContentWidth 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.text.TextStyle 11 | import androidx.compose.ui.text.font.FontWeight 12 | import androidx.compose.ui.unit.sp 13 | 14 | @Composable 15 | internal fun DialogTitle(text: String, modifier: Modifier = Modifier) { 16 | Text( 17 | text, 18 | modifier = modifier 19 | .fillMaxWidth() 20 | .wrapContentWidth(Alignment.CenterHorizontally), 21 | color = MaterialTheme.colors.onBackground, 22 | fontSize = 20.sp, 23 | style = TextStyle(fontWeight = FontWeight.W600) 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.ui.geometry.Offset 4 | import androidx.compose.ui.text.intl.Locale 5 | import kotlinx.datetime.DayOfWeek 6 | import kotlinx.datetime.LocalDate 7 | import kotlinx.datetime.LocalTime 8 | import kotlinx.datetime.Month 9 | import kotlin.math.cos 10 | import kotlin.math.sin 11 | 12 | internal fun Float.getOffset(angle: Double): Offset = 13 | Offset((this * cos(angle)).toFloat(), (this * sin(angle)).toFloat()) 14 | 15 | /*internal val LocalDate.yearMonth: YearMonth 16 | get() = YearMonth.of(this.year, this.month)*/ 17 | 18 | internal expect val LocalDate.isLeapYear: Boolean 19 | 20 | internal val LocalTime.isAM: Boolean 21 | get() = this.hour in 0..11 22 | 23 | internal val LocalTime.simpleHour: Int 24 | get() { 25 | val tempHour = this.hour % 12 26 | return if (tempHour == 0) 12 else tempHour 27 | } 28 | 29 | internal expect fun Month.getShortLocalName(locale: Locale): String 30 | 31 | internal expect fun Month.getFullLocalName(locale: Locale): String 32 | 33 | internal expect fun DayOfWeek.getShortLocalName(locale: Locale): String 34 | 35 | internal expect fun Month.testLength(year: Int, isLeapYear: Boolean): Int 36 | 37 | internal fun LocalTime.toAM(): LocalTime = if (this.isAM) this else this.minusHours(12) 38 | internal fun LocalTime.toPM(): LocalTime = if (!this.isAM) this else this.plusHours(12) 39 | 40 | internal expect fun LocalTime.minusHours(hoursToSubtract: Long): LocalTime 41 | internal expect fun LocalTime.plusHours(hoursToAdd: Long): LocalTime 42 | 43 | internal fun LocalTime.noSeconds(): LocalTime = LocalTime(this.hour, this.minute, 0, 0) 44 | 45 | internal fun LocalTime.withHour(hour: Int): LocalTime = LocalTime(hour, this.minute, this.second, this.nanosecond) 46 | 47 | internal fun LocalTime.withMinute(minute: Int): LocalTime = LocalTime(this.hour, minute, this.second, this.nanosecond) 48 | 49 | internal fun LocalTime.withSecond(second: Int): LocalTime = LocalTime(this.hour, this.minute, second, this.nanosecond) 50 | 51 | internal fun LocalTime.withNanosecond(nanosecond: Int): LocalTime = LocalTime(this.hour, this.minute, this.second, nanosecond) 52 | 53 | internal fun LocalDate.withDayOfMonth(dayOfMonth: Int) = LocalDate(this.year, this.month, dayOfMonth) 54 | 55 | private val minTime = LocalTime(0, 0, 0, 0) 56 | internal val LocalTime.Companion.Min: LocalTime get() = minTime 57 | 58 | private val maxTime = LocalTime(23, 59, 59, 999_999_999) 59 | internal val LocalTime.Companion.Max: LocalTime get() = maxTime 60 | 61 | internal expect operator fun DayOfWeek.plus(days: Long): DayOfWeek 62 | 63 | internal expect fun DayOfWeek.getNarrowDisplayName(locale: Locale): String -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.Canvas 5 | import androidx.compose.ui.graphics.Color 6 | 7 | @Composable 8 | internal expect fun isSmallDevice(): Boolean 9 | 10 | @Composable 11 | internal expect fun isLargeDevice(): Boolean 12 | 13 | internal expect fun Canvas.drawText( 14 | text: String, 15 | x: Float, 16 | y: Float, 17 | color: Color, 18 | textSize: Float, 19 | angle: Float, 20 | radius: Float, 21 | isCenter: Boolean?, 22 | alpha: Int, 23 | ) -------------------------------------------------------------------------------- /datetime/src/commonMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/WeekFields.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.ui.text.intl.Locale 4 | import kotlinx.datetime.DayOfWeek 5 | 6 | internal expect class WeekFields { 7 | val firstDayOfWeek: DayOfWeek 8 | companion object { 9 | fun of(locale: Locale): WeekFields 10 | } 11 | } -------------------------------------------------------------------------------- /datetime/src/iosMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/IosUIUtils.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(InternalComposeMaterialDialogsApi::class) 2 | 3 | package com.vanpra.composematerialdialogs.datetime.util 4 | 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Canvas 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.graphics.nativeCanvas 9 | import androidx.compose.ui.graphics.toArgb 10 | import com.vanpra.composematerialdialogs.InternalComposeMaterialDialogsApi 11 | import com.vanpra.composematerialdialogs.getDialogScreenWidthDp 12 | import org.jetbrains.skia.Font 13 | import org.jetbrains.skia.Paint 14 | import org.jetbrains.skia.TextBlobBuilder 15 | import kotlin.math.abs 16 | import kotlin.math.cos 17 | import kotlin.math.sin 18 | 19 | @Composable 20 | internal actual fun isSmallDevice(): Boolean { 21 | return getDialogScreenWidthDp() <= 360 22 | } 23 | 24 | @Composable 25 | internal actual fun isLargeDevice(): Boolean { 26 | return getDialogScreenWidthDp() <= 600 27 | } 28 | 29 | // todo This function needs to be corrected 30 | internal actual fun Canvas.drawText( 31 | text: String, 32 | x: Float, 33 | y: Float, 34 | color: Color, 35 | textSize: Float, 36 | angle: Float, 37 | radius: Float, 38 | isCenter: Boolean?, 39 | alpha: Int, 40 | ) { 41 | val outerText = Paint() 42 | outerText.color = color.toArgb() 43 | 44 | val font = Font() 45 | 46 | nativeCanvas.drawTextBlob( 47 | blob = TextBlobBuilder().apply { 48 | appendRun(font = font, text = text, x = 0f, y = 0f) 49 | }.build()!!, 50 | x = x + (radius * cos(angle)), 51 | y = y + (radius * sin(angle)) + (abs(font.metrics.height)) / 2, 52 | paint = Paint() 53 | ) 54 | 55 | } -------------------------------------------------------------------------------- /datetime/src/iosMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/IosUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.ui.text.intl.Locale 4 | import kotlinx.cinterop.ExperimentalForeignApi 5 | import kotlinx.cinterop.convert 6 | import kotlinx.cinterop.useContents 7 | import kotlinx.datetime.DayOfWeek 8 | import kotlinx.datetime.LocalDate 9 | import kotlinx.datetime.LocalTime 10 | import kotlinx.datetime.Month 11 | import kotlinx.datetime.TimeZone 12 | import kotlinx.datetime.number 13 | import kotlinx.datetime.toKotlinInstant 14 | import kotlinx.datetime.toLocalDateTime 15 | import platform.Foundation.NSCalendar 16 | import platform.Foundation.NSCalendarUnitDay 17 | import platform.Foundation.NSCalendarUnitMonth 18 | import platform.Foundation.NSDateComponents 19 | import platform.Foundation.calendarIdentifier 20 | import platform.Foundation.NSLocale as PlatformLocale 21 | 22 | @OptIn(ExperimentalForeignApi::class) 23 | fun LocalTime.toNSDateComponents(): NSDateComponents { 24 | val components = NSDateComponents() 25 | components.hour = hour.convert() 26 | components.minute = minute.convert() 27 | components.second = second.convert() 28 | components.nanosecond = nanosecond.convert() 29 | return components 30 | } 31 | 32 | internal fun getCalendar(locale: Locale): NSCalendar { 33 | val platformLocale = locale.toPlatform() 34 | return NSCalendar(platformLocale.calendarIdentifier).apply { 35 | this.locale = platformLocale 36 | } 37 | } 38 | 39 | internal actual val LocalDate.isLeapYear: Boolean 40 | get() = if (year % 400 == 0) { 41 | true 42 | } else if (year % 100 == 0) { 43 | false 44 | } else { 45 | year % 4 == 0 46 | } 47 | 48 | private fun NSDateComponents.toKotlinInstant() = NSCalendar.currentCalendar.dateFromComponents(this)!!.toKotlinInstant() 49 | 50 | internal actual fun LocalTime.minusHours(hoursToSubtract: Long): LocalTime = toNSDateComponents().apply { 51 | hour -= hoursToSubtract 52 | }.toKotlinInstant().toLocalDateTime(TimeZone.UTC).time 53 | internal actual fun LocalTime.plusHours(hoursToAdd: Long): LocalTime = toNSDateComponents().apply { 54 | hour += hoursToAdd 55 | }.toKotlinInstant().toLocalDateTime(TimeZone.UTC).time 56 | 57 | internal fun Locale.toPlatform() = PlatformLocale(language) 58 | 59 | internal actual fun Month.getShortLocalName(locale: Locale): String = getCalendar(locale).shortStandaloneMonthSymbols() 60 | .getOrNull(this.ordinal) 61 | .toString() 62 | 63 | internal actual fun Month.getFullLocalName(locale: Locale) = 64 | getCalendar(locale).standaloneMonthSymbols() 65 | .getOrNull(this.ordinal) 66 | .toString() 67 | 68 | internal actual fun DayOfWeek.getShortLocalName(locale: Locale) = getCalendar(locale).shortStandaloneWeekdaySymbols() 69 | .getOrNull(toNSCalendarWeekday()) 70 | .toString() 71 | 72 | @OptIn(ExperimentalForeignApi::class) 73 | internal actual fun Month.testLength(year: Int, isLeapYear: Boolean): Int { 74 | val cal = NSCalendar.currentCalendar() 75 | val dateComponents = NSDateComponents().apply { 76 | this.year = year.convert() 77 | month = number.convert() 78 | } 79 | val date = cal.dateFromComponents(dateComponents)!! 80 | val range = cal.rangeOfUnit(NSCalendarUnitDay, NSCalendarUnitMonth, date) 81 | return range.useContents { length }.convert() 82 | } 83 | 84 | internal actual operator fun DayOfWeek.plus(days: Long): DayOfWeek { 85 | return DayOfWeek.values()[((ordinal + days) % 7).toInt()] 86 | } 87 | 88 | internal actual fun DayOfWeek.getNarrowDisplayName(locale: Locale): String = getCalendar(locale).veryShortWeekdaySymbols() 89 | .getOrNull(toNSCalendarWeekday()) 90 | .toString() 91 | 92 | private fun DayOfWeek.toNSCalendarWeekday() = if (this == DayOfWeek.SUNDAY) { 93 | 0 // SUNDAY is 7 on ISO and 0 on NSCalendar 94 | } else { 95 | this.ordinal + 1 96 | } -------------------------------------------------------------------------------- /datetime/src/iosMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/IosWeekFields.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.ui.text.intl.Locale 4 | import kotlinx.cinterop.ExperimentalForeignApi 5 | import kotlinx.cinterop.convert 6 | import kotlinx.datetime.DayOfWeek 7 | import platform.Foundation.NSCalendar 8 | 9 | internal actual class WeekFields(private val calendar: NSCalendar) { 10 | @OptIn(ExperimentalForeignApi::class) 11 | actual val firstDayOfWeek: DayOfWeek 12 | get() = DayOfWeek(calendar.firstWeekday.convert().toIsoWeekday()) 13 | 14 | actual companion object { 15 | actual fun of(locale: Locale): WeekFields { 16 | return WeekFields(getCalendar(locale)) 17 | } 18 | } 19 | } 20 | 21 | private fun Int.toIsoWeekday() = if (this == 1) { 22 | 7 // Sunday is 1 for NSCalendar 23 | } else { 24 | this - 1 25 | } -------------------------------------------------------------------------------- /datetime/src/jvmCommonMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/JvmExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.ui.text.intl.Locale 4 | import kotlinx.datetime.DayOfWeek 5 | import kotlinx.datetime.LocalDate 6 | import kotlinx.datetime.LocalTime 7 | import kotlinx.datetime.Month 8 | import kotlinx.datetime.toJavaLocalDate 9 | import kotlinx.datetime.toJavaLocalTime 10 | import kotlinx.datetime.toKotlinLocalTime 11 | import java.time.format.TextStyle 12 | 13 | internal actual val LocalDate.isLeapYear: Boolean 14 | get() = toJavaLocalDate().isLeapYear 15 | 16 | internal actual fun LocalTime.minusHours(hoursToSubtract: Long): LocalTime = toJavaLocalTime().minusHours(hoursToSubtract).toKotlinLocalTime() 17 | internal actual fun LocalTime.plusHours(hoursToAdd: Long): LocalTime = toJavaLocalTime().plusHours(hoursToAdd).toKotlinLocalTime() 18 | 19 | internal fun Locale.toPlatform() = java.util.Locale.forLanguageTag(toLanguageTag()) 20 | 21 | internal actual fun Month.getShortLocalName(locale: Locale): String = 22 | this.getDisplayName(TextStyle.SHORT_STANDALONE, locale.toPlatform()) 23 | 24 | internal actual fun Month.getFullLocalName(locale: Locale) = 25 | this.getDisplayName(TextStyle.FULL_STANDALONE, locale.toPlatform()) 26 | 27 | internal actual fun DayOfWeek.getShortLocalName(locale: Locale) = 28 | this.getDisplayName(TextStyle.SHORT_STANDALONE, locale.toPlatform()) 29 | 30 | internal actual fun Month.testLength(year: Int, isLeapYear: Boolean): Int = this.length(isLeapYear) 31 | 32 | internal actual operator fun DayOfWeek.plus(days: Long): DayOfWeek = 33 | plus(days) 34 | 35 | internal actual fun DayOfWeek.getNarrowDisplayName(locale: Locale): String = getDisplayName(TextStyle.NARROW, locale.toPlatform()) -------------------------------------------------------------------------------- /datetime/src/jvmCommonMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/JvmWeekFields.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.ui.text.intl.Locale 4 | import kotlinx.datetime.DayOfWeek 5 | import java.time.temporal.WeekFields as jWeekFields 6 | 7 | internal actual class WeekFields(private val weekFields: jWeekFields) { 8 | actual val firstDayOfWeek: DayOfWeek 9 | get() = weekFields.firstDayOfWeek 10 | 11 | actual companion object { 12 | actual fun of(locale: Locale): WeekFields { 13 | return WeekFields(jWeekFields.of(locale.toPlatform())) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /datetime/src/jvmMain/kotlin/com/vanpra/composematerialdialogs/datetime/util/DesktopUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.datetime.util 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.Canvas 5 | import androidx.compose.ui.graphics.Color 6 | import androidx.compose.ui.graphics.nativeCanvas 7 | import androidx.compose.ui.graphics.toArgb 8 | import org.jetbrains.skia.Font 9 | import org.jetbrains.skia.Paint 10 | import org.jetbrains.skia.TextBlobBuilder 11 | import kotlin.math.abs 12 | import kotlin.math.cos 13 | import kotlin.math.sin 14 | 15 | @Composable 16 | internal actual fun isSmallDevice(): Boolean { 17 | return false 18 | } 19 | 20 | @Composable 21 | internal actual fun isLargeDevice(): Boolean { 22 | return true 23 | } 24 | 25 | // todo This function needs to be corrected 26 | internal actual fun Canvas.drawText( 27 | text: String, 28 | x: Float, 29 | y: Float, 30 | color: Color, 31 | textSize: Float, 32 | angle: Float, 33 | radius: Float, 34 | isCenter: Boolean?, 35 | alpha: Int, 36 | ) { 37 | val outerText = Paint() 38 | outerText.color = color.toArgb() 39 | 40 | val font = Font() 41 | 42 | nativeCanvas.drawTextBlob( 43 | blob = TextBlobBuilder().apply { 44 | appendRun(font = font, text = text, x = 0f, y = 0f) 45 | }.build()!!, 46 | x = x + (radius * cos(angle)), 47 | y = y + (radius * sin(angle)) + (abs(font.metrics.height)) / 2, 48 | paint = Paint() 49 | ) 50 | } -------------------------------------------------------------------------------- /docs/ColorPicker.md: -------------------------------------------------------------------------------- 1 | # Color Picker 2 | 3 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/color_picker.png) 4 | 5 | ### Main Color Picker 6 | 7 | Here is an example of how to add a color picker to a dialog: 8 | 9 | ``` kotlin 10 | val dialogState = rememberMaterialDialogState() 11 | MaterialDialog(dialogState = dialogState) { 12 | ... 13 | colorPicker(colors = ColorPalette.Primary) 14 | ... 15 | } 16 | 17 | /* This should be called in an onClick or an Effect */ 18 | dialogState.show() 19 | ``` 20 | 21 | `ColorPalette.Primary` is a predefined list of colors and can be replaced with a list of custom colors. 22 | 23 | ### Sub Color Picker 24 | 25 | Here is an example of how to add a color picker with sub colors to a dialog: 26 | 27 | ``` kotlin 28 | MaterialDialog(dialogState = dialogState) { 29 | ... 30 | colorPicker(colors = ColorPalette.Primary, subColors = ColorPalette.PrimarySub) 31 | ... 32 | } 33 | ``` 34 | 35 | The `subColors` parameter is passed in a list of list of colors which are show to the user when they click on a color from `colors` list. These lists are matched by the order they appear in the list ie. the first color in `colors` matches with the first list in `subColors` 36 | 37 | ### ARGB Color Picker 38 | 39 | Here is an example of how to add a color picker with custom argb selector to a dialog: 40 | 41 | ``` kotlin 42 | MaterialDialog(dialogState = dialogState) { 43 | ... 44 | colorPicker(colors = ColorPalette.Primary, allowCustomArgb = true) 45 | ... 46 | } 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /docs/DateTimePicker.md: -------------------------------------------------------------------------------- 1 | # Date Time Picker 2 | 3 | ## Prerequisite 4 | 5 | The date time picker relies on parts of the`java.time` API which are only available on Android API levels >= 26. Therefore, in order to make this library backwards comparability with older Android API a few options have to be set in your app/module `build.gradle` file which enables desugaring: 6 | 7 | ````gradle 8 | android { 9 | ... 10 | compileOptions { 11 | // Flag to enable support for the new language APIs 12 | coreLibraryDesugaringEnabled true 13 | 14 | // Sets Java compatibility to Java 8 15 | sourceCompatibility JavaVersion.VERSION_1_8 16 | targetCompatibility JavaVersion.VERSION_1_8 17 | } 18 | ... 19 | } 20 | 21 | dependencies { 22 | ... 23 | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' 24 | } 25 | ```` 26 | 27 | Note, this only has to be done if you intend to target an Android API level < 26. To find out more about desugaring you can check out: https://developer.android.com/studio/write/java8-support#library-desugaring. 28 | 29 | ## Documentation 30 | 31 | ### Date Picker 32 | 33 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/date.png) 34 | 35 | ```kotlin 36 | val dialogState = rememberMaterialDialogState() 37 | MaterialDialog( 38 | dialogState = dialogState, 39 | buttons = { 40 | positiveButton("Ok") 41 | negativeButton("Cancel") 42 | } 43 | ) { 44 | ... 45 | datepicker { date -> 46 | // Do stuff with java.time.LocalDate object which is passed in 47 | } 48 | } 49 | 50 | /* This should be called in an onClick or an Effect */ 51 | dialogState.show() 52 | ``` 53 | 54 | ### Time Picker 55 | 56 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/time.png) 57 | 58 | ```kotlin 59 | val dialogState = rememberMaterialDialogState() 60 | MaterialDialog( 61 | dialogState = dialogState, 62 | buttons = { 63 | positiveButton("Ok") 64 | negativeButton("Cancel") 65 | } 66 | ) { 67 | ... 68 | timepicker { time -> 69 | // Do stuff with java.time.LocalTime object which is passed in 70 | } 71 | ... 72 | } 73 | ``` 74 | 75 | ### Theming 76 | 77 | Both the date and time pickers have a `colors` argument which can be used to modify colors for the background and text of different elements within the dialog. For example to change the header background color of the date picker dialog you can use the following code: 78 | 79 | ```kotlin 80 | datepicker(colors = DatePickerDefaults.colors(headerBackgroundColor = Color.Red)) 81 | ``` 82 | 83 | To find out about other colors which can be changed have a look at the arguments of `DatePickerDefaults.colors` and `TimePickerDefaults.colors`. 84 | -------------------------------------------------------------------------------- /docs/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and reference any bug or feature request this will close. 4 | 5 | ## Type of change 6 | 7 | Please delete options that are not relevant. 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] Documentation update/fix 13 | 14 | ## Checklist 15 | 16 | - [ ] My code follows the style guidelines of this project 17 | - [ ] I have performed a self-review of my own code 18 | - [ ] I have made corresponding changes to the documentation 19 | - [ ] My changes generate no new warnings 20 | - [ ] I have added tests that prove my fix is effective or that my feature works 21 | - [ ] New and existing unit and screenshot tests pass locally with my changes -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Compose Material Dialogs 2 | 3 | ## Core 4 | 5 | #### [Core Documentation](Core.md) 6 | 7 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/full_core.png) 8 | 9 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.vanpra.compose-material-dialogs/core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.vanpra.compose-material-dialogs/core) 10 | 11 | ```gradle 12 | dependencies { 13 | ... 14 | implementation "io.github.vanpra.compose-material-dialogs:core:0.5.2" 15 | ... 16 | } 17 | ``` 18 | 19 | ## Date and Time Picker 20 | 21 | #### [Date and Time Picker Documentation](DateTimePicker.md) 22 | 23 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/date_and_time.png) 24 | 25 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.vanpra.compose-material-dialogs/datetime/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.vanpra.compose-material-dialogs/datetime) 26 | 27 | ```gradle 28 | dependencies { 29 | ... 30 | implementation "io.github.vanpra.compose-material-dialogs:datetime:0.5.2" 31 | ... 32 | } 33 | ``` 34 | 35 | ## Color Picker 36 | 37 | #### [Color Picker Documentation](ColorPicker.md) 38 | 39 | ![](https://raw.githubusercontent.com/vanpra/compose-material-dialogs/main/imgs/color_picker.png) 40 | 41 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.vanpra.compose-material-dialogs/color/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.vanpra.compose-material-dialogs/color) 42 | 43 | ```gradle 44 | dependencies { 45 | ... 46 | implementation "io.github.vanpra.compose-material-dialogs:color:0.5.2" 47 | ... 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 2 | kotlin.code.style=official 3 | 4 | android.useAndroidX=true 5 | android.enableJetifier=false 6 | 7 | kotlin.mpp.stability.nowarn=true 8 | kotlin.mpp.androidSourceSetLayoutVersion=2 9 | kotlin.native.ignoreDisabledTargets=true 10 | 11 | org.jetbrains.compose.experimental.uikit.enabled=true 12 | 13 | # Compose dependencies currently requires to be CompileOny if we want to both 14 | # support Android only projects and multiplatform projects. 15 | # based on the issue below, it seems to only have issues with compileOnly 16 | # when cache is enable, so, based on the currently state of Compose Multiplatform 17 | # the native target also does not work propertly with caching, so, any compose project 18 | # will require `kotlin.native.cacheKind=none` making possible to we continue using compileOnly. 19 | # https://youtrack.jetbrains.com/issue/KT-46377 20 | kotlin.native.ignoreIncorrectDependencies=true 21 | 22 | GROUP=ca.gosyer 23 | VERSION_NAME=0.9.6 24 | 25 | POM_DESCRIPTION=A Material Dialog Builder for Jetpack Compose 26 | POM_INCEPTION_YEAR=2020 27 | 28 | POM_URL=https://github.com/syer10/compose-material-dialogs 29 | POM_SCM_URL=https://github.com/syer10/compose-material-dialogs 30 | POM_SCM_CONNECTION=scm:git:git://github.com/syer10/compose-material-dialogs.git 31 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/syer10/compose-material-dialogs.git 32 | 33 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 34 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 35 | POM_LICENCE_DIST=repo 36 | 37 | POM_DEVELOPER_ID=syer10 38 | POM_DEVELOPER_NAME=Syer10 39 | POM_DEVELOPER_URL=https://github.com/Syer10/ 40 | 41 | SONATYPE_HOST=S01 42 | RELEASE_SIGNING_ENABLED=true 43 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /imgs/basic_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/basic_core.png -------------------------------------------------------------------------------- /imgs/basic_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/basic_list.jpg -------------------------------------------------------------------------------- /imgs/color_picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/color_picker.png -------------------------------------------------------------------------------- /imgs/date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/date.png -------------------------------------------------------------------------------- /imgs/date_and_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/date_and_time.png -------------------------------------------------------------------------------- /imgs/full_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/full_core.png -------------------------------------------------------------------------------- /imgs/input.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/input.jpg -------------------------------------------------------------------------------- /imgs/multi_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/multi_selection.png -------------------------------------------------------------------------------- /imgs/single_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/single_selection.png -------------------------------------------------------------------------------- /imgs/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/imgs/time.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 'Compose Material Dialogs' 2 | site_description: 'A Material Dialog Builder for Jetpack Compose' 3 | site_author: 'Pranav Maganti' 4 | site_url: 'https://vanpra.github.io/compose-material-dialogs/' 5 | remote_branch: gh-pages 6 | 7 | docs_dir: docs 8 | 9 | repo_name: 'compose-material-dialogs' 10 | repo_url: 'https://github.com/vanpra/compose-material-dialogs' 11 | 12 | nav: 13 | - 'Overview': index.md 14 | - 'Core': 15 | - 'Guide': Core.md 16 | - 'API': api/core/index.html 17 | - 'Color': 18 | - 'Guide': ColorPicker.md 19 | - 'API': api/color/index.html 20 | - 'DateTime': 21 | - 'Guide': DateTimePicker.md 22 | - 'API': api/datetime/index.html 23 | 24 | theme: 25 | name: material 26 | palette: 27 | - deep purple: "(prefers-color-scheme: light)" 28 | scheme: default 29 | primary: indigo 30 | accent: indigo 31 | toggle: 32 | icon: material/toggle-switch-off-outline 33 | name: Switch to dark mode 34 | 35 | - media: "(prefers-color-scheme: dark)" 36 | scheme: slate 37 | primary: blue 38 | accent: blue 39 | toggle: 40 | icon: material/toggle-switch 41 | name: Switch to light mode -------------------------------------------------------------------------------- /release/secring.gpg.aes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/release/secring.gpg.aes -------------------------------------------------------------------------------- /release/signing-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2021 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | rm -f release/*.gpg 18 | rm -f release/*.properties 19 | -------------------------------------------------------------------------------- /release/signing-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | ENCRYPT_KEY=$1 18 | 19 | if [[ ! -z "$ENCRYPT_KEY" ]]; then 20 | # Decrypt GnuPG keyring 21 | openssl aes-256-cbc -md sha256 -d -in release/secring.gpg.aes -out release/secring.gpg -k ${ENCRYPT_KEY} 22 | 23 | # Decrypt Play Store key 24 | openssl aes-256-cbc -md sha256 -d -in release/signing.properties.aes -out release/signing.properties -k ${ENCRYPT_KEY} 25 | 26 | else 27 | echo "ENCRYPT_KEY is empty" 28 | fi 29 | -------------------------------------------------------------------------------- /release/signing.properties.aes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/release/signing.properties.aes -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "semanticCommits": "disabled" 7 | } 8 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | google() 6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 7 | } 8 | } 9 | 10 | rootProject.name = "compose-material-dialogs" 11 | include(":compose-material-dialogs-core") 12 | include(":compose-material-dialogs-color") 13 | include(":compose-material-dialogs-datetime") 14 | include(":test-utils") 15 | include(":app:common") 16 | include(":app:android") 17 | include(":app:desktop") 18 | include(":app:ios") 19 | 20 | project(":compose-material-dialogs-core").projectDir = file("core") 21 | project(":compose-material-dialogs-color").projectDir = file("color") 22 | project(":compose-material-dialogs-datetime").projectDir = file("datetime") 23 | 24 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -------------------------------------------------------------------------------- /test-utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /test-utils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.ExperimentalComposeLibrary 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 4 | 5 | plugins { 6 | id("com.android.library") 7 | id("kotlin-android") 8 | id("org.jetbrains.compose") 9 | id("org.jetbrains.kotlin.plugin.compose") 10 | } 11 | 12 | android { 13 | namespace = "com.vanpra.composematerialdialogs.test.utils" 14 | 15 | buildTypes { 16 | getByName("release") { 17 | isMinifyEnabled = false 18 | proguardFiles( 19 | getDefaultProguardFile("proguard-android.txt"), 20 | "proguard-rules.pro" 21 | ) 22 | } 23 | } 24 | 25 | packagingOptions.excludes.addAll( 26 | listOf( 27 | "META-INF/LICENSE", 28 | "META-INF/AL2.0", 29 | "META-INF/**", 30 | "META-INF/*.kotlin_module", 31 | "META-INF/*.kotlin_module" 32 | ) 33 | ) 34 | 35 | buildFeatures { 36 | buildConfig = false 37 | compose = true 38 | } 39 | } 40 | 41 | dependencies { 42 | api(projects.composeMaterialDialogsCore) 43 | 44 | implementation(compose.ui) 45 | implementation(compose.material) 46 | implementation(compose.materialIconsExtended) 47 | implementation(Dependencies.AndroidX.Compose.activity) 48 | 49 | implementation(compose.desktop.uiTestJUnit4) 50 | implementation(Dependencies.Shot.android) 51 | } 52 | 53 | tasks.withType { 54 | compilerOptions { 55 | jvmTarget = JvmTarget.JVM_1_8 56 | } 57 | } -------------------------------------------------------------------------------- /test-utils/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Syer10/compose-material-dialogs/9c3389f752a139baa1fdfdc86e639775d060ef89/test-utils/consumer-rules.pro -------------------------------------------------------------------------------- /test-utils/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /test-utils/src/main/java/com/vanpra/composematerialdialogs/test/utils/DialogTestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.SideEffect 5 | import com.vanpra.composematerialdialogs.MaterialDialog 6 | import com.vanpra.composematerialdialogs.MaterialDialogButtons 7 | import com.vanpra.composematerialdialogs.MaterialDialogScope 8 | import com.vanpra.composematerialdialogs.MaterialDialogState 9 | import com.vanpra.composematerialdialogs.rememberMaterialDialogState 10 | 11 | @Composable 12 | fun DialogWithContent( 13 | autoDismiss: Boolean = true, 14 | dialogState: MaterialDialogState = rememberMaterialDialogState(true), 15 | buttons: @Composable MaterialDialogButtons.() -> Unit = {}, 16 | content: @Composable MaterialDialogScope.() -> Unit = {} 17 | ) { 18 | MaterialDialog(dialogState = dialogState, buttons = buttons, autoDismiss = autoDismiss) { 19 | content() 20 | } 21 | SideEffect { dialogState.show() } 22 | } 23 | 24 | @Composable 25 | fun MaterialDialogButtons.defaultButtons() { 26 | negativeButton("Cancel") 27 | positiveButton("Ok") 28 | } 29 | 30 | fun Collection.powerSet(): Set> = when { 31 | isEmpty() -> setOf(setOf()) 32 | else -> drop(1).powerSet().let { it + it.map { rest -> rest + first() } } 33 | } 34 | -------------------------------------------------------------------------------- /test-utils/src/main/java/com/vanpra/composematerialdialogs/test/utils/ScreenshotTestFilter.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.utils 2 | 3 | import com.karumi.shot.ScreenshotTest 4 | import org.junit.runner.Description 5 | import org.junit.runner.manipulation.Filter 6 | 7 | class ScreenshotTestFilter : Filter() { 8 | override fun shouldRun(description: Description): Boolean = 9 | ScreenshotTest::class.java.isAssignableFrom(description.testClass) 10 | 11 | override fun describe(): String = 12 | "all tests implementing com.karumi.shot.ScreenshotTest interface" 13 | } 14 | 15 | class NotScreenshotTestFilter : Filter() { 16 | override fun shouldRun(description: Description): Boolean = 17 | !ScreenshotTest::class.java.isAssignableFrom(description.testClass) 18 | 19 | override fun describe(): String = 20 | "all tests not implementing com.karumi.shot.ScreenshotTest interface" 21 | } 22 | -------------------------------------------------------------------------------- /test-utils/src/main/java/com/vanpra/composematerialdialogs/test/utils/extensions/ColorComposeTestRule.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.utils.extensions 2 | 3 | import androidx.compose.ui.test.junit4.ComposeTestRule 4 | import androidx.compose.ui.test.onNodeWithTag 5 | import java.util.Locale 6 | 7 | enum class ColorPickerSlider { 8 | Alpha, 9 | Red, 10 | Blue, 11 | Green 12 | } 13 | 14 | fun ComposeTestRule.onDialogColorPicker() = 15 | this.onNodeWithTag("dialog_color_picker") 16 | 17 | fun ComposeTestRule.onDialogColorSelector(index: Int) = 18 | this.onNodeWithTag("dialog_color_selector_$index") 19 | 20 | fun ComposeTestRule.onDialogSubColorSelector(index: Int) = 21 | this.onNodeWithTag("dialog_sub_color_selector_$index") 22 | 23 | fun ComposeTestRule.onDialogSubColorBackButton() = 24 | this.onNodeWithTag("dialog_sub_color_back_btn") 25 | 26 | fun ComposeTestRule.onDialogColorSlider(slider: ColorPickerSlider) = 27 | this.onNodeWithTag("dialog_color_picker_${slider.toString().lowercase(Locale.ROOT)}_slider") 28 | -------------------------------------------------------------------------------- /test-utils/src/main/java/com/vanpra/composematerialdialogs/test/utils/extensions/CoreComposeTestRule.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.utils.extensions 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.test.SemanticsNodeInteraction 5 | import androidx.compose.ui.test.junit4.ComposeContentTestRule 6 | import androidx.compose.ui.test.junit4.ComposeTestRule 7 | import androidx.compose.ui.test.onNodeWithTag 8 | import androidx.compose.ui.test.performTouchInput 9 | import androidx.compose.ui.test.swipeUp 10 | 11 | fun ComposeContentTestRule.setContentAndWaitForIdle(content: @Composable () -> Unit) { 12 | this.setContent { 13 | content() 14 | } 15 | this.waitForIdle() 16 | } 17 | 18 | fun ComposeTestRule.onPositiveButton() = 19 | this.onNodeWithTag("positive") 20 | 21 | fun ComposeTestRule.onNegativeButton() = 22 | this.onNodeWithTag("negative") 23 | 24 | fun ComposeTestRule.onDialog() = 25 | this.onNodeWithTag("dialog") 26 | 27 | fun ComposeTestRule.assertDialogExists() = 28 | this.onDialog().assertExists() 29 | 30 | fun ComposeTestRule.assertDialogDoesNotExist() = 31 | this.onDialog().assertDoesNotExist() 32 | 33 | fun ComposeTestRule.onDialogList() = 34 | this.onNodeWithTag("dialog_list") 35 | 36 | fun ComposeTestRule.onDialogListItem(index: Int): SemanticsNodeInteraction { 37 | try { 38 | onNodeWithTag("dialog_list_item_$index").assertExists() 39 | } catch (e: AssertionError) { 40 | onDialogList().performTouchInput { swipeUp() } 41 | waitForIdle() 42 | } 43 | 44 | return onNodeWithTag("dialog_list_item_$index").assertExists() 45 | } 46 | 47 | fun ComposeTestRule.onDialogInput() = 48 | this.onNodeWithTag("dialog_input") 49 | 50 | fun ComposeTestRule.onDialogInputError() = 51 | this.onNodeWithTag("dialog_input_error") 52 | -------------------------------------------------------------------------------- /test-utils/src/main/java/com/vanpra/composematerialdialogs/test/utils/extensions/DatetimeComposeTestRule.kt: -------------------------------------------------------------------------------- 1 | package com.vanpra.composematerialdialogs.test.utils.extensions 2 | 3 | import androidx.compose.ui.test.junit4.ComposeTestRule 4 | import androidx.compose.ui.test.onAllNodesWithTag 5 | 6 | fun ComposeTestRule.onDialogDateSelector(date: Int) = 7 | this.onAllNodesWithTag("dialog_date_selection_$date")[0] 8 | 9 | fun ComposeTestRule.onDialogDatePrevMonth() = 10 | this.onAllNodesWithTag("dialog_date_prev_month")[0] 11 | 12 | fun ComposeTestRule.onDialogDateNextMonth() = 13 | this.onAllNodesWithTag("dialog_date_next_month")[0] 14 | --------------------------------------------------------------------------------