├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── app ├── .gitignore ├── SAHEL-FONT-LICENSE ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xdev │ │ └── arch │ │ └── persianlibrary │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── font │ └── sahel.ttf │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── persiancalendar ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xdev │ │ └── arch │ │ └── persiancalendar │ │ └── datepicker │ │ ├── CalendarConstraints.kt │ │ ├── CalendarItemStyle.kt │ │ ├── CalendarStyle.kt │ │ ├── DateSelector.kt │ │ ├── DateValidatorPointForward.kt │ │ ├── DaysOfWeekAdapter.kt │ │ ├── MaterialCalendar.kt │ │ ├── MaterialCalendarGridView.kt │ │ ├── MaterialDatePicker.kt │ │ ├── MaterialPickerOnPositiveButtonClickListener.kt │ │ ├── Month.kt │ │ ├── MonthAdapter.kt │ │ ├── MonthsPagerAdapter.kt │ │ ├── OnSelectionChangedListener.kt │ │ ├── PickerFragment.kt │ │ ├── RangeDateSelector.kt │ │ ├── RtlGridLayoutManager.kt │ │ ├── SimpleTextView.kt │ │ ├── SingleDateSelector.kt │ │ ├── SmoothCalendarLayoutManager.kt │ │ ├── YearGridAdapter.kt │ │ ├── calendar │ │ └── PersianCalendar.kt │ │ └── utils │ │ ├── DateStrings.kt │ │ ├── UtcDates.kt │ │ └── Utils.kt │ └── res │ ├── color │ ├── calendar_selected_range.xml │ └── text_btn_text_color_selector.xml │ ├── drawable │ ├── btn_ripple_background.xml │ ├── dialog_background.xml │ ├── ic_arrow_left.xml │ ├── ic_arrow_right.xml │ ├── ic_menu_arrow_down.xml │ ├── nvn_btn_ripple_background.xml │ └── nvn_toggle_btn_ripple_background.xml │ ├── layout-land │ ├── calendar_month.xml │ ├── calendar_months.xml │ ├── picker_dialog.xml │ ├── picker_header_dialog.xml │ ├── picker_header_selection_text.xml │ └── picker_header_title_text.xml │ ├── layout │ ├── calendar_day.xml │ ├── calendar_day_of_week.xml │ ├── calendar_days_of_week.xml │ ├── calendar_horizontal.xml │ ├── calendar_month.xml │ ├── calendar_month_labeled.xml │ ├── calendar_month_navigation.xml │ ├── calendar_months.xml │ ├── calendar_year.xml │ ├── picker_actions.xml │ ├── picker_dialog.xml │ ├── picker_header_dialog.xml │ ├── picker_header_selection_text.xml │ └── picker_header_title_text.xml │ ├── values-h360dp-land-v13 │ └── dimens.xml │ ├── values-h480dp-land-v13 │ └── dimens.xml │ ├── values-land │ ├── dimens.xml │ ├── integers.xml │ └── strings.xml │ ├── values-v28 │ └── dimens.xml │ ├── values-w360dp-port-v13 │ └── dimens.xml │ ├── values-w480dp-port-v13 │ └── dimens.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── integers.xml │ ├── public.xml │ ├── strings.xml │ └── styles.xml ├── screenshots ├── date_picker_land.png ├── date_picker_portrait.png ├── land.gif ├── portrait.gif ├── range_picker_land.png ├── range_picker_portrait.png └── range_picker_year_portrait.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | /.idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PersianDatePicker [ ![Download](https://api.bintray.com/packages/xdeveloper/PersianDatePicker/com.xdev.arch.persiancalendar.datepicker/images/download.svg) ](https://bintray.com/xdeveloper/PersianDatePicker/com.xdev.arch.persiancalendar.datepicker/_latestVersion) 2 | Shamsi/Jalali date picker with material design ([Google Material Components Date Picker](https://github.com/material-components/material-components-android)) 3 | 4 |

5 | Single Selection Demo Portrait  6 | Range Selection Demo Portrait  7 | Range Selection Year Demo Portrait 8 |

9 | 10 |

11 | Single Selection Demo Land  12 | Range Selection Demo Land 13 |

14 | 15 | This library is based on [Googles Material Date Picker](https://github.com/material-components/material-components-android). 16 | I reimplemented everything in Kotlin. 17 | 18 | ## How To Use 19 | Minimum SDK: 21 20 | 21 | Add this to your gradle file 22 | ```groovy 23 | implementation 'com.xdev.arch.persiancalendar.datepicker:datepicker:0.3.2' 24 | ``` 25 | 26 | ## Customization 27 | The picker can be customized via the [MaterialDatePicker.Builder](https://github.com/axdeveloper/PersianDatePicker/blob/master/persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/MaterialDatePicker.kt) 28 | and the 29 | [CalendarConstraints.Builder](https://github.com/axdeveloper/PersianDatePicker/blob/master/persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/CalendarConstraints.kt). 30 | These classes allow you to 31 | 32 | - Select the mode: single date or range of dates. 33 | - Select the bounds: bounds can be restricted to any contiguous set of months. Defaults Farvardin, 1388. to Esfand, 1409. 34 | - Select valid days: valid days can restrict selections to weekdays only. Defaults to all days as valid. 35 | - Set a title. 36 | - Set the month to which the picker opens (defaults to the current month if within the bounds otherwise the earliest month within the bounds). 37 | - Set a default selection (defaults to no selection). 38 | 39 | ## Examples 40 | First you can define a [CalendarConstraints](https://github.com/axdeveloper/PersianDatePicker/blob/master/persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/CalendarConstraints.kt) to limit dates 41 | You can use [PersianCalendar](https://github.com/axdeveloper/PersianDatePicker/blob/master/persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/calendar/PersianCalendar.kt) class to set a Persian/Shamsi date or a java.util.Calendar instance 42 | ```kotlin 43 | val calendar = PersianCalendar() 44 | calendar.setPersian(1340, Month.FARVARDIN, 1) 45 | 46 | val start = calendar.timeInMillis 47 | 48 | calendar.setPersian(1409, Month.ESFAND, 29) 49 | val end = calendar.timeInMillis 50 | 51 | val openAt = PersianCalendar.getToday().timeInMillis 52 | 53 | val constraints = CalendarConstraints.Builder() 54 | .setStart(start) 55 | .setEnd(end) 56 | .setOpenAt(openAt) 57 | .setValidator(DateValidatorPointForward.from(start)).build() 58 | ``` 59 | 60 | Then we should create [MaterialDatePicker](https://github.com/axdeveloper/PersianDatePicker/blob/master/persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/MaterialDatePicker.kt) 61 | 62 | ### Single Date Picker 63 | ```kotlin 64 | val datePicker = MaterialDatePicker.Builder 65 | .datePicker() 66 | .setTitleText("تاریخ را انتخاب کنید.") 67 | .setCalendarConstraints(constraints).build() 68 | ``` 69 | ### Range Date Picker 70 | ```kotlin 71 | val rangePicker = MaterialDatePicker.Builder 72 | .dateRangePicker() 73 | .setTitleText("محدوده را انتخاب کنید.") 74 | .setCalendarConstraints(constraints).build() 75 | ``` 76 | And finaly call show() 77 | ```kotlin 78 | datepicker.show(supportFragmentManager, "aTag") 79 | rangePicker.show(supportFragmentManager, "aTag") 80 | ``` 81 | ## Listeners 82 | There are 4 listeners: 83 | - OnPositiveButtonClickListener 84 | - OnNegetiveButtonClickListener 85 | - OnCancelListener 86 | - OnDismissListener 87 | 88 | ## Styling and Attributes 89 | Refer to [Sample's styles.xml](https://github.com/axdeveloper/PersianDatePicker/blob/master/app/src/main/res/values/styles.xml) 90 | 91 | ## Contributors 92 | Rahman Mohammadi [@axdeveloper](https://github.com/axdeveloper) 93 | 94 | ## License 95 | 2020 Rahman Mohammadi ([@axdeveloper](https://github.com/axdeveloper)). See the `LICENSE` file. 96 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/SAHEL-FONT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Saber Rastikerdar (saber.rastikerdar@gmail.com), 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | 95 | 96 | ---------------------------------------------- 97 | Non-Arabic glyphs and data are borrowed from Open Sans font under the Apache License, Version 2.0 -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.3" 8 | defaultConfig { 9 | applicationId "com.xdev.arch.persianlibrary" 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "0.1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | } 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility = 1.8 23 | targetCompatibility = 1.8 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 30 | implementation 'androidx.appcompat:appcompat:1.1.0' 31 | implementation 'androidx.core:core-ktx:1.2.0' 32 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 33 | implementation project(path: ':persiancalendar') 34 | implementation 'com.google.android.material:material:1.1.0' 35 | } 36 | -------------------------------------------------------------------------------- /app/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 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/xdev/arch/persianlibrary/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.xdev.arch.persianlibrary 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.xdev.arch.persiancalendar.datepicker.* 7 | import com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | 10 | class MainActivity : AppCompatActivity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_main) 15 | 16 | val calendar = PersianCalendar() 17 | calendar.setPersian(1340, Month.FARVARDIN, 1) 18 | 19 | val start = calendar.timeInMillis 20 | 21 | calendar.setPersian(1409, Month.ESFAND, 29) 22 | val end = calendar.timeInMillis 23 | 24 | val openAt = PersianCalendar.getToday().timeInMillis 25 | 26 | val constraints = CalendarConstraints.Builder() 27 | .setStart(start) 28 | .setEnd(end) 29 | .setOpenAt(openAt) 30 | .setValidator(DateValidatorPointForward.from(start)).build() 31 | 32 | val rangePicker = MaterialDatePicker.Builder 33 | .dateRangePicker() 34 | .setTitleText("محدوده را انتخاب کنید.") 35 | .setCalendarConstraints(constraints).build() 36 | 37 | rangePicker.addOnPositiveButtonClickListener( 38 | object : MaterialPickerOnPositiveButtonClickListener> { 39 | @SuppressLint("SetTextI18n") 40 | override fun onPositiveButtonClick(selection: Pair) { 41 | val first = PersianCalendar(selection.first!!) 42 | val second = PersianCalendar(selection.second!!) 43 | 44 | result.text = "تاریخ شروع:‌ $first تاریخ پایان:‌ $second" 45 | } 46 | } 47 | ) 48 | 49 | 50 | val datePicker = MaterialDatePicker.Builder 51 | .datePicker() 52 | .setTitleText("تاریخ را انتخاب کنید.") 53 | .setCalendarConstraints(constraints).build() 54 | 55 | datePicker.addOnPositiveButtonClickListener( 56 | object : MaterialPickerOnPositiveButtonClickListener { 57 | @SuppressLint("SetTextI18n") 58 | override fun onPositiveButtonClick(selection: Long?) { 59 | val date = PersianCalendar(selection!!) 60 | 61 | result.text = "تاریخ انتخاب شده:‌ $date" 62 | } 63 | } 64 | ) 65 | 66 | rangeBtn.setOnClickListener { 67 | rangePicker.show(supportFragmentManager, "RangePickerTag") 68 | } 69 | 70 | pickerBtn.setOnClickListener { 71 | datePicker.show(supportFragmentManager, "DatePickerTag") 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/font/sahel.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/font/sahel.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 23 | 24 | 29 | 30 | 31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #F4511E 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Persian Date Picker 3 | از تاریخ 4 | تا تاریخ 5 | انتخاب تاریخ 6 | انتخاب محدوده 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 39 | 40 | 41 | 59 | 60 | 61 | 75 | 76 | 84 | 85 | 93 | 94 | 104 | 105 | 113 | 114 | 123 | 124 | 132 | 133 | 139 | 140 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.61' 3 | repositories { 4 | google() 5 | jcenter() 6 | 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.5.3' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | subprojects { 26 | tasks.withType(Javadoc).all { enabled = false } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 02 15:35:27 IRST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /persiancalendar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /persiancalendar/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'com.jfrog.bintray' 5 | apply plugin: 'com.github.dcendents.android-maven' 6 | 7 | ext { 8 | bintrayRepo = "PersianDatePicker" 9 | bintrayName = "com.xdev.arch.persiancalendar.datepicker" 10 | 11 | libraryName = "persiancalendar" 12 | 13 | publishedGroupId = 'com.xdev.arch.persiancalendar.datepicker' 14 | artifact = 'datepicker' 15 | libraryVersion = '0.3.2' 16 | 17 | libraryDescription = "Shamsi/Jalali date picker with material design (rewritten from Googls's material-components for android in Kotlin)" 18 | siteUrl = 'https://github.com/axdeveloper/PersianDatePicker' 19 | gitUrl = 'https://github.com/axdeveloper/PersianDatePicker.git' 20 | developerId = 'xdeveloper' 21 | developerName = 'Rahman Mohammadi' 22 | developerEmail = 'xdev.arch@gmail.com' 23 | licenseName = 'The Apache Software License, Version 2.0' 24 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 25 | allLicenses = ["Apache-2.0"] 26 | } 27 | 28 | android { 29 | compileSdkVersion 29 30 | buildToolsVersion "29.0.3" 31 | 32 | 33 | defaultConfig { 34 | minSdkVersion 21 35 | targetSdkVersion 29 36 | versionCode 1 37 | versionName "0.3.2" 38 | consumerProguardFiles 'consumer-rules.pro' 39 | } 40 | 41 | buildTypes { 42 | release { 43 | minifyEnabled false 44 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 45 | } 46 | } 47 | 48 | } 49 | 50 | dependencies { 51 | implementation fileTree(dir: 'libs', include: ['*.jar']) 52 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 53 | implementation 'androidx.appcompat:appcompat:1.1.0' 54 | implementation 'androidx.core:core-ktx:1.2.0' 55 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 56 | } 57 | 58 | if (project.rootProject.file('local.properties').exists()) { 59 | apply from: 'http://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 60 | apply from: 'http://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' 61 | } -------------------------------------------------------------------------------- /persiancalendar/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/persiancalendar/consumer-rules.pro -------------------------------------------------------------------------------- /persiancalendar/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 22 | -------------------------------------------------------------------------------- /persiancalendar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/CalendarConstraints.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.os.Bundle 21 | import android.os.Parcel 22 | import android.os.Parcelable 23 | 24 | /** 25 | * Used to limit the display range of [MaterialCalendar] and set an openAt month. 26 | * 27 | * Implements [Parcelable] in order to maintain the [CalendarConstraints] across 28 | * device configuration changes. Parcelable breaks when passed between processes. 29 | */ 30 | class CalendarConstraints private constructor( 31 | 32 | /** Earliest [Month] allowed by this set of bounds. */ 33 | val start: Month, 34 | /** Latest [Month] allowed by this set of bounds. */ 35 | val end: Month, 36 | /** OpenAt [Month] within this set of bounds. */ 37 | val openAt: Month, 38 | 39 | /** 40 | * The [DateValidator] that determines whether a date can be clicked and selected. 41 | */ 42 | val dateValidator: DateValidator 43 | ) : Parcelable { 44 | 45 | /** 46 | * The total number of [java.util.Calendar.MONTH] included in [start] to [end]. 47 | */ 48 | val yearSpan: Int 49 | 50 | /** 51 | * The total number of [java.util.Calendar.YEAR] included in [start] to [end]. 52 | */ 53 | val monthSpan: Int 54 | 55 | /** 56 | * Used to determine whether [MaterialCalendar] days are enabled. 57 | * 58 | * Extends [Parcelable] in order to maintain the [DateValidator] across device 59 | * configuration changes. Parcelable breaks when passed between processes. 60 | */ 61 | interface DateValidator : Parcelable { 62 | 63 | /** Returns true if the provided [date] is enabled. */ 64 | fun isValid(date: Long): Boolean 65 | } 66 | 67 | override fun equals(other: Any?): Boolean { 68 | if (this === other) { 69 | return true 70 | } 71 | if (other !is CalendarConstraints) { 72 | return false 73 | } 74 | return start == other.start 75 | && end == other.end 76 | && openAt == other.openAt 77 | && dateValidator == other.dateValidator 78 | } 79 | 80 | override fun hashCode(): Int { 81 | val hashedFields = 82 | arrayOf(start, end, openAt, dateValidator) 83 | return hashedFields.contentHashCode() 84 | } 85 | 86 | override fun describeContents(): Int { 87 | return 0 88 | } 89 | 90 | override fun writeToParcel(dest: Parcel, flags: Int) { 91 | dest.writeParcelable(start, 0) 92 | dest.writeParcelable(end, 0) 93 | dest.writeParcelable(openAt, 0) 94 | dest.writeParcelable(dateValidator, 0) 95 | } 96 | 97 | /** Builder for [CalendarConstraints] */ 98 | class Builder { 99 | private var start = DEFAULT_START 100 | private var end = DEFAULT_END 101 | private var openAt: Long? = null 102 | private var validator: DateValidator? = DateValidatorPointForward.from(Long.MIN_VALUE) 103 | 104 | constructor() 105 | 106 | internal constructor(clone: CalendarConstraints) { 107 | start = clone.start.timeInMillis 108 | end = clone.end.timeInMillis 109 | openAt = clone.openAt.timeInMillis 110 | validator = clone.dateValidator 111 | } 112 | 113 | /** 114 | * A timeInMilliseconds contained within the earliest month the calendar will page to. 115 | * Defaults [DEFAULT_START]. 116 | */ 117 | fun setStart(month: Long): Builder { 118 | start = month 119 | return this 120 | } 121 | 122 | /** 123 | * A timeInMilliseconds contained within the latest month the calendar will page to. 124 | * Defaults [DEFAULT_END]. 125 | */ 126 | fun setEnd(month: Long): Builder { 127 | end = month 128 | return this 129 | } 130 | 131 | 132 | /** 133 | * A timeInMilliseconds contained within the month the calendar should openAt. Defaults to 134 | * the month containing today if within bounds; otherwise, defaults to the starting month. 135 | */ 136 | fun setOpenAt(month: Long): Builder { 137 | openAt = month 138 | return this 139 | } 140 | 141 | /** 142 | * Limits valid dates to those for which [DateValidator.isValid] is true. Defaults 143 | * to all dates as valid. 144 | */ 145 | fun setValidator(validator: DateValidator?): Builder { 146 | this.validator = validator 147 | return this 148 | } 149 | 150 | /** Builds the [CalendarConstraints] object using the set parameters or defaults. */ 151 | fun build(): CalendarConstraints { 152 | if (openAt == null) { 153 | val today: Long = MaterialDatePicker.thisMonthInIrstMilliseconds() 154 | openAt = if (today in start..end) today else start 155 | } 156 | val deepCopyBundle = Bundle() 157 | deepCopyBundle.putParcelable( 158 | DEEP_COPY_VALIDATOR_KEY, 159 | validator 160 | ) 161 | 162 | return CalendarConstraints( 163 | Month.create(start), 164 | Month.create(end), 165 | Month.create(openAt!!), 166 | deepCopyBundle.getParcelable(DEEP_COPY_VALIDATOR_KEY) as DateValidator 167 | ) 168 | } 169 | 170 | companion object { 171 | 172 | /** 173 | * Default IRST timeInMilliseconds for the first selectable month unless [Builder.setStart] 174 | * is called. Set to Farvardin, 1388. 175 | */ 176 | @JvmField 177 | val DEFAULT_START = Month.create(1388, Month.FARVARDIN).timeInMillis 178 | 179 | /** 180 | * Default IRST timeInMilliseconds for the last selectable month unless [Builder.setEnd] is 181 | * called. Set to Esfand, 1409. 182 | */ 183 | @JvmField 184 | val DEFAULT_END = Month.create(1409, Month.ESFAND).timeInMillis 185 | 186 | private const val DEEP_COPY_VALIDATOR_KEY = "DEEP_COPY_VALIDATOR_KEY" 187 | } 188 | } 189 | 190 | companion object { 191 | @JvmField 192 | val CREATOR: Parcelable.Creator = 193 | object : 194 | Parcelable.Creator { 195 | override fun createFromParcel(source: Parcel): CalendarConstraints { 196 | val start: Month = source.readParcelable(Month::class.java.classLoader)!! 197 | val end: Month = source.readParcelable(Month::class.java.classLoader)!! 198 | val openAt: Month = source.readParcelable(Month::class.java.classLoader)!! 199 | val validator: DateValidator = 200 | source.readParcelable(DateValidator::class.java.classLoader)!! 201 | return CalendarConstraints(start, end, openAt, validator) 202 | } 203 | 204 | override fun newArray(size: Int): Array { 205 | return arrayOfNulls(size) 206 | } 207 | } 208 | } 209 | 210 | init { 211 | require(start <= openAt) { "start Month cannot be after current Month $start $openAt" } 212 | require(openAt <= end) { "current Month cannot be after end Month $openAt $end" } 213 | monthSpan = start.monthsUntil(end) + 1 214 | yearSpan = end.year - start.year + 1 215 | } 216 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/CalendarItemStyle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.content.res.ColorStateList 22 | import android.graphics.Color 23 | import android.graphics.Rect 24 | import android.graphics.drawable.* 25 | import android.util.Log 26 | import androidx.annotation.StyleRes 27 | import androidx.core.content.ContextCompat 28 | import androidx.core.util.Preconditions 29 | import androidx.core.view.ViewCompat 30 | import com.xdev.arch.persiancalendar.BuildConfig 31 | import com.xdev.arch.persiancalendar.R 32 | import com.xdev.arch.persiancalendar.datepicker.utils.getColorStateList 33 | 34 | /** 35 | * Loads and applies [R.styleable.PersianMaterialCalendarItem] attributes to [SimpleTextView] 36 | * instances. 37 | */ 38 | class CalendarItemStyle private constructor( 39 | backgroundColor: ColorStateList?, 40 | textColor: ColorStateList?, 41 | strokeColor: ColorStateList?, 42 | strokeWidth: Int, 43 | itemShape: Int, 44 | cornerRadius: Float, 45 | insets: Rect 46 | ) { 47 | private val insets: Rect 48 | private val textColor: ColorStateList? 49 | private val backgroundColor: ColorStateList? 50 | private val strokeColor: ColorStateList? 51 | private val strokeWidth: Int 52 | private val itemShape: Int 53 | private val cornerRadius: Float 54 | 55 | fun styleItem(item: SimpleTextView) { 56 | val shape = GradientDrawable() 57 | shape.shape = if (itemShape == 0) GradientDrawable.OVAL else GradientDrawable.RECTANGLE 58 | 59 | val mask = GradientDrawable() 60 | mask.shape = if (itemShape == 0) GradientDrawable.OVAL else GradientDrawable.RECTANGLE 61 | 62 | shape.color = backgroundColor 63 | mask.color = ColorStateList.valueOf(ContextCompat.getColor(item.context, R.color.default_day_ripple_color)) 64 | 65 | shape.setStroke(strokeWidth, strokeColor) 66 | 67 | if (cornerRadius > 0F) { 68 | shape.cornerRadius = cornerRadius 69 | mask.cornerRadius = cornerRadius 70 | } 71 | 72 | if (textColor == null) { 73 | if (BuildConfig.DEBUG) { 74 | Log.w(CalendarItemStyle::class::java.name,"Text color is null using white instead!" + 75 | "\n looks like you haven't specified a color for attribute: textColor of this item") 76 | } 77 | 78 | item.setTextColor(Color.WHITE) 79 | } else 80 | item.setTextColor(textColor) 81 | 82 | val d: Drawable 83 | d = RippleDrawable(textColor?.withAlpha(30) ?: ColorStateList.valueOf(Color.WHITE).withAlpha(30), 84 | shape, mask) 85 | 86 | ViewCompat.setBackground( 87 | item, 88 | InsetDrawable(d, insets.left, insets.top, insets.right, insets.bottom) 89 | ) 90 | } 91 | 92 | val leftInset: Int 93 | get() = insets.left 94 | 95 | val rightInset: Int 96 | get() = insets.right 97 | 98 | val topInset: Int 99 | get() = insets.top 100 | 101 | val bottomInset: Int 102 | get() = insets.bottom 103 | 104 | companion object { 105 | /** 106 | * Creates a [CalendarItemStyle] using the provided [ ][R.styleable.PersianMaterialCalendarItem]. 107 | */ 108 | fun create( 109 | context: Context, @StyleRes materialCalendarItemStyle: Int): CalendarItemStyle { 110 | Preconditions.checkArgument( 111 | materialCalendarItemStyle != 0, 112 | "Cannot create a CalendarItemStyle with a styleResId of 0" 113 | ) 114 | val styleableArray = context.obtainStyledAttributes( 115 | materialCalendarItemStyle, 116 | R.styleable.PersianMaterialCalendarItem 117 | ) 118 | val insetLeft = styleableArray.getDimensionPixelOffset( 119 | R.styleable.PersianMaterialCalendarItem_android_insetLeft, 0 120 | ) 121 | val insetTop = styleableArray.getDimensionPixelOffset( 122 | R.styleable.PersianMaterialCalendarItem_android_insetTop, 0 123 | ) 124 | val insetRight = styleableArray.getDimensionPixelOffset( 125 | R.styleable.PersianMaterialCalendarItem_android_insetRight, 0 126 | ) 127 | val insetBottom = styleableArray.getDimensionPixelOffset( 128 | R.styleable.PersianMaterialCalendarItem_android_insetBottom, 0 129 | ) 130 | val insets = 131 | Rect(insetLeft, insetTop, insetRight, insetBottom) 132 | val backgroundColor = 133 | getColorStateList( 134 | context, styleableArray, R.styleable.PersianMaterialCalendarItem_itemFillColor 135 | ) 136 | val textColor = 137 | getColorStateList( 138 | context, styleableArray, R.styleable.PersianMaterialCalendarItem_itemTextColor 139 | ) 140 | val strokeColor = 141 | getColorStateList( 142 | context, styleableArray, R.styleable.PersianMaterialCalendarItem_itemStrokeColor 143 | ) 144 | val strokeWidth = styleableArray.getDimensionPixelSize( 145 | R.styleable.PersianMaterialCalendarItem_itemStrokeWidth, 146 | 0 147 | ) 148 | val itemShape = 149 | styleableArray.getInt(R.styleable.PersianMaterialCalendarItem_itemShape, 0) 150 | 151 | val cornerRadius = styleableArray.getFloat( 152 | R.styleable.PersianMaterialCalendarItem_itemShapeCornerRadius, 153 | 0F 154 | ) 155 | 156 | styleableArray.recycle() 157 | return CalendarItemStyle( 158 | backgroundColor, 159 | textColor, 160 | strokeColor, 161 | strokeWidth, 162 | itemShape, 163 | cornerRadius, 164 | insets) 165 | } 166 | } 167 | 168 | init { 169 | Preconditions.checkArgumentNonnegative(insets.left) 170 | Preconditions.checkArgumentNonnegative(insets.top) 171 | Preconditions.checkArgumentNonnegative(insets.right) 172 | Preconditions.checkArgumentNonnegative(insets.bottom) 173 | this.insets = insets 174 | this.textColor = textColor 175 | this.backgroundColor = backgroundColor 176 | this.strokeColor = strokeColor 177 | this.strokeWidth = strokeWidth 178 | this.itemShape = itemShape 179 | this.cornerRadius = cornerRadius 180 | } 181 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/CalendarStyle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.graphics.Paint 22 | import com.xdev.arch.persiancalendar.R 23 | import com.xdev.arch.persiancalendar.datepicker.utils.getColorStateList 24 | import com.xdev.arch.persiancalendar.datepicker.utils.resolve 25 | 26 | /** 27 | * Data class for loaded [R.styleable.PersianMaterialCalendar] and 28 | * [R.styleable.PersianMaterialCalendarItem] attributes. 29 | */ 30 | class CalendarStyle(context: Context) { 31 | val day: CalendarItemStyle 32 | val selectedDay: CalendarItemStyle 33 | val todayDay: CalendarItemStyle 34 | val year: CalendarItemStyle 35 | val selectedYear: CalendarItemStyle 36 | 37 | val todayYear: CalendarItemStyle 38 | val invalidDay: CalendarItemStyle 39 | 40 | val rangeFill: Paint 41 | 42 | init { 43 | val calendarStyle: Int 44 | val style = resolve(context, R.attr.persianMaterialCalendarStyle) 45 | 46 | calendarStyle = style?.data ?: R.style.PersianMaterialCalendar_Default 47 | 48 | val calendarAttributes = 49 | context.obtainStyledAttributes(calendarStyle, R.styleable.PersianMaterialCalendar) 50 | 51 | day = CalendarItemStyle.create( 52 | context, 53 | calendarAttributes.getResourceId(R.styleable.PersianMaterialCalendar_dayStyle, 0) 54 | ) 55 | 56 | selectedDay = CalendarItemStyle.create( 57 | context, 58 | calendarAttributes.getResourceId(R.styleable.PersianMaterialCalendar_daySelectedStyle, 0) 59 | ) 60 | 61 | invalidDay = CalendarItemStyle.create( 62 | context, 63 | calendarAttributes.getResourceId(R.styleable.PersianMaterialCalendar_dayInvalidStyle, 0) 64 | ) 65 | todayDay = CalendarItemStyle.create( 66 | context, 67 | calendarAttributes.getResourceId(R.styleable.PersianMaterialCalendar_dayTodayStyle, 0) 68 | ) 69 | val rangeFillColorList = 70 | getColorStateList( 71 | context, calendarAttributes, R.styleable.PersianMaterialCalendar_rangeFillColor) 72 | 73 | year = CalendarItemStyle.create( 74 | context, 75 | calendarAttributes.getResourceId(R.styleable.PersianMaterialCalendar_yearStyle, 0) 76 | ) 77 | 78 | selectedYear = CalendarItemStyle.create( 79 | context, 80 | calendarAttributes.getResourceId( 81 | R.styleable.PersianMaterialCalendar_yearSelectedStyle, 82 | 0 83 | ) 84 | ) 85 | 86 | todayYear = CalendarItemStyle.create( 87 | context, 88 | calendarAttributes.getResourceId(R.styleable.PersianMaterialCalendar_yearTodayStyle, 0)) 89 | rangeFill = Paint() 90 | rangeFill.color = rangeFillColorList!!.defaultColor 91 | calendarAttributes.recycle() 92 | } 93 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/DateSelector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.os.Parcelable 22 | import android.widget.AdapterView 23 | import androidx.annotation.StringRes 24 | import androidx.annotation.StyleRes 25 | import com.xdev.arch.persiancalendar.R 26 | 27 | /** 28 | * Interface for users of [<S>][MaterialCalendar] to control how the Calendar displays and 29 | * returns selections. 30 | * 31 | * 32 | * Implementors must implement [Parcelable] so that selection can be maintained through 33 | * Lifecycle events (e.g., Fragment destruction). 34 | * 35 | * 36 | * Dates are represented as times in UTC milliseconds. 37 | * 38 | * @param The type of item available when cells are selected in the [AdapterView] 39 | * @hide 40 | */ 41 | interface DateSelector : Parcelable { 42 | 43 | /** Returns the current selection. */ 44 | fun getSelection(): S? 45 | 46 | /** Returns true if the current selection is acceptable. */ 47 | val isSelectionComplete: Boolean 48 | 49 | /** 50 | * Sets the current selection to `selection`. 51 | * 52 | * @throws IllegalArgumentException If `selection` creates an invalid state. 53 | */ 54 | fun setSelection(selection: S) 55 | 56 | /** 57 | * Allows this selection handler to respond to clicks within the [AdapterView]. 58 | * 59 | * @param selection The selected day represented as time in UTC milliseconds. 60 | */ 61 | fun select(selection: Long) 62 | 63 | /** 64 | * Returns a list of longs whose time value represents days that should be marked selected. 65 | * 66 | * 67 | * Uses [R.styleable.PersianMaterialCalendar_daySelectedStyle] for styling. 68 | */ 69 | val selectedDays: Collection 70 | 71 | /** 72 | * Returns a list of ranges whose time values represent ranges that should be filled. 73 | * 74 | * 75 | * Uses [R.styleable.PersianMaterialCalendar_rangeFillColor] for styling. 76 | */ 77 | val selectedRanges: Collection> 78 | 79 | fun getSelectionDisplayString(context: Context): String 80 | 81 | @get:StringRes 82 | val defaultTitleResId: Int 83 | 84 | @StyleRes 85 | fun getDefaultThemeResId(context: Context): Int 86 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/DateValidatorPointForward.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.os.Parcel 21 | import android.os.Parcelable 22 | import com.xdev.arch.persiancalendar.datepicker.utils.todayCalendar 23 | 24 | /** 25 | * A [CalendarConstraints.DateValidator] that enables dates from a given point forward. 26 | * Defaults to the current moment in device time forward using 27 | * [DateValidatorPointForward.now], but can be set to any point, as UTC milliseconds, using 28 | * [DateValidatorPointForward.from]. 29 | */ 30 | class DateValidatorPointForward private constructor(private val point: Long) : 31 | CalendarConstraints.DateValidator { 32 | 33 | override fun isValid(date: Long): Boolean { 34 | return date >= point 35 | } 36 | 37 | override fun describeContents(): Int { 38 | return 0 39 | } 40 | 41 | override fun writeToParcel(dest: Parcel, flags: Int) { 42 | dest.writeLong(point) 43 | } 44 | 45 | override fun equals(other: Any?): Boolean { 46 | if (this === other) { 47 | return true 48 | } 49 | if (other !is DateValidatorPointForward) { 50 | return false 51 | } 52 | return point == other.point 53 | } 54 | 55 | override fun hashCode(): Int { 56 | val hashedFields = arrayOf(point) 57 | return hashedFields.contentHashCode() 58 | } 59 | 60 | companion object { 61 | 62 | /** 63 | * Returns a [CalendarConstraints.DateValidator] which enables days from `point`, in 64 | * UTC milliseconds, forward. 65 | */ 66 | fun from(point: Long): DateValidatorPointForward { 67 | return DateValidatorPointForward(point) 68 | } 69 | 70 | /** 71 | * Returns a [CalendarConstraints.DateValidator] enabled from the current moment in device 72 | * time forward. 73 | */ 74 | fun now(): DateValidatorPointForward { 75 | return from(todayCalendar.timeInMillis) 76 | } 77 | 78 | /** Part of [Parcelable] requirements. Do not use. */ 79 | @JvmField 80 | val CREATOR: Parcelable.Creator = 81 | object : 82 | Parcelable.Creator { 83 | override fun createFromParcel(source: Parcel): DateValidatorPointForward { 84 | return DateValidatorPointForward(source.readLong()) 85 | } 86 | 87 | override fun newArray(size: Int): Array { 88 | return arrayOfNulls(size) 89 | } 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/DaysOfWeekAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import android.widget.BaseAdapter 24 | import com.xdev.arch.persiancalendar.R 25 | import com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar 26 | import com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar.Companion.DAYS_IN_WEEK 27 | import com.xdev.arch.persiancalendar.datepicker.utils.getAbbrDayName 28 | 29 | /** 30 | * A single row adapter representing the days of the week for [PersianCalendar]. 31 | * This [android.widget.Adapter] respects the [PersianCalendar.getFirstDayOfWeek] 32 | */ 33 | internal class DaysOfWeekAdapter : BaseAdapter() { 34 | 35 | override fun getItem(position: Int): Int? { 36 | return if (position > DAYS_IN_WEEK) { 37 | null 38 | } else position 39 | } 40 | 41 | override fun getItemId(position: Int): Long = 0 42 | 43 | override fun getCount(): Int = DAYS_IN_WEEK + 1 44 | 45 | override fun getView( 46 | position: Int, 47 | convertView: View?, 48 | parent: ViewGroup 49 | ): View? { 50 | var dayOfWeek = convertView 51 | if (dayOfWeek == null) { 52 | val layoutInflater = LayoutInflater.from(parent.context) 53 | dayOfWeek = layoutInflater.inflate(R.layout.calendar_day_of_week, parent, false) 54 | } 55 | 56 | (dayOfWeek as SimpleTextView).text = getAbbrDayName(position) 57 | return dayOfWeek 58 | } 59 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/MaterialCalendarGridView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.graphics.Canvas 22 | import android.graphics.Rect 23 | import android.util.AttributeSet 24 | import android.view.KeyEvent 25 | import android.view.View 26 | import android.widget.AdapterView 27 | import android.widget.GridView 28 | import android.widget.ListAdapter 29 | import androidx.core.view.AccessibilityDelegateCompat 30 | import androidx.core.view.ViewCompat 31 | import androidx.core.view.accessibility.AccessibilityNodeInfoCompat 32 | import com.xdev.arch.persiancalendar.datepicker.utils.iranCalendar 33 | 34 | internal class MaterialCalendarGridView @JvmOverloads constructor( 35 | context: Context?, 36 | attrs: AttributeSet? = null, 37 | defStyleAttr: Int = 0 38 | ) : GridView(context, attrs, defStyleAttr) { 39 | private val dayCompute = iranCalendar 40 | 41 | override fun onAttachedToWindow() { 42 | super.onAttachedToWindow() 43 | adapter?.notifyDataSetChanged() 44 | } 45 | 46 | override fun setSelection(position: Int) { 47 | adapter?.let { 48 | if (position < it.firstPositionInMonth()) { 49 | super.setSelection(it.firstPositionInMonth()) 50 | } else { 51 | super.setSelection(position) 52 | } 53 | } 54 | } 55 | 56 | override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { 57 | val result = super.onKeyDown(keyCode, event) 58 | if (!result) { 59 | return false 60 | } 61 | if (selectedItemPosition == AdapterView.INVALID_POSITION 62 | || selectedItemPosition >= adapter!!.firstPositionInMonth() 63 | ) { 64 | return true 65 | } 66 | if (KeyEvent.KEYCODE_DPAD_UP == keyCode) { 67 | adapter?.let { setSelection(it.firstPositionInMonth()) } 68 | return true 69 | } 70 | return false 71 | } 72 | 73 | override fun getAdapter(): MonthAdapter? { 74 | return super.getAdapter() as MonthAdapter? 75 | } 76 | 77 | override fun setAdapter(adapter: ListAdapter) { 78 | require(adapter is MonthAdapter) { 79 | String.format( 80 | "%1\$s must have its Adapter set to a %2\$s", 81 | MaterialCalendarGridView::class.java.canonicalName, 82 | MonthAdapter::class.java.canonicalName 83 | ) 84 | } 85 | super.setAdapter(adapter) 86 | } 87 | 88 | override fun getLayoutDirection(): Int { 89 | return View.LAYOUT_DIRECTION_RTL 90 | } 91 | 92 | override fun onDraw(canvas: Canvas) { 93 | super.onDraw(canvas) 94 | adapter?.let { 95 | val monthAdapter = it 96 | val dateSelector = monthAdapter.dateSelector 97 | val calendarStyle = monthAdapter.calendarStyle 98 | val firstOfMonth = monthAdapter.getItem(monthAdapter.firstPositionInMonth()) 99 | val lastOfMonth = monthAdapter.getItem(monthAdapter.lastPositionInMonth()) 100 | 101 | for (range in dateSelector!!.selectedRanges) { 102 | if (range.first == null || range.second == null) { 103 | continue 104 | } 105 | 106 | val startItem = range.first!! 107 | val endItem = range.second!! 108 | if (skipMonth( 109 | firstOfMonth, 110 | lastOfMonth, 111 | startItem, 112 | endItem 113 | ) 114 | ) { 115 | return 116 | } 117 | 118 | var firstHighlightPosition: Int 119 | var rangeHighlightStart: Int 120 | if (startItem < firstOfMonth!!) { 121 | firstHighlightPosition = monthAdapter.firstPositionInMonth() 122 | rangeHighlightStart = 123 | if (monthAdapter.isFirstInRow(firstHighlightPosition)) width 124 | else getChildAt(firstHighlightPosition - 1).left 125 | } else { 126 | dayCompute.timeInMillis = startItem 127 | firstHighlightPosition = 128 | monthAdapter.dayToPosition(dayCompute.day) 129 | rangeHighlightStart = horizontalMidPoint(getChildAt(firstHighlightPosition)) 130 | } 131 | var lastHighlightPosition: Int 132 | var rangeHighlightEnd: Int 133 | if (endItem > lastOfMonth!!) { 134 | lastHighlightPosition = monthAdapter.lastPositionInMonth() 135 | rangeHighlightEnd = 136 | if (monthAdapter.isLastInRow(lastHighlightPosition)) 0 137 | else getChildAt(lastHighlightPosition).left 138 | } else { 139 | dayCompute.timeInMillis = endItem 140 | lastHighlightPosition = 141 | monthAdapter.dayToPosition(dayCompute.day) 142 | rangeHighlightEnd = 143 | horizontalMidPoint(getChildAt(lastHighlightPosition)) 144 | } 145 | val firstRow = monthAdapter.getItemId(firstHighlightPosition).toInt() 146 | val lastRow = monthAdapter.getItemId(lastHighlightPosition).toInt() 147 | for (row in firstRow..lastRow) { 148 | val firstPositionInRow = row * numColumns 149 | val lastPositionInRow = firstPositionInRow + numColumns - 1 150 | val firstView = getChildAt(firstPositionInRow) 151 | val top = firstView.top + calendarStyle!!.day.topInset 152 | val bottom = firstView.bottom - calendarStyle.day.bottomInset 153 | 154 | val left = 155 | if (firstPositionInRow > firstHighlightPosition) width else rangeHighlightStart 156 | val right = 157 | if (lastHighlightPosition > lastPositionInRow) 0 else rangeHighlightEnd 158 | canvas.drawRect( 159 | left.toFloat(), 160 | top.toFloat(), 161 | right.toFloat(), 162 | bottom.toFloat(), 163 | calendarStyle.rangeFill 164 | ) 165 | } 166 | } 167 | } 168 | } 169 | 170 | override fun onFocusChanged( 171 | gainFocus: Boolean, 172 | direction: Int, 173 | previouslyFocusedRect: Rect? 174 | ) { 175 | if (gainFocus) { 176 | gainFocus(direction, previouslyFocusedRect) 177 | } else { 178 | super.onFocusChanged(false, direction, previouslyFocusedRect) 179 | } 180 | } 181 | 182 | private fun gainFocus(direction: Int, previouslyFocusedRect: Rect?) { 183 | adapter?.let { 184 | when (direction) { 185 | View.FOCUS_UP -> { 186 | setSelection(it.lastPositionInMonth()) 187 | } 188 | View.FOCUS_DOWN -> { 189 | setSelection(it.firstPositionInMonth()) 190 | } 191 | else -> { 192 | super.onFocusChanged(true, direction, previouslyFocusedRect) 193 | } 194 | } 195 | } 196 | } 197 | 198 | companion object { 199 | private fun skipMonth( 200 | firstOfMonth: Long?, 201 | lastOfMonth: Long?, 202 | startDay: Long?, 203 | endDay: Long? 204 | ): Boolean { 205 | return if (firstOfMonth == null || lastOfMonth == null || startDay == null || endDay == null) { 206 | true 207 | } else startDay > lastOfMonth || endDay < firstOfMonth 208 | } 209 | 210 | private fun horizontalMidPoint(view: View): Int { 211 | return view.left + view.width / 2 212 | } 213 | } 214 | 215 | init { 216 | ViewCompat.setAccessibilityDelegate( 217 | this, 218 | object : AccessibilityDelegateCompat() { 219 | override fun onInitializeAccessibilityNodeInfo( 220 | view: View, 221 | accessibilityNodeInfoCompat: AccessibilityNodeInfoCompat 222 | ) { 223 | super.onInitializeAccessibilityNodeInfo(view, accessibilityNodeInfoCompat) 224 | accessibilityNodeInfoCompat.setCollectionInfo(null) 225 | } 226 | }) 227 | } 228 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/MaterialPickerOnPositiveButtonClickListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | interface MaterialPickerOnPositiveButtonClickListener { 21 | fun onPositiveButtonClick(selection: S) 22 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/Month.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.os.Parcel 21 | import android.os.Parcelable 22 | import androidx.annotation.IntDef 23 | import com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar 24 | import com.xdev.arch.persiancalendar.datepicker.utils.formatYearMonth 25 | import com.xdev.arch.persiancalendar.datepicker.utils.getDayCopy 26 | import com.xdev.arch.persiancalendar.datepicker.utils.todayCalendar 27 | import com.xdev.arch.persiancalendar.datepicker.utils.iranCalendar 28 | import java.util.* 29 | 30 | /** Contains convenience operations for a month within a specific year. */ 31 | class Month private constructor(rawCalendar: PersianCalendar) : 32 | Comparable, Parcelable { 33 | 34 | /** The acceptable int values for month when using [Month.create] */ 35 | @kotlin.annotation.Retention(AnnotationRetention.SOURCE) 36 | @IntDef( 37 | FARVARDIN, 38 | ORDIBEHESHT, 39 | KHORDAD, 40 | TIR, 41 | MORDAD, 42 | SHAHRIVAR, 43 | MEHR, 44 | ABAN, 45 | AZAR, 46 | DAY, 47 | BAHMAN, 48 | ESFAND 49 | ) 50 | annotation class Months 51 | 52 | private val firstOfMonth: PersianCalendar 53 | val longName: String 54 | @Months val month: Int 55 | val year: Int 56 | val daysInWeek: Int 57 | val daysInMonth: Int 58 | val timeInMillis: Long 59 | 60 | fun daysFromStartOfWeekToFirstOfMonth(): Int { 61 | var difference = firstOfMonth.getFirstDayOfMonth() - firstOfMonth.firstDayOfWeek 62 | if (difference < 0) { 63 | difference += daysInWeek 64 | } 65 | return difference 66 | } 67 | 68 | override fun equals(other: Any?): Boolean { 69 | if (this === other) { 70 | return true 71 | } 72 | if (other !is Month) { 73 | return false 74 | } 75 | 76 | return month == other.month && year == other.year 77 | } 78 | 79 | override fun hashCode(): Int { 80 | val hashedFields = arrayOf(month, year) 81 | return hashedFields.contentHashCode() 82 | } 83 | 84 | override fun compareTo(other: Month): Int { 85 | return firstOfMonth.compareTo(other.firstOfMonth) 86 | } 87 | 88 | override fun toString(): String { 89 | return "$year/$month" 90 | } 91 | 92 | fun monthsUntil(other: Month): Int { 93 | return (other.year - year) * 12 + (other.month - month) 94 | } 95 | 96 | val stableId: Long 97 | get() = firstOfMonth.timeInMillis 98 | 99 | fun getDay(day: Int): Long { 100 | val dayCalendar = getDayCopy(firstOfMonth) 101 | dayCalendar.setDayOfMonth(day) 102 | return dayCalendar.timeInMillis 103 | } 104 | 105 | fun monthsLater(months: Int): Month { 106 | val laterMonth = getDayCopy(firstOfMonth) 107 | laterMonth.add(Calendar.MONTH, months) 108 | return Month(laterMonth) 109 | } 110 | 111 | override fun describeContents(): Int { 112 | return 0 113 | } 114 | 115 | override fun writeToParcel(dest: Parcel, flags: Int) { 116 | dest.writeInt(year) 117 | dest.writeInt(month) 118 | } 119 | 120 | companion object { 121 | 122 | const val FARVARDIN = 0 123 | const val ORDIBEHESHT = 1 124 | const val KHORDAD = 2 125 | const val TIR = 3 126 | const val MORDAD = 4 127 | const val SHAHRIVAR = 5 128 | const val MEHR = 6 129 | const val ABAN = 7 130 | const val AZAR = 8 131 | const val DAY = 9 132 | const val BAHMAN = 10 133 | const val ESFAND = 11 134 | 135 | /** 136 | * Creates an instance of Month that contains the provided [timeInMillis] where [timeInMillis] 137 | * is in milliseconds. 138 | */ 139 | fun create(timeInMillis: Long): Month { 140 | val calendar = iranCalendar 141 | calendar.timeInMillis = timeInMillis 142 | 143 | return Month(calendar) 144 | } 145 | 146 | /** 147 | * Creates an instance of Month with the given parameters backed by a [PersianCalendar]. 148 | * 149 | * @param year The year 150 | * @param month The 0-index based month. Use [Month] constants (e.g., [Month.FARVARDIN]]) 151 | * @return A Month object backed by a new [PersianCalendar] instance 152 | */ 153 | fun create(year: Int, @Months month: Int): Month { 154 | val calendar = iranCalendar 155 | calendar.setPersian(year, month, 1) 156 | return Month(calendar) 157 | } 158 | 159 | /** 160 | * Returns the [Month] that contains today in the IRST timezone (as per [Calendar.getInstance]. 161 | */ 162 | fun today(): Month { 163 | return Month(todayCalendar) 164 | } 165 | 166 | @JvmField 167 | val CREATOR: Parcelable.Creator = 168 | object : Parcelable.Creator { 169 | override fun createFromParcel(source: Parcel): Month { 170 | val year = source.readInt() 171 | val month = source.readInt() 172 | return create(year, month) 173 | } 174 | 175 | override fun newArray(size: Int): Array { 176 | return arrayOfNulls(size) 177 | } 178 | } 179 | } 180 | 181 | init { 182 | rawCalendar.setDayOfMonth(1) 183 | firstOfMonth = getDayCopy(rawCalendar) 184 | month = firstOfMonth.month 185 | year = firstOfMonth.year 186 | daysInWeek = firstOfMonth.getMaximum(Calendar.DAY_OF_WEEK) 187 | daysInMonth = firstOfMonth.getMaxDaysInMonth() 188 | longName = formatYearMonth(firstOfMonth) 189 | timeInMillis = firstOfMonth.timeInMillis 190 | } 191 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/MonthAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.view.LayoutInflater 22 | import android.view.View 23 | import android.view.ViewGroup 24 | import android.widget.BaseAdapter 25 | import android.widget.TextView 26 | import com.xdev.arch.persiancalendar.R 27 | import com.xdev.arch.persiancalendar.datepicker.utils.canonicalYearMonthDay 28 | import com.xdev.arch.persiancalendar.datepicker.utils.todayCalendar 29 | import java.util.* 30 | 31 | /** 32 | * Represents the days of a month with [SimpleTextView] instances for each day. 33 | * 34 | * 35 | * The number of rows is always equal to the maximum number of weeks that can exist across all 36 | * months (e.g., 6 for the GregorianCalendar). 37 | */ 38 | internal class MonthAdapter( 39 | val month: Month, 40 | /** 41 | * The [DateSelector] dictating the draw behavior of [getView]. 42 | */ 43 | val dateSelector: DateSelector<*>?, 44 | private val calendarConstraints: CalendarConstraints 45 | ) : BaseAdapter() { 46 | var calendarStyle: CalendarStyle? = null 47 | override fun hasStableIds(): Boolean { 48 | return true 49 | } 50 | 51 | /** 52 | * Returns a [Long] object for the given grid position 53 | * 54 | * @param position Index for the item. 0 matches the 55 | * [com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar.getFirstDayOfWeek] for the 56 | * first week of the month represented by [Month]. 57 | * @return A [Long] representing the day at the position or null if the position does not 58 | * represent a valid day in the month. 59 | */ 60 | override fun getItem(position: Int): Long? { 61 | return if (position < month.daysFromStartOfWeekToFirstOfMonth() || position > lastPositionInMonth()) { 62 | null 63 | } else month.getDay(positionToDay(position)) 64 | } 65 | 66 | override fun getItemId(position: Int): Long { 67 | return (position / month.daysInWeek).toLong() 68 | } 69 | 70 | /** 71 | * Returns the number of days in a month plus the amount required to off-set for the first day to 72 | * the correct position within the week. 73 | * 74 | * 75 | * [MonthAdapter.firstPositionInMonth]. 76 | * 77 | * @return The maximum valid position index 78 | */ 79 | override fun getCount(): Int { 80 | return month.daysInMonth + firstPositionInMonth() 81 | } 82 | 83 | override fun getView( 84 | position: Int, 85 | convertView: View?, 86 | parent: ViewGroup 87 | ): SimpleTextView { 88 | initializeStyles(parent.context) 89 | var day = convertView 90 | 91 | if (convertView == null) { 92 | val layoutInflater = LayoutInflater.from(parent.context) 93 | day = layoutInflater.inflate(R.layout.calendar_day, parent, false) as SimpleTextView 94 | } 95 | 96 | day as SimpleTextView 97 | 98 | val offsetPosition = position - firstPositionInMonth() 99 | if (offsetPosition < 0 || offsetPosition >= month.daysInMonth) { 100 | day.visibility = View.GONE 101 | day.isEnabled = false 102 | } else { 103 | val dayNumber = offsetPosition + 1 104 | day.setText(dayNumber) 105 | day.visibility = View.VISIBLE 106 | day.isEnabled = true 107 | } 108 | val date = getItem(position) ?: return day 109 | return if (calendarConstraints.dateValidator.isValid(date)) { 110 | day.isEnabled = true 111 | for (selectedDay in dateSelector!!.selectedDays) { 112 | if (canonicalYearMonthDay(date) == canonicalYearMonthDay(selectedDay)) { 113 | calendarStyle!!.selectedDay.styleItem(day) 114 | println("selected item $day") 115 | return day 116 | } 117 | } 118 | 119 | if (canonicalYearMonthDay(todayCalendar.timeInMillis) == date) { 120 | calendarStyle!!.todayDay.styleItem(day) 121 | day 122 | } else { 123 | calendarStyle!!.day.styleItem(day) 124 | day 125 | } 126 | } else { 127 | day.isEnabled = false 128 | calendarStyle!!.invalidDay.styleItem(day) 129 | day 130 | } 131 | } 132 | 133 | private fun initializeStyles(context: Context) { 134 | if (calendarStyle == null) { 135 | calendarStyle = CalendarStyle(context) 136 | } 137 | } 138 | 139 | /** 140 | * Returns the index of the first position which is part of the month. 141 | * 142 | * 143 | * For example, this returns the position index representing Ordibehesht 1st. Since position 0 144 | * represents a day which must be the first day of the week, the first position in the month may 145 | * be greater than 0. 146 | */ 147 | fun firstPositionInMonth(): Int { 148 | return month.daysFromStartOfWeekToFirstOfMonth() 149 | } 150 | 151 | /** 152 | * Returns the index of the last position which is part of the month. 153 | * 154 | * 155 | * For example, this returns the position index representing Khordad 31th. Since position 0 156 | * represents a day which must be the first day of the week, the last position in the month may 157 | * not match the number of days in the month. 158 | */ 159 | fun lastPositionInMonth(): Int { 160 | return month.daysFromStartOfWeekToFirstOfMonth() + month.daysInMonth - 1 161 | } 162 | 163 | /** 164 | * Returns the day representing the provided adapter index 165 | * 166 | * @param position The adapter index 167 | * @return The day corresponding to the adapter index. May be non-positive for position inputs 168 | * less than [MonthAdapter.firstPositionInMonth]. 169 | */ 170 | private fun positionToDay(position: Int): Int { 171 | return position - month.daysFromStartOfWeekToFirstOfMonth() + 1 172 | } 173 | 174 | /** Returns the adapter index representing the provided day. */ 175 | fun dayToPosition(day: Int): Int { 176 | val offsetFromFirst = day - 1 177 | return firstPositionInMonth() + offsetFromFirst 178 | } 179 | 180 | /** True when a provided adapter position is within the calendar month */ 181 | fun withinMonth(position: Int): Boolean { 182 | return position >= firstPositionInMonth() && position <= lastPositionInMonth() 183 | } 184 | 185 | /** 186 | * True when the provided adapter position is the smallest position for a value of [ ][MonthAdapter.getItemId]. 187 | */ 188 | fun isFirstInRow(position: Int): Boolean { 189 | return position % month.daysInWeek == 0 190 | } 191 | 192 | /** 193 | * True when the provided adapter position is the largest position for a value of [ ][MonthAdapter.getItemId]. 194 | */ 195 | fun isLastInRow(position: Int): Boolean { 196 | return (position + 1) % month.daysInWeek == 0 197 | } 198 | 199 | companion object { 200 | /** 201 | * The maximum number of weeks possible in any month. 6 for [java.util.GregorianCalendar]. 202 | */ 203 | const val MAXIMUM_WEEKS = 6 204 | } 205 | 206 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/MonthsPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.view.LayoutInflater 22 | import android.view.ViewGroup 23 | import android.widget.AdapterView.OnItemClickListener 24 | import android.widget.LinearLayout 25 | import androidx.recyclerview.widget.RecyclerView 26 | import com.xdev.arch.persiancalendar.R 27 | 28 | internal class MonthsPagerAdapter( 29 | context: Context, 30 | dateSelector: DateSelector<*>?, 31 | calendarConstraints: CalendarConstraints, 32 | onDayClickListener: MaterialCalendar.OnDayClickListener 33 | ) : RecyclerView.Adapter() { 34 | private val calendarConstraints: CalendarConstraints 35 | private val dateSelector: DateSelector<*>? 36 | private val onDayClickListener: MaterialCalendar.OnDayClickListener 37 | private val itemHeight: Int 38 | 39 | class ViewHolder internal constructor(container: LinearLayout) : 40 | RecyclerView.ViewHolder(container) { 41 | val monthGrid: MaterialCalendarGridView = container.findViewById(R.id.month_grid) 42 | 43 | } 44 | 45 | override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { 46 | val container = LayoutInflater.from(viewGroup.context) 47 | .inflate(R.layout.calendar_month_labeled, viewGroup, false) as LinearLayout 48 | 49 | container.layoutParams = 50 | RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, itemHeight) 51 | return ViewHolder(container) 52 | } 53 | 54 | override fun onBindViewHolder( 55 | viewHolder: ViewHolder, 56 | position: Int 57 | ) { 58 | val month = calendarConstraints.start.monthsLater(position) 59 | val monthGrid: MaterialCalendarGridView = viewHolder.monthGrid.findViewById(R.id.month_grid) 60 | 61 | if (monthGrid.adapter != null && month == monthGrid.adapter?.month) 62 | monthGrid.adapter?.notifyDataSetChanged() 63 | else { 64 | val monthAdapter = MonthAdapter(month, dateSelector, calendarConstraints) 65 | monthGrid.numColumns = month.daysInWeek 66 | monthGrid.setAdapter(monthAdapter) 67 | } 68 | 69 | monthGrid.onItemClickListener = OnItemClickListener { _, _, p, _ -> 70 | if (monthGrid.adapter!!.withinMonth(p)) { 71 | onDayClickListener.onDayClick(monthGrid.adapter?.getItem(p)!!) 72 | } 73 | } 74 | } 75 | 76 | override fun getItemId(position: Int): Long { 77 | return calendarConstraints.start.monthsLater(position).stableId 78 | } 79 | 80 | override fun getItemCount(): Int { 81 | return calendarConstraints.monthSpan 82 | } 83 | 84 | fun getPageTitle(position: Int): CharSequence { 85 | return getPageMonth(position).longName 86 | } 87 | 88 | fun getPageMonth(position: Int): Month { 89 | return calendarConstraints.start.monthsLater(position) 90 | } 91 | 92 | fun getPosition(month: Month): Int { 93 | return calendarConstraints.start.monthsUntil(month) 94 | } 95 | 96 | init { 97 | val firstPage = calendarConstraints.start 98 | val lastPage = calendarConstraints.end 99 | val currentPage = calendarConstraints.openAt 100 | 101 | require(firstPage <= currentPage) { "firstPage cannot be after currentPage" } 102 | require(currentPage <= lastPage) { "currentPage cannot be after lastPage" } 103 | val daysHeight: Int = MonthAdapter.MAXIMUM_WEEKS * MaterialCalendar.getDayHeight(context) 104 | itemHeight = daysHeight 105 | this.calendarConstraints = calendarConstraints 106 | this.dateSelector = dateSelector 107 | this.onDayClickListener = onDayClickListener 108 | setHasStableIds(true) 109 | } 110 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/OnSelectionChangedListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | interface OnSelectionChangedListener { 21 | fun onSelectionChanged(selection: S?) 22 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/PickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import androidx.fragment.app.Fragment 21 | import java.util.* 22 | 23 | abstract class PickerFragment : Fragment() { 24 | protected val onSelectionChangedListeners = 25 | LinkedHashSet>() 26 | 27 | abstract val dateSelector: DateSelector? 28 | 29 | /** Adds a listener for selection changes. */ 30 | fun addOnSelectionChangedListener(listener: OnSelectionChangedListener): Boolean { 31 | return onSelectionChangedListeners.add(listener) 32 | } 33 | 34 | /** Removes a listener for selection changes. */ 35 | fun removeOnSelectionChangedListener(listener: OnSelectionChangedListener?): Boolean { 36 | return onSelectionChangedListeners.remove(listener) 37 | } 38 | 39 | /** Removes all listeners for selection changes. */ 40 | fun clearOnSelectionChangedListeners() { 41 | onSelectionChangedListeners.clear() 42 | } 43 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/RangeDateSelector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.os.Parcel 22 | import android.os.Parcelable 23 | import androidx.core.util.Preconditions 24 | import com.xdev.arch.persiancalendar.R 25 | import com.xdev.arch.persiancalendar.datepicker.utils.canonicalYearMonthDay 26 | import com.xdev.arch.persiancalendar.datepicker.utils.getDateRangeString 27 | import com.xdev.arch.persiancalendar.datepicker.utils.getDateString 28 | import com.xdev.arch.persiancalendar.datepicker.utils.resolve 29 | import java.util.* 30 | 31 | class RangeDateSelector : DateSelector> { 32 | private var selectedStartItem: Long? = null 33 | private var selectedEndItem: Long? = null 34 | override fun select(selection: Long) { 35 | if (selectedStartItem == null) { 36 | selectedStartItem = selection 37 | } else if (selectedEndItem == null && isValidRange(selectedStartItem!!, selection)) { 38 | selectedEndItem = selection 39 | } else { 40 | selectedEndItem = null 41 | selectedStartItem = selection 42 | } 43 | } 44 | 45 | override val isSelectionComplete: Boolean 46 | get() = selectedStartItem != null && selectedEndItem != null && isValidRange( 47 | selectedStartItem!!, 48 | selectedEndItem!! 49 | ) 50 | 51 | override fun setSelection(selection: Pair) { 52 | if (selection.first != null && selection.second != null) { 53 | Preconditions.checkArgument( 54 | isValidRange( 55 | selection.first!!, 56 | selection.second!! 57 | ) 58 | ) 59 | } 60 | selectedStartItem = 61 | if (selection.first == null) null else canonicalYearMonthDay( 62 | selection.first!! 63 | ) 64 | selectedEndItem = 65 | if (selection.second == null) null else canonicalYearMonthDay( 66 | selection.second!! 67 | ) 68 | } 69 | 70 | override fun getSelection(): Pair { 71 | return Pair( 72 | selectedStartItem, 73 | selectedEndItem 74 | ) 75 | } 76 | 77 | override val selectedRanges: Collection> 78 | get() { 79 | if (selectedStartItem == null || selectedEndItem == null) { 80 | return ArrayList() 81 | } 82 | val ranges = ArrayList>() 83 | val range = Pair(selectedStartItem, selectedEndItem) 84 | ranges.add(range) 85 | return ranges 86 | } 87 | 88 | override val selectedDays: Collection 89 | get() { 90 | val selections = ArrayList() 91 | if (selectedStartItem != null) { 92 | selections.add(selectedStartItem!!) 93 | } 94 | if (selectedEndItem != null) { 95 | selections.add(selectedEndItem!!) 96 | } 97 | return selections 98 | } 99 | 100 | override fun getDefaultThemeResId(context: Context): Int { 101 | val theme = resolve(context, R.attr.persianMaterialCalendarTheme) 102 | return theme?.data ?: R.style.PersianMaterialCalendar_Default_MaterialCalendar 103 | } 104 | 105 | override fun getSelectionDisplayString(context: Context): String { 106 | val res = context.resources 107 | if (selectedStartItem == null && selectedEndItem == null) { 108 | return res.getString(R.string.picker_range_header_unselected) 109 | } 110 | if (selectedEndItem == null) { 111 | return res.getString( 112 | R.string.picker_range_header_only_start_selected, 113 | getDateString(selectedStartItem!!, res) 114 | ) 115 | } 116 | if (selectedStartItem == null) { 117 | return res.getString( 118 | R.string.picker_range_header_only_end_selected, 119 | getDateString(selectedEndItem!!, res) 120 | ) 121 | } 122 | val dateRangeStrings = getDateRangeString(selectedStartItem, selectedEndItem, res) 123 | return res.getString( 124 | R.string.picker_range_header_selected, 125 | dateRangeStrings.first, 126 | dateRangeStrings.second 127 | ) 128 | } 129 | 130 | override val defaultTitleResId: Int 131 | get() = R.string.picker_range_header_title 132 | 133 | private fun isValidRange(start: Long, end: Long): Boolean { 134 | return start <= end 135 | } 136 | 137 | override fun describeContents(): Int { 138 | return 0 139 | } 140 | 141 | override fun writeToParcel(dest: Parcel, flags: Int) { 142 | dest.writeValue(selectedStartItem) 143 | dest.writeValue(selectedEndItem) 144 | } 145 | 146 | companion object { 147 | @JvmField 148 | val CREATOR: Parcelable.Creator = 149 | object : 150 | Parcelable.Creator { 151 | override fun createFromParcel(source: Parcel): RangeDateSelector { 152 | val rangeDateSelector = 153 | RangeDateSelector() 154 | rangeDateSelector.selectedStartItem = 155 | source.readValue(Long::class.java.classLoader) as Long? 156 | rangeDateSelector.selectedEndItem = 157 | source.readValue(Long::class.java.classLoader) as Long? 158 | return rangeDateSelector 159 | } 160 | 161 | override fun newArray(size: Int): Array { 162 | return arrayOfNulls(size) 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/RtlGridLayoutManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.view.View 22 | import androidx.recyclerview.widget.GridLayoutManager 23 | 24 | class RtlGridLayoutManager constructor( 25 | context: Context, 26 | spanCount: Int, 27 | orientation: Int, 28 | reverse: Boolean 29 | ) : GridLayoutManager(context, spanCount, orientation, reverse) { 30 | 31 | override fun getLayoutDirection(): Int { 32 | return View.LAYOUT_DIRECTION_RTL 33 | } 34 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/SimpleTextView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.content.res.ColorStateList 22 | import android.graphics.Canvas 23 | import android.graphics.Color 24 | import android.text.BoringLayout 25 | import android.text.Layout 26 | import android.text.TextPaint 27 | import android.text.TextUtils 28 | import android.util.AttributeSet 29 | import android.view.View 30 | import androidx.core.content.res.ResourcesCompat 31 | import com.xdev.arch.persiancalendar.R 32 | import com.xdev.arch.persiancalendar.datepicker.utils.resolve 33 | import kotlin.math.min 34 | 35 | /** Simple [android.widget.TextView] to avoid using too much memory */ 36 | class SimpleTextView @JvmOverloads constructor( 37 | context: Context, 38 | attrs: AttributeSet? = null, 39 | defStyleAttr: Int = 0 40 | ) : View(context, attrs, defStyleAttr) { 41 | 42 | var text = "" 43 | set(value) { 44 | field = value 45 | init() 46 | invalidate() 47 | } 48 | 49 | private lateinit var mTextPaint: TextPaint 50 | private lateinit var mMetrics: BoringLayout.Metrics 51 | private lateinit var mLayout: BoringLayout 52 | 53 | init { 54 | if (!TextUtils.isEmpty(this.text)) 55 | init() 56 | } 57 | 58 | private fun init() { 59 | val r = context.resources 60 | val textStyle: Int 61 | 62 | val style = resolve(context, R.attr.dayTextAppearance) 63 | 64 | textStyle = style?.data ?: R.style.PersianMaterialCalendar_Default_DayTextAppearance 65 | 66 | val textAttributes = context.obtainStyledAttributes(textStyle, R.styleable.SimpleTextView) 67 | 68 | val typefaceResId = textAttributes.getResourceId(R.styleable.SimpleTextView_typeface, -1) 69 | val textSize = textAttributes.getDimension(R.styleable.SimpleTextView_textSize, r.getDimension(R.dimen.day_text_size)) 70 | val textColor = textAttributes.getColor(R.styleable.SimpleTextView_textColor, Color.BLACK) 71 | 72 | textAttributes.recycle() 73 | 74 | mTextPaint = TextPaint() 75 | mTextPaint.isAntiAlias = true 76 | mTextPaint.color = textColor 77 | mTextPaint.textSize = textSize 78 | if (typefaceResId != -1) 79 | mTextPaint.typeface = ResourcesCompat.getFont(context, typefaceResId) 80 | 81 | val width = mTextPaint.measureText(this.text).toInt() 82 | 83 | mMetrics = BoringLayout.Metrics() 84 | 85 | val metrics = mTextPaint.fontMetricsInt 86 | 87 | mMetrics.ascent = metrics.ascent 88 | mMetrics.bottom = metrics.bottom 89 | mMetrics.descent = metrics.descent 90 | mMetrics.leading = metrics.leading 91 | mMetrics.top = metrics.top 92 | 93 | mLayout = BoringLayout( 94 | this.text, mTextPaint, width, 95 | Layout.Alignment.ALIGN_CENTER, 96 | 0f, 97 | 0f, 98 | mMetrics, 99 | false 100 | ) 101 | } 102 | 103 | fun setText(number: Int) { 104 | this.text = number.toString() 105 | init() 106 | invalidate() 107 | } 108 | 109 | fun setTextColor(color: Int) { 110 | mTextPaint.color = color 111 | } 112 | 113 | fun setTextColor(color: ColorStateList) { 114 | mTextPaint.color = color.defaultColor 115 | } 116 | 117 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 118 | val width: Int 119 | val widthMode = MeasureSpec.getMode(widthMeasureSpec) 120 | val widthRequirement = MeasureSpec.getSize(widthMeasureSpec) 121 | 122 | width = if (widthMode == MeasureSpec.EXACTLY) 123 | widthRequirement 124 | else 125 | mLayout.width + paddingLeft + paddingRight 126 | 127 | var height: Int 128 | val heightMode = MeasureSpec.getMode(heightMeasureSpec) 129 | val heightRequirement = MeasureSpec.getSize(heightMeasureSpec) 130 | 131 | if (heightMode == MeasureSpec.EXACTLY) 132 | height = heightRequirement 133 | else { 134 | height = mLayout.height + paddingTop + paddingBottom 135 | 136 | if (heightMode == MeasureSpec.AT_MOST) 137 | height = min(height, heightRequirement) 138 | } 139 | 140 | setMeasuredDimension(width, height) 141 | } 142 | 143 | override fun onDraw(canvas: Canvas) { 144 | super.onDraw(canvas) 145 | 146 | val centerStart = width / 2 - mLayout.width / 2 147 | val centerTop = height / 2 - mLayout.height / 2 148 | 149 | canvas.save() 150 | canvas.translate(centerStart.toFloat(), centerTop.toFloat()) 151 | mLayout.draw(canvas) 152 | canvas.restore() 153 | } 154 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/SingleDateSelector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.os.Parcel 22 | import android.os.Parcelable 23 | import com.xdev.arch.persiancalendar.R 24 | import com.xdev.arch.persiancalendar.datepicker.utils.canonicalYearMonthDay 25 | import com.xdev.arch.persiancalendar.datepicker.utils.getYearMonthDay 26 | import com.xdev.arch.persiancalendar.datepicker.utils.resolve 27 | import java.util.* 28 | 29 | /** 30 | * A [DateSelector] that uses a [Long] for its selection state. 31 | * 32 | * @hide 33 | */ 34 | class SingleDateSelector : DateSelector { 35 | private var selectedItem: Long? = null 36 | override fun select(selection: Long) { 37 | selectedItem = selection 38 | } 39 | 40 | override fun setSelection(selection: Long?) { 41 | selectedItem = if (selection == null) null else canonicalYearMonthDay(selection) 42 | } 43 | 44 | override val isSelectionComplete: Boolean 45 | get() = selectedItem != null 46 | 47 | override val selectedRanges: Collection> 48 | get() = ArrayList() 49 | 50 | override val selectedDays: Collection 51 | get() { 52 | val selections = ArrayList() 53 | if (selectedItem != null) { 54 | selections.add(selectedItem!!) 55 | } 56 | return selections 57 | } 58 | 59 | override fun getSelection(): Long? { 60 | return selectedItem 61 | } 62 | 63 | override fun getDefaultThemeResId(context: Context): Int { 64 | val theme = resolve(context, R.attr.persianMaterialCalendarTheme) 65 | return theme?.data ?: R.style.PersianMaterialCalendar_Default_MaterialCalendar 66 | } 67 | 68 | override fun getSelectionDisplayString(context: Context): String { 69 | val res = context.resources 70 | if (selectedItem == null) { 71 | return res.getString(R.string.picker_date_header_unselected) 72 | } 73 | val startString = getYearMonthDay(selectedItem!!) 74 | return res.getString(R.string.picker_date_header_selected, startString) 75 | } 76 | 77 | override val defaultTitleResId: Int 78 | get() = R.string.picker_date_header_title 79 | 80 | override fun describeContents(): Int { 81 | return 0 82 | } 83 | 84 | override fun writeToParcel(dest: Parcel, flags: Int) { 85 | dest.writeValue(selectedItem) 86 | } 87 | 88 | companion object { 89 | @JvmField 90 | val CREATOR: Parcelable.Creator = 91 | object : 92 | Parcelable.Creator { 93 | override fun createFromParcel(source: Parcel): SingleDateSelector { 94 | val singleDateSelector = 95 | SingleDateSelector() 96 | singleDateSelector.selectedItem = 97 | source.readValue(Long::class.java.classLoader) as Long? 98 | return singleDateSelector 99 | } 100 | 101 | override fun newArray(size: Int): Array { 102 | return arrayOfNulls(size) 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/SmoothCalendarLayoutManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.content.Context 21 | import android.util.DisplayMetrics 22 | import androidx.recyclerview.widget.LinearLayoutManager 23 | import androidx.recyclerview.widget.LinearSmoothScroller 24 | import androidx.recyclerview.widget.RecyclerView 25 | 26 | /** 27 | * Layout manager for [MaterialCalendar] that slows the scroll down to appear smoother for 28 | * months. 29 | */ 30 | internal open class SmoothCalendarLayoutManager( 31 | context: Context?, 32 | orientation: Int, 33 | reverseLayout: Boolean 34 | ) : LinearLayoutManager(context, orientation, reverseLayout) { 35 | override fun smoothScrollToPosition( 36 | recyclerView: RecyclerView, state: RecyclerView.State, position: Int 37 | ) { 38 | val linearSmoothScroller: LinearSmoothScroller = 39 | object : LinearSmoothScroller(recyclerView.context) { 40 | override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { 41 | return MILLISECONDS_PER_INCH / displayMetrics.densityDpi 42 | } 43 | } 44 | linearSmoothScroller.targetPosition = position 45 | startSmoothScroll(linearSmoothScroller) 46 | } 47 | 48 | companion object { 49 | private const val MILLISECONDS_PER_INCH = 100f 50 | } 51 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/YearGridAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker 19 | 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import androidx.recyclerview.widget.RecyclerView 24 | import com.xdev.arch.persiancalendar.R 25 | import com.xdev.arch.persiancalendar.datepicker.utils.todayCalendar 26 | 27 | internal class YearGridAdapter(private val materialCalendar: MaterialCalendar<*>) : 28 | RecyclerView.Adapter() { 29 | 30 | class ViewHolder internal constructor(val textView: SimpleTextView) : 31 | RecyclerView.ViewHolder(textView) 32 | 33 | override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { 34 | val yearTextView = LayoutInflater.from(viewGroup.context).inflate( 35 | R.layout.calendar_year, 36 | viewGroup, 37 | false 38 | ) as SimpleTextView 39 | return ViewHolder(yearTextView) 40 | } 41 | 42 | override fun onBindViewHolder( 43 | viewHolder: ViewHolder, 44 | position: Int 45 | ) { 46 | val year = getYearForPosition(position) 47 | viewHolder.textView.setText(year) 48 | val styles = materialCalendar.calendarStyle 49 | val calendar = todayCalendar 50 | var style = if (calendar.year == year) styles.todayYear else styles.year 51 | 52 | for (day in materialCalendar.dateSelector!!.selectedDays) { 53 | calendar.timeInMillis = day 54 | if (calendar.year == year) style = styles.selectedYear 55 | } 56 | 57 | style.styleItem(viewHolder.textView) 58 | viewHolder.textView.setOnClickListener(createYearClickListener(year)) 59 | } 60 | 61 | private fun createYearClickListener(year: Int): View.OnClickListener { 62 | return View.OnClickListener { 63 | val moveTo: Month = Month.create(year, materialCalendar.currentMonth.month) 64 | materialCalendar.currentMonth = moveTo 65 | materialCalendar.setSelector(MaterialCalendar.CalendarSelector.DAY) 66 | } 67 | } 68 | 69 | override fun getItemCount(): Int { 70 | return materialCalendar.calendarConstraints.yearSpan 71 | } 72 | 73 | fun getPositionForYear(year: Int): Int { 74 | return year - materialCalendar.calendarConstraints.start.year 75 | } 76 | 77 | private fun getYearForPosition(position: Int): Int { 78 | return materialCalendar.calendarConstraints.start.year + position 79 | } 80 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/calendar/PersianCalendar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker.calendar 19 | 20 | import com.xdev.arch.persiancalendar.datepicker.utils.MONTH_NAMES 21 | import com.xdev.arch.persiancalendar.datepicker.utils.todayCalendar 22 | import java.util.* 23 | import kotlin.math.floor 24 | 25 | /** 26 | * Persian calendar class to handle dates the way we need 27 | */ 28 | class PersianCalendar() : GregorianCalendar() { 29 | 30 | var year = 0 31 | var month = 0 32 | var day = 0 33 | 34 | constructor(timeInMillis: Long) : this() { 35 | this.timeInMillis = timeInMillis 36 | } 37 | 38 | constructor(calendar: Calendar) : this(calendar.timeInMillis) 39 | 40 | /** 41 | * First day of week is [Calendar.SATURDAY] in Persian Calendar 42 | */ 43 | override fun getFirstDayOfWeek(): Int = Calendar.SATURDAY 44 | 45 | override fun setTimeInMillis(millis: Long) { 46 | super.setTimeInMillis(millis) 47 | internalCalculate() 48 | } 49 | 50 | override fun set(field: Int, value: Int) { 51 | super.set(field, value) 52 | internalCalculate() 53 | } 54 | 55 | override fun add(field: Int, a: Int) { 56 | var amount = a 57 | if (field == Calendar.MONTH) { 58 | amount += month 59 | year += amount / 12 60 | month = amount % 12 61 | 62 | if (day > PERSIAN_DAYS_IN_MONTH[amount % 12]) { 63 | day = PERSIAN_DAYS_IN_MONTH[amount % 12] 64 | if (month == 11 && isLeapYear(year)) 65 | day = 30 66 | } 67 | 68 | setPersian(year, month, day) 69 | } 70 | } 71 | 72 | override fun toString(): String { 73 | return "$year/$month/$day" 74 | } 75 | 76 | 77 | /** 78 | * @return first day of Persian month in this calendar 79 | */ 80 | fun getFirstDayOfMonth(): Int { 81 | setDayOfMonth(1) 82 | return get(Calendar.DAY_OF_WEEK) 83 | } 84 | 85 | /** 86 | * Sets day of persian month 87 | */ 88 | fun setDayOfMonth(day: Int) = internalToGregory(year, month, day) 89 | 90 | /** 91 | * @return total days in this month (checks for leap) 92 | */ 93 | fun getMaxDaysInMonth(): Int { 94 | return if (isPersianLeapYear()) PERSIAN_DAYS_IN_MONTH_LEAP[month] 95 | else PERSIAN_DAYS_IN_MONTH[month] 96 | } 97 | 98 | /** 99 | * Determines if this year is a Persian leap year or not 100 | * @return true if this year is a Persian leap year 101 | */ 102 | private fun isPersianLeapYear(): Boolean { 103 | val a = year - 474L 104 | val b: Long = mod(a.toDouble(), 2820L.toDouble()) + 474L 105 | return mod((b + 38.0) * 682.0, 2816.0) < 682L 106 | } 107 | 108 | /** 109 | * Sets date in Persian format 110 | * @param year Persian year 111 | * @param month Persian month 112 | * @param day of month 113 | */ 114 | fun setPersian(year: Int, month: Int, day: Int) = internalToGregory(year, month, day) 115 | 116 | /** 117 | * Converts Gregorian date to Persian 118 | * and sets the [year] [month] [day] properties 119 | */ 120 | private fun internalCalculate() { 121 | var y = get(YEAR) 122 | val m = get(MONTH) 123 | var d = get(Calendar.DAY_OF_MONTH) 124 | 125 | var pYear: Int 126 | val pMonth: Int 127 | val pDay: Int 128 | var pDayNo: Int 129 | val pNP: Int 130 | var gDayNo: Int 131 | 132 | y -= 1600 133 | d -= 1 134 | 135 | gDayNo = (365 * y + floor((y + 3) / 4.toDouble()).toInt() 136 | - floor((y + 99) / 100.toDouble()).toInt() 137 | + floor((y + 399) / 400.toDouble()).toInt()) 138 | 139 | var i = 0 140 | while (i < m) { 141 | gDayNo += GREGORIAN_DAYS_IN_MONTH[i] 142 | ++i 143 | } 144 | 145 | if (m > 1 && (y % 4 == 0 && y % 100 != 0 || year % 400 == 0)) 146 | ++gDayNo 147 | 148 | gDayNo += d 149 | 150 | pDayNo = gDayNo - 79 151 | pNP = floor(pDayNo / 12053.toDouble()).toInt() 152 | pDayNo %= 12053 153 | pYear = 979 + 33 * pNP + 4 * (pDayNo / 1461) 154 | pDayNo %= 1461 155 | if (pDayNo >= 366) { 156 | pYear += floor((pDayNo - 1) / 365.toDouble()).toInt() 157 | pDayNo = (pDayNo - 1) % 365 158 | } 159 | 160 | i = 0 161 | while (i < 11 && pDayNo >= PERSIAN_DAYS_IN_MONTH[i]) { 162 | pDayNo -= PERSIAN_DAYS_IN_MONTH[i] 163 | ++i 164 | } 165 | 166 | pMonth = i 167 | pDay = pDayNo + 1 168 | 169 | this.year = pYear 170 | this.month = pMonth 171 | this.day = pDay 172 | 173 | } 174 | 175 | /** 176 | * Converts Persian date to Gregory and 177 | * sets [Calendar.YEAR] [Calendar.MONTH] [Calendar.DAY_OF_MONTH] of this calendar 178 | * @param y Persian year 179 | * @param m Persian month 180 | * @param d Persian day 181 | * @see Calendar.set() 182 | */ 183 | private fun internalToGregory(y: Int, m: Int, d: Int) { 184 | require(d > 0 && m < 12 && m >= 0 && d < 33) 185 | var year = y 186 | var day = d 187 | 188 | var gYear: Int 189 | val gMonth: Int 190 | val gDay: Int 191 | var gDayNo: Int 192 | var pDayNo: Int 193 | var leap: Int 194 | 195 | year -= 979 196 | day -= 1 197 | 198 | pDayNo = 365 * year + (year / 33) * 8 + floor(((year % 33 + 3) / 4).toDouble()).toInt() 199 | var i = 0 200 | while (i < m) { 201 | pDayNo += PERSIAN_DAYS_IN_MONTH[i] 202 | ++i 203 | } 204 | 205 | pDayNo += day 206 | gDayNo = pDayNo + 79 207 | gYear = 1600 + 400 * floor(gDayNo / 146097.toDouble()).toInt() 208 | gDayNo %= 146097 209 | leap = 1 210 | if (gDayNo >= 36525) { 211 | gDayNo-- 212 | gYear += 100 * floor(gDayNo / 36524.toDouble()).toInt() 213 | gDayNo %= 36524 214 | if (gDayNo >= 365) 215 | gDayNo++ 216 | else leap = 0 217 | } 218 | gYear += 4 * floor(gDayNo / 1461.toDouble()).toInt() 219 | gDayNo %= 1461 220 | if (gDayNo >= 366) { 221 | leap = 0 222 | gDayNo-- 223 | gYear += floor(gDayNo / 365.toDouble()).toInt() 224 | gDayNo %= 365 225 | } 226 | i = 0 227 | while (gDayNo >= GREGORIAN_DAYS_IN_MONTH[i] + if (i == 1 && leap == 1) i else 0) { 228 | gDayNo -= GREGORIAN_DAYS_IN_MONTH[i] + if (i == 1 && leap == 1) i else 0 229 | i++ 230 | } 231 | 232 | gMonth = i 233 | gDay = gDayNo + 1 234 | 235 | set(gYear, gMonth, gDay) 236 | } 237 | 238 | /** 239 | * @return name of this month in Persian 240 | * @see MONTH_NAMES 241 | */ 242 | fun getMonthName(): String = MONTH_NAMES[month] 243 | 244 | 245 | companion object { 246 | 247 | /** 248 | * Max days in a week in Persian Calendar -1 249 | */ 250 | const val DAYS_IN_WEEK = 6 251 | 252 | /** 253 | * Max days in a month in Gregorian calendar 254 | */ 255 | private val GREGORIAN_DAYS_IN_MONTH = intArrayOf( 256 | 31, 28, 31, 30, 31, 30, 31, 257 | 31, 30, 31, 30, 31 258 | ) 259 | 260 | /** 261 | * Max days in a month in Persian calendar 262 | */ 263 | private val PERSIAN_DAYS_IN_MONTH = intArrayOf( 264 | 31, 31, 31, 31, 31, 31, 30, 30, 265 | 30, 30, 30, 29 266 | ) 267 | 268 | /** 269 | * Max days in a month in Persian leap year calendar 270 | */ 271 | private val PERSIAN_DAYS_IN_MONTH_LEAP = intArrayOf( 272 | 31, 31, 31, 31, 31, 31, 30, 30, 273 | 30, 30, 30, 30 274 | ) 275 | 276 | /** 277 | * 278 | * A modulo function suitable for our purpose. 279 | * 280 | * @param a the dividend. 281 | * @param b the divisor. 282 | * @return the remainder of integer division. 283 | */ 284 | fun mod(a: Double, b: Double): Long = (a - b * floor(a / b)).toLong() 285 | 286 | 287 | /** 288 | * @return an instance of [PersianCalendar] for today 289 | */ 290 | fun getToday(): PersianCalendar = todayCalendar 291 | } 292 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/utils/DateStrings.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker.utils 19 | 20 | import android.content.res.Configuration 21 | import android.content.res.Resources 22 | import androidx.core.util.Pair 23 | import com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar 24 | import java.util.* 25 | 26 | 27 | /** 28 | * Persian month names 29 | */ 30 | internal val MONTH_NAMES = arrayOf( 31 | "فروردین", 32 | "اردیبهشت", 33 | "خرداد", 34 | "تیر", 35 | "مرداد", 36 | "شهریور", 37 | "مهر", 38 | "آبان", 39 | "آذر", 40 | "دی", 41 | "بهمن", 42 | "اسفند" 43 | ) 44 | 45 | /** 46 | * Persian days of week abbreviated 47 | */ 48 | private val ABBR_WEEK_DAY_NAMES = arrayOf( 49 | "ج", 50 | "پ", 51 | "چ", 52 | "س", 53 | "د", 54 | "ی", 55 | "ش" 56 | ) 57 | 58 | /** 59 | * @param day of week 60 | * @return Abbreviated day name 61 | */ 62 | fun getAbbrDayName(day: Int): String { 63 | return ABBR_WEEK_DAY_NAMES[day] 64 | } 65 | 66 | /** 67 | * Formats date 68 | * @param timeInMillis time in millis representing the date 69 | * @return Formatted string: year/month/day 70 | * @see Calendar.getTimeInMillis 71 | */ 72 | fun getYearMonthDay(timeInMillis: Long): String { 73 | val calendar = PersianCalendar() 74 | calendar.timeInMillis = timeInMillis 75 | return "${calendar.day} ${MONTH_NAMES[calendar.month]}، ${calendar.year}" 76 | } 77 | 78 | /** 79 | * Formats date without year 80 | * @param timeInMillis time in millis representing the date 81 | * @return Formatted string: month/day 82 | * @see Calendar.getTimeInMillis 83 | */ 84 | fun getMonthDay(timeInMillis: Long): String { 85 | val calendar = PersianCalendar() 86 | calendar.timeInMillis = timeInMillis 87 | return "${calendar.day} ${MONTH_NAMES[calendar.month]}" 88 | } 89 | 90 | /** 91 | * Formats date 92 | * if selected date was in the same year as [todayCalendar] was 93 | * it wont show the year 94 | * 95 | * @param timeInMillis representing selected date 96 | * @param res [Resources] to retrieve strings from resource 97 | * @return formatted date 98 | */ 99 | fun getDateString(timeInMillis: Long, res: Resources): String { 100 | val currentCalendar = todayCalendar 101 | val calendarDate = iranCalendar 102 | calendarDate.timeInMillis = timeInMillis 103 | 104 | if (currentCalendar.year == calendarDate.year) 105 | return getMonthDay(timeInMillis) 106 | 107 | return if (res.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 108 | "${getMonthDay(timeInMillis)}\n${calendarDate.year}" 109 | else getYearMonthDay(timeInMillis) 110 | } 111 | 112 | /** 113 | * Formats date to show in [com.xdev.arch.persiancalendar.datepicker.MonthsPagerAdapter.getPageTitle] 114 | * @return formatted date 115 | */ 116 | fun formatYearMonth(calendar: PersianCalendar): String { 117 | return "${calendar.getMonthName()}، ${calendar.year}" 118 | } 119 | 120 | /** 121 | * Formats two dates for [com.xdev.arch.persiancalendar.datepicker.RangeDateSelector] 122 | * @return formatted string 123 | */ 124 | fun getDateRangeString(start: Long?, end: Long?, res: Resources): Pair { 125 | if (start == null && end == null) return Pair.create(null, null) 126 | else if (start == null && end != null) return Pair.create(null, getDateString(end, res)) 127 | else if (start != null && end == null) return Pair.create(getDateString(start, res), null) 128 | 129 | start as Long 130 | end as Long 131 | 132 | val currentCalendar = todayCalendar 133 | val startCalendar = iranCalendar 134 | startCalendar.timeInMillis = start 135 | val endCalendar = iranCalendar 136 | endCalendar.timeInMillis = end 137 | 138 | if (startCalendar.year == endCalendar.year) { 139 | return if (startCalendar.year == currentCalendar.year) 140 | Pair.create(getMonthDay(start), getMonthDay(end)) 141 | else { 142 | if (res.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 143 | Pair.create(getMonthDay(start), "${getMonthDay(end)}\n${endCalendar.year}") 144 | else 145 | Pair.create(getMonthDay(start), getYearMonthDay(end)) 146 | } 147 | } 148 | 149 | return if (res.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 150 | Pair.create( 151 | "${getMonthDay(start)}\n${startCalendar.year}", 152 | "${getMonthDay(end)}\n${endCalendar.year}" 153 | ) else 154 | Pair.create(getYearMonthDay(start), getYearMonthDay(end)) 155 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/utils/UtcDates.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker.utils 19 | 20 | import com.xdev.arch.persiancalendar.datepicker.calendar.PersianCalendar 21 | import java.util.* 22 | import java.util.Calendar.* 23 | 24 | /** 25 | * Iran's timezone code 26 | */ 27 | private const val IRST = "IRST" 28 | 29 | /** 30 | * Iran's timezone 31 | */ 32 | private val timeZone: TimeZone 33 | get() = TimeZone.getTimeZone(IRST) 34 | 35 | /** 36 | * A [PersianCalendar] instance representing the day 37 | */ 38 | val todayCalendar: PersianCalendar 39 | get() { 40 | val calendar = getInstance(timeZone) 41 | return PersianCalendar(calendar) 42 | } 43 | 44 | /** 45 | * A [PersianCalendar] with no data 46 | */ 47 | val iranCalendar: PersianCalendar 48 | get() = getIranCalendar(null) 49 | 50 | /** 51 | * Converts a [Calendar] to a [PersianCalendar] with [IRST] timezone 52 | * @return [PersianCalendar] with [IRST] timezone 53 | */ 54 | private fun getIranCalendar(rawCalendar: Calendar?): PersianCalendar { 55 | val utc = getInstance(timeZone) 56 | 57 | if (rawCalendar == null) utc.clear() 58 | else utc.timeInMillis = rawCalendar.timeInMillis 59 | 60 | return PersianCalendar(utc) 61 | } 62 | 63 | /** 64 | * Clears all data from rawCalendar except date and 65 | * returns a [PersianCalendar] with same date as rawCalendar 66 | */ 67 | fun getDayCopy(rawCalendar: Calendar): PersianCalendar { 68 | val raw = getIranCalendar(rawCalendar) 69 | val irCalendar = iranCalendar 70 | 71 | irCalendar.set(raw.get(YEAR), raw.get(MONTH), raw.get(DAY_OF_MONTH)) 72 | return irCalendar 73 | } 74 | 75 | /** 76 | * Sets date to midnight 00:00:00:00 77 | * @param rawDate date in millis 78 | * @return midnight date in millis 79 | */ 80 | fun canonicalYearMonthDay(rawDate: Long): Long { 81 | val calendar = PersianCalendar() 82 | calendar.timeInMillis = rawDate 83 | calendar.timeZone = TimeZone.getTimeZone(IRST) 84 | calendar.set(HOUR_OF_DAY, 0) 85 | calendar.set(MINUTE, 0) 86 | calendar.set(SECOND, 0) 87 | calendar.set(MILLISECOND, 0) 88 | return calendar.timeInMillis 89 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/java/com/xdev/arch/persiancalendar/datepicker/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Rahman Mohammadi 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 | 18 | package com.xdev.arch.persiancalendar.datepicker.utils 19 | 20 | import android.content.Context 21 | import android.content.res.ColorStateList 22 | import android.content.res.TypedArray 23 | import android.util.TypedValue 24 | import androidx.annotation.AttrRes 25 | import androidx.annotation.StyleableRes 26 | import androidx.appcompat.content.res.AppCompatResources 27 | 28 | 29 | fun getColorStateList( 30 | context: Context, attributes: TypedArray, @StyleableRes index: Int 31 | ): ColorStateList? { 32 | if (attributes.hasValue(index)) { 33 | val resourceId = attributes.getResourceId(index, 0) 34 | if (resourceId != 0) { 35 | val value = 36 | AppCompatResources.getColorStateList(context, resourceId) 37 | if (value != null) { 38 | return value 39 | } 40 | } 41 | } 42 | 43 | return attributes.getColorStateList(index) 44 | } 45 | 46 | fun resolve(context: Context, @AttrRes attributeResId: Int): TypedValue? { 47 | val typedValue = TypedValue() 48 | return if (context.theme.resolveAttribute(attributeResId, typedValue, true)) { 49 | typedValue 50 | } else null 51 | } -------------------------------------------------------------------------------- /persiancalendar/src/main/res/color/calendar_selected_range.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/color/text_btn_text_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/btn_ripple_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/dialog_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/ic_arrow_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 25 | 30 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/ic_arrow_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/ic_menu_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/nvn_btn_ripple_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/drawable/nvn_toggle_btn_ripple_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout-land/calendar_month.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout-land/calendar_months.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout-land/picker_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 29 | 30 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout-land/picker_header_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 24 | 25 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout-land/picker_header_selection_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout-land/picker_header_title_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_day_of_week.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_days_of_week.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_horizontal.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 25 | 26 | 30 | 31 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_month.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_month_labeled.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_month_navigation.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 34 | 35 | 41 | 42 | 47 | 48 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_months.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/calendar_year.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/picker_actions.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 30 | 31 | 39 | 40 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/picker_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 25 | 26 | 31 | 32 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/picker_header_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 36 | 37 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/picker_header_selection_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 33 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/layout/picker_header_title_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-h360dp-land-v13/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 4dp 21 | 40dp 22 | 2dp 23 | 2dp 24 | 40dp 25 | 20dp 26 | 128dp 27 | 4dp 28 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-h480dp-land-v13/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 0dp 21 | 48dp 22 | 2dp 23 | 2dp 24 | 48dp 25 | 20dp 26 | 168dp 27 | 0dp 28 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 64dp 22 | 24dp 23 | 102dp 24 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-land/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 6 21 | 0 22 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-land/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | تاریخ شروع\n%1$s 21 | %1$s\nتاریخ پایان 22 | %1$s\n%2$s 23 | تاریخ را انتخاب کنید 24 | تاریخ شروع\nتاریخ پایان 25 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-v28/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-w360dp-port-v13/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 4dp 21 | 40dp 22 | 2dp 23 | 2dp 24 | 40dp 25 | 40dp 26 | 4dp 27 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values-w480dp-port-v13/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 0dp 21 | 48dp 22 | 2dp 23 | 2dp 24 | 48dp 25 | 48dp 26 | 0dp 27 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | #00897B 21 | #59000000 22 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 14sp 21 | 22 | 32dp 23 | 3dp 24 | 1dp 25 | 1dp 26 | 36dp 27 | 24dp 28 | 0dp 29 | 0dp 30 | 2dp 31 | 0dp 32 | 48dp 33 | 4dp 34 | 4dp 35 | 12dp 36 | 88dp 37 | 52dp 38 | 8dp 39 | 8dp 40 | 52dp 41 | 8dp 42 | 12dp 43 | 120dp 44 | 32dp 45 | 12dp 46 | 28dp 47 | 100dp 48 | 49 | 48dp 50 | 64dp 51 | 8dp 52 | 16dp 53 | 4dp 54 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 1 21 | 3 22 | 1 23 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /persiancalendar/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | Current selection: %1$s 21 | %1$s 22 | یک تاریخ انتخاب کنید 23 | یک تاریخ انتخاب کنید 24 | تاریخ شروع – %1$s 25 | %1$s – تاریخ پایان 26 | %1$s – %2$s 27 | تاریخ را انتخاب کنید 28 | تاریخ شروع - تاریخ پایان 29 | لغو 30 | تایید 31 | 32 | -------------------------------------------------------------------------------- /screenshots/date_picker_land.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/date_picker_land.png -------------------------------------------------------------------------------- /screenshots/date_picker_portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/date_picker_portrait.png -------------------------------------------------------------------------------- /screenshots/land.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/land.gif -------------------------------------------------------------------------------- /screenshots/portrait.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/portrait.gif -------------------------------------------------------------------------------- /screenshots/range_picker_land.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/range_picker_land.png -------------------------------------------------------------------------------- /screenshots/range_picker_portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/range_picker_portrait.png -------------------------------------------------------------------------------- /screenshots/range_picker_year_portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axdeveloper/PersianDatePicker/0516b6756d837082ec51dca816e00fe8e97cc769/screenshots/range_picker_year_portrait.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':persiancalendar' 2 | rootProject.name='PersianLibrary' 3 | --------------------------------------------------------------------------------