+ 1488927969808
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
-
-
-
-
+
+
+
+
-
-
+
-
-
+
-
@@ -3770,42 +4996,194 @@
-
+
-
-
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
diff --git a/HijriDatePicker.iml b/HijriDatePicker.iml
index d5a23c1..5feb710 100644
--- a/HijriDatePicker.iml
+++ b/HijriDatePicker.iml
@@ -1,5 +1,5 @@
-
+
@@ -9,11 +9,10 @@
-
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 9bcbfb3..06de287 100644
--- a/README.md
+++ b/README.md
@@ -5,30 +5,34 @@
![](https://img.shields.io/badge/Platform-Android-brightgreen.svg)
![](https://img.shields.io/badge/Android-CustomView-blue.svg)
![](https://img.shields.io/crates/l/rustc-serialize.svg)
-![](https://img.shields.io/badge/version-1.3.6-blue.svg)
+![](https://img.shields.io/badge/version-2.0.0_beta-blue.svg)
This library offers a hijri (Islamic Calendar) Date Picker designed on [Google's Material Design Principals For Pickers](http://www.google.com/design/spec/components/pickers.html) for Android 4.1 (API 16) +.
-عربي | English
+Demo | Hijri
---- | ----
-![Arabic](https://cloud.githubusercontent.com/assets/4659608/10579117/0466d434-767f-11e5-8172-534f1a47c608.png) | ![English](https://cloud.githubusercontent.com/assets/4659608/10579118/04684ee0-767f-11e5-8432-a9b5d67713f9.png)
+ |
+Time | Gregorian
+ |
You can report any issue on issues page. **Note: If you speak Arabic, you can submit issues with Arabic language and I will check them. :)**
##Installation
**Maven**
+
```xml
net.alhazmy13.hijridatepicker
-libary
-1.3.6
+library
+2.0.0-beta
```
**Gradle**
+
```gradle
dependencies {
- compile 'net.alhazmy13.hijridatepicker:libary:1.3.6'
+ compile 'net.alhazmy13.hijridatepicker:library:2.0.0-beta'
}
```
@@ -36,78 +40,80 @@ dependencies {
The library follows the same API as other pickers in the Android framework.
After adding the library, you need to:
-1. Implement an `OnDateSetListener`
+1. Implement an `onDateSet`
2. Create a `HijriCalendarDialog` using the supplied factory
3. Theme the pickers
-### Implement an `OnDateSetListener`
+### Implement an `OnTimeSetListener/OnDateSetListener`
In order to receive the date set in the picker, you will need to implement the `OnDateSetListener` interfaces. Typically this will be the `Dialog` that creates the Pickers. The callbacks use the same API as the standard Android pickers.
Note: Months start from 0.
```java
- @Override
- public void onDateSet(int year, int month, int day) {
- Toast.makeText(getApplicationContext(),year+"/"+(month+1)+"/"+day+"/",Toast.LENGTH_SHORT).show();
-
+ // TimePickerDialog
+ @Override
+ public void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second) {
+ // YOUR CODE
+ }
+ // GregorianDatePickerDialog
+ @Override
+ public void onDateSet(GregorianDatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
+ //YOUR CODE
}
+
+ // HijriDatePickerDialog
+ @Override
+ public void onDateSet(HijriDatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
+ //YOUR CODE
+ }
+
```
-### Create a `HijriCalendarDialog`
-You will need to create a new instance of `HijriCalendarDialog`. Once the dialogs are configured, you can call `show()`.
+### Create a `GregorianDatePickerDialog`
+You will need to create a new instance of `GregorianDatePickerDialog `. Once the dialogs are configured, you can call `show()`.
+
```java
- new HijriCalendarDialog.Builder(this)
- .setOnDateSetListener(this)
- .show();
+Calendar now = Calendar.getInstance();
+GregorianDatePickerDialog dpd = GregorianDatePickerDialog(
+ CONTEXT,
+ now.get(Calendar.YEAR),
+ now.get(Calendar.MONTH),
+ now.get(Calendar.DAY_OF_MONTH));
+dpd.show(getFragmentManager(), "GregorianDatePickerDialog");
```
-### Theme the pickers
-You can theme the pickers by overwriting the color resources `hijri_date_picker_accent_color` in your project.
-```xml
-#009688
-```
+### Create a `HijriDatePickerDialog`
+Same as `GregorianDatePickerDialog` but you need to use `UmmalquraCalendar` insted of `Calendar` class.
-### Additional Options
-* `SetUILanguage` You can change the display language to your preferred language `ARABIC` or `ENGLISH`
-```java
-.setUILanguage(HijriCalendarDialog.Language.Arabic);
-```
-* `setMode` to change the mode from `Hijri` to `Gregorian`
-```java
-.setMode(HijriCalendarDialog.Mode.Gregorian)
-```
-* `setMaxHijriYear` To set the maximum Hijri year for dialg
-```java
-.setMaxHijriYear(1440);
-```
-* `setMinHijriYear` To set the minimum hijri year for dialg
-```java
-.setMinYear(1437);
-```
-* `setMinMaxHijriYear` To set the minimum and maximum hijri year for dialog
-```java
-.setMinMaxHijriYear(1430,1440);
-```
-* `setMaxGregorianYear` To set the maximum Gregorian year for dialg
-```java
-.setMaxGregorianYear(1440);
-```
-* `setMinGregorianYear` To set the minimum Gregorian year for dialg
```java
-.setMinGregorianYear(1437);
+UmmalquraCalendar now = new UmmalquraCalendar();
+HijriDatePickerDialog dpd = HijriDatePickerDialog(
+ CONTEXT,
+ now.get(UmmalquraCalendar.YEAR),
+ now.get(UmmalquraCalendar.MONTH),
+ now.get(UmmalquraCalendar.DAY_OF_MONTH));
+dpd.show(getFragmentManager(), "HijriDatePickerDialog");
```
-* `setMinMaxGregorianYear` To set the minimum and maximum Gregorian year for dialog
-```java
-.setMinMaxGregorianYear(1430,1440);
-```
-* `setDefaultHijriDate`
+
+### `TimePickerDialog`
+
+
```java
-.setDefaultHijriDate(8, 0, 1437) //months start from 0
+Calendar now = Calendar.getInstance();
+TimePickerDialog tpd = TimePickerDialog.newInstance(
+ TimePickerFragment.this,
+ now.get(Calendar.HOUR_OF_DAY),
+ now.get(Calendar.MINUTE),
+ mode24Hours.isChecked()
+ );
```
+
+
+
## Credits
* [ummalqura-calendar Library](https://github.com/msarhan/ummalqura-calendar)
-* [AndroidViewAnimations](https://github.com/daimajia/AndroidViewAnimations).
+* [MaterialDateTimePicker](https://github.com/wdullaer/MaterialDateTimePicker).
## License
diff --git a/app/app.iml b/app/app.iml
deleted file mode 100644
index e91fda8..0000000
--- a/app/app.iml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- generateDebugSources
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-<<<<<<< HEAD
-
-=======
-
-
->>>>>>> c5fbecb437ed0012cb0ff07d2f0b5e9abafa8771
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index 671df74..0000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 23
- buildToolsVersion "23.0.1"
-
- defaultConfig {
- applicationId "net.alhazmy13.hijridatepicker"
- minSdkVersion 16
- targetSdkVersion 23
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- compile fileTree(include: ['*.jar'], dir: 'libs')
- compile 'com.android.support:appcompat-v7:23.0.1'
-}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
deleted file mode 100644
index ff3561b..0000000
--- a/app/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/Alhazmy13/Library/Android/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/app/src/androidTest/java/net/alhazmy13/hijridatepicker/ApplicationTest.java b/app/src/androidTest/java/net/alhazmy13/hijridatepicker/ApplicationTest.java
deleted file mode 100644
index c93b91d..0000000
--- a/app/src/androidTest/java/net/alhazmy13/hijridatepicker/ApplicationTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package net.alhazmy13.hijridatepicker;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * Testing Fundamentals
- */
-public class ApplicationTest extends ApplicationTestCase {
- public ApplicationTest() {
- super(Application.class);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 82ce108..0000000
--- a/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/net/alhazmy13/hijridatepicker/MainActivity.java b/app/src/main/java/net/alhazmy13/hijridatepicker/MainActivity.java
deleted file mode 100644
index 7b3457b..0000000
--- a/app/src/main/java/net/alhazmy13/hijridatepicker/MainActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package net.alhazmy13.hijridatepicker;
-
-import android.support.v7.app.AppCompatActivity;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-
-public class MainActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index f7158b8..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
deleted file mode 100644
index b1cb908..0000000
--- a/app/src/main/res/menu/menu_main.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
deleted file mode 100644
index db9ed63..0000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
- HijriDatePicker
-
- Hello world!
- Settings
-
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
deleted file mode 100644
index 766ab99..0000000
--- a/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
diff --git a/build.gradle b/build.gradle
index 221a626..33e7a5d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,9 +5,8 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.5.0'
- classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
+ classpath 'com.android.tools.build:gradle:2.2.3'
+ classpath 'com.novoda:bintray-release:0.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
diff --git a/example/.gitignore b/example/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/example/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/example/build.gradle b/example/build.gradle
deleted file mode 100644
index 469b455..0000000
--- a/example/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 23
- buildToolsVersion "23.0.1"
-
- defaultConfig {
- applicationId "net.alhazmy13.hijricalenderexample"
- minSdkVersion 16
- targetSdkVersion 23
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- compile fileTree(include: ['*.jar'], dir: 'libs')
- compile 'com.android.support:appcompat-v7:23.0.1'
- compile project(':libary')
-}
diff --git a/example/example.iml b/example/example.iml
deleted file mode 100644
index 1bb43b8..0000000
--- a/example/example.iml
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- generateDebugSources
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/src/androidTest/java/net/alhazmy13/hijricalenderexample/ApplicationTest.java b/example/src/androidTest/java/net/alhazmy13/hijricalenderexample/ApplicationTest.java
deleted file mode 100644
index e615e6b..0000000
--- a/example/src/androidTest/java/net/alhazmy13/hijricalenderexample/ApplicationTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package net.alhazmy13.hijricalenderexample;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * Testing Fundamentals
- */
-public class ApplicationTest extends ApplicationTestCase {
- public ApplicationTest() {
- super(Application.class);
- }
-}
\ No newline at end of file
diff --git a/example/src/main/java/net/alhazmy13/hijricalenderexample/MainActivity.java b/example/src/main/java/net/alhazmy13/hijricalenderexample/MainActivity.java
deleted file mode 100644
index 98656d8..0000000
--- a/example/src/main/java/net/alhazmy13/hijricalenderexample/MainActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package net.alhazmy13.hijricalenderexample;
-
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import net.alhazmy13.hijridatepicker.HijriCalendarDialog;
-import net.alhazmy13.hijridatepicker.HijriCalendarView;
-
-public class MainActivity extends AppCompatActivity implements HijriCalendarView.OnDateSetListener {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
-
- //Without setting default date
-// new HijriCalendarDialog.Builder(MainActivity.this)
-// .setOnDateSetListener(MainActivity.this)
-// .setMinMaxHijriYear(1430,1450)
-// .setMinMaxGregorianYear(2013,2020)
-// .setUILanguage(HijriCalendarDialog.Language.English)
-// .setMode(HijriCalendarDialog.Mode.Hijri)
-// .show();
-
- //Setting default date
- new HijriCalendarDialog.Builder(MainActivity.this)
- .setOnDateSetListener(MainActivity.this)
- .setMinMaxHijriYear(1430,1450)
- .setMinMaxGregorianYear(2013,2020)
- .setMode(HijriCalendarDialog.Mode.Hijri)
- .setDefaultHijriDate(8, 0, 1437)//months start from 0
- .setEnableScrolling(false)
- .show();
-
-
- }
- });
-
- }
-
- @Override
- public void onDateSet(int year, int month, int day) {
- ((TextView) findViewById(R.id.textView)).setText(year+"/"+(month+1)+"/"+day);
- }
-
-
-}
diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 3651fff..0000000
--- a/example/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
diff --git a/example/src/main/res/menu/menu_main.xml b/example/src/main/res/menu/menu_main.xml
deleted file mode 100644
index b1cb908..0000000
--- a/example/src/main/res/menu/menu_main.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/example/src/main/res/mipmap-hdpi/ic_launcher.png b/example/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bc..0000000
Binary files a/example/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/example/src/main/res/mipmap-mdpi/ic_launcher.png b/example/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0c..0000000
Binary files a/example/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/example/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0..0000000
Binary files a/example/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/example/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72c..0000000
Binary files a/example/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/example/src/main/res/values-w820dp/dimens.xml b/example/src/main/res/values-w820dp/dimens.xml
deleted file mode 100644
index 63fc816..0000000
--- a/example/src/main/res/values-w820dp/dimens.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- 64dp
-
diff --git a/example/src/main/res/values/color.xml b/example/src/main/res/values/color.xml
deleted file mode 100644
index 96957a5..0000000
--- a/example/src/main/res/values/color.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #FF9688
-
\ No newline at end of file
diff --git a/example/src/main/res/values/dimens.xml b/example/src/main/res/values/dimens.xml
deleted file mode 100644
index 47c8224..0000000
--- a/example/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 16dp
- 16dp
-
diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml
deleted file mode 100644
index 4402c6f..0000000
--- a/example/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
- HijriCalender Example
-
- Hello world!
- Settings
- xx/xx/xxxx
-
diff --git a/example/src/main/res/values/styles.xml b/example/src/main/res/values/styles.xml
deleted file mode 100644
index 766ab99..0000000
--- a/example/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
diff --git a/gradle.properties b/gradle.properties
index 21a66c0..89e0d99 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,5 +16,3 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-#sonatype login
-
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0b48f6b..fb19cf7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Oct 17 14:28:05 AST 2015
+#Wed Dec 28 19:54:34 CET 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew.bat b/gradlew.bat
index aec9973..8a0b282 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,90 +1,90 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/libary/.gitignore b/libary/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/libary/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/libary/build.gradle b/libary/build.gradle
deleted file mode 100644
index caf398b..0000000
--- a/libary/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-apply plugin: 'com.android.library'
-
-//./gradlew bintrayUpload
-ext {
- bintrayRepo = 'maven'
- bintrayName = 'HijriDatePicker'
-
- publishedGroupId = 'net.alhazmy13.hijridatepicker'
- libraryName = 'libary'
-
- artifact = 'libary'
-
- libraryDescription = 'Hijri Date Picker'
-
- siteUrl = 'https://github.com/alhzmy13/HijriDatePicker'
- gitUrl = 'https://github.com/alhzmy13/HijriDatePicker.git'
-
- libraryVersion = '1.3.6'
-
- developerId = 'alhazmy13'
- developerName = 'Abdullah Alhazmy'
- developerEmail = 'me@alhazmy13.net'
-
- licenseName = 'The Apache Software License, Version 2.0'
- licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- allLicenses = ["Apache-2.0"]
-}
-
-android {
- compileSdkVersion 23
- buildToolsVersion "23.0.2"
-
- defaultConfig {
- minSdkVersion 15
- targetSdkVersion 23
- versionCode 1
- versionName "1.0.1"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- lintOptions {
- abortOnError false
- }
-
-
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.android.support:appcompat-v7:23.1.1'
- compile group: 'com.github.msarhan', name: 'ummalqura-calendar', version:'1.1.7'
- compile 'com.nineoldandroids:library:2.4.0'
- compile 'com.android.support:cardview-v7:23.1.1'
- compile 'com.daimajia.easing:library:1.0.1@aar'
- compile 'com.daimajia.androidanimations:library:1.1.3@aar'
-}
-
-apply from: './bintrayv1.gradle'
-apply from: './installv1.gradle'
diff --git a/libary/libary.iml b/libary/libary.iml
deleted file mode 100644
index 6341a27..0000000
--- a/libary/libary.iml
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- generateDebugSources
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-<<<<<<< HEAD
-
-=======
->>>>>>> c5fbecb437ed0012cb0ff07d2f0b5e9abafa8771
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libary/proguard-rules.pro b/libary/proguard-rules.pro
deleted file mode 100644
index 47a473f..0000000
--- a/libary/proguard-rules.pro
+++ /dev/null
@@ -1,23 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/Alhazmy13/Library/Android/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-##---------------Begin: proguard configuration for UmmAlqura ----------
--dontwarn ummalqura.**
--dontwarn com.github.msarhan.ummalqura.**
--keep class com.github.msarhan.ummalqura.** { *; }
-
-##---------------End: proguard configuration for UmmAlqura ---------
\ No newline at end of file
diff --git a/libary/src/androidTest/java/net/alhazmy13/hijridatepicker/ApplicationTest.java b/libary/src/androidTest/java/net/alhazmy13/hijridatepicker/ApplicationTest.java
deleted file mode 100644
index c93b91d..0000000
--- a/libary/src/androidTest/java/net/alhazmy13/hijridatepicker/ApplicationTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package net.alhazmy13.hijridatepicker;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * Testing Fundamentals
- */
-public class ApplicationTest extends ApplicationTestCase {
- public ApplicationTest() {
- super(Application.class);
- }
-}
\ No newline at end of file
diff --git a/libary/src/main/AndroidManifest.xml b/libary/src/main/AndroidManifest.xml
deleted file mode 100644
index 78cde95..0000000
--- a/libary/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
diff --git a/libary/src/main/java/net/alhazmy13/hijridatepicker/DefaultValue.java b/libary/src/main/java/net/alhazmy13/hijridatepicker/DefaultValue.java
deleted file mode 100644
index e907c53..0000000
--- a/libary/src/main/java/net/alhazmy13/hijridatepicker/DefaultValue.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (c) 2016.
- * Created by Alhazmy13 6/1/2016.
- * http://Alhazmy13.net
- */
-
-package net.alhazmy13.hijridatepicker;
-
-/**
- * Created by Alhazmy13 on 1/6/16.
- */
-interface DefaultValue {
- int MIN_DISTANCE = 150;
-}
diff --git a/libary/src/main/java/net/alhazmy13/hijridatepicker/GeneralAttribute.java b/libary/src/main/java/net/alhazmy13/hijridatepicker/GeneralAttribute.java
deleted file mode 100644
index 3f9aedb..0000000
--- a/libary/src/main/java/net/alhazmy13/hijridatepicker/GeneralAttribute.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2016.
- * Created by Alhazmy13 6/1/2016.
- * http://Alhazmy13.net
- */
-
-package net.alhazmy13.hijridatepicker;
-
-import android.content.Context;
-
-/**
- * Created by Alhazmy13 on 1/6/16.
- */
-class GeneralAttribute {
- public static Context mContext;
- public static String title;
- public static int language;
- public static HijriCalendarView.OnDateSetListener onDateSetListener;
- public static HijriCalendarDialog.Mode mode = HijriCalendarDialog.Mode.Hijri;
- public static int hijri_min;
- public static int hijri_max;
- public static int gregorian_min;
- public static int gregorian_max;
- public static boolean setDefaultDate = false;
- public static int defaultDay;
- public static int defaultMonth;
- public static int defaultYear;
- public static boolean scrolling;
-}
diff --git a/libary/src/main/java/net/alhazmy13/hijridatepicker/HijriCalendarDialog.java b/libary/src/main/java/net/alhazmy13/hijridatepicker/HijriCalendarDialog.java
deleted file mode 100644
index a54a05e..0000000
--- a/libary/src/main/java/net/alhazmy13/hijridatepicker/HijriCalendarDialog.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (c) 2016.
- * Created by Alhazmy13 6/1/2016.
- * http://Alhazmy13.net
- */
-
-package net.alhazmy13.hijridatepicker;
-
-import android.content.Context;
-
-/**
- * Created by Alhazmy13 on 1/6/16.
- * HijriDatePicker
- */
-public class HijriCalendarDialog {
-
- public HijriCalendarDialog(){
-
- }
- public enum Mode{
- Hijri(1),
- Gregorian(2);
- private int mode;
- Mode(int mode) {
- this.mode = mode;
- }
- public int getModeValue() {
- return mode;
- }
-
- }
-
- public enum Language{
- Arabic(1),
- English(2),
- Default(3);
- private int language;
- Language(int language) {
- this.language = language;
- }
- public int getLanguageValue() {
- return language;
- }
-
- }
- public static class Builder{
- public Builder(Context context) {
- GeneralAttribute.mContext=context;
- GeneralAttribute.title="";
- GeneralAttribute.hijri_max = 1450;
- GeneralAttribute.hijri_min = 1437;
- GeneralAttribute.gregorian_max = 2050;
- GeneralAttribute.gregorian_min = 2013;
- GeneralAttribute.language = Language.Default.getLanguageValue();
- GeneralAttribute.scrolling = true;
- }
- public HijriCalendarDialog.Builder setMaxHijriYear(int maxYear) {
- GeneralAttribute.hijri_max=maxYear;
- return this;
- }
-
- public HijriCalendarDialog.Builder setMinHijriYear(int minYear) {
- GeneralAttribute.hijri_min=minYear;
- return this;
- }
-
- public HijriCalendarDialog.Builder setMinMaxHijriYear(int min,int max){
- GeneralAttribute.hijri_max = max;
- GeneralAttribute.hijri_min = min;
- return this;
- }
-
- public HijriCalendarDialog.Builder setEnableScrolling(boolean scrolling){
- GeneralAttribute.scrolling = scrolling;
- return this;
- }
-
- public HijriCalendarDialog.Builder setMaxGregorianYear(int maxYear) {
- GeneralAttribute.gregorian_max=maxYear;
- return this;
- }
-
- public HijriCalendarDialog.Builder setMinGregorianYear(int minYear) {
- GeneralAttribute.gregorian_min=minYear;
- return this;
- }
-
- public HijriCalendarDialog.Builder setMinMaxGregorianYear(int min,int max){
- GeneralAttribute.gregorian_max = max;
- GeneralAttribute.gregorian_min = min;
- return this;
- }
-
- public HijriCalendarDialog.Builder setUILanguage(Language language){
- GeneralAttribute.language = language.getLanguageValue();
- return this;
- }
- public HijriCalendarDialog.Builder setOnDateSetListener(HijriCalendarView.OnDateSetListener onDateSetListener){
- GeneralAttribute.onDateSetListener = onDateSetListener;
- return this;
- }
- public HijriCalendarDialog.Builder show(){
- new HijriCalendarView(GeneralAttribute.mContext).show();
- return this;
- }
- public HijriCalendarDialog.Builder setMode(Mode mode){
- GeneralAttribute.mode = mode;
- return this;
- }
- public HijriCalendarDialog.Builder setDefaultHijriDate(int day, int month, int year){
- if (month>11 || month<0)
- throw new RuntimeException("Month must be between 0-11");
- GeneralAttribute.setDefaultDate = true;
- GeneralAttribute.defaultDay = day;
- GeneralAttribute.defaultMonth = month>11?0:month;
- GeneralAttribute.defaultYear = year;
- return this;
- }
-
-
- }
-}
diff --git a/libary/src/main/java/net/alhazmy13/hijridatepicker/HijriCalendarView.java b/libary/src/main/java/net/alhazmy13/hijridatepicker/HijriCalendarView.java
deleted file mode 100644
index 664887c..0000000
--- a/libary/src/main/java/net/alhazmy13/hijridatepicker/HijriCalendarView.java
+++ /dev/null
@@ -1,317 +0,0 @@
-package net.alhazmy13.hijridatepicker;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.AppCompatButton;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.TableLayout;
-import android.widget.TableRow;
-import android.widget.TextView;
-
-import com.daimajia.androidanimations.library.Techniques;
-import com.daimajia.androidanimations.library.YoYo;
-
-import net.alhazmy13.hijridatepicker.calendar.CalendarInstance;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Created by Alhazmy13 on 10/15/15.
- * HijriDatePicker
- */
-public class HijriCalendarView extends Dialog implements MonthDialog.OnMonthChanged, View.OnClickListener,YearDialog.OnYearChanged,DefaultValue {
- private Context context;
- private String[] days ;
- private TextView dayTextView, monthTextView, yearTextView,lastSelectedDay;
- private TableLayout tableLayout;
- private CalendarInstance calendarInstance;
- private List textViewList;
- private Button doneButton, cancelButton;
- private TableRow daysHeader;
-
-
- public interface OnDateSetListener{
- @Deprecated
- void onDateSet(int year, int month, int day);
- }
-
- /**
- *
- * @param langCode
- */
- private void callSwitchLang(String langCode) {
- Locale locale = new Locale(langCode);
- Locale.setDefault(locale);
- Configuration config = new Configuration();
- config.locale = locale;
- context.getResources().updateConfiguration(config,
- context.getResources().getDisplayMetrics());
- onCreate(null);
- }
-
- /**
- *
- * @param context
- */
- public HijriCalendarView(final Context context) {
- super(context);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- this.setContentView(R.layout.dialog_hijri_calendar);
- this.context=context;
-
- flowFunctions();
-
- }
-
-
- private void flowFunctions(){
- initViews();
- initHeaderOfCalender();
- initDays();
- initListener();
- }
-
-
- /**
- *
- * @param month
- */
- @Override
- public void onMonthChanged(int month) {
- calendarInstance.setMonth(month+1);
- initDays();
- }
-
- /**
- *
- * @param view
- */
- @Override
- public void onClick(View view) {
- TextView temp = (TextView) view;
- if (!temp.getText().toString().trim().isEmpty()) {
- lastSelectedDay.setTextColor(context.getResources().getColor(android.R.color.darker_gray));
- lastSelectedDay.setBackgroundColor(context.getResources().getColor(android.R.color.transparent));
-
-
- temp.setBackgroundColor(context.getResources().getColor(R.color.hijri_date_picker_accent_color));
- temp.setTextColor(context.getResources().getColor(android.R.color.white));
- lastSelectedDay = temp;
- dayTextView.setText(temp.getText().toString());
- calendarInstance.setDay(Integer.parseInt(temp.getText().toString()));
-
- }
- }
-
- /**
- *
- */
- private void initViews() {
- tableLayout = (TableLayout) findViewById(R.id.calendarTableLayout);
- dayTextView = (TextView) findViewById(R.id.dayTextView);
- monthTextView = (TextView) findViewById(R.id.monthTextView);
- yearTextView = (TextView) findViewById(R.id.yearTextView);
- doneButton=(Button)findViewById(R.id.doneButton);
- cancelButton =(Button)findViewById(R.id.closeButton);
- setButtonTint(doneButton);
- setButtonTint(cancelButton);
- days=context.getResources().getStringArray(R.array.hijri_date_picker_days);
- textViewList = new ArrayList<>();
- if(GeneralAttribute.language == HijriCalendarDialog.Language.Arabic.getLanguageValue())
- callSwitchLang("ar");
- else if(GeneralAttribute.language == HijriCalendarDialog.Language.English.getLanguageValue())
- callSwitchLang("en");
- calendarInstance = new CalendarInstance(context,GeneralAttribute.mode.getModeValue());
- if (GeneralAttribute.setDefaultDate)
- {
- calendarInstance.setDay(GeneralAttribute.defaultDay);
- calendarInstance.setMonth(GeneralAttribute.defaultMonth);
- calendarInstance.setYear(GeneralAttribute.defaultYear);
- }
- }
-
- public void setButtonTint(Button button) {
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP && button instanceof AppCompatButton) {
- ((AppCompatButton) button).setSupportBackgroundTintList(ColorStateList.valueOf(context.getResources().getColor(R.color.hijri_date_picker_accent_color)));
- } else {
- ViewCompat.setBackgroundTintList(button, ColorStateList.valueOf(context.getResources().getColor(R.color.hijri_date_picker_accent_color)));
- }
- }
- private void initHeaderOfCalender() {
- TableRow.LayoutParams params = new TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f);
- params.setMargins(0, 8, 0, 8);
- daysHeader = new TableRow(context);
- daysHeader.setGravity(Gravity.CENTER);
- for (int i = 0; i < 7; i++) {
- TextView textView = new TextView(context);
- textView.setLayoutParams(params);
- textView.setTextSize(11);
- textView.setTextColor(context.getResources().getColor(R.color.hijri_date_picker_accent_color));
- textView.setGravity(Gravity.CENTER);
- textView.setText(days[i]);
- daysHeader.addView(textView);
- }
-
-
- }
-
- private void initDays() {
- tableLayout.removeAllViews();
- tableLayout.addView(daysHeader);
- updateCalenderInformation();
-
- int count = 1;
- boolean firstTime = true;
- TableRow.LayoutParams params = new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT, 1f);
- params.setMargins(0, 8, 0, 8);
- for (int i = 0; i < 5; i++) {
- TableRow row = new TableRow(context);
- row.setGravity(Gravity.CENTER);
- for (int j = 1; j <= 7; j++) {
- TextView textView = new TextView(context);
- textView.setLayoutParams(params);
- textView.setGravity(Gravity.CENTER);
- textView.setOnClickListener(this);
- textView.setTextColor(context.getResources().getColor(android.R.color.darker_gray));
- if (count <= calendarInstance.lengthOfMonth()) {
- if (firstTime && j == calendarInstance.getWeekStartFrom()) {
- textView.setText(GeneralAttribute.language == HijriCalendarDialog.Language.Arabic.getLanguageValue()?Utility.toArabicNumbers(count+""):count+"");
- firstTime = false;
- count++;
- } else if (!firstTime) {
- textView.setText(GeneralAttribute.language == HijriCalendarDialog.Language.Arabic.getLanguageValue()?Utility.toArabicNumbers(count+""):count+"");
- count++;
- } else {
- textView.setText(" ");
-
- }
- } else {
- textView.setText(" ");
-
- }
- if ((calendarInstance.isCurrentMonth() || (calendarInstance.getCurrentMonth() == GeneralAttribute.defaultMonth && calendarInstance.getCurrentYear() == GeneralAttribute.defaultYear)) && count - 1 == calendarInstance.getDayOfMonth()) {
- textView.setBackgroundColor(context.getResources().getColor(R.color.hijri_date_picker_accent_color));
- textView.setTextColor(context.getResources().getColor(android.R.color.white));
- lastSelectedDay = textView;
- }
- textViewList.add(textView);
- row.addView(textView);
- }
- tableLayout.addView(row);
- }
- }
-
-
- private void initListener() {
- monthTextView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent motionEvent) {
- MonthDialog monthDialog = new MonthDialog(context);
- monthDialog.setOnDateChanged(HijriCalendarView.this);
- monthDialog.setCurrentMonth(calendarInstance.getCurrentMonth());
- monthDialog.setMonthNames(calendarInstance.getMonths());
- monthDialog.show();
-
- return false;
- }
- });
- yearTextView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent motionEvent) {
- YearDialog yearDialog = new YearDialog(context);
- yearDialog.setOnYearChanged(HijriCalendarView.this);
- yearDialog.setYear(Integer.parseInt(yearTextView.getText().toString()));
- yearDialog.show();
- return false;
- }
- });
- doneButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (GeneralAttribute.onDateSetListener != null) {
- GeneralAttribute.onDateSetListener.onDateSet(calendarInstance.getYear(), calendarInstance.getMonth(), calendarInstance.getDayOfMonth());
-
-
- }
- dismiss();
- }
- });
- cancelButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- dismiss();
- }
- });
- }
-
- private void updateCalenderInformation(){
- dayTextView.setText(GeneralAttribute.language == HijriCalendarDialog.Language.Arabic.getLanguageValue() ? Utility.toArabicNumbers(calendarInstance.getDayOfMonth() + "") : calendarInstance.getDayOfMonth() + "");
- monthTextView.setText(calendarInstance.getMonthName());
- yearTextView.setText(GeneralAttribute.language == HijriCalendarDialog.Language.Arabic.getLanguageValue() ? Utility.toArabicNumbers(calendarInstance.getYear() + "") : calendarInstance.getYear() + "");
- }
-
-
- private void slideLeftToRight() {
- YoYo.with(Techniques.SlideInLeft)
- .duration(700)
- .playOn(tableLayout);
- }
- private void slideRightToLeft() {
- YoYo.with(Techniques.SlideInRight)
- .duration(700)
- .playOn(tableLayout);
- }
- @Override
- public void onYearChanged(int year) {
- yearTextView.setText(year+"");
- calendarInstance.setYear(year);
- initDays();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if(GeneralAttribute.scrolling) {
- float x1 = 0, x2;
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- x1 = event.getX();
- break;
- case MotionEvent.ACTION_UP:
- x2 = event.getX();
- float deltaX = x2 - x1;
-
- if (Math.abs(deltaX) > MIN_DISTANCE) {
- // Left to Right swipe action
- if (x2 > x1) {
- calendarInstance.plusMonth();
- slideLeftToRight();
- initDays();
- }
-
- // Right to left swipe action
- else {
- calendarInstance.minusMonth();
- slideRightToLeft();
- initDays();
- }
-
- }
- break;
- }
- }
- return super.onTouchEvent(event);
- }
-
-
-
-}
diff --git a/libary/src/main/java/net/alhazmy13/hijridatepicker/MonthDialog.java b/libary/src/main/java/net/alhazmy13/hijridatepicker/MonthDialog.java
deleted file mode 100644
index 3a550c5..0000000
--- a/libary/src/main/java/net/alhazmy13/hijridatepicker/MonthDialog.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package net.alhazmy13.hijridatepicker;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.Window;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
-/**
- * Created by Alhazmy13 on 10/15/15.
- */
-class MonthDialog extends Dialog implements View.OnTouchListener {
- private Context mContext;
- ArrayList textViews=new ArrayList<>();
- private OnMonthChanged onMonthChanged;
- private int currentMonth;
- private String[] monthNames;
-
- public void setCurrentMonth(int currentMonth) {
- this.currentMonth = currentMonth;
- textViews.get(currentMonth).setBackground(mContext.getResources().getDrawable(R.drawable.hijri_date_picker_card_selected));
- textViews.get(currentMonth).setTextColor(mContext.getResources().getColor(android.R.color.white));
-
- }
-
- public void setMonthNames(String[] monthNames) {
- for (int i = 0; i < monthNames.length; i++) {
- textViews.get(i).setText(monthNames[i]);
- }
- }
-
- /**
- *
- */
- interface OnMonthChanged{
- void onMonthChanged(int month);
- }
-
- /**
- *
- * @param listen
- */
- public void setOnDateChanged(OnMonthChanged listen) {
- onMonthChanged = listen;
- }
-
- /**
- *
- * @param context
- */
- public MonthDialog(Context context) {
- super(context);
- this.mContext = context;
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- this.setContentView(R.layout.dialog_months);
- textViews.add((TextView)findViewById(R.id.m1TextView));
- textViews.add((TextView)findViewById(R.id.m2TextView));
- textViews.add((TextView)findViewById(R.id.m3TextView));
- textViews.add((TextView)findViewById(R.id.m4TextView));
- textViews.add((TextView)findViewById(R.id.m5TextView));
- textViews.add((TextView)findViewById(R.id.m6TextView));
- textViews.add((TextView)findViewById(R.id.m7TextView));
- textViews.add((TextView)findViewById(R.id.m8TextView));
- textViews.add((TextView)findViewById(R.id.m9TextView));
- textViews.add((TextView)findViewById(R.id.m10TextView));
- textViews.add((TextView)findViewById(R.id.m11TextView));
- textViews.add((TextView)findViewById(R.id.m12TextView));
- for(int i=0;i 11)
- return 0;
- return countMonth;
- }
-
- @Override
- public String[] getMonths() {
- return monthNames;
- }
-
- @Override
- public int getCurrentYear() {
- return countYear;
- }
-}
diff --git a/libary/src/main/java/net/alhazmy13/hijridatepicker/calendar/HijriCalendar.java b/libary/src/main/java/net/alhazmy13/hijridatepicker/calendar/HijriCalendar.java
deleted file mode 100644
index e4252e5..0000000
--- a/libary/src/main/java/net/alhazmy13/hijridatepicker/calendar/HijriCalendar.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package net.alhazmy13.hijridatepicker.calendar;
-
-import android.content.Context;
-import android.util.Log;
-
-
-import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
-
-import net.alhazmy13.hijridatepicker.R;
-
-import java.util.Calendar;
-
-/**
- * Created by Alhazmy13 on 10/14/15.
- */
-class HijriCalendar implements CustomCalendarView {
- private static final String TAG = "HijriCalendar";
- private UmmalquraCalendar calendar;
- private String[] monthNames;
- private int countMonth,countYear,currentMonth,currentYear;
-
- public HijriCalendar(Context context){
- calendar=new UmmalquraCalendar();
- monthNames=new String[]{
- context.getResources().getString(R.string.muharram),
- context.getResources().getString(R.string.safar),
- context.getResources().getString(R.string.rabi_i),
- context.getResources().getString(R.string.rabi_ii),
- context.getResources().getString(R.string.jamada_i),
- context.getResources().getString(R.string.jamada_ii),
- context.getResources().getString(R.string.rajab),
- context.getResources().getString(R.string.shaban),
- context.getResources().getString(R.string.ramadan),
- context.getResources().getString(R.string.shawal),
- context.getResources().getString(R.string.dhu_alqadah),
- context.getResources().getString(R.string.dhu_alhijjah)};
- countMonth=calendar.get(Calendar.MONTH);
- countYear=calendar.get(Calendar.YEAR);
- currentMonth=countMonth;
- Log.d(TAG, "HijriCalendar: "+countMonth);
- currentYear=countYear;
- }
-
-
-
- @Override
- public void plusMonth(){
- countMonth++;
- if(countMonth==13) {
- countMonth = 1;
- countYear++;
- }
- calendar=new UmmalquraCalendar(countYear,countMonth,calendar.get(Calendar.DAY_OF_MONTH));
-
- }
-
- @Override
- public void minusMonth(){
- countMonth--;
- if(countMonth==0) {
- countMonth = 12;
- countYear--;
- }
- calendar=new UmmalquraCalendar(countYear,countMonth,calendar.get(Calendar.DAY_OF_MONTH));
-
- }
-
- @Override
- public boolean isCurrentMonth(){
- return (countMonth==currentMonth && currentYear==countYear);
- }
- @Override
- public void setMonth(int month){
- this.countMonth=month;
- calendar.set(Calendar.MONTH,month);
- }
- @Override
- public void setDay(int day){
- calendar.set(Calendar.DAY_OF_MONTH,day);
- }
- @Override
- public void setYear(int year) {
- this.countYear = year;
- calendar.set(Calendar.YEAR,year);
- }
- @Override
- public int getWeekStartFrom(){
- UmmalquraCalendar temp=new UmmalquraCalendar();
- temp.set(calendar.get(Calendar.YEAR),calendar.get(Calendar.MONTH),1);
- return temp.get(Calendar.DAY_OF_WEEK);
- }
- @Override
- public int getLastDayOfMonth(){
- return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
- }
- @Override
- public int getDayOfMonth(){
- return calendar.get(Calendar.DAY_OF_MONTH);
- }
- @Override
- public int getMonth(){
- return countMonth;
- }
- @Override
- public String getMonthName() {
-
- return monthNames[getOffsetMonthCount()];
- }
- @Override
- public int getYear(){
- return calendar.get(Calendar.YEAR);
- }
-
- @Override
- public int lengthOfMonth() {
- return calendar.lengthOfMonth();
- }
-
- @Override
- public int getCurrentMonth() {
- return getOffsetMonthCount();
- }
- @Override
- public int getOffsetMonthCount() {
- int temp = countMonth;
- if(temp == -1)
- temp = 11;
- else if(temp>= 12)
- temp = 0;
- return temp;
- }
-
- @Override
- public String[] getMonths() {
- return monthNames;
- }
-
- @Override
- public int getCurrentYear() {
- return countYear;
- }
-}
diff --git a/libary/src/main/res/drawable/hijri_date_picker_card.xml b/libary/src/main/res/drawable/hijri_date_picker_card.xml
deleted file mode 100644
index 739f0d8..0000000
--- a/libary/src/main/res/drawable/hijri_date_picker_card.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
->
-
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/drawable/hijri_date_picker_card_selected.xml b/libary/src/main/res/drawable/hijri_date_picker_card_selected.xml
deleted file mode 100644
index 37154ab..0000000
--- a/libary/src/main/res/drawable/hijri_date_picker_card_selected.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
->
-
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/drawable/hijri_date_picker_oval.xml b/libary/src/main/res/drawable/hijri_date_picker_oval.xml
deleted file mode 100644
index 418a892..0000000
--- a/libary/src/main/res/drawable/hijri_date_picker_oval.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
->
-
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/layout/dialog_hijri_calendar.xml b/libary/src/main/res/layout/dialog_hijri_calendar.xml
deleted file mode 100644
index 00e6637..0000000
--- a/libary/src/main/res/layout/dialog_hijri_calendar.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/libary/src/main/res/layout/dialog_months.xml b/libary/src/main/res/layout/dialog_months.xml
deleted file mode 100644
index ff97423..0000000
--- a/libary/src/main/res/layout/dialog_months.xml
+++ /dev/null
@@ -1,186 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/layout/dialog_year.xml b/libary/src/main/res/layout/dialog_year.xml
deleted file mode 100644
index 4f3ef7d..0000000
--- a/libary/src/main/res/layout/dialog_year.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/libary/src/main/res/values-ar/array.xml b/libary/src/main/res/values-ar/array.xml
deleted file mode 100644
index bd89d46..0000000
--- a/libary/src/main/res/values-ar/array.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- الاحد
- الاثنين
- الثلاثاء
- الاربعاء
- الخميس
- الجمعة
- السبت
-
-
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/values-ar/strings.xml b/libary/src/main/res/values-ar/strings.xml
deleted file mode 100644
index be2fa21..0000000
--- a/libary/src/main/res/values-ar/strings.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
- الغاء
- ذو الحجة
- موافق
- ذو القعدة
- جماد الاول
- جماد الثاني
- محرم
- ربيع الاول
- ربيع الثاني
- رجب
- رمضان
- صفر
- شعبان
- شوال
- السنة
- يناير
- فبراير
- ديسمبر
- أغسطس
- ابريل
- نوفمبر
- اكتوبر
- سبتمبر
- يونيو
- يوليو
- مايو
- مارس
-
\ No newline at end of file
diff --git a/libary/src/main/res/values-w820dp/dimens.xml b/libary/src/main/res/values-w820dp/dimens.xml
deleted file mode 100644
index 63fc816..0000000
--- a/libary/src/main/res/values-w820dp/dimens.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- 64dp
-
diff --git a/libary/src/main/res/values/array.xml b/libary/src/main/res/values/array.xml
deleted file mode 100644
index 2cf5102..0000000
--- a/libary/src/main/res/values/array.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- Su
- Mo
- Tu
- We
- Th
- Fr
- Sa
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/values/color.xml b/libary/src/main/res/values/color.xml
deleted file mode 100644
index d248789..0000000
--- a/libary/src/main/res/values/color.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- #009688
-
-
\ No newline at end of file
diff --git a/libary/src/main/res/values/dimens.xml b/libary/src/main/res/values/dimens.xml
deleted file mode 100644
index 47c8224..0000000
--- a/libary/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 16dp
- 16dp
-
diff --git a/libary/src/main/res/values/strings.xml b/libary/src/main/res/values/strings.xml
deleted file mode 100644
index 59fdd50..0000000
--- a/libary/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
- Muharam
- Safar
- Rabi I
- Rabi II
- Jamada I
- Jamada II
- Rajab
- Shaban
- Ramadan
- Shawal
- Dhu Alqadah
- Dhu Alhijjah
- Done
- Cancel
- Year
- January
- February
- March
- April
- May
- June
- July
- August
- September
- October
- November
- December
-
-
diff --git a/libary/src/main/res/values/style.xml b/libary/src/main/res/values/style.xml
deleted file mode 100644
index f11f745..0000000
--- a/libary/src/main/res/values/style.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/libary/bintrayv1.gradle b/library/bintrayv1.gradle
similarity index 100%
rename from libary/bintrayv1.gradle
rename to library/bintrayv1.gradle
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 0000000..ca8202d
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,55 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.novoda.bintray-release'
+//./gradlew clean build bintrayUpload
+
+def getBintrayUserProperty() {
+ return hasProperty('BINTRAY_USER') ? BINTRAY_USER : ""
+}
+
+def getBintrayApiKeyProperty() {
+ return hasProperty('BINTRAY_APIKEY') ? BINTRAY_APIKEY : ""
+}
+publish {
+ userOrg = 'alhazmy13'
+ groupId = 'net.alhazmy13.hijridatepicker'
+ artifactId = 'library'
+ uploadName = 'HijriDatePicker'
+ publishVersion = '2.0.0-beta'
+ desc = 'Hijri Date Picker'
+ dryRun = false
+ website = 'https://github.com/alhzmy13/HijriDatePicker'
+}
+
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+
+ defaultConfig {
+ minSdkVersion 17
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0.1"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ lintOptions {
+ abortOnError false
+ }
+
+
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:support-v4:25.2.0'
+ compile 'com.android.support:support-v13:25.2.0'
+ compile 'com.android.support:design:25.2.0'
+ compile group: 'com.github.msarhan', name: 'ummalqura-calendar', version:'1.1.7'
+
+}
+
diff --git a/library/gradle.properties b/library/gradle.properties
new file mode 100644
index 0000000..1e339f4
--- /dev/null
+++ b/library/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=MaterialDateTimePicker
+POM_ARTIFACT_ID=materialdatetimepicker
+POM_PACKAGING=aar
\ No newline at end of file
diff --git a/libary/installv1.gradle b/library/installv1.gradle
similarity index 100%
rename from libary/installv1.gradle
rename to library/installv1.gradle
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2147fe6
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/library/src/main/assets/fonts/Roboto-Medium.ttf b/library/src/main/assets/fonts/Roboto-Medium.ttf
new file mode 100644
index 0000000..39c63d7
Binary files /dev/null and b/library/src/main/assets/fonts/Roboto-Medium.ttf differ
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/AccessibleLinearLayout.java b/library/src/main/java/net/alhazmy13/hijridatepicker/AccessibleLinearLayout.java
new file mode 100644
index 0000000..9f08ca2
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/AccessibleLinearLayout.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+/**
+ * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ */
+public class AccessibleLinearLayout extends LinearLayout {
+
+ public AccessibleLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(Button.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(Button.class.getName());
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/AccessibleTextView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/AccessibleTextView.java
new file mode 100644
index 0000000..35ae052
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/AccessibleTextView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ */
+public class AccessibleTextView extends TextView {
+
+ public AccessibleTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(Button.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(Button.class.getName());
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/HapticFeedbackController.java b/library/src/main/java/net/alhazmy13/hijridatepicker/HapticFeedbackController.java
new file mode 100644
index 0000000..e0d3716
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/HapticFeedbackController.java
@@ -0,0 +1,89 @@
+package net.alhazmy13.hijridatepicker;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/**
+ * A simple utility class to handle haptic feedback.
+ */
+public class HapticFeedbackController {
+ private static final int VIBRATE_DELAY_MS = 125;
+ private static final int VIBRATE_LENGTH_MS = 50;
+
+ private static boolean checkGlobalSetting(Context context) {
+ return Settings.System.getInt(context.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 1;
+ }
+
+ private final Context mContext;
+ private final ContentObserver mContentObserver;
+
+ private Vibrator mVibrator;
+ private boolean mIsGloballyEnabled;
+ private long mLastVibrate;
+
+ public HapticFeedbackController(Context context) {
+ mContext = context;
+ mContentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mIsGloballyEnabled = checkGlobalSetting(mContext);
+ }
+ };
+ }
+
+ /**
+ * Call to setup the controller.
+ */
+ public void start() {
+ if (hasVibratePermission(mContext)) {
+ mVibrator = (Vibrator) mContext.getSystemService(Service.VIBRATOR_SERVICE);
+ }
+
+ // Setup a listener for changes in haptic feedback settings
+ mIsGloballyEnabled = checkGlobalSetting(mContext);
+ Uri uri = Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED);
+ mContext.getContentResolver().registerContentObserver(uri, false, mContentObserver);
+ }
+
+ /**
+ * Method to verify that vibrate permission has been granted.
+ *
+ * Allows users of the library to disabled vibrate support if desired.
+ * @return true if Vibrate permission has been granted
+ */
+ private boolean hasVibratePermission(Context context) {
+ PackageManager pm = context.getPackageManager();
+ int hasPerm = pm.checkPermission(android.Manifest.permission.VIBRATE, context.getPackageName());
+ return hasPerm == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Call this when you don't need the controller anymore.
+ */
+ public void stop() {
+ mVibrator = null;
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ /**
+ * Try to vibrate. To prevent this becoming a single continuous vibration, nothing will
+ * happen if we have vibrated very recently.
+ */
+ public void tryVibrate() {
+ if (mVibrator != null && mIsGloballyEnabled) {
+ long now = SystemClock.uptimeMillis();
+ // We want to try to vibrate each individual tick discretely.
+ if (now - mLastVibrate >= VIBRATE_DELAY_MS) {
+ mVibrator.vibrate(VIBRATE_LENGTH_MS);
+ mLastVibrate = now;
+ }
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/TypefaceHelper.java b/library/src/main/java/net/alhazmy13/hijridatepicker/TypefaceHelper.java
new file mode 100644
index 0000000..05918c7
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/TypefaceHelper.java
@@ -0,0 +1,39 @@
+package net.alhazmy13.hijridatepicker;
+
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.support.v4.util.SimpleArrayMap;
+
+/*
+ Each call to Typeface.createFromAsset will load a new instance of the typeface into memory,
+ and this memory is not consistently get garbage collected
+ http://code.google.com/p/android/issues/detail?id=9904
+ (It states released but even on Lollipop you can see the typefaces accumulate even after
+ multiple GC passes)
+ You can detect this by running:
+ adb shell dumpsys meminfo com.your.packagenage
+ You will see output like:
+ Asset Allocations
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Regular.ttf: 123K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+*/
+public class TypefaceHelper {
+
+ private static final SimpleArrayMap cache = new SimpleArrayMap<>();
+
+ public static Typeface get(Context c, String name) {
+ synchronized (cache) {
+ if (!cache.containsKey(name)) {
+ Typeface t = Typeface.createFromAsset(
+ c.getAssets(), String.format("fonts/%s.ttf", name));
+ cache.put(name, t);
+ return t;
+ }
+ return cache.get(name);
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/Utils.java b/library/src/main/java/net/alhazmy13/hijridatepicker/Utils.java
new file mode 100644
index 0000000..b845dd6
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/Utils.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker;
+
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.os.Build;
+import android.support.annotation.AttrRes;
+import android.support.v4.content.ContextCompat;
+import android.util.TypedValue;
+import android.view.View;
+
+/**
+ * Utility helper functions for time and date pickers.
+ */
+public class Utils {
+
+ //public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
+ public static final int PULSE_ANIMATOR_DURATION = 544;
+
+ // Alpha level for time picker selection.
+ public static final int SELECTED_ALPHA = 255;
+ public static final int SELECTED_ALPHA_THEME_DARK = 255;
+ // Alpha level for fully opaque.
+ public static final int FULL_ALPHA = 255;
+
+ public static boolean isJellybeanOrLater() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ }
+
+ /**
+ * Try to speak the specified text, for accessibility. Only available on JB or later.
+ * @param text Text to announce.
+ */
+ @SuppressLint("NewApi")
+ public static void tryAccessibilityAnnounce(View view, CharSequence text) {
+ if (isJellybeanOrLater() && view != null && text != null) {
+ view.announceForAccessibility(text);
+ }
+ }
+
+ /**
+ * Takes a number of weeks since the epoch and calculates the Julian day of
+ * the Monday for that week.
+ *
+ * This assumes that the week containing the {@link Time#EPOCH_JULIAN_DAY}
+ * is considered week 0. It returns the Julian day for the Monday
+ * {@code week} weeks after the Monday of the week containing the epoch.
+ *
+ * @param week Number of weeks since the epoch
+ * @return The julian day for the Monday of the given week since the epoch
+ */
+ /**
+ public static int getJulianMondayFromWeeksSinceEpoch(int week) {
+ return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
+ }
+ */
+
+ /**
+ * Returns the week since {@link Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
+ * adjusted for first day of week.
+ *
+ * This takes a julian day and the week start day and calculates which
+ * week since {@link Time#EPOCH_JULIAN_DAY} that day occurs in, starting
+ * at 0. *Do not* use this to compute the ISO week number for the year.
+ *
+ * @param julianDay The julian day to calculate the week number for
+ * @param firstDayOfWeek Which week day is the first day of the week,
+ * see {@link Time#SUNDAY}
+ * @return Weeks since the epoch
+ */
+ /**
+ public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
+ int diff = Time.THURSDAY - firstDayOfWeek;
+ if (diff < 0) {
+ diff += 7;
+ }
+ int refDay = Time.EPOCH_JULIAN_DAY - diff;
+ return (julianDay - refDay) / 7;
+ }
+ */
+
+ /**
+ * Render an animator to pulsate a view in place.
+ * @param labelToAnimate the view to pulsate.
+ * @return The animator object. Use .start() to begin.
+ */
+ public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio,
+ float increaseRatio) {
+ Keyframe k0 = Keyframe.ofFloat(0f, 1f);
+ Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio);
+ Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
+ Keyframe k3 = Keyframe.ofFloat(1f, 1f);
+
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3);
+ ObjectAnimator pulseAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
+ pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
+
+ return pulseAnimator;
+ }
+
+ /**
+ * Convert Dp to Pixel
+ */
+ @SuppressWarnings("unused")
+ public static int dpToPx(float dp, Resources resources){
+ float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
+ return (int) px;
+ }
+
+ public static int darkenColor(int color) {
+ float[] hsv = new float[3];
+ Color.colorToHSV(color, hsv);
+ hsv[2] = hsv[2] * 0.8f; // value component
+ return Color.HSVToColor(hsv);
+ }
+
+ /**
+ * Gets the colorAccent from the current context, if possible/available
+ * @param context The context to use as reference for the color
+ * @return the accent color of the current context
+ */
+ public static int getAccentColorFromThemeIfAvailable(Context context) {
+ TypedValue typedValue = new TypedValue();
+ // First, try the android:colorAccent
+ if (Build.VERSION.SDK_INT >= 21) {
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ return typedValue.data;
+ }
+ // Next, try colorAccent from support lib
+ int colorAccentResId = context.getResources().getIdentifier("colorAccent", "attr", context.getPackageName());
+ if (colorAccentResId != 0 && context.getTheme().resolveAttribute(colorAccentResId, typedValue, true)) {
+ return typedValue.data;
+ }
+ // Return the value in mdtp_accent_color
+ return ContextCompat.getColor(context, R.color.mdtp_accent_color);
+ }
+
+ /**
+ * Gets dialog type (Light/Dark) from current theme
+ * @param context The context to use as reference for the boolean
+ * @param current Default value to return if cannot resolve the attribute
+ * @return true if dark mode, false if light.
+ */
+ public static boolean isDarkTheme(Context context, boolean current) {
+ return resolveBoolean(context, R.attr.mdtp_theme_dark, current);
+ }
+
+ /**
+ * Gets the required boolean value from the current context, if possible/available
+ * @param context The context to use as reference for the boolean
+ * @param attr Attribute id to resolve
+ * @param fallback Default value to return if no value is specified in theme
+ * @return the boolean value from current theme
+ */
+ private static boolean resolveBoolean(Context context, @AttrRes int attr, boolean fallback) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
+ try {
+ return a.getBoolean(0, fallback);
+ } finally {
+ a.recycle();
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/VerticalTextView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/VerticalTextView.java
new file mode 100644
index 0000000..578c499
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/VerticalTextView.java
@@ -0,0 +1,57 @@
+package net.alhazmy13.hijridatepicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.TextView;
+
+/**
+ * TextView that renders it's contents vertically. (Just using rotate doesn't work because onMeasure
+ * happens before the View is rotated causing incorrect View boundaries)
+ * Created by wdullaer on 28/03/16.
+ */
+public class VerticalTextView extends TextView {
+ final boolean topDown;
+
+ public VerticalTextView(Context context, AttributeSet attrs){
+ super(context, attrs);
+ final int gravity = getGravity();
+ if (Gravity.isVertical(gravity) && (gravity&Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
+ setGravity((gravity&Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
+ topDown = false;
+ } else {
+ topDown = true;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
+ super.onMeasure(heightMeasureSpec, widthMeasureSpec);
+ setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas){
+ TextPaint textPaint = getPaint();
+ textPaint.setColor(getCurrentTextColor());
+ textPaint.drawableState = getDrawableState();
+
+ canvas.save();
+
+ if (topDown){
+ canvas.translate(getWidth(), 0);
+ canvas.rotate(90);
+ } else {
+ canvas.translate(0, getHeight());
+ canvas.rotate(-90);
+ }
+
+
+ canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
+
+ getLayout().draw(canvas);
+ canvas.restore();
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/AccessibleDateAnimator.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/AccessibleDateAnimator.java
new file mode 100644
index 0000000..2a824ff
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/AccessibleDateAnimator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ViewAnimator;
+
+public class AccessibleDateAnimator extends ViewAnimator {
+ private long mDateMillis;
+
+ public AccessibleDateAnimator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setDateMillis(long dateMillis) {
+ mDateMillis = dateMillis;
+ }
+
+ /**
+ * Announce the currently-selected date when launched.
+ */
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ // Clear the event's current text so that only the current date will be spoken.
+ event.getText().clear();
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_WEEKDAY;
+
+ String dateString = DateUtils.formatDateTime(getContext(), mDateMillis, flags);
+ event.getText().add(dateString);
+ return true;
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/DatePickerController.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/DatePickerController.java
new file mode 100644
index 0000000..fc5eade
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/DatePickerController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Controller class to communicate among the various components of the date picker dialog.
+ */
+public interface DatePickerController {
+
+ void onYearSelected(int year);
+
+ void onDayOfMonthSelected(int year, int month, int day);
+
+ void registerOnDateChangedListener(GregorianDatePickerDialog.OnDateChangedListener listener);
+
+ void unregisterOnDateChangedListener(GregorianDatePickerDialog.OnDateChangedListener listener);
+
+ MonthAdapter.CalendarDay getSelectedDay();
+
+ boolean isThemeDark();
+
+ int getAccentColor();
+
+ boolean isHighlighted(int year, int month, int day);
+
+ int getFirstDayOfWeek();
+
+ int getMinYear();
+
+ int getMaxYear();
+
+ Calendar getStartDate();
+
+ Calendar getEndDate();
+
+ boolean isOutOfRange(int year, int month, int day);
+
+ void tryVibrate();
+
+ TimeZone getTimeZone();
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/DayPickerView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/DayPickerView.java
new file mode 100644
index 0000000..2a88159
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/DayPickerView.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.ListView;
+
+import net.alhazmy13.hijridatepicker.Utils;
+import net.alhazmy13.hijridatepicker.date.gregorian.GregorianDatePickerDialog.OnDateChangedListener;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * This displays a list of months in a calendar format with selectable days.
+ */
+public abstract class DayPickerView extends ListView implements OnScrollListener,
+ OnDateChangedListener {
+
+ private static final String TAG = "MonthFragment";
+
+ // Affects when the month selection will change while scrolling up
+ protected static final int SCROLL_HYST_WEEKS = 2;
+ // How long the GoTo fling animation should last
+ protected static final int GOTO_SCROLL_DURATION = 250;
+ // How long to wait after receiving an onScrollStateChanged notification
+ // before acting on it
+ protected static final int SCROLL_CHANGE_DELAY = 40;
+ // The number of days to display in each week
+ public static final int DAYS_PER_WEEK = 7;
+ public static int LIST_TOP_OFFSET = -1; // so that the top line will be
+ // under the separator
+ // You can override these numbers to get a different appearance
+ protected int mNumWeeks = 6;
+ protected boolean mShowWeekNumber = false;
+ protected int mDaysPerWeek = 7;
+ private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
+
+ // These affect the scroll speed and feel
+ protected float mFriction = 1.0f;
+
+ protected Context mContext;
+ protected Handler mHandler;
+
+ // highlighted time
+ protected MonthAdapter.CalendarDay mSelectedDay;
+ protected MonthAdapter mAdapter;
+
+ protected MonthAdapter.CalendarDay mTempDay;
+
+ // When the week starts; numbered like Time. (e.g. SUNDAY=0).
+ protected int mFirstDayOfWeek;
+ // The last name announced by accessibility
+ protected CharSequence mPrevMonthName;
+ // which month should be displayed/highlighted [0-11]
+ protected int mCurrentMonthDisplayed;
+ // used for tracking during a scroll
+ protected long mPreviousScrollPosition;
+ // used for tracking what state listview is in
+ protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ // used for tracking what state listview is in
+ protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ private DatePickerController mController;
+ private boolean mPerformingScroll;
+
+ public DayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public DayPickerView(Context context, DatePickerController controller) {
+ super(context);
+ init(context);
+ setController(controller);
+ }
+
+ public void setController(DatePickerController controller) {
+ mController = controller;
+ mController.registerOnDateChangedListener(this);
+ mSelectedDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
+ mTempDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
+ refreshAdapter();
+ onDateChanged();
+ }
+
+ public void init(Context context) {
+ mHandler = new Handler();
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setDrawSelectorOnTop(false);
+
+ mContext = context;
+ setUpListView();
+ }
+
+ public void onChange() {
+ refreshAdapter();
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
+ */
+ protected void refreshAdapter() {
+ if (mAdapter == null) {
+ mAdapter = createMonthAdapter(getContext(), mController);
+ } else {
+ mAdapter.setSelectedDay(mSelectedDay);
+ }
+ // refresh the view with the new parameters
+ setAdapter(mAdapter);
+ }
+
+ public abstract MonthAdapter createMonthAdapter(Context context,
+ DatePickerController controller);
+
+ /*
+ * Sets all the required fields for the list view. Override this method to
+ * set a different list view behavior.
+ */
+ protected void setUpListView() {
+ // Transparent background on scroll
+ setCacheColorHint(0);
+ // No dividers
+ setDivider(null);
+ // Items are clickable
+ setItemsCanFocus(true);
+ // The thumb gets in the way, so disable it
+ setFastScrollEnabled(false);
+ setVerticalScrollBarEnabled(false);
+ setOnScrollListener(this);
+ setFadingEdgeLength(0);
+ // Make the scrolling behavior nicer
+ setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+ }
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param day The day to move to
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location
+ * @param setSelected Whether to set the given time as selected
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible
+ * @return Whether or not the view animated to the new location
+ */
+ public boolean goTo(MonthAdapter.CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
+
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.set(day);
+ }
+
+ mTempDay.set(day);
+ int minMonth = mController.getStartDate().get(Calendar.MONTH);
+ final int position = (day.year - mController.getMinYear())
+ * MonthAdapter.MONTHS_IN_YEAR + day.month - minMonth;
+
+ View child;
+ int i = 0;
+ int top = 0;
+ // Find a child that's completely in the view
+ do {
+ child = getChildAt(i++);
+ if (child == null) {
+ break;
+ }
+ top = child.getTop();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "child at " + (i - 1) + " has top " + top);
+ }
+ } while (top < 0);
+
+ // Compute the first and last position visible
+ int selectedPosition;
+ if (child != null) {
+ selectedPosition = getPositionForView(child);
+ } else {
+ selectedPosition = 0;
+ }
+
+ if (setSelected) {
+ mAdapter.setSelectedDay(mSelectedDay);
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GoTo position " + position);
+ }
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position != selectedPosition || forceScroll) {
+ setMonthDisplayed(mTempDay);
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ smoothScrollToPositionFromTop(
+ position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
+ return true;
+ } else {
+ postSetSelection(position);
+ }
+ } else if (setSelected) {
+ setMonthDisplayed(mSelectedDay);
+ }
+ return false;
+ }
+
+ public void postSetSelection(final int position) {
+ clearFocus();
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSelection(position);
+ }
+ });
+ onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+
+ /**
+ * Updates the title and selected month if the view has moved to a new
+ * month.
+ */
+ @Override
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ MonthView child = (MonthView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
+
+ // Figure out where we are
+ long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ */
+ protected void setMonthDisplayed(MonthAdapter.CalendarDay date) {
+ mCurrentMonthDisplayed = date.month;
+ invalidateViews();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // use a post to prevent re-entering onScrollStateChanged before it
+ // exits
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
+
+ protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ protected class ScrollStateRunnable implements Runnable {
+ private int mNewState;
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mHandler.removeCallbacks(this);
+ mNewState = scrollState;
+ mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
+ }
+
+ @Override
+ public void run() {
+ mCurrentScrollState = mNewState;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
+ }
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ mPreviousScrollState = mNewState;
+ int i = 0;
+ View child = getChildAt(i);
+ while (child != null && child.getBottom() <= 0) {
+ child = getChildAt(++i);
+ }
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int firstPosition = getFirstVisiblePosition();
+ int lastPosition = getLastVisiblePosition();
+ boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
+ final int top = child.getTop();
+ final int bottom = child.getBottom();
+ final int midpoint = getHeight() / 2;
+ if (scroll && top < LIST_TOP_OFFSET) {
+ if (bottom > midpoint) {
+ smoothScrollBy(top, GOTO_SCROLL_DURATION);
+ } else {
+ smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
+ }
+ }
+ } else {
+ mPreviousScrollState = mNewState;
+ }
+ }
+ }
+
+ /**
+ * Gets the position of the view that is most prominently displayed within the list view.
+ */
+ public int getMostVisiblePosition() {
+ final int firstPosition = getFirstVisiblePosition();
+ final int height = getHeight();
+
+ int maxDisplayedHeight = 0;
+ int mostVisibleIndex = 0;
+ int i=0;
+ int bottom = 0;
+ while (bottom < height) {
+ View child = getChildAt(i);
+ if (child == null) {
+ break;
+ }
+ bottom = child.getBottom();
+ int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop());
+ if (displayedHeight > maxDisplayedHeight) {
+ mostVisibleIndex = i;
+ maxDisplayedHeight = displayedHeight;
+ }
+ i++;
+ }
+ return firstPosition + mostVisibleIndex;
+ }
+
+ @Override
+ public void onDateChanged() {
+ goTo(mController.getSelectedDay(), false, true, true);
+ }
+
+ /**
+ * Attempts to return the date that has accessibility focus.
+ *
+ * @return The date that has accessibility focus, or {@code null} if no date
+ * has focus.
+ */
+ private MonthAdapter.CalendarDay findAccessibilityFocus() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof MonthView) {
+ final MonthAdapter.CalendarDay focus = ((MonthView) child).getAccessibilityFocus();
+ if (focus != null) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Clear focus to avoid ListView bug in Jelly Bean MR1.
+ ((MonthView) child).clearAccessibilityFocus();
+ }
+ return focus;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to restore accessibility focus to a given date. No-op if
+ * {@code day} is {@code null}.
+ *
+ * @param day The date that should receive accessibility focus
+ * @return {@code true} if focus was restored
+ */
+ private boolean restoreAccessibilityFocus(MonthAdapter.CalendarDay day) {
+ if (day == null) {
+ return false;
+ }
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof MonthView) {
+ if (((MonthView) child).restoreAccessibilityFocus(day)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ final MonthAdapter.CalendarDay focusedDay = findAccessibilityFocus();
+ super.layoutChildren();
+ if (mPerformingScroll) {
+ mPerformingScroll = false;
+ } else {
+ restoreAccessibilityFocus(focusedDay);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setItemCount(-1);
+ }
+
+ private static String getMonthAndYearString(MonthAdapter.CalendarDay day) {
+ Calendar cal = Calendar.getInstance();
+ cal.set(day.year, day.month, day.day);
+
+ String sbuf = "";
+ sbuf += cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
+ sbuf += " ";
+ sbuf += YEAR_FORMAT.format(cal.getTime());
+ return sbuf;
+ }
+
+ /**
+ * Necessary for accessibility, to ensure we support "scrolling" forward and backward
+ * in the month list.
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if(Build.VERSION.SDK_INT >= 21) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ }
+ else {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ /**
+ * When scroll forward/backward events are received, announce the newly scrolled-to month.
+ */
+ @SuppressLint("NewApi")
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
+ action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ return super.performAccessibilityAction(action, arguments);
+ }
+
+ // Figure out what month is showing.
+ int firstVisiblePosition = getFirstVisiblePosition();
+ int minMonth = mController.getStartDate().get(Calendar.MONTH);
+ int month = (firstVisiblePosition + minMonth) % MonthAdapter.MONTHS_IN_YEAR;
+ int year = (firstVisiblePosition + minMonth) / MonthAdapter.MONTHS_IN_YEAR + mController.getMinYear();
+ MonthAdapter.CalendarDay day = new MonthAdapter.CalendarDay(year, month, 1);
+
+ // Scroll either forward or backward one month.
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+ day.month++;
+ if (day.month == 12) {
+ day.month = 0;
+ day.year++;
+ }
+ } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ View firstVisibleView = getChildAt(0);
+ // If the view is fully visible, jump one month back. Otherwise, we'll just jump
+ // to the first day of first visible month.
+ if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
+ // There's an off-by-one somewhere, so the top of the first visible item will
+ // actually be -1 when it's at the exact top.
+ day.month--;
+ if (day.month == -1) {
+ day.month = 11;
+ day.year--;
+ }
+ }
+ }
+
+ // Go to that month.
+ Utils.tryAccessibilityAnnounce(this, getMonthAndYearString(day));
+ goTo(day, true, false, true);
+ mPerformingScroll = true;
+ return true;
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/GregorianDatePickerDialog.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/GregorianDatePickerDialog.java
new file mode 100644
index 0000000..7595d44
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/GregorianDatePickerDialog.java
@@ -0,0 +1,1134 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.support.v4.content.ContextCompat;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import net.alhazmy13.hijridatepicker.HapticFeedbackController;
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.TypefaceHelper;
+import net.alhazmy13.hijridatepicker.Utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Dialog allowing users to select a date.
+ */
+public class GregorianDatePickerDialog extends DialogFragment implements
+ OnClickListener, DatePickerController {
+
+ public enum Version {
+ VERSION_1,
+ VERSION_2
+ }
+
+ private static final int UNINITIALIZED = -1;
+ private static final int MONTH_AND_DAY_VIEW = 0;
+ private static final int YEAR_VIEW = 1;
+
+ private static final String KEY_SELECTED_YEAR = "year";
+ private static final String KEY_SELECTED_MONTH = "month";
+ private static final String KEY_SELECTED_DAY = "day";
+ private static final String KEY_LIST_POSITION = "list_position";
+ private static final String KEY_WEEK_START = "week_start";
+ private static final String KEY_YEAR_START = "year_start";
+ private static final String KEY_YEAR_END = "year_end";
+ private static final String KEY_CURRENT_VIEW = "current_view";
+ private static final String KEY_LIST_POSITION_OFFSET = "list_position_offset";
+ private static final String KEY_MIN_DATE = "min_date";
+ private static final String KEY_MAX_DATE = "max_date";
+ private static final String KEY_HIGHLIGHTED_DAYS = "highlighted_days";
+ private static final String KEY_SELECTABLE_DAYS = "selectable_days";
+ private static final String KEY_DISABLED_DAYS = "disabled_days";
+ private static final String KEY_THEME_DARK = "theme_dark";
+ private static final String KEY_THEME_DARK_CHANGED = "theme_dark_changed";
+ private static final String KEY_ACCENT = "accent";
+ private static final String KEY_VIBRATE = "vibrate";
+ private static final String KEY_DISMISS = "dismiss";
+ private static final String KEY_AUTO_DISMISS = "auto_dismiss";
+ private static final String KEY_DEFAULT_VIEW = "default_view";
+ private static final String KEY_TITLE = "title";
+ private static final String KEY_OK_RESID = "ok_resid";
+ private static final String KEY_OK_STRING = "ok_string";
+ private static final String KEY_OK_COLOR = "ok_color";
+ private static final String KEY_CANCEL_RESID = "cancel_resid";
+ private static final String KEY_CANCEL_STRING = "cancel_string";
+ private static final String KEY_CANCEL_COLOR = "cancel_color";
+ private static final String KEY_VERSION = "version";
+ private static final String KEY_TIMEZONE = "timezone";
+
+
+ private static final int DEFAULT_START_YEAR = 1900;
+ private static final int DEFAULT_END_YEAR = 2100;
+
+ private static final int ANIMATION_DURATION = 300;
+ private static final int ANIMATION_DELAY = 500;
+
+ private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
+ private static SimpleDateFormat MONTH_FORMAT = new SimpleDateFormat("MMM", Locale.getDefault());
+ private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());
+ private static SimpleDateFormat VERSION_2_FORMAT;
+
+ private final Calendar mCalendar = trimToMidnight(Calendar.getInstance(getTimeZone()));
+ private OnDateSetListener mCallBack;
+ private HashSet mListeners = new HashSet<>();
+ private DialogInterface.OnCancelListener mOnCancelListener;
+ private DialogInterface.OnDismissListener mOnDismissListener;
+
+ private AccessibleDateAnimator mAnimator;
+
+ private TextView mDatePickerHeaderView;
+ private LinearLayout mMonthAndDayView;
+ private TextView mSelectedMonthTextView;
+ private TextView mSelectedDayTextView;
+ private TextView mYearView;
+ private DayPickerView mDayPickerView;
+ private YearPickerView mYearPickerView;
+
+ private int mCurrentView = UNINITIALIZED;
+
+ private int mWeekStart = mCalendar.getFirstDayOfWeek();
+ private int mMinYear = DEFAULT_START_YEAR;
+ private int mMaxYear = DEFAULT_END_YEAR;
+ private String mTitle;
+ private Calendar mMinDate;
+ private Calendar mMaxDate;
+ private HashSet highlightedDays = new HashSet<>();
+ private TreeSet selectableDays = new TreeSet<>();
+ private HashSet disabledDays = new HashSet<>();
+ private boolean mThemeDark = false;
+ private boolean mThemeDarkChanged = false;
+ private int mAccentColor = -1;
+ private boolean mVibrate = true;
+ private boolean mDismissOnPause = false;
+ private boolean mAutoDismiss = false;
+ private int mDefaultView = MONTH_AND_DAY_VIEW;
+ private int mOkResid = R.string.mdtp_ok;
+ private String mOkString;
+ private int mOkColor = -1;
+ private int mCancelResid = R.string.mdtp_cancel;
+ private String mCancelString;
+ private int mCancelColor = -1;
+ private Version mVersion;
+ private TimeZone mTimezone;
+
+ private HapticFeedbackController mHapticFeedbackController;
+
+ private boolean mDelayAnimation = true;
+
+ // Accessibility strings.
+ private String mDayPickerDescription;
+ private String mSelectDay;
+ private String mYearPickerDescription;
+ private String mSelectYear;
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnDateSetListener {
+
+ /**
+ * @param view The view associated with this listener.
+ * @param year The year that was set.
+ * @param monthOfYear The month that was set (0-11) for compatibility
+ * with {@link java.util.Calendar}.
+ * @param dayOfMonth The day of the month that was set.
+ */
+ void onDateSet(GregorianDatePickerDialog view, int year, int monthOfYear, int dayOfMonth);
+ }
+
+ /**
+ * The callback used to notify other date picker components of a change in selected date.
+ */
+ interface OnDateChangedListener {
+
+ void onDateChanged();
+ }
+
+
+ public GregorianDatePickerDialog() {
+ // Empty constructor required for dialog fragment.
+ }
+
+ /**
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param monthOfYear The initial month of the dialog.
+ * @param dayOfMonth The initial day of the dialog.
+ */
+ public static GregorianDatePickerDialog newInstance(OnDateSetListener callBack, int year,
+ int monthOfYear,
+ int dayOfMonth) {
+ GregorianDatePickerDialog ret = new GregorianDatePickerDialog();
+ ret.initialize(callBack, year, monthOfYear, dayOfMonth);
+ return ret;
+ }
+
+ public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
+ mCallBack = callBack;
+ mCalendar.set(Calendar.YEAR, year);
+ mCalendar.set(Calendar.MONTH, monthOfYear);
+ mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+
+ mVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? Version.VERSION_1 : Version.VERSION_2;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Activity activity = getActivity();
+ activity.getWindow().setSoftInputMode(
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ mCurrentView = UNINITIALIZED;
+ if (savedInstanceState != null) {
+ mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
+ mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
+ mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
+ mDefaultView = savedInstanceState.getInt(KEY_DEFAULT_VIEW);
+ }
+ if (Build.VERSION.SDK_INT < 18) {
+ VERSION_2_FORMAT = new SimpleDateFormat(activity.getResources().getString(R.string.mdtp_date_v2_daymonthyear), Locale.getDefault());
+ } else {
+ VERSION_2_FORMAT = new SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "EEEMMMdd"), Locale.getDefault());
+ }
+ VERSION_2_FORMAT.setTimeZone(getTimeZone());
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR));
+ outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH));
+ outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH));
+ outState.putInt(KEY_WEEK_START, mWeekStart);
+ outState.putInt(KEY_YEAR_START, mMinYear);
+ outState.putInt(KEY_YEAR_END, mMaxYear);
+ outState.putInt(KEY_CURRENT_VIEW, mCurrentView);
+ int listPosition = -1;
+ if (mCurrentView == MONTH_AND_DAY_VIEW) {
+ listPosition = mDayPickerView.getMostVisiblePosition();
+ } else if (mCurrentView == YEAR_VIEW) {
+ listPosition = mYearPickerView.getFirstVisiblePosition();
+ outState.putInt(KEY_LIST_POSITION_OFFSET, mYearPickerView.getFirstPositionOffset());
+ }
+ outState.putInt(KEY_LIST_POSITION, listPosition);
+ outState.putSerializable(KEY_MIN_DATE, mMinDate);
+ outState.putSerializable(KEY_MAX_DATE, mMaxDate);
+ outState.putSerializable(KEY_HIGHLIGHTED_DAYS, highlightedDays);
+ outState.putSerializable(KEY_SELECTABLE_DAYS, selectableDays);
+ outState.putSerializable(KEY_DISABLED_DAYS, disabledDays);
+ outState.putBoolean(KEY_THEME_DARK, mThemeDark);
+ outState.putBoolean(KEY_THEME_DARK_CHANGED, mThemeDarkChanged);
+ outState.putInt(KEY_ACCENT, mAccentColor);
+ outState.putBoolean(KEY_VIBRATE, mVibrate);
+ outState.putBoolean(KEY_DISMISS, mDismissOnPause);
+ outState.putBoolean(KEY_AUTO_DISMISS, mAutoDismiss);
+ outState.putInt(KEY_DEFAULT_VIEW, mDefaultView);
+ outState.putString(KEY_TITLE, mTitle);
+ outState.putInt(KEY_OK_RESID, mOkResid);
+ outState.putString(KEY_OK_STRING, mOkString);
+ outState.putInt(KEY_OK_COLOR, mOkColor);
+ outState.putInt(KEY_CANCEL_RESID, mCancelResid);
+ outState.putString(KEY_CANCEL_STRING, mCancelString);
+ outState.putInt(KEY_CANCEL_COLOR, mCancelColor);
+ outState.putSerializable(KEY_VERSION, mVersion);
+ outState.putSerializable(KEY_TIMEZONE, mTimezone);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ int listPosition = -1;
+ int listPositionOffset = 0;
+ int currentView = mDefaultView;
+ if (savedInstanceState != null) {
+ mWeekStart = savedInstanceState.getInt(KEY_WEEK_START);
+ mMinYear = savedInstanceState.getInt(KEY_YEAR_START);
+ mMaxYear = savedInstanceState.getInt(KEY_YEAR_END);
+ currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW);
+ listPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
+ listPositionOffset = savedInstanceState.getInt(KEY_LIST_POSITION_OFFSET);
+ mMinDate = (Calendar)savedInstanceState.getSerializable(KEY_MIN_DATE);
+ mMaxDate = (Calendar)savedInstanceState.getSerializable(KEY_MAX_DATE);
+ highlightedDays = (HashSet) savedInstanceState.getSerializable(KEY_HIGHLIGHTED_DAYS);
+ selectableDays = (TreeSet) savedInstanceState.getSerializable(KEY_SELECTABLE_DAYS);
+ disabledDays = (HashSet) savedInstanceState.getSerializable(KEY_DISABLED_DAYS);
+ mThemeDark = savedInstanceState.getBoolean(KEY_THEME_DARK);
+ mThemeDarkChanged = savedInstanceState.getBoolean(KEY_THEME_DARK_CHANGED);
+ mAccentColor = savedInstanceState.getInt(KEY_ACCENT);
+ mVibrate = savedInstanceState.getBoolean(KEY_VIBRATE);
+ mDismissOnPause = savedInstanceState.getBoolean(KEY_DISMISS);
+ mAutoDismiss = savedInstanceState.getBoolean(KEY_AUTO_DISMISS);
+ mTitle = savedInstanceState.getString(KEY_TITLE);
+ mOkResid = savedInstanceState.getInt(KEY_OK_RESID);
+ mOkString = savedInstanceState.getString(KEY_OK_STRING);
+ mOkColor = savedInstanceState.getInt(KEY_OK_COLOR);
+ mCancelResid = savedInstanceState.getInt(KEY_CANCEL_RESID);
+ mCancelString = savedInstanceState.getString(KEY_CANCEL_STRING);
+ mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR);
+ mVersion = (Version) savedInstanceState.getSerializable(KEY_VERSION);
+ mTimezone = (TimeZone) savedInstanceState.getSerializable(KEY_TIMEZONE);
+ }
+
+ int viewRes = mVersion == Version.VERSION_1 ? R.layout.mdtp_date_picker_dialog : R.layout.mdtp_date_picker_dialog_v2;
+ View view = inflater.inflate(viewRes, container, false);
+ // All options have been set at this point: round the initial selection if necessary
+ setToNearestDate(mCalendar);
+
+ mDatePickerHeaderView = (TextView) view.findViewById(R.id.mdtp_date_picker_header);
+ mMonthAndDayView = (LinearLayout) view.findViewById(R.id.mdtp_date_picker_month_and_day);
+ mMonthAndDayView.setOnClickListener(this);
+ mSelectedMonthTextView = (TextView) view.findViewById(R.id.mdtp_date_picker_month);
+ mSelectedDayTextView = (TextView) view.findViewById(R.id.mdtp_date_picker_day);
+ mYearView = (TextView) view.findViewById(R.id.mdtp_date_picker_year);
+ mYearView.setOnClickListener(this);
+
+ final Activity activity = getActivity();
+ mDayPickerView = new SimpleDayPickerView(activity, this);
+ mYearPickerView = new YearPickerView(activity, this);
+
+ // if theme mode has not been set by java code, check if it is specified in Style.xml
+ if (!mThemeDarkChanged) {
+ mThemeDark = Utils.isDarkTheme(activity, mThemeDark);
+ }
+
+ Resources res = getResources();
+ mDayPickerDescription = res.getString(R.string.mdtp_day_picker_description);
+ mSelectDay = res.getString(R.string.mdtp_select_day);
+ mYearPickerDescription = res.getString(R.string.mdtp_year_picker_description);
+ mSelectYear = res.getString(R.string.mdtp_select_year);
+
+ int bgColorResource = mThemeDark ? R.color.mdtp_date_picker_view_animator_dark_theme : R.color.mdtp_date_picker_view_animator;
+ view.setBackgroundColor(ContextCompat.getColor(activity, bgColorResource));
+
+ mAnimator = (AccessibleDateAnimator) view.findViewById(R.id.mdtp_animator);
+ mAnimator.addView(mDayPickerView);
+ mAnimator.addView(mYearPickerView);
+ mAnimator.setDateMillis(mCalendar.getTimeInMillis());
+ // TODO: Replace with animation decided upon by the design team.
+ Animation animation = new AlphaAnimation(0.0f, 1.0f);
+ animation.setDuration(ANIMATION_DURATION);
+ mAnimator.setInAnimation(animation);
+ // TODO: Replace with animation decided upon by the design team.
+ Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
+ animation2.setDuration(ANIMATION_DURATION);
+ mAnimator.setOutAnimation(animation2);
+
+ Button okButton = (Button) view.findViewById(R.id.mdtp_ok);
+ okButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ notifyOnDateListener();
+ dismiss();
+ }
+ });
+ okButton.setTypeface(TypefaceHelper.get(activity, "Roboto-Medium"));
+ if(mOkString != null) okButton.setText(mOkString);
+ else okButton.setText(mOkResid);
+
+ Button cancelButton = (Button) view.findViewById(R.id.mdtp_cancel);
+ cancelButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if(getDialog() != null) getDialog().cancel();
+ }
+ });
+ cancelButton.setTypeface(TypefaceHelper.get(activity,"Roboto-Medium"));
+ if(mCancelString != null) cancelButton.setText(mCancelString);
+ else cancelButton.setText(mCancelResid);
+ cancelButton.setVisibility(isCancelable() ? View.VISIBLE : View.GONE);
+
+ // If an accent color has not been set manually, get it from the context
+ if (mAccentColor == -1) {
+ mAccentColor = Utils.getAccentColorFromThemeIfAvailable(getActivity());
+ }
+ if(mDatePickerHeaderView != null) mDatePickerHeaderView.setBackgroundColor(Utils.darkenColor(mAccentColor));
+ view.findViewById(R.id.mdtp_day_picker_selected_date_layout).setBackgroundColor(mAccentColor);
+
+ // Buttons can have a different color
+ if (mOkColor != -1) okButton.setTextColor(mOkColor);
+ else okButton.setTextColor(mAccentColor);
+ if (mCancelColor != -1) cancelButton.setTextColor(mCancelColor);
+ else cancelButton.setTextColor(mAccentColor);
+
+ if(getDialog() == null) {
+ view.findViewById(R.id.mdtp_done_background).setVisibility(View.GONE);
+ }
+
+ updateDisplay(false);
+ setCurrentView(currentView);
+
+ if (listPosition != -1) {
+ if (currentView == MONTH_AND_DAY_VIEW) {
+ mDayPickerView.postSetSelection(listPosition);
+ } else if (currentView == YEAR_VIEW) {
+ mYearPickerView.postSetSelectionFromTop(listPosition, listPositionOffset);
+ }
+ }
+
+ mHapticFeedbackController = new HapticFeedbackController(activity);
+ return view;
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ ViewGroup viewGroup = (ViewGroup) getView();
+ if (viewGroup != null) {
+ viewGroup.removeAllViewsInLayout();
+ View view = onCreateView(getActivity().getLayoutInflater(), viewGroup, null);
+ viewGroup.addView(view);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mHapticFeedbackController.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHapticFeedbackController.stop();
+ if(mDismissOnPause) dismiss();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ if(mOnCancelListener != null) mOnCancelListener.onCancel(dialog);
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ if(mOnDismissListener != null) mOnDismissListener.onDismiss(dialog);
+ }
+
+ private void setCurrentView(final int viewIndex) {
+ long millis = mCalendar.getTimeInMillis();
+
+ switch (viewIndex) {
+ case MONTH_AND_DAY_VIEW:
+ if (mVersion == Version.VERSION_1) {
+ ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f,
+ 1.05f);
+ if (mDelayAnimation) {
+ pulseAnimator.setStartDelay(ANIMATION_DELAY);
+ mDelayAnimation = false;
+ }
+ mDayPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(true);
+ mYearView.setSelected(false);
+ mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
+ mCurrentView = viewIndex;
+ }
+ pulseAnimator.start();
+ } else {
+ mDayPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(true);
+ mYearView.setSelected(false);
+ mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
+ mCurrentView = viewIndex;
+ }
+ }
+
+ int flags = DateUtils.FORMAT_SHOW_DATE;
+ String dayString = DateUtils.formatDateTime(getActivity(), millis, flags);
+ mAnimator.setContentDescription(mDayPickerDescription+": "+dayString);
+ Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay);
+ break;
+ case YEAR_VIEW:
+ if (mVersion == Version.VERSION_1) {
+ ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f);
+ if (mDelayAnimation) {
+ pulseAnimator.setStartDelay(ANIMATION_DELAY);
+ mDelayAnimation = false;
+ }
+ mYearPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(false);
+ mYearView.setSelected(true);
+ mAnimator.setDisplayedChild(YEAR_VIEW);
+ mCurrentView = viewIndex;
+ }
+ pulseAnimator.start();
+ } else {
+ mYearPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(false);
+ mYearView.setSelected(true);
+ mAnimator.setDisplayedChild(YEAR_VIEW);
+ mCurrentView = viewIndex;
+ }
+ }
+
+ CharSequence yearString = YEAR_FORMAT.format(millis);
+ mAnimator.setContentDescription(mYearPickerDescription+": "+yearString);
+ Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear);
+ break;
+ }
+ }
+
+ private void updateDisplay(boolean announce) {
+ mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));
+
+ if (mVersion == Version.VERSION_1) {
+ if (mDatePickerHeaderView != null) {
+ if (mTitle != null)
+ mDatePickerHeaderView.setText(mTitle.toUpperCase(Locale.getDefault()));
+ else {
+ mDatePickerHeaderView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()));
+ }
+ }
+ mSelectedMonthTextView.setText(MONTH_FORMAT.format(mCalendar.getTime()));
+ mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime()));
+ }
+
+ if (mVersion == Version.VERSION_2) {
+ mSelectedDayTextView.setText(VERSION_2_FORMAT.format(mCalendar.getTime()));
+ if (mTitle != null)
+ mDatePickerHeaderView.setText(mTitle.toUpperCase(Locale.getDefault()));
+ else
+ mDatePickerHeaderView.setVisibility(View.GONE);
+ }
+
+ // Accessibility.
+ long millis = mCalendar.getTimeInMillis();
+ mAnimator.setDateMillis(millis);
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
+ String monthAndDayText = DateUtils.formatDateTime(getActivity(), millis, flags);
+ mMonthAndDayView.setContentDescription(monthAndDayText);
+
+ if (announce) {
+ flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
+ String fullDateText = DateUtils.formatDateTime(getActivity(), millis, flags);
+ Utils.tryAccessibilityAnnounce(mAnimator, fullDateText);
+ }
+ }
+
+ /**
+ * Set whether the device should vibrate when touching fields
+ * @param vibrate true if the device should vibrate when touching a field
+ */
+ public void vibrate(boolean vibrate) {
+ mVibrate = vibrate;
+ }
+
+ /**
+ * Set whether the picker should dismiss itself when being paused or whether it should try to survive an orientation change
+ * @param dismissOnPause true if the dialog should dismiss itself when it's pausing
+ */
+ public void dismissOnPause(boolean dismissOnPause) {
+ mDismissOnPause = dismissOnPause;
+ }
+
+ /**
+ * Set whether the picker should dismiss itself when a day is selected
+ * @param autoDismiss true if the dialog should dismiss itself when a day is selected
+ */
+ @SuppressWarnings("unused")
+ public void autoDismiss(boolean autoDismiss) {
+ mAutoDismiss = autoDismiss;
+ }
+
+ /**
+ * Set whether the dark theme should be used
+ * @param themeDark true if the dark theme should be used, false if the default theme should be used
+ */
+ public void setThemeDark(boolean themeDark) {
+ mThemeDark = themeDark;
+ mThemeDarkChanged = true;
+ }
+
+ /**
+ * Returns true when the dark theme should be used
+ * @return true if the dark theme should be used, false if the default theme should be used
+ */
+ @Override
+ public boolean isThemeDark() {
+ return mThemeDark;
+ }
+
+ /**
+ * Set the accent color of this dialog
+ * @param color the accent color you want
+ */
+ @SuppressWarnings("unused")
+ public void setAccentColor(String color) {
+ mAccentColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the accent color of this dialog
+ * @param color the accent color you want
+ */
+ public void setAccentColor(@ColorInt int color) {
+ mAccentColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Set the text color of the OK button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setOkColor(String color) {
+ mOkColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the text color of the OK button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setOkColor(@ColorInt int color) {
+ mOkColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Set the text color of the Cancel button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setCancelColor(String color) {
+ mCancelColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the text color of the Cancel button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setCancelColor(@ColorInt int color) {
+ mCancelColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Get the accent color of this dialog
+ * @return accent color
+ */
+ @Override
+ public int getAccentColor() {
+ return mAccentColor;
+ }
+
+ /**
+ * Set whether the year picker of the month and day picker is shown first
+ * @param yearPicker boolean
+ */
+ public void showYearPickerFirst(boolean yearPicker) {
+ mDefaultView = yearPicker ? YEAR_VIEW : MONTH_AND_DAY_VIEW;
+ }
+
+ @SuppressWarnings("unused")
+ public void setFirstDayOfWeek(int startOfWeek) {
+ if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
+ throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
+ "Calendar.SATURDAY");
+ }
+ mWeekStart = startOfWeek;
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void setYearRange(int startYear, int endYear) {
+ if (endYear < startYear) {
+ throw new IllegalArgumentException("Year end must be larger than or equal to year start");
+ }
+
+ mMinYear = startYear;
+ mMaxYear = endYear;
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ /**
+ * Sets the minimal date supported by this DatePicker. Dates before (but not including) the
+ * specified date will be disallowed from being selected.
+ * @param calendar a Calendar object set to the year, month, day desired as the mindate.
+ */
+ @SuppressWarnings("unused")
+ public void setMinDate(Calendar calendar) {
+ mMinDate = trimToMidnight((Calendar) calendar.clone());
+
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ /**
+ * @return The minimal date supported by this DatePicker. Null if it has not been set.
+ */
+ @SuppressWarnings("unused")
+ public Calendar getMinDate() {
+ return mMinDate;
+ }
+
+ /**
+ * Sets the minimal date supported by this DatePicker. Dates after (but not including) the
+ * specified date will be disallowed from being selected.
+ * @param calendar a Calendar object set to the year, month, day desired as the maxdate.
+ */
+ @SuppressWarnings("unused")
+ public void setMaxDate(Calendar calendar) {
+ mMaxDate = trimToMidnight((Calendar) calendar.clone());
+
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ /**
+ * @return The maximal date supported by this DatePicker. Null if it has not been set.
+ */
+ @SuppressWarnings("unused")
+ public Calendar getMaxDate() {
+ return mMaxDate;
+ }
+
+ /**
+ * Sets an array of dates which should be highlighted when the picker is drawn
+ * @param highlightedDays an Array of Calendar objects containing the dates to be highlighted
+ */
+ @SuppressWarnings("unused")
+ public void setHighlightedDays(Calendar[] highlightedDays) {
+ for (Calendar highlightedDay : highlightedDays) trimToMidnight(highlightedDay);
+ this.highlightedDays.addAll(Arrays.asList(highlightedDays));
+ if (mDayPickerView != null) mDayPickerView.onChange();
+ }
+
+ /**
+ * @return The list of dates, as Calendar Objects, which should be highlighted. null is no dates should be highlighted
+ */
+ @SuppressWarnings("unused")
+ public Calendar[] getHighlightedDays() {
+ if (highlightedDays.isEmpty()) return null;
+ Calendar[] output = highlightedDays.toArray(new Calendar[0]);
+ Arrays.sort(output);
+ return output;
+ }
+
+ @Override
+ public boolean isHighlighted(int year, int month, int day) {
+ Calendar date = Calendar.getInstance();
+ date.set(Calendar.YEAR, year);
+ date.set(Calendar.MONTH, month);
+ date.set(Calendar.DAY_OF_MONTH, day);
+ trimToMidnight(date);
+ return highlightedDays.contains(date);
+ }
+
+ /**
+ * Sets a list of days which are the only valid selections.
+ * Setting this value will take precedence over using setMinDate() and setMaxDate()
+ * @param selectableDays an Array of Calendar Objects containing the selectable dates
+ */
+ @SuppressWarnings("unused")
+ public void setSelectableDays(Calendar[] selectableDays) {
+ for (Calendar selectableDay : selectableDays) trimToMidnight(selectableDay);
+ this.selectableDays.addAll(Arrays.asList(selectableDays));
+ if (mDayPickerView != null) mDayPickerView.onChange();
+ }
+
+ /**
+ * @return an Array of Calendar objects containing the list with selectable items. null if no restriction is set
+ */
+ @SuppressWarnings("unused")
+ public Calendar[] getSelectableDays() {
+ return selectableDays.isEmpty() ? null : selectableDays.toArray(new Calendar[0]);
+ }
+
+ /**
+ * Sets a list of days that are not selectable in the picker
+ * Setting this value will take precedence over using setMinDate() and setMaxDate(), but stacks with setSelectableDays()
+ * @param disabledDays an Array of Calendar Objects containing the disabled dates
+ */
+ @SuppressWarnings("unused")
+ public void setDisabledDays(Calendar[] disabledDays) {
+ for (Calendar disabledDay : disabledDays) trimToMidnight(disabledDay);
+ this.disabledDays.addAll(Arrays.asList(disabledDays));
+ if (mDayPickerView != null) mDayPickerView.onChange();
+ }
+
+ /**
+ * @return an Array of Calendar objects containing the list of days that are not selectable. null if no restriction is set
+ */
+ @SuppressWarnings("unused")
+ public Calendar[] getDisabledDays() {
+ if (disabledDays.isEmpty()) return null;
+ Calendar[] output = disabledDays.toArray(new Calendar[0]);
+ Arrays.sort(output);
+ return output;
+ }
+
+ /**
+ * Set a title to be displayed instead of the weekday
+ * @param title String - The title to be displayed
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ /**
+ * Set the label for the Ok button (max 12 characters)
+ * @param okString A literal String to be used as the Ok button label
+ */
+ @SuppressWarnings("unused")
+ public void setOkText(String okString) {
+ mOkString = okString;
+ }
+
+ /**
+ * Set the label for the Ok button (max 12 characters)
+ * @param okResid A resource ID to be used as the Ok button label
+ */
+ @SuppressWarnings("unused")
+ public void setOkText(@StringRes int okResid) {
+ mOkString = null;
+ mOkResid = okResid;
+ }
+
+ /**
+ * Set the label for the Cancel button (max 12 characters)
+ * @param cancelString A literal String to be used as the Cancel button label
+ */
+ @SuppressWarnings("unused")
+ public void setCancelText(String cancelString) {
+ mCancelString = cancelString;
+ }
+
+ /**
+ * Set the label for the Cancel button (max 12 characters)
+ * @param cancelResid A resource ID to be used as the Cancel button label
+ */
+ @SuppressWarnings("unused")
+ public void setCancelText(@StringRes int cancelResid) {
+ mCancelString = null;
+ mCancelResid = cancelResid;
+ }
+
+ /**
+ * Set which layout version the picker should use
+ * @param version The version to use
+ */
+ public void setVersion(Version version) {
+ mVersion = version;
+ }
+
+ /**
+ * Set which timezone the picker should use
+ * @param timeZone The timezone to use
+ */
+ @SuppressWarnings("unused")
+ public void setTimeZone(TimeZone timeZone) {
+ mTimezone = timeZone;
+ mCalendar.setTimeZone(timeZone);
+ YEAR_FORMAT.setTimeZone(timeZone);
+ MONTH_FORMAT.setTimeZone(timeZone);
+ DAY_FORMAT.setTimeZone(timeZone);
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnDateSetListener(OnDateSetListener listener) {
+ mCallBack = listener;
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
+ mOnCancelListener = onCancelListener;
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) {
+ mOnDismissListener = onDismissListener;
+ }
+
+ // If the newly selected month / year does not contain the currently selected day number,
+ // change the selected day number to the last day of the selected month or year.
+ // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
+ // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
+ private void adjustDayInMonthIfNeeded(Calendar calendar) {
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+ if (day > daysInMonth) {
+ calendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
+ }
+ setToNearestDate(calendar);
+ }
+
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if (v.getId() == R.id.mdtp_date_picker_year) {
+ setCurrentView(YEAR_VIEW);
+ } else if (v.getId() == R.id.mdtp_date_picker_month_and_day) {
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ }
+ }
+
+ @Override
+ public void onYearSelected(int year) {
+ mCalendar.set(Calendar.YEAR, year);
+ adjustDayInMonthIfNeeded(mCalendar);
+ updatePickers();
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ updateDisplay(true);
+ }
+
+ @Override
+ public void onDayOfMonthSelected(int year, int month, int day) {
+ mCalendar.set(Calendar.YEAR, year);
+ mCalendar.set(Calendar.MONTH, month);
+ mCalendar.set(Calendar.DAY_OF_MONTH, day);
+ updatePickers();
+ updateDisplay(true);
+ if (mAutoDismiss) {
+ notifyOnDateListener();
+ dismiss();
+ }
+ }
+
+ private void updatePickers() {
+ for(OnDateChangedListener listener : mListeners) listener.onDateChanged();
+ }
+
+
+ @Override
+ public MonthAdapter.CalendarDay getSelectedDay() {
+ return new MonthAdapter.CalendarDay(mCalendar, getTimeZone());
+ }
+
+ @Override
+ public Calendar getStartDate() {
+ if (!selectableDays.isEmpty()) return selectableDays.first();
+ if (mMinDate != null) return mMinDate;
+ Calendar output = Calendar.getInstance(getTimeZone());
+ output.set(Calendar.YEAR, mMinYear);
+ output.set(Calendar.DAY_OF_MONTH, 1);
+ output.set(Calendar.MONTH, Calendar.JANUARY);
+ return output;
+ }
+
+ @Override
+ public Calendar getEndDate() {
+ if (!selectableDays.isEmpty()) return selectableDays.last();
+ if (mMaxDate != null) return mMaxDate;
+ Calendar output = Calendar.getInstance(getTimeZone());
+ output.set(Calendar.YEAR, mMaxYear);
+ output.set(Calendar.DAY_OF_MONTH, 31);
+ output.set(Calendar.MONTH, Calendar.DECEMBER);
+ return output;
+ }
+
+ @Override
+ public int getMinYear() {
+ if (!selectableDays.isEmpty()) return selectableDays.first().get(Calendar.YEAR);
+ // Ensure no years can be selected outside of the given minimum date
+ return mMinDate != null && mMinDate.get(Calendar.YEAR) > mMinYear ? mMinDate.get(Calendar.YEAR) : mMinYear;
+ }
+
+ @Override
+ public int getMaxYear() {
+ if (!selectableDays.isEmpty()) return selectableDays.last().get(Calendar.YEAR);
+ // Ensure no years can be selected outside of the given maximum date
+ return mMaxDate != null && mMaxDate.get(Calendar.YEAR) < mMaxYear ? mMaxDate.get(Calendar.YEAR) : mMaxYear;
+ }
+
+ /**
+ * @return true if the specified year/month/day are within the selectable days or the range set by minDate and maxDate.
+ * If one or either have not been set, they are considered as Integer.MIN_VALUE and
+ * Integer.MAX_VALUE.
+ */
+ @Override
+ public boolean isOutOfRange(int year, int month, int day) {
+ Calendar date = Calendar.getInstance();
+ date.set(Calendar.YEAR, year);
+ date.set(Calendar.MONTH, month);
+ date.set(Calendar.DAY_OF_MONTH, day);
+ return isOutOfRange(date);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isOutOfRange(Calendar calendar) {
+ trimToMidnight(calendar);
+ return isDisabled(calendar) || !isSelectable(calendar);
+ }
+
+ private boolean isDisabled(Calendar c) {
+ return disabledDays.contains(trimToMidnight(c)) || isBeforeMin(c) || isAfterMax(c);
+ }
+
+ private boolean isSelectable(Calendar c) {
+ return selectableDays.isEmpty() || selectableDays.contains(trimToMidnight(c));
+ }
+
+ private boolean isBeforeMin(Calendar calendar) {
+ return mMinDate != null && calendar.before(mMinDate);
+ }
+
+ private boolean isAfterMax(Calendar calendar) {
+ return mMaxDate != null && calendar.after(mMaxDate);
+ }
+
+ private void setToNearestDate(Calendar calendar) {
+ if (!selectableDays.isEmpty()) {
+ Calendar newCalendar = null;
+ Calendar higher = selectableDays.ceiling(calendar);
+ Calendar lower = selectableDays.lower(calendar);
+
+ if (higher == null && lower != null) newCalendar = lower;
+ else if (lower == null && higher != null) newCalendar = higher;
+
+ if (newCalendar != null || higher == null) {
+ newCalendar = newCalendar == null ? calendar : newCalendar;
+ newCalendar.setTimeZone(getTimeZone());
+ calendar.setTimeInMillis(newCalendar.getTimeInMillis());
+ return;
+ }
+
+ long highDistance = Math.abs(higher.getTimeInMillis() - calendar.getTimeInMillis());
+ long lowDistance = Math.abs(calendar.getTimeInMillis() - lower.getTimeInMillis());
+
+ if (lowDistance < highDistance) calendar.setTimeInMillis(lower.getTimeInMillis());
+ else calendar.setTimeInMillis(higher.getTimeInMillis());
+
+ return;
+ }
+
+ if (!disabledDays.isEmpty()) {
+ Calendar forwardDate = (Calendar) calendar.clone();
+ Calendar backwardDate = (Calendar) calendar.clone();
+ while (isDisabled(forwardDate) && isDisabled(backwardDate)) {
+ forwardDate.add(Calendar.DAY_OF_MONTH, 1);
+ backwardDate.add(Calendar.DAY_OF_MONTH, -1);
+ }
+ if (!isDisabled(backwardDate)) {
+ calendar.setTimeInMillis(backwardDate.getTimeInMillis());
+ return;
+ }
+ if (!isDisabled(forwardDate)) {
+ calendar.setTimeInMillis(forwardDate.getTimeInMillis());
+ return;
+ }
+ }
+
+
+ if(isBeforeMin(calendar)) {
+ calendar.setTimeInMillis(mMinDate.getTimeInMillis());
+ return;
+ }
+
+ if(isAfterMax(calendar)) {
+ calendar.setTimeInMillis(mMaxDate.getTimeInMillis());
+ return;
+ }
+ }
+
+ /**
+ * Trims off all time information, effectively setting it to midnight
+ * Makes it easier to compare at just the day level
+ * @param calendar The Calendar object to trim
+ * @return The trimmed Calendar object
+ */
+ private Calendar trimToMidnight(Calendar calendar) {
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar;
+ }
+
+ @Override
+ public int getFirstDayOfWeek() {
+ return mWeekStart;
+ }
+
+ @Override
+ public void registerOnDateChangedListener(OnDateChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void tryVibrate() {
+ if(mVibrate) mHapticFeedbackController.tryVibrate();
+ }
+
+ @Override public TimeZone getTimeZone() {
+ return mTimezone == null ? TimeZone.getDefault() : mTimezone;
+ }
+
+ public void notifyOnDateListener() {
+ if (mCallBack != null) {
+ mCallBack.onDateSet(GregorianDatePickerDialog.this, mCalendar.get(Calendar.YEAR),
+ mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/MonthAdapter.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/MonthAdapter.java
new file mode 100644
index 0000000..86d7c5c
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/MonthAdapter.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.BaseAdapter;
+
+import net.alhazmy13.hijridatepicker.date.gregorian.MonthView.OnDayClickListener;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.TimeZone;
+
+/**
+ * An adapter for a list of {@link MonthView} items.
+ */
+public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
+
+ private static final String TAG = "SimpleMonthAdapter";
+
+ private final Context mContext;
+ protected final DatePickerController mController;
+
+ private CalendarDay mSelectedDay;
+
+ protected static int WEEK_7_OVERHANG_HEIGHT = 7;
+ protected static final int MONTHS_IN_YEAR = 12;
+
+ /**
+ * A convenience class to represent a specific date.
+ */
+ public static class CalendarDay {
+ private Calendar calendar;
+ int year;
+ int month;
+ int day;
+ TimeZone mTimeZone;
+
+ public CalendarDay(TimeZone timeZone) {
+ mTimeZone = timeZone;
+ setTime(System.currentTimeMillis());
+ }
+
+ public CalendarDay(long timeInMillis, TimeZone timeZone) {
+ mTimeZone = timeZone;
+ setTime(timeInMillis);
+ }
+
+ public CalendarDay(Calendar calendar, TimeZone timeZone) {
+ mTimeZone = timeZone;
+ year = calendar.get(Calendar.YEAR);
+ month = calendar.get(Calendar.MONTH);
+ day = calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ public CalendarDay(int year, int month, int day) {
+ setDay(year, month, day);
+ }
+
+ public void set(CalendarDay date) {
+ year = date.year;
+ month = date.month;
+ day = date.day;
+ }
+
+ public void setDay(int year, int month, int day) {
+ this.year = year;
+ this.month = month;
+ this.day = day;
+ }
+
+ private void setTime(long timeInMillis) {
+ if (calendar == null) {
+ calendar = Calendar.getInstance(mTimeZone);
+ }
+ calendar.setTimeInMillis(timeInMillis);
+ month = calendar.get(Calendar.MONTH);
+ year = calendar.get(Calendar.YEAR);
+ day = calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public int getMonth() {
+ return month;
+ }
+
+ public int getDay() {
+ return day;
+ }
+ }
+
+ public MonthAdapter(Context context,
+ DatePickerController controller) {
+ mContext = context;
+ mController = controller;
+ init();
+ setSelectedDay(mController.getSelectedDay());
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param day The day to highlight
+ */
+ public void setSelectedDay(CalendarDay day) {
+ mSelectedDay = day;
+ notifyDataSetChanged();
+ }
+
+ @SuppressWarnings("unused")
+ public CalendarDay getSelectedDay() {
+ return mSelectedDay;
+ }
+
+ /**
+ * Set up the gesture detector and selected time
+ */
+ protected void init() {
+ mSelectedDay = new CalendarDay(System.currentTimeMillis(), mController.getTimeZone());
+ }
+
+ @Override
+ public int getCount() {
+ Calendar endDate = mController.getEndDate();
+ Calendar startDate = mController.getStartDate();
+ int endMonth = endDate.get(Calendar.YEAR) * MONTHS_IN_YEAR + endDate.get(Calendar.MONTH);
+ int startMonth = startDate.get(Calendar.YEAR) * MONTHS_IN_YEAR + startDate.get(Calendar.MONTH);
+ return endMonth - startMonth + 1;
+ //return ((mController.getMaxYear() - mController.getMinYear()) + 1) * MONTHS_IN_YEAR;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ @SuppressWarnings("unchecked")
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ MonthView v;
+ HashMap drawingParams = null;
+ if (convertView != null) {
+ v = (MonthView) convertView;
+ // We store the drawing parameters in the view so it can be recycled
+ drawingParams = (HashMap) v.getTag();
+ } else {
+ v = createMonthView(mContext);
+ // Set up the new view
+ LayoutParams params = new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ v.setLayoutParams(params);
+ v.setClickable(true);
+ v.setOnDayClickListener(this);
+ }
+ if (drawingParams == null) {
+ drawingParams = new HashMap<>();
+ }
+ drawingParams.clear();
+
+ final int month = (position + mController.getStartDate().get(Calendar.MONTH)) % MONTHS_IN_YEAR;
+ final int year = (position + mController.getStartDate().get(Calendar.MONTH)) / MONTHS_IN_YEAR + mController.getMinYear();
+
+ int selectedDay = -1;
+ if (isSelectedDayInMonth(year, month)) {
+ selectedDay = mSelectedDay.day;
+ }
+
+ // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
+ // height/number of weeks before being displayed.
+ v.reuse();
+
+ drawingParams.put(MonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
+ drawingParams.put(MonthView.VIEW_PARAMS_YEAR, year);
+ drawingParams.put(MonthView.VIEW_PARAMS_MONTH, month);
+ drawingParams.put(MonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
+ v.setMonthParams(drawingParams);
+ v.invalidate();
+ return v;
+ }
+
+ public abstract MonthView createMonthView(Context context);
+
+ private boolean isSelectedDayInMonth(int year, int month) {
+ return mSelectedDay.year == year && mSelectedDay.month == month;
+ }
+
+
+ @Override
+ public void onDayClick(MonthView view, CalendarDay day) {
+ if (day != null) {
+ onDayTapped(day);
+ }
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ protected void onDayTapped(CalendarDay day) {
+ mController.tryVibrate();
+ mController.onDayOfMonthSelected(day.year, day.month, day.day);
+ setSelectedDay(day);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/MonthView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/MonthView.java
new file mode 100644
index 0000000..7459bfc
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/MonthView.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.TypefaceHelper;
+import net.alhazmy13.hijridatepicker.date.gregorian.MonthAdapter.CalendarDay;
+
+import java.security.InvalidParameterException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A calendar-like view displaying a specified month and the appropriate selectable day numbers
+ * within the specified month.
+ */
+public abstract class MonthView extends View {
+ private static final String TAG = "MonthView";
+
+ /**
+ * These params can be passed into the view to control how it appears.
+ * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
+ * values are unlikely to fit most layouts correctly.
+ */
+ /**
+ * This sets the height of this week in pixels
+ */
+ public static final String VIEW_PARAMS_HEIGHT = "height";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week.
+ */
+ public static final String VIEW_PARAMS_MONTH = "month";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week.
+ */
+ public static final String VIEW_PARAMS_YEAR = "year";
+ /**
+ * This sets one of the days in this view as selected {@link Calendar#SUNDAY}
+ * through {@link Calendar#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
+ /**
+ * Which day the week should start on. {@link Calendar#SUNDAY} through
+ * {@link Calendar#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_WEEK_START = "week_start";
+ /**
+ * How many days to display at a time. Days will be displayed starting with
+ * {@link #mWeekStart}.
+ */
+ public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
+ /**
+ * Which month is currently in focus, as defined by {@link Calendar#MONTH}
+ * [0-11].
+ */
+ public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
+ /**
+ * If this month should display week numbers. false if 0, true otherwise.
+ */
+ public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
+
+ protected static int DEFAULT_HEIGHT = 32;
+ protected static int MIN_HEIGHT = 10;
+ protected static final int DEFAULT_SELECTED_DAY = -1;
+ protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
+ protected static final int DEFAULT_NUM_DAYS = 7;
+ protected static final int DEFAULT_SHOW_WK_NUM = 0;
+ protected static final int DEFAULT_FOCUS_MONTH = -1;
+ protected static final int DEFAULT_NUM_ROWS = 6;
+ protected static final int MAX_NUM_ROWS = 6;
+
+ private static final int SELECTED_CIRCLE_ALPHA = 255;
+
+ protected static int DAY_SEPARATOR_WIDTH = 1;
+ protected static int MINI_DAY_NUMBER_TEXT_SIZE;
+ protected static int MONTH_LABEL_TEXT_SIZE;
+ protected static int MONTH_DAY_LABEL_TEXT_SIZE;
+ protected static int MONTH_HEADER_SIZE;
+ protected static int DAY_SELECTED_CIRCLE_SIZE;
+
+ // used for scaling to the device density
+ protected static float mScale = 0;
+
+ protected DatePickerController mController;
+
+ // affects the padding on the sides of this view
+ protected int mEdgePadding = 0;
+
+ private String mDayOfWeekTypeface;
+ private String mMonthTitleTypeface;
+
+ protected Paint mMonthNumPaint;
+ protected Paint mMonthTitlePaint;
+ protected Paint mSelectedCirclePaint;
+ protected Paint mMonthDayLabelPaint;
+
+ private final Formatter mFormatter;
+ private final StringBuilder mStringBuilder;
+
+ // The Julian day of the first day displayed by this item
+ protected int mFirstJulianDay = -1;
+ // The month of the first day in this week
+ protected int mFirstMonth = -1;
+ // The month of the last day in this week
+ protected int mLastMonth = -1;
+
+ protected int mMonth;
+
+ protected int mYear;
+ // Quick reference to the width of this view, matches parent
+ protected int mWidth;
+ // The height this view should draw at in pixels, set by height param
+ protected int mRowHeight = DEFAULT_HEIGHT;
+ // If this view contains the today
+ protected boolean mHasToday = false;
+ // Which day is selected [0-6] or -1 if no day is selected
+ protected int mSelectedDay = -1;
+ // Which day is today [0-6] or -1 if no day is today
+ protected int mToday = DEFAULT_SELECTED_DAY;
+ // Which day of the week to start on [0-6]
+ protected int mWeekStart = DEFAULT_WEEK_START;
+ // How many days to display
+ protected int mNumDays = DEFAULT_NUM_DAYS;
+ // The number of days + a spot for week number if it is displayed
+ protected int mNumCells = mNumDays;
+ // The left edge of the selected day
+ protected int mSelectedLeft = -1;
+ // The right edge of the selected day
+ protected int mSelectedRight = -1;
+
+ private final Calendar mCalendar;
+ protected final Calendar mDayLabelCalendar;
+ private final MonthViewTouchHelper mTouchHelper;
+
+ protected int mNumRows = DEFAULT_NUM_ROWS;
+
+ // Optional listener for handling day click actions
+ protected OnDayClickListener mOnDayClickListener;
+
+ // Whether to prevent setting the accessibility delegate
+ private boolean mLockAccessibilityDelegate;
+
+ protected int mDayTextColor;
+ protected int mSelectedDayTextColor;
+ protected int mMonthDayTextColor;
+ protected int mTodayNumberColor;
+ protected int mHighlightedDayTextColor;
+ protected int mDisabledDayTextColor;
+ protected int mMonthTitleColor;
+
+ public MonthView(Context context) {
+ this(context, null, null);
+ }
+
+ public MonthView(Context context, AttributeSet attr, DatePickerController controller) {
+ super(context, attr);
+ mController = controller;
+ Resources res = context.getResources();
+
+ mDayLabelCalendar = Calendar.getInstance(mController.getTimeZone());
+ mCalendar = Calendar.getInstance(mController.getTimeZone());
+
+ mDayOfWeekTypeface = res.getString(R.string.mdtp_day_of_week_label_typeface);
+ mMonthTitleTypeface = res.getString(R.string.mdtp_sans_serif);
+
+ boolean darkTheme = mController != null && mController.isThemeDark();
+ if(darkTheme) {
+ mDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_normal_dark_theme);
+ mMonthDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_month_day_dark_theme);
+ mDisabledDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled_dark_theme);
+ mHighlightedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_highlighted_dark_theme);
+ }
+ else {
+ mDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_normal);
+ mMonthDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_month_day);
+ mDisabledDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled);
+ mHighlightedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_highlighted);
+ }
+ mSelectedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
+ mTodayNumberColor = mController.getAccentColor();
+ mMonthTitleColor = ContextCompat.getColor(context, R.color.mdtp_white);
+
+ mStringBuilder = new StringBuilder(50);
+ mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+
+ MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_day_number_size);
+ MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_label_size);
+ MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_day_label_text_size);
+ MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.mdtp_month_list_item_header_height);
+ DAY_SELECTED_CIRCLE_SIZE = res
+ .getDimensionPixelSize(R.dimen.mdtp_day_number_select_circle_radius);
+
+ mRowHeight = (res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height)
+ - getMonthHeaderSize()) / MAX_NUM_ROWS;
+
+ // Set up accessibility components.
+ mTouchHelper = getMonthViewTouchHelper();
+ ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
+ ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mLockAccessibilityDelegate = true;
+
+ // Sets up any standard paints that will be used
+ initView();
+ }
+
+ public void setDatePickerController(DatePickerController controller) {
+ mController = controller;
+ }
+
+ protected MonthViewTouchHelper getMonthViewTouchHelper() {
+ return new MonthViewTouchHelper(this);
+ }
+
+ @Override
+ public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+ // Workaround for a JB MR1 issue where accessibility delegates on
+ // top-level ListView items are overwritten.
+ if (!mLockAccessibilityDelegate) {
+ super.setAccessibilityDelegate(delegate);
+ }
+ }
+
+ public void setOnDayClickListener(OnDayClickListener listener) {
+ mOnDayClickListener = listener;
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(@NonNull MotionEvent event) {
+ // First right-of-refusal goes the touch exploration helper.
+ if (mTouchHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ final int day = getDayFromLocation(event.getX(), event.getY());
+ if (day >= 0) {
+ onDayClick(day);
+ }
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Sets up the text and style properties for painting. Override this if you
+ * want to use a different paint.
+ */
+ protected void initView() {
+ mMonthTitlePaint = new Paint();
+ mMonthTitlePaint.setFakeBoldText(true);
+ mMonthTitlePaint.setAntiAlias(true);
+ mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
+ mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+ mMonthTitlePaint.setColor(mDayTextColor);
+ mMonthTitlePaint.setTextAlign(Align.CENTER);
+ mMonthTitlePaint.setStyle(Style.FILL);
+
+ mSelectedCirclePaint = new Paint();
+ mSelectedCirclePaint.setFakeBoldText(true);
+ mSelectedCirclePaint.setAntiAlias(true);
+ mSelectedCirclePaint.setColor(mTodayNumberColor);
+ mSelectedCirclePaint.setTextAlign(Align.CENTER);
+ mSelectedCirclePaint.setStyle(Style.FILL);
+ mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+
+ mMonthDayLabelPaint = new Paint();
+ mMonthDayLabelPaint.setAntiAlias(true);
+ mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
+ mMonthDayLabelPaint.setColor(mMonthDayTextColor);
+ mMonthDayLabelPaint.setTypeface(TypefaceHelper.get(getContext(),"Roboto-Medium"));
+ mMonthDayLabelPaint.setStyle(Style.FILL);
+ mMonthDayLabelPaint.setTextAlign(Align.CENTER);
+ mMonthDayLabelPaint.setFakeBoldText(true);
+
+ mMonthNumPaint = new Paint();
+ mMonthNumPaint.setAntiAlias(true);
+ mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
+ mMonthNumPaint.setStyle(Style.FILL);
+ mMonthNumPaint.setTextAlign(Align.CENTER);
+ mMonthNumPaint.setFakeBoldText(false);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawMonthTitle(canvas);
+ drawMonthDayLabels(canvas);
+ drawMonthNums(canvas);
+ }
+
+ private int mDayOfWeekStart = 0;
+
+ /**
+ * Sets all the parameters for displaying this week. The only required
+ * parameter is the week number. Other parameters have a default value and
+ * will only update if a new value is included, except for focus month,
+ * which will always default to no focus month if no value is passed in. See
+ * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
+ *
+ * @param params A map of the new parameters, see
+ * {@link #VIEW_PARAMS_HEIGHT}
+ */
+ public void setMonthParams(HashMap params) {
+ if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+ throw new InvalidParameterException("You must specify month and year for this view");
+ }
+ setTag(params);
+ // We keep the current value for any params not present
+ if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+ mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+ if (mRowHeight < MIN_HEIGHT) {
+ mRowHeight = MIN_HEIGHT;
+ }
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+ mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
+ }
+
+ // Allocate space for caching the day numbers and focus values
+ mMonth = params.get(VIEW_PARAMS_MONTH);
+ mYear = params.get(VIEW_PARAMS_YEAR);
+
+ // Figure out what day today is
+ //final Time today = new Time(Time.getCurrentTimezone());
+ //today.setToNow();
+ final Calendar today = Calendar.getInstance(mController.getTimeZone());
+ mHasToday = false;
+ mToday = -1;
+
+ mCalendar.set(Calendar.MONTH, mMonth);
+ mCalendar.set(Calendar.YEAR, mYear);
+ mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+ mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
+
+ if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+ mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+ } else {
+ mWeekStart = mCalendar.getFirstDayOfWeek();
+ }
+
+ mNumCells = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+ for (int i = 0; i < mNumCells; i++) {
+ final int day = i + 1;
+ if (sameDay(day, today)) {
+ mHasToday = true;
+ mToday = day;
+ }
+ }
+ mNumRows = calculateNumRows();
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ public void setSelectedDay(int day) {
+ mSelectedDay = day;
+ }
+
+ public void reuse() {
+ mNumRows = DEFAULT_NUM_ROWS;
+ requestLayout();
+ }
+
+ private int calculateNumRows() {
+ int offset = findDayOffset();
+ int dividend = (offset + mNumCells) / mNumDays;
+ int remainder = (offset + mNumCells) % mNumDays;
+ return (dividend + (remainder > 0 ? 1 : 0));
+ }
+
+ private boolean sameDay(int day, Calendar today) {
+ return mYear == today.get(Calendar.YEAR) &&
+ mMonth == today.get(Calendar.MONTH) &&
+ day == today.get(Calendar.DAY_OF_MONTH);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
+ + getMonthHeaderSize() + 5);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ public int getMonth() {
+ return mMonth;
+ }
+
+ public int getYear() {
+ return mYear;
+ }
+
+ /**
+ * A wrapper to the MonthHeaderSize to allow override it in children
+ */
+ protected int getMonthHeaderSize() {
+ return MONTH_HEADER_SIZE;
+ }
+
+ @NonNull
+ private String getMonthAndYearString() {
+ Locale locale = Locale.getDefault();
+ String pattern = "MMMM yyyy";
+
+ if(Build.VERSION.SDK_INT < 18) pattern = getContext().getResources().getString(R.string.mdtp_date_v1_monthyear);
+ else pattern = DateFormat.getBestDateTimePattern(locale, pattern);
+
+ SimpleDateFormat formatter = new SimpleDateFormat(pattern, locale);
+ formatter.setTimeZone(mController.getTimeZone());
+ formatter.applyLocalizedPattern(pattern);
+ mStringBuilder.setLength(0);
+ return formatter.format(mCalendar.getTime());
+ }
+
+ protected void drawMonthTitle(Canvas canvas) {
+ int x = (mWidth + 2 * mEdgePadding) / 2;
+ int y = (getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE) / 2;
+ canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+ }
+
+ protected void drawMonthDayLabels(Canvas canvas) {
+ int y = getMonthHeaderSize() - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
+ int dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2);
+
+ for (int i = 0; i < mNumDays; i++) {
+ int x = (2 * i + 1) * dayWidthHalf + mEdgePadding;
+
+ int calendarDay = (i + mWeekStart) % mNumDays;
+ mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
+ String weekString = getWeekDayLabel(mDayLabelCalendar);
+ canvas.drawText(weekString, x, y, mMonthDayLabelPaint);
+ }
+ }
+
+ /**
+ * Draws the week and month day numbers for this week. Override this method
+ * if you need different placement.
+ *
+ * @param canvas The canvas to draw on
+ */
+ protected void drawMonthNums(Canvas canvas) {
+ int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
+ + getMonthHeaderSize();
+ final float dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2.0f);
+ int j = findDayOffset();
+ for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
+ final int x = (int)((2 * j + 1) * dayWidthHalf + mEdgePadding);
+
+ int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
+
+ final int startX = (int)(x - dayWidthHalf);
+ final int stopX = (int)(x + dayWidthHalf);
+ final int startY = (int)(y - yRelativeToDay);
+ final int stopY = (int)(startY + mRowHeight);
+
+ drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
+
+ j++;
+ if (j == mNumDays) {
+ j = 0;
+ y += mRowHeight;
+ }
+ }
+ }
+
+ /**
+ * This method should draw the month day. Implemented by sub-classes to allow customization.
+ *
+ * @param canvas The canvas to draw on
+ * @param year The year of this month day
+ * @param month The month of this month day
+ * @param day The day number of this month day
+ * @param x The default x position to draw the day number
+ * @param y The default y position to draw the day number
+ * @param startX The left boundary of the day number rect
+ * @param stopX The right boundary of the day number rect
+ * @param startY The top boundary of the day number rect
+ * @param stopY The bottom boundary of the day number rect
+ */
+ public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
+ int x, int y, int startX, int stopX, int startY, int stopY);
+
+ protected int findDayOffset() {
+ return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+ - mWeekStart;
+ }
+
+
+ /**
+ * Calculates the day that the given x position is in, accounting for week
+ * number. Returns the day or -1 if the position wasn't in a day.
+ *
+ * @param x The x position of the touch event
+ * @return The day number, or -1 if the position wasn't in a day
+ */
+ public int getDayFromLocation(float x, float y) {
+ final int day = getInternalDayFromLocation(x, y);
+ if (day < 1 || day > mNumCells) {
+ return -1;
+ }
+ return day;
+ }
+
+ /**
+ * Calculates the day that the given x position is in, accounting for week
+ * number.
+ *
+ * @param x The x position of the touch event
+ * @return The day number
+ */
+ protected int getInternalDayFromLocation(float x, float y) {
+ int dayStart = mEdgePadding;
+ if (x < dayStart || x > mWidth - mEdgePadding) {
+ return -1;
+ }
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ int row = (int) (y - getMonthHeaderSize()) / mRowHeight;
+ int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mEdgePadding));
+
+ int day = column - findDayOffset() + 1;
+ day += row * mNumDays;
+ return day;
+ }
+
+ /**
+ * Called when the user clicks on a day. Handles callbacks to the
+ * {@link OnDayClickListener} if one is set.
+ *
+ * If the day is out of the range set by minDate and/or maxDate, this is a no-op.
+ *
+ * @param day The day that was clicked
+ */
+ private void onDayClick(int day) {
+ // If the min / max date are set, only process the click if it's a valid selection.
+ if (mController.isOutOfRange(mYear, mMonth, day)) {
+ return;
+ }
+
+
+ if (mOnDayClickListener != null) {
+ mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
+ }
+
+ // This is a no-op if accessibility is turned off.
+ mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+
+ /**
+ * @param year
+ * @param month
+ * @param day
+ * @return true if the given date should be highlighted
+ */
+ protected boolean isHighlighted(int year, int month, int day) {
+ return mController.isHighlighted(year, month, day);
+ }
+
+ /**
+ * Return a 1 or 2 letter String for use as a weekday label
+ * @param day The day for which to generate a label
+ * @return The weekday label
+ */
+ private String getWeekDayLabel(Calendar day) {
+ Locale locale = Locale.getDefault();
+
+ // Localised short version of the string is not available on API < 18
+ if(Build.VERSION.SDK_INT < 18) {
+ String dayName = new SimpleDateFormat("E", locale).format(day.getTime());
+ String dayLabel = dayName.toUpperCase(locale).substring(0, 1);
+
+ // Chinese labels should be fetched right to left
+ if (locale.equals(Locale.CHINA) || locale.equals(Locale.CHINESE) || locale.equals(Locale.SIMPLIFIED_CHINESE) || locale.equals(Locale.TRADITIONAL_CHINESE)) {
+ int len = dayName.length();
+ dayLabel = dayName.substring(len -1, len);
+ }
+
+ // Most hebrew labels should select the second to last character
+ if (locale.getLanguage().equals("he") || locale.getLanguage().equals("iw")) {
+ if(mDayLabelCalendar.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY) {
+ int len = dayName.length();
+ dayLabel = dayName.substring(len - 2, len - 1);
+ }
+ else {
+ // I know this is duplication, but it makes the code easier to grok by
+ // having all hebrew code in the same block
+ dayLabel = dayName.toUpperCase(locale).substring(0, 1);
+ }
+ }
+
+ // Catalan labels should be two digits in lowercase
+ if (locale.getLanguage().equals("ca"))
+ dayLabel = dayName.toLowerCase().substring(0,2);
+
+ // Correct single character label in Spanish is X
+ if (locale.getLanguage().equals("es") && day.get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY)
+ dayLabel = "X";
+
+ return dayLabel;
+ }
+ // Getting the short label is a one liner on API >= 18
+ return new SimpleDateFormat("EEEEE", locale).format(day.getTime());
+ }
+
+ /**
+ * @return The date that has accessibility focus, or {@code null} if no date
+ * has focus
+ */
+ public CalendarDay getAccessibilityFocus() {
+ final int day = mTouchHelper.getFocusedVirtualView();
+ if (day >= 0) {
+ return new CalendarDay(mYear, mMonth, day);
+ }
+ return null;
+ }
+
+ /**
+ * Clears accessibility focus within the view. No-op if the view does not
+ * contain accessibility focus.
+ */
+ public void clearAccessibilityFocus() {
+ mTouchHelper.clearFocusedVirtualView();
+ }
+
+ /**
+ * Attempts to restore accessibility focus to the specified date.
+ *
+ * @param day The date which should receive focus
+ * @return {@code false} if the date is not valid for this month view, or
+ * {@code true} if the date received focus
+ */
+ public boolean restoreAccessibilityFocus(CalendarDay day) {
+ if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
+ return false;
+ }
+ mTouchHelper.setFocusedVirtualView(day.day);
+ return true;
+ }
+
+ /**
+ * Provides a virtual view hierarchy for interfacing with an accessibility
+ * service.
+ */
+ protected class MonthViewTouchHelper extends ExploreByTouchHelper {
+ private static final String DATE_FORMAT = "dd MMMM yyyy";
+
+ private final Rect mTempRect = new Rect();
+ private final Calendar mTempCalendar = Calendar.getInstance(mController.getTimeZone());
+
+ public MonthViewTouchHelper(View host) {
+ super(host);
+ }
+
+ public void setFocusedVirtualView(int virtualViewId) {
+ getAccessibilityNodeProvider(MonthView.this).performAction(
+ virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ public void clearFocusedVirtualView() {
+ final int focusedVirtualView = getFocusedVirtualView();
+ if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
+ getAccessibilityNodeProvider(MonthView.this).performAction(
+ focusedVirtualView,
+ AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ null);
+ }
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ final int day = getDayFromLocation(x, y);
+ if (day >= 0) {
+ return day;
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List virtualViewIds) {
+ for (int day = 1; day <= mNumCells; day++) {
+ virtualViewIds.add(day);
+ }
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ event.setContentDescription(getItemDescription(virtualViewId));
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId,
+ AccessibilityNodeInfoCompat node) {
+ getItemBounds(virtualViewId, mTempRect);
+
+ node.setContentDescription(getItemDescription(virtualViewId));
+ node.setBoundsInParent(mTempRect);
+ node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+ if (virtualViewId == mSelectedDay) {
+ node.setSelected(true);
+ }
+
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK:
+ onDayClick(virtualViewId);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculates the bounding rectangle of a given time object.
+ *
+ * @param day The day to calculate bounds for
+ * @param rect The rectangle in which to store the bounds
+ */
+ protected void getItemBounds(int day, Rect rect) {
+ final int offsetX = mEdgePadding;
+ final int offsetY = getMonthHeaderSize();
+ final int cellHeight = mRowHeight;
+ final int cellWidth = ((mWidth - (2 * mEdgePadding)) / mNumDays);
+ final int index = ((day - 1) + findDayOffset());
+ final int row = (index / mNumDays);
+ final int column = (index % mNumDays);
+ final int x = (offsetX + (column * cellWidth));
+ final int y = (offsetY + (row * cellHeight));
+
+ rect.set(x, y, (x + cellWidth), (y + cellHeight));
+ }
+
+ /**
+ * Generates a description for a given time object. Since this
+ * description will be spoken, the components are ordered by descending
+ * specificity as DAY MONTH YEAR.
+ *
+ * @param day The day to generate a description for
+ * @return A description of the time object
+ */
+ protected CharSequence getItemDescription(int day) {
+ mTempCalendar.set(mYear, mMonth, day);
+ final CharSequence date = DateFormat.format(DATE_FORMAT,
+ mTempCalendar.getTimeInMillis());
+
+ if (day == mSelectedDay) {
+ return getContext().getString(R.string.mdtp_item_is_selected, date);
+ }
+
+ return date;
+ }
+ }
+
+ /**
+ * Handles callbacks when the user clicks on a time object.
+ */
+ public interface OnDayClickListener {
+ void onDayClick(MonthView view, CalendarDay day);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleDayPickerView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleDayPickerView.java
new file mode 100644
index 0000000..353e54b
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleDayPickerView.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * A DayPickerView customized for {@link SimpleMonthAdapter}
+ */
+public class SimpleDayPickerView extends DayPickerView {
+
+ public SimpleDayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SimpleDayPickerView(Context context, DatePickerController controller) {
+ super(context, controller);
+ }
+
+ @Override
+ public MonthAdapter createMonthAdapter(Context context, DatePickerController controller) {
+ return new SimpleMonthAdapter(context, controller);
+ }
+
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleMonthAdapter.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleMonthAdapter.java
new file mode 100644
index 0000000..ca3f622
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleMonthAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+
+/**
+ * An adapter for a list of {@link SimpleMonthView} items.
+ */
+public class SimpleMonthAdapter extends MonthAdapter {
+
+ public SimpleMonthAdapter(Context context, DatePickerController controller) {
+ super(context, controller);
+ }
+
+ @Override
+ public MonthView createMonthView(Context context) {
+ final MonthView monthView = new SimpleMonthView(context, null, mController);
+ return monthView;
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleMonthView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleMonthView.java
new file mode 100644
index 0000000..a3c2f42
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/SimpleMonthView.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+
+public class SimpleMonthView extends MonthView {
+
+ public SimpleMonthView(Context context, AttributeSet attr, DatePickerController controller) {
+ super(context, attr, controller);
+ }
+
+ @Override
+ public void drawMonthDay(Canvas canvas, int year, int month, int day,
+ int x, int y, int startX, int stopX, int startY, int stopY) {
+ if (mSelectedDay == day) {
+ canvas.drawCircle(x , y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE,
+ mSelectedCirclePaint);
+ }
+
+ if(isHighlighted(year, month, day)) {
+ mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
+ }
+ else {
+ mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
+ }
+
+ // If we have a mindate or maxdate, gray out the day number if it's outside the range.
+ if (mController.isOutOfRange(year, month, day)) {
+ mMonthNumPaint.setColor(mDisabledDayTextColor);
+ }
+ else if (mSelectedDay == day) {
+ mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
+ mMonthNumPaint.setColor(mSelectedDayTextColor);
+ } else if (mHasToday && mToday == day) {
+ mMonthNumPaint.setColor(mTodayNumberColor);
+ } else {
+ mMonthNumPaint.setColor(isHighlighted(year, month, day) ? mHighlightedDayTextColor : mDayTextColor);
+ }
+
+ canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/TextViewWithCircularIndicator.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/TextViewWithCircularIndicator.java
new file mode 100644
index 0000000..5cbc9b0
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/TextViewWithCircularIndicator.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import net.alhazmy13.hijridatepicker.R;
+
+/**
+ * A text view which, when pressed or activated, displays a colored circle around the text.
+ */
+public class TextViewWithCircularIndicator extends TextView {
+
+ private static final int SELECTED_CIRCLE_ALPHA = 255;
+
+ Paint mCirclePaint = new Paint();
+
+ private int mCircleColor;
+ private final String mItemIsSelectedText;
+
+ private boolean mDrawCircle;
+
+ public TextViewWithCircularIndicator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mCircleColor = ContextCompat.getColor(context, R.color.mdtp_accent_color);
+ mItemIsSelectedText = context.getResources().getString(R.string.mdtp_item_is_selected);
+
+ init();
+ }
+
+ private void init() {
+ mCirclePaint.setFakeBoldText(true);
+ mCirclePaint.setAntiAlias(true);
+ mCirclePaint.setColor(mCircleColor);
+ mCirclePaint.setTextAlign(Align.CENTER);
+ mCirclePaint.setStyle(Style.FILL);
+ mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ }
+
+ public void setAccentColor(int color, boolean darkMode) {
+ mCircleColor = color;
+ mCirclePaint.setColor(mCircleColor);
+ setTextColor(createTextColor(color, darkMode));
+ }
+
+ /**
+ * Programmatically set the color state list (see mdtp_date_picker_year_selector)
+ * @param accentColor pressed state text color
+ * @param darkMode current theme mode
+ * @return ColorStateList with pressed state
+ */
+ private ColorStateList createTextColor(int accentColor, boolean darkMode) {
+ int[][] states = new int[][]{
+ new int[]{android.R.attr.state_pressed}, // pressed
+ new int[]{android.R.attr.state_selected}, // selected
+ new int[]{}
+ };
+ int[] colors = new int[]{
+ accentColor,
+ Color.WHITE,
+ darkMode ? Color.WHITE : Color.BLACK
+ };
+ return new ColorStateList(states, colors);
+ }
+
+ public void drawIndicator(boolean drawCircle) {
+ mDrawCircle = drawCircle;
+ }
+
+ @Override
+ public void onDraw(@NonNull Canvas canvas) {
+ if (mDrawCircle) {
+ final int width = getWidth();
+ final int height = getHeight();
+ int radius = Math.min(width, height) / 2;
+ canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
+ }
+ setSelected(mDrawCircle);
+ super.onDraw(canvas);
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ CharSequence itemText = getText();
+ if (mDrawCircle) {
+ return String.format(mItemIsSelectedText, itemText);
+ } else {
+ return itemText;
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/YearPickerView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/YearPickerView.java
new file mode 100644
index 0000000..f3a0b38
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/gregorian/YearPickerView.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.gregorian;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.StateListDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.date.gregorian.GregorianDatePickerDialog.OnDateChangedListener;
+
+/**
+ * Displays a selectable list of years.
+ */
+public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
+ private static final String TAG = "YearPickerView";
+
+ private final DatePickerController mController;
+ private YearAdapter mAdapter;
+ private int mViewSize;
+ private int mChildSize;
+ private TextViewWithCircularIndicator mSelectedView;
+
+ public YearPickerView(Context context, DatePickerController controller) {
+ super(context);
+ mController = controller;
+ mController.registerOnDateChangedListener(this);
+ ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ setLayoutParams(frame);
+ Resources res = context.getResources();
+ mViewSize = res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height);
+ mChildSize = res.getDimensionPixelOffset(R.dimen.mdtp_year_label_height);
+ setVerticalFadingEdgeEnabled(true);
+ setFadingEdgeLength(mChildSize / 3);
+ init();
+ setOnItemClickListener(this);
+ setSelector(new StateListDrawable());
+ setDividerHeight(0);
+ onDateChanged();
+ }
+
+ private void init() {
+ mAdapter = new YearAdapter(mController.getMinYear(), mController.getMaxYear());
+ setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ mController.tryVibrate();
+ TextViewWithCircularIndicator clickedView = (TextViewWithCircularIndicator) view;
+ if (clickedView != null) {
+ if (clickedView != mSelectedView) {
+ if (mSelectedView != null) {
+ mSelectedView.drawIndicator(false);
+ mSelectedView.requestLayout();
+ }
+ clickedView.drawIndicator(true);
+ clickedView.requestLayout();
+ mSelectedView = clickedView;
+ }
+ mController.onYearSelected(getYearFromTextView(clickedView));
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private static int getYearFromTextView(TextView view) {
+ return Integer.valueOf(view.getText().toString());
+ }
+
+ private final class YearAdapter extends BaseAdapter {
+ private final int mMinYear;
+ private final int mMaxYear;
+
+ YearAdapter(int minYear, int maxYear) {
+ if (minYear > maxYear) {
+ throw new IllegalArgumentException("minYear > maxYear");
+ }
+ mMinYear = minYear;
+ mMaxYear = maxYear;
+ }
+
+ @Override
+ public int getCount() {
+ return mMaxYear - mMinYear + 1;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mMinYear + position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextViewWithCircularIndicator v;
+ if (convertView != null) {
+ v = (TextViewWithCircularIndicator) convertView;
+ } else {
+ v = (TextViewWithCircularIndicator) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.mdtp_year_label_text_view, parent, false);
+ v.setAccentColor(mController.getAccentColor(), mController.isThemeDark());
+ }
+ int year = mMinYear + position;
+ boolean selected = mController.getSelectedDay().year == year;
+ v.setText(String.valueOf(year));
+ v.drawIndicator(selected);
+ v.requestLayout();
+ if (selected) {
+ mSelectedView = v;
+ }
+ return v;
+ }
+ }
+
+ public void postSetSelectionCentered(final int position) {
+ postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2);
+ }
+
+ public void postSetSelectionFromTop(final int position, final int offset) {
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSelectionFromTop(position, offset);
+ requestLayout();
+ }
+ });
+ }
+
+ public int getFirstPositionOffset() {
+ final View firstChild = getChildAt(0);
+ if (firstChild == null) {
+ return 0;
+ }
+ return firstChild.getTop();
+ }
+
+ @Override
+ public void onDateChanged() {
+ mAdapter.notifyDataSetChanged();
+ postSetSelectionCentered(mController.getSelectedDay().year - mController.getMinYear());
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ event.setFromIndex(0);
+ event.setToIndex(0);
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/AccessibleDateAnimator.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/AccessibleDateAnimator.java
new file mode 100644
index 0000000..55a9dbc
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/AccessibleDateAnimator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ViewAnimator;
+
+public class AccessibleDateAnimator extends ViewAnimator {
+ private long mDateMillis;
+
+ public AccessibleDateAnimator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setDateMillis(long dateMillis) {
+ mDateMillis = dateMillis;
+ }
+
+ /**
+ * Announce the currently-selected date when launched.
+ */
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ // Clear the event's current text so that only the current date will be spoken.
+ event.getText().clear();
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_WEEKDAY;
+
+ String dateString = "HH";
+ event.getText().add(dateString);
+ return true;
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/DatePickerController.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/DatePickerController.java
new file mode 100644
index 0000000..b9ced00
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/DatePickerController.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
+
+import java.util.TimeZone;
+
+/**
+ * Controller class to communicate among the various components of the date picker dialog.
+ */
+public interface DatePickerController {
+
+ void onYearSelected(int year);
+
+ void onDayOfMonthSelected(int year, int month, int day);
+
+ void registerOnDateChangedListener(HijriDatePickerDialog.OnDateChangedListener listener);
+
+ void unregisterOnDateChangedListener(HijriDatePickerDialog.OnDateChangedListener listener);
+
+ MonthAdapter.CalendarDay getSelectedDay();
+
+ boolean isThemeDark();
+
+ int getAccentColor();
+
+ boolean isHighlighted(int year, int month, int day);
+
+ int getFirstDayOfWeek();
+
+ int getMinYear();
+
+ int getMaxYear();
+
+ UmmalquraCalendar getStartDate();
+
+ UmmalquraCalendar getEndDate();
+
+ boolean isOutOfRange(int year, int month, int day);
+
+ void tryVibrate();
+
+ TimeZone getTimeZone();
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/DayPickerView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/DayPickerView.java
new file mode 100644
index 0000000..f206133
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/DayPickerView.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.ListView;
+import net.alhazmy13.hijridatepicker.date.hijri.HijriDatePickerDialog.OnDateChangedListener;
+
+import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
+import net.alhazmy13.hijridatepicker.Utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * This displays a list of months in a calendar format with selectable days.
+ */
+public abstract class DayPickerView extends ListView implements OnScrollListener,
+ OnDateChangedListener {
+
+ private static final String TAG = "MonthFragment";
+
+ // Affects when the month selection will change while scrolling up
+ protected static final int SCROLL_HYST_WEEKS = 2;
+ // How long the GoTo fling animation should last
+ protected static final int GOTO_SCROLL_DURATION = 250;
+ // How long to wait after receiving an onScrollStateChanged notification
+ // before acting on it
+ protected static final int SCROLL_CHANGE_DELAY = 40;
+ // The number of days to display in each week
+ public static final int DAYS_PER_WEEK = 7;
+ public static int LIST_TOP_OFFSET = -1; // so that the top line will be
+ // under the separator
+ // You can override these numbers to get a different appearance
+ protected int mNumWeeks = 6;
+ protected boolean mShowWeekNumber = false;
+ protected int mDaysPerWeek = 7;
+ private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
+
+ // These affect the scroll speed and feel
+ protected float mFriction = 1.0f;
+
+ protected Context mContext;
+ protected Handler mHandler;
+
+ // highlighted time
+ protected MonthAdapter.CalendarDay mSelectedDay;
+ protected MonthAdapter mAdapter;
+
+ protected MonthAdapter.CalendarDay mTempDay;
+
+ // When the week starts; numbered like Time. (e.g. SUNDAY=0).
+ protected int mFirstDayOfWeek;
+ // The last name announced by accessibility
+ protected CharSequence mPrevMonthName;
+ // which month should be displayed/highlighted [0-11]
+ protected int mCurrentMonthDisplayed;
+ // used for tracking during a scroll
+ protected long mPreviousScrollPosition;
+ // used for tracking what state listview is in
+ protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ // used for tracking what state listview is in
+ protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ private DatePickerController mController;
+ private boolean mPerformingScroll;
+
+ public DayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public DayPickerView(Context context, DatePickerController controller) {
+ super(context);
+ init(context);
+ setController(controller);
+ }
+
+ public void setController(DatePickerController controller) {
+ mController = controller;
+ mController.registerOnDateChangedListener(this);
+ mSelectedDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
+ mTempDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
+ refreshAdapter();
+ onDateChanged();
+ }
+
+ public void init(Context context) {
+ mHandler = new Handler();
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setDrawSelectorOnTop(false);
+
+ mContext = context;
+ setUpListView();
+ }
+
+ public void onChange() {
+ refreshAdapter();
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
+ */
+ protected void refreshAdapter() {
+ if (mAdapter == null) {
+ mAdapter = createMonthAdapter(getContext(), mController);
+ } else {
+ mAdapter.setSelectedDay(mSelectedDay);
+ }
+ // refresh the view with the new parameters
+ setAdapter(mAdapter);
+ }
+
+ public abstract MonthAdapter createMonthAdapter(Context context,
+ DatePickerController controller);
+
+ /*
+ * Sets all the required fields for the list view. Override this method to
+ * set a different list view behavior.
+ */
+ protected void setUpListView() {
+ // Transparent background on scroll
+ setCacheColorHint(0);
+ // No dividers
+ setDivider(null);
+ // Items are clickable
+ setItemsCanFocus(true);
+ // The thumb gets in the way, so disable it
+ setFastScrollEnabled(false);
+ setVerticalScrollBarEnabled(false);
+ setOnScrollListener(this);
+ setFadingEdgeLength(0);
+ // Make the scrolling behavior nicer
+ setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+ }
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param day The day to move to
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location
+ * @param setSelected Whether to set the given time as selected
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible
+ * @return Whether or not the view animated to the new location
+ */
+ public boolean goTo(MonthAdapter.CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {
+
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.set(day);
+ }
+
+ mTempDay.set(day);
+ int minMonth = mController.getStartDate().get(UmmalquraCalendar.MONTH);
+ final int position = (day.year - mController.getMinYear())
+ * MonthAdapter.MONTHS_IN_YEAR + day.month - minMonth;
+
+ View child;
+ int i = 0;
+ int top = 0;
+ // Find a child that's completely in the view
+ do {
+ child = getChildAt(i++);
+ if (child == null) {
+ break;
+ }
+ top = child.getTop();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "child at " + (i - 1) + " has top " + top);
+ }
+ } while (top < 0);
+
+ // Compute the first and last position visible
+ int selectedPosition;
+ if (child != null) {
+ selectedPosition = getPositionForView(child);
+ } else {
+ selectedPosition = 0;
+ }
+
+ if (setSelected) {
+ mAdapter.setSelectedDay(mSelectedDay);
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GoTo position " + position);
+ }
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position != selectedPosition || forceScroll) {
+ setMonthDisplayed(mTempDay);
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ smoothScrollToPositionFromTop(
+ position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
+ return true;
+ } else {
+ postSetSelection(position);
+ }
+ } else if (setSelected) {
+ setMonthDisplayed(mSelectedDay);
+ }
+ return false;
+ }
+
+ public void postSetSelection(final int position) {
+ clearFocus();
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSelection(position);
+ }
+ });
+ onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+
+ /**
+ * Updates the title and selected month if the view has moved to a new
+ * month.
+ */
+ @Override
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ MonthView child = (MonthView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
+
+ // Figure out where we are
+ long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ */
+ protected void setMonthDisplayed(MonthAdapter.CalendarDay date) {
+ mCurrentMonthDisplayed = date.month;
+ invalidateViews();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // use a post to prevent re-entering onScrollStateChanged before it
+ // exits
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
+
+ protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ protected class ScrollStateRunnable implements Runnable {
+ private int mNewState;
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mHandler.removeCallbacks(this);
+ mNewState = scrollState;
+ mHandler.postDelayed(this, SCROLL_CHANGE_DELAY);
+ }
+
+ @Override
+ public void run() {
+ mCurrentScrollState = mNewState;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
+ }
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ mPreviousScrollState = mNewState;
+ int i = 0;
+ View child = getChildAt(i);
+ while (child != null && child.getBottom() <= 0) {
+ child = getChildAt(++i);
+ }
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int firstPosition = getFirstVisiblePosition();
+ int lastPosition = getLastVisiblePosition();
+ boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
+ final int top = child.getTop();
+ final int bottom = child.getBottom();
+ final int midpoint = getHeight() / 2;
+ if (scroll && top < LIST_TOP_OFFSET) {
+ if (bottom > midpoint) {
+ smoothScrollBy(top, GOTO_SCROLL_DURATION);
+ } else {
+ smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
+ }
+ }
+ } else {
+ mPreviousScrollState = mNewState;
+ }
+ }
+ }
+
+ /**
+ * Gets the position of the view that is most prominently displayed within the list view.
+ */
+ public int getMostVisiblePosition() {
+ final int firstPosition = getFirstVisiblePosition();
+ final int height = getHeight();
+
+ int maxDisplayedHeight = 0;
+ int mostVisibleIndex = 0;
+ int i=0;
+ int bottom = 0;
+ while (bottom < height) {
+ View child = getChildAt(i);
+ if (child == null) {
+ break;
+ }
+ bottom = child.getBottom();
+ int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop());
+ if (displayedHeight > maxDisplayedHeight) {
+ mostVisibleIndex = i;
+ maxDisplayedHeight = displayedHeight;
+ }
+ i++;
+ }
+ return firstPosition + mostVisibleIndex;
+ }
+
+ @Override
+ public void onDateChanged() {
+ goTo(mController.getSelectedDay(), false, true, true);
+ }
+
+ /**
+ * Attempts to return the date that has accessibility focus.
+ *
+ * @return The date that has accessibility focus, or {@code null} if no date
+ * has focus.
+ */
+ private MonthAdapter.CalendarDay findAccessibilityFocus() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof MonthView) {
+ final MonthAdapter.CalendarDay focus = ((MonthView) child).getAccessibilityFocus();
+ if (focus != null) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Clear focus to avoid ListView bug in Jelly Bean MR1.
+ ((MonthView) child).clearAccessibilityFocus();
+ }
+ return focus;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to restore accessibility focus to a given date. No-op if
+ * {@code day} is {@code null}.
+ *
+ * @param day The date that should receive accessibility focus
+ * @return {@code true} if focus was restored
+ */
+ private boolean restoreAccessibilityFocus(MonthAdapter.CalendarDay day) {
+ if (day == null) {
+ return false;
+ }
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof MonthView) {
+ if (((MonthView) child).restoreAccessibilityFocus(day)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ final MonthAdapter.CalendarDay focusedDay = findAccessibilityFocus();
+ super.layoutChildren();
+ if (mPerformingScroll) {
+ mPerformingScroll = false;
+ } else {
+ restoreAccessibilityFocus(focusedDay);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setItemCount(-1);
+ }
+
+ private static String getMonthAndYearString(MonthAdapter.CalendarDay day) {
+ UmmalquraCalendar cal = new UmmalquraCalendar();
+ cal.set(day.year, day.month, day.day);
+
+ String sbuf = "";
+ sbuf += cal.getDisplayName(UmmalquraCalendar.MONTH, UmmalquraCalendar.SHORT, Locale.getDefault());
+ sbuf += " ";
+ sbuf += YEAR_FORMAT.format(cal.getTime());
+ return sbuf;
+ }
+
+ /**
+ * Necessary for accessibility, to ensure we support "scrolling" forward and backward
+ * in the month list.
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if(Build.VERSION.SDK_INT >= 21) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ }
+ else {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ /**
+ * When scroll forward/backward events are received, announce the newly scrolled-to month.
+ */
+ @SuppressLint("NewApi")
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
+ action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ return super.performAccessibilityAction(action, arguments);
+ }
+
+ // Figure out what month is showing.
+ int firstVisiblePosition = getFirstVisiblePosition();
+ int minMonth = mController.getStartDate().get(UmmalquraCalendar.MONTH);
+ int month = (firstVisiblePosition + minMonth) % MonthAdapter.MONTHS_IN_YEAR;
+ int year = (firstVisiblePosition + minMonth) / MonthAdapter.MONTHS_IN_YEAR + mController.getMinYear();
+ MonthAdapter.CalendarDay day = new MonthAdapter.CalendarDay(year, month, 1);
+
+ // Scroll either forward or backward one month.
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+ day.month++;
+ if (day.month == 12) {
+ day.month = 0;
+ day.year++;
+ }
+ } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ View firstVisibleView = getChildAt(0);
+ // If the view is fully visible, jump one month back. Otherwise, we'll just jump
+ // to the first day of first visible month.
+ if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
+ // There's an off-by-one somewhere, so the top of the first visible item will
+ // actually be -1 when it's at the exact top.
+ day.month--;
+ if (day.month == -1) {
+ day.month = 11;
+ day.year--;
+ }
+ }
+ }
+
+ // Go to that month.
+ Utils.tryAccessibilityAnnounce(this, getMonthAndYearString(day));
+ goTo(day, true, false, true);
+ mPerformingScroll = true;
+ return true;
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/HijriDatePickerDialog.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/HijriDatePickerDialog.java
new file mode 100644
index 0000000..225bc02
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/HijriDatePickerDialog.java
@@ -0,0 +1,1166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.support.v4.content.ContextCompat;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
+import net.alhazmy13.hijridatepicker.HapticFeedbackController;
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.TypefaceHelper;
+import net.alhazmy13.hijridatepicker.Utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Dialog allowing users to select a date.
+ */
+public class HijriDatePickerDialog extends DialogFragment implements
+ OnClickListener, DatePickerController {
+ private static final String TAG = "GregorianDatePickerDialog";
+ public enum Version {
+ VERSION_1,
+ VERSION_2
+ }
+
+ private static final int UNINITIALIZED = -1;
+ private static final int MONTH_AND_DAY_VIEW = 0;
+ private static final int YEAR_VIEW = 1;
+
+ private static final String KEY_SELECTED_YEAR = "year";
+ private static final String KEY_SELECTED_MONTH = "month";
+ private static final String KEY_SELECTED_DAY = "day";
+ private static final String KEY_LIST_POSITION = "list_position";
+ private static final String KEY_WEEK_START = "week_start";
+ private static final String KEY_YEAR_START = "year_start";
+ private static final String KEY_YEAR_END = "year_end";
+ private static final String KEY_CURRENT_VIEW = "current_view";
+ private static final String KEY_LIST_POSITION_OFFSET = "list_position_offset";
+ private static final String KEY_MIN_DATE = "min_date";
+ private static final String KEY_MAX_DATE = "max_date";
+ private static final String KEY_HIGHLIGHTED_DAYS = "highlighted_days";
+ private static final String KEY_SELECTABLE_DAYS = "selectable_days";
+ private static final String KEY_DISABLED_DAYS = "disabled_days";
+ private static final String KEY_THEME_DARK = "theme_dark";
+ private static final String KEY_THEME_DARK_CHANGED = "theme_dark_changed";
+ private static final String KEY_ACCENT = "accent";
+ private static final String KEY_VIBRATE = "vibrate";
+ private static final String KEY_DISMISS = "dismiss";
+ private static final String KEY_AUTO_DISMISS = "auto_dismiss";
+ private static final String KEY_DEFAULT_VIEW = "default_view";
+ private static final String KEY_TITLE = "title";
+ private static final String KEY_OK_RESID = "ok_resid";
+ private static final String KEY_OK_STRING = "ok_string";
+ private static final String KEY_OK_COLOR = "ok_color";
+ private static final String KEY_CANCEL_RESID = "cancel_resid";
+ private static final String KEY_CANCEL_STRING = "cancel_string";
+ private static final String KEY_CANCEL_COLOR = "cancel_color";
+ private static final String KEY_VERSION = "version";
+ private static final String KEY_TIMEZONE = "timezone";
+
+
+ private static final int DEFAULT_START_YEAR = 1400;
+ private static final int DEFAULT_END_YEAR = 1450;
+
+ private static final int ANIMATION_DURATION = 300;
+ private static final int ANIMATION_DELAY = 500;
+
+ private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("y", getLocal());
+ private static SimpleDateFormat MONTH_FORMAT = new SimpleDateFormat("MMMM", getLocal());
+ private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", getLocal());
+ private static SimpleDateFormat VERSION_2_FORMAT;
+
+ private final UmmalquraCalendar mCalendar = trimToMidnight(new UmmalquraCalendar(getTimeZone(), getLocal()));
+ private OnDateSetListener mCallBack;
+ private HashSet mListeners = new HashSet<>();
+ private DialogInterface.OnCancelListener mOnCancelListener;
+ private DialogInterface.OnDismissListener mOnDismissListener;
+
+ private AccessibleDateAnimator mAnimator;
+
+ private TextView mDatePickerHeaderView;
+ private LinearLayout mMonthAndDayView;
+ private TextView mSelectedMonthTextView;
+ private TextView mSelectedDayTextView;
+ private TextView mYearView;
+ private DayPickerView mDayPickerView;
+ private YearPickerView mYearPickerView;
+
+ private int mCurrentView = UNINITIALIZED;
+
+ private int mWeekStart = mCalendar.getFirstDayOfWeek();
+ private int mMinYear = DEFAULT_START_YEAR;
+ private int mMaxYear = DEFAULT_END_YEAR;
+ private String mTitle;
+ private UmmalquraCalendar mMinDate;
+ private UmmalquraCalendar mMaxDate;
+ private HashSet highlightedDays = new HashSet<>();
+ private TreeSet selectableDays = new TreeSet<>();
+ private HashSet disabledDays = new HashSet<>();
+ private boolean mThemeDark = false;
+ private boolean mThemeDarkChanged = false;
+ private int mAccentColor = -1;
+ private boolean mVibrate = true;
+ private boolean mDismissOnPause = false;
+ private boolean mAutoDismiss = false;
+ private int mDefaultView = MONTH_AND_DAY_VIEW;
+ private int mOkResid = R.string.mdtp_ok;
+ private String mOkString;
+ private int mOkColor = -1;
+ private int mCancelResid = R.string.mdtp_cancel;
+ private String mCancelString;
+ private int mCancelColor = -1;
+ private Version mVersion;
+ private TimeZone mTimezone;
+
+ private HapticFeedbackController mHapticFeedbackController;
+
+ private boolean mDelayAnimation = true;
+
+ // Accessibility strings.
+ private String mDayPickerDescription;
+ private String mSelectDay;
+ private String mYearPickerDescription;
+ private String mSelectYear;
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnDateSetListener {
+
+ /**
+ * @param view The view associated with this listener.
+ * @param year The year that was set.
+ * @param monthOfYear The month that was set (0-11) for compatibility
+ * with {@link UmmalquraCalendar}.
+ * @param dayOfMonth The day of the month that was set.
+ */
+ void onDateSet(HijriDatePickerDialog view, int year, int monthOfYear, int dayOfMonth);
+ }
+
+ /**
+ * The callback used to notify other date picker components of a change in selected date.
+ */
+ interface OnDateChangedListener {
+
+ void onDateChanged();
+ }
+
+
+ public HijriDatePickerDialog() {
+ // Empty constructor required for dialog fragment.
+ }
+
+ /**
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param monthOfYear The initial month of the dialog.
+ * @param dayOfMonth The initial day of the dialog.
+ */
+ public static HijriDatePickerDialog newInstance(OnDateSetListener callBack, int year,
+ int monthOfYear,
+ int dayOfMonth) {
+ HijriDatePickerDialog ret = new HijriDatePickerDialog();
+ ret.initialize(callBack, year, monthOfYear, dayOfMonth);
+ return ret;
+ }
+
+ public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
+ mCallBack = callBack;
+ mCalendar.set(Calendar.YEAR, year);
+ mCalendar.set(Calendar.MONTH, monthOfYear);
+ mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+
+ mVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? Version.VERSION_1 : Version.VERSION_2;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Activity activity = getActivity();
+ activity.getWindow().setSoftInputMode(
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ mCurrentView = UNINITIALIZED;
+ if (savedInstanceState != null) {
+ mCalendar.set(UmmalquraCalendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
+ mCalendar.set(UmmalquraCalendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
+ mCalendar.set(UmmalquraCalendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
+ mDefaultView = savedInstanceState.getInt(KEY_DEFAULT_VIEW);
+ }
+ if (Build.VERSION.SDK_INT < 18) {
+ VERSION_2_FORMAT = new SimpleDateFormat(activity.getResources().getString(R.string.mdtp_date_v2_daymonthyear), Locale.getDefault());
+ } else {
+ VERSION_2_FORMAT = new SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "EEEMMMdd"), Locale.getDefault());
+ }
+ VERSION_2_FORMAT.setTimeZone(getTimeZone());
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(UmmalquraCalendar.YEAR));
+ outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(UmmalquraCalendar.MONTH));
+ outState.putInt(KEY_SELECTED_DAY, mCalendar.get(UmmalquraCalendar.DAY_OF_MONTH));
+ outState.putInt(KEY_WEEK_START, mWeekStart);
+ outState.putInt(KEY_YEAR_START, mMinYear);
+ outState.putInt(KEY_YEAR_END, mMaxYear);
+ outState.putInt(KEY_CURRENT_VIEW, mCurrentView);
+ int listPosition = -1;
+ if (mCurrentView == MONTH_AND_DAY_VIEW) {
+ listPosition = mDayPickerView.getMostVisiblePosition();
+ } else if (mCurrentView == YEAR_VIEW) {
+ listPosition = mYearPickerView.getFirstVisiblePosition();
+ outState.putInt(KEY_LIST_POSITION_OFFSET, mYearPickerView.getFirstPositionOffset());
+ }
+ outState.putInt(KEY_LIST_POSITION, listPosition);
+ outState.putSerializable(KEY_MIN_DATE, mMinDate);
+ outState.putSerializable(KEY_MAX_DATE, mMaxDate);
+ outState.putSerializable(KEY_HIGHLIGHTED_DAYS, highlightedDays);
+ outState.putSerializable(KEY_SELECTABLE_DAYS, selectableDays);
+ outState.putSerializable(KEY_DISABLED_DAYS, disabledDays);
+ outState.putBoolean(KEY_THEME_DARK, mThemeDark);
+ outState.putBoolean(KEY_THEME_DARK_CHANGED, mThemeDarkChanged);
+ outState.putInt(KEY_ACCENT, mAccentColor);
+ outState.putBoolean(KEY_VIBRATE, mVibrate);
+ outState.putBoolean(KEY_DISMISS, mDismissOnPause);
+ outState.putBoolean(KEY_AUTO_DISMISS, mAutoDismiss);
+ outState.putInt(KEY_DEFAULT_VIEW, mDefaultView);
+ outState.putString(KEY_TITLE, mTitle);
+ outState.putInt(KEY_OK_RESID, mOkResid);
+ outState.putString(KEY_OK_STRING, mOkString);
+ outState.putInt(KEY_OK_COLOR, mOkColor);
+ outState.putInt(KEY_CANCEL_RESID, mCancelResid);
+ outState.putString(KEY_CANCEL_STRING, mCancelString);
+ outState.putInt(KEY_CANCEL_COLOR, mCancelColor);
+ outState.putSerializable(KEY_VERSION, mVersion);
+ outState.putSerializable(KEY_TIMEZONE, mTimezone);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ int listPosition = -1;
+ int listPositionOffset = 0;
+ int currentView = mDefaultView;
+ if (savedInstanceState != null) {
+ mWeekStart = savedInstanceState.getInt(KEY_WEEK_START);
+ mMinYear = savedInstanceState.getInt(KEY_YEAR_START);
+ mMaxYear = savedInstanceState.getInt(KEY_YEAR_END);
+ currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW);
+ listPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
+ listPositionOffset = savedInstanceState.getInt(KEY_LIST_POSITION_OFFSET);
+ mMinDate = (UmmalquraCalendar) savedInstanceState.getSerializable(KEY_MIN_DATE);
+ mMaxDate = (UmmalquraCalendar) savedInstanceState.getSerializable(KEY_MAX_DATE);
+ highlightedDays = (HashSet) savedInstanceState.getSerializable(KEY_HIGHLIGHTED_DAYS);
+ selectableDays = (TreeSet) savedInstanceState.getSerializable(KEY_SELECTABLE_DAYS);
+ disabledDays = (HashSet) savedInstanceState.getSerializable(KEY_DISABLED_DAYS);
+ mThemeDark = savedInstanceState.getBoolean(KEY_THEME_DARK);
+ mThemeDarkChanged = savedInstanceState.getBoolean(KEY_THEME_DARK_CHANGED);
+ mAccentColor = savedInstanceState.getInt(KEY_ACCENT);
+ mVibrate = savedInstanceState.getBoolean(KEY_VIBRATE);
+ mDismissOnPause = savedInstanceState.getBoolean(KEY_DISMISS);
+ mAutoDismiss = savedInstanceState.getBoolean(KEY_AUTO_DISMISS);
+ mTitle = savedInstanceState.getString(KEY_TITLE);
+ mOkResid = savedInstanceState.getInt(KEY_OK_RESID);
+ mOkString = savedInstanceState.getString(KEY_OK_STRING);
+ mOkColor = savedInstanceState.getInt(KEY_OK_COLOR);
+ mCancelResid = savedInstanceState.getInt(KEY_CANCEL_RESID);
+ mCancelString = savedInstanceState.getString(KEY_CANCEL_STRING);
+ mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR);
+ mVersion = (Version) savedInstanceState.getSerializable(KEY_VERSION);
+ mTimezone = (TimeZone) savedInstanceState.getSerializable(KEY_TIMEZONE);
+ }
+
+ int viewRes = mVersion == Version.VERSION_1 ? R.layout.mdtp_hijri_date_picker_dialog : R.layout.mdtp_hijri_date_picker_dialog_v2;
+ View view = inflater.inflate(viewRes, container, false);
+ // All options have been set at this point: round the initial selection if necessary
+ setToNearestDate(mCalendar);
+
+ mDatePickerHeaderView = (TextView) view.findViewById(R.id.mdtp_hijri_date_picker_header);
+ mMonthAndDayView = (LinearLayout) view.findViewById(R.id.mdtp_hijri_date_picker_month_and_day);
+ mMonthAndDayView.setOnClickListener(this);
+ mSelectedMonthTextView = (TextView) view.findViewById(R.id.mdtp_hijri_date_picker_month);
+ mSelectedDayTextView = (TextView) view.findViewById(R.id.mdtp_hijri_date_picker_day);
+ mYearView = (TextView) view.findViewById(R.id.mdtp_hijri_date_picker_year);
+ mYearView.setOnClickListener(this);
+
+ final Activity activity = getActivity();
+ mDayPickerView = new SimpleDayPickerView(activity, this);
+ mYearPickerView = new YearPickerView(activity, this);
+
+ // if theme mode has not been set by java code, check if it is specified in Style.xml
+ if (!mThemeDarkChanged) {
+ mThemeDark = Utils.isDarkTheme(activity, mThemeDark);
+ }
+
+ Resources res = getResources();
+ mDayPickerDescription = res.getString(R.string.mdtp_day_picker_description);
+ mSelectDay = res.getString(R.string.mdtp_select_day);
+ mYearPickerDescription = res.getString(R.string.mdtp_year_picker_description);
+ mSelectYear = res.getString(R.string.mdtp_select_year);
+
+ int bgColorResource = mThemeDark ? R.color.mdtp_date_picker_view_animator_dark_theme : R.color.mdtp_date_picker_view_animator;
+ view.setBackgroundColor(ContextCompat.getColor(activity, bgColorResource));
+
+ mAnimator = (AccessibleDateAnimator) view.findViewById(R.id.mdtp_hijri_animator);
+ mAnimator.addView(mDayPickerView);
+ mAnimator.addView(mYearPickerView);
+ mAnimator.setDateMillis(mCalendar.getTimeInMillis());
+ // TODO: Replace with animation decided upon by the design team.
+ Animation animation = new AlphaAnimation(0.0f, 1.0f);
+ animation.setDuration(ANIMATION_DURATION);
+ mAnimator.setInAnimation(animation);
+ // TODO: Replace with animation decided upon by the design team.
+ Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
+ animation2.setDuration(ANIMATION_DURATION);
+ mAnimator.setOutAnimation(animation2);
+
+ Button okButton = (Button) view.findViewById(R.id.mdtp_ok);
+ okButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ notifyOnDateListener();
+ dismiss();
+ }
+ });
+ okButton.setTypeface(TypefaceHelper.get(activity, "Roboto-Medium"));
+ if (mOkString != null) okButton.setText(mOkString);
+ else okButton.setText(mOkResid);
+
+ Button cancelButton = (Button) view.findViewById(R.id.mdtp_cancel);
+ cancelButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if (getDialog() != null) getDialog().cancel();
+ }
+ });
+ cancelButton.setTypeface(TypefaceHelper.get(activity, "Roboto-Medium"));
+ if (mCancelString != null) cancelButton.setText(mCancelString);
+ else cancelButton.setText(mCancelResid);
+ cancelButton.setVisibility(isCancelable() ? View.VISIBLE : View.GONE);
+
+ // If an accent color has not been set manually, get it from the context
+ if (mAccentColor == -1) {
+ mAccentColor = Utils.getAccentColorFromThemeIfAvailable(getActivity());
+ }
+ if (mDatePickerHeaderView != null)
+ mDatePickerHeaderView.setBackgroundColor(Utils.darkenColor(mAccentColor));
+ view.findViewById(R.id.mdtp_hijri_day_picker_selected_date_layout).setBackgroundColor(mAccentColor);
+
+ // Buttons can have a different color
+ if (mOkColor != -1) okButton.setTextColor(mOkColor);
+ else okButton.setTextColor(mAccentColor);
+ if (mCancelColor != -1) cancelButton.setTextColor(mCancelColor);
+ else cancelButton.setTextColor(mAccentColor);
+
+ if (getDialog() == null) {
+ view.findViewById(R.id.mdtp_done_background).setVisibility(View.GONE);
+ }
+
+ updateDisplay(false);
+ setCurrentView(currentView);
+
+ if (listPosition != -1) {
+ if (currentView == MONTH_AND_DAY_VIEW) {
+ mDayPickerView.postSetSelection(listPosition);
+ } else if (currentView == YEAR_VIEW) {
+ mYearPickerView.postSetSelectionFromTop(listPosition, listPositionOffset);
+ }
+ }
+
+ mHapticFeedbackController = new HapticFeedbackController(activity);
+ return view;
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ ViewGroup viewGroup = (ViewGroup) getView();
+ if (viewGroup != null) {
+ viewGroup.removeAllViewsInLayout();
+ View view = onCreateView(getActivity().getLayoutInflater(), viewGroup, null);
+ viewGroup.addView(view);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mHapticFeedbackController.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHapticFeedbackController.stop();
+ if (mDismissOnPause) dismiss();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ if (mOnCancelListener != null) mOnCancelListener.onCancel(dialog);
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ if (mOnDismissListener != null) mOnDismissListener.onDismiss(dialog);
+ }
+
+ private void setCurrentView(final int viewIndex) {
+ long millis = mCalendar.getTimeInMillis();
+
+ switch (viewIndex) {
+ case MONTH_AND_DAY_VIEW:
+ if (mVersion == Version.VERSION_1) {
+ ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f,
+ 1.05f);
+ if (mDelayAnimation) {
+ pulseAnimator.setStartDelay(ANIMATION_DELAY);
+ mDelayAnimation = false;
+ }
+ mDayPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(true);
+ mYearView.setSelected(false);
+ mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
+ mCurrentView = viewIndex;
+ }
+ pulseAnimator.start();
+ } else {
+ mDayPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(true);
+ mYearView.setSelected(false);
+ mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
+ mCurrentView = viewIndex;
+ }
+ }
+
+ int flags = DateUtils.FORMAT_SHOW_DATE;
+ String dayString = DateUtils.formatDateTime(getActivity(), millis, flags);
+ mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
+ Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay);
+ break;
+ case YEAR_VIEW:
+ if (mVersion == Version.VERSION_1) {
+ ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f);
+ if (mDelayAnimation) {
+ pulseAnimator.setStartDelay(ANIMATION_DELAY);
+ mDelayAnimation = false;
+ }
+ mYearPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(false);
+ mYearView.setSelected(true);
+ mAnimator.setDisplayedChild(YEAR_VIEW);
+ mCurrentView = viewIndex;
+ }
+ pulseAnimator.start();
+ } else {
+ mYearPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayView.setSelected(false);
+ mYearView.setSelected(true);
+ mAnimator.setDisplayedChild(YEAR_VIEW);
+ mCurrentView = viewIndex;
+ }
+ }
+
+ CharSequence yearString = YEAR_FORMAT.format(millis);
+ mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
+ Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear);
+ break;
+ }
+ }
+
+ private void updateDisplay(boolean announce) {
+ mYearView.setText(String.valueOf(mCalendar.get(Calendar.YEAR)));
+ if (mVersion == Version.VERSION_1) {
+ if (mDatePickerHeaderView != null) {
+ if (mTitle != null)
+ mDatePickerHeaderView.setText(mTitle.toUpperCase(Locale.getDefault()));
+ else {
+ mDatePickerHeaderView.setText(mCalendar.getDisplayName(UmmalquraCalendar.DAY_OF_WEEK, UmmalquraCalendar.LONG,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()));
+ }
+ }
+ mSelectedMonthTextView.setText(String.valueOf(mCalendar.getDisplayName(Calendar.MONTH,Calendar.SHORT,getLocal())));
+ mSelectedDayTextView.setText(String.valueOf(mCalendar.get(Calendar.DAY_OF_MONTH)));
+ }
+
+ if (mVersion == Version.VERSION_2) {
+ mSelectedDayTextView.setText(VERSION_2_FORMAT.format(mCalendar.getTime()));
+ if (mTitle != null)
+ mDatePickerHeaderView.setText(mTitle.toUpperCase(Locale.getDefault()));
+ else
+ mDatePickerHeaderView.setVisibility(View.GONE);
+ }
+
+ // Accessibility.
+ long millis = mCalendar.getTimeInMillis();
+ mAnimator.setDateMillis(millis);
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
+ String monthAndDayText = DateUtils.formatDateTime(getActivity(), millis, flags);
+ mMonthAndDayView.setContentDescription(monthAndDayText);
+
+ if (announce) {
+ flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
+ String fullDateText = DateUtils.formatDateTime(getActivity(), millis, flags);
+ Utils.tryAccessibilityAnnounce(mAnimator, fullDateText);
+ }
+ }
+
+ /**
+ * Set whether the device should vibrate when touching fields
+ *
+ * @param vibrate true if the device should vibrate when touching a field
+ */
+ public void vibrate(boolean vibrate) {
+ mVibrate = vibrate;
+ }
+
+ /**
+ * Set whether the picker should dismiss itself when being paused or whether it should try to survive an orientation change
+ *
+ * @param dismissOnPause true if the dialog should dismiss itself when it's pausing
+ */
+ public void dismissOnPause(boolean dismissOnPause) {
+ mDismissOnPause = dismissOnPause;
+ }
+
+ /**
+ * Set whether the picker should dismiss itself when a day is selected
+ *
+ * @param autoDismiss true if the dialog should dismiss itself when a day is selected
+ */
+ @SuppressWarnings("unused")
+ public void autoDismiss(boolean autoDismiss) {
+ mAutoDismiss = autoDismiss;
+ }
+
+ /**
+ * Set whether the dark theme should be used
+ *
+ * @param themeDark true if the dark theme should be used, false if the default theme should be used
+ */
+ public void setThemeDark(boolean themeDark) {
+ mThemeDark = themeDark;
+ mThemeDarkChanged = true;
+ }
+
+ /**
+ * Returns true when the dark theme should be used
+ *
+ * @return true if the dark theme should be used, false if the default theme should be used
+ */
+ @Override
+ public boolean isThemeDark() {
+ return mThemeDark;
+ }
+
+ /**
+ * Set the accent color of this dialog
+ *
+ * @param color the accent color you want
+ */
+ @SuppressWarnings("unused")
+ public void setAccentColor(String color) {
+ mAccentColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the accent color of this dialog
+ *
+ * @param color the accent color you want
+ */
+ public void setAccentColor(@ColorInt int color) {
+ mAccentColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Set the text color of the OK button
+ *
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setOkColor(String color) {
+ mOkColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the text color of the OK button
+ *
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setOkColor(@ColorInt int color) {
+ mOkColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Set the text color of the Cancel button
+ *
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setCancelColor(String color) {
+ mCancelColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the text color of the Cancel button
+ *
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setCancelColor(@ColorInt int color) {
+ mCancelColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Get the accent color of this dialog
+ *
+ * @return accent color
+ */
+ @Override
+ public int getAccentColor() {
+ return mAccentColor;
+ }
+
+ /**
+ * Set whether the year picker of the month and day picker is shown first
+ *
+ * @param yearPicker boolean
+ */
+ public void showYearPickerFirst(boolean yearPicker) {
+ mDefaultView = yearPicker ? YEAR_VIEW : MONTH_AND_DAY_VIEW;
+ }
+
+ @SuppressWarnings("unused")
+ public void setFirstDayOfWeek(int startOfWeek) {
+ if (startOfWeek < UmmalquraCalendar.SUNDAY || startOfWeek > UmmalquraCalendar.SATURDAY) {
+ throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
+ "Calendar.SATURDAY");
+ }
+ mWeekStart = startOfWeek;
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void setYearRange(int startYear, int endYear) {
+ if (endYear < startYear) {
+ throw new IllegalArgumentException("Year end must be larger than or equal to year start");
+ }
+
+ mMinYear = startYear;
+ mMaxYear = endYear;
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ /**
+ * Sets the minimal date supported by this DatePicker. Dates before (but not including) the
+ * specified date will be disallowed from being selected.
+ *
+ * @param calendar a Calendar object set to the year, month, day desired as the mindate.
+ */
+ @SuppressWarnings("unused")
+ public void setMinDate(UmmalquraCalendar calendar) {
+ mMinDate = trimToMidnight((UmmalquraCalendar) calendar.clone());
+
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ /**
+ * @return The minimal date supported by this DatePicker. Null if it has not been set.
+ */
+ @SuppressWarnings("unused")
+ public UmmalquraCalendar getMinDate() {
+ return mMinDate;
+ }
+
+ /**
+ * Sets the minimal date supported by this DatePicker. Dates after (but not including) the
+ * specified date will be disallowed from being selected.
+ *
+ * @param calendar a Calendar object set to the year, month, day desired as the maxdate.
+ */
+ @SuppressWarnings("unused")
+ public void setMaxDate(UmmalquraCalendar calendar) {
+ mMaxDate = trimToMidnight((UmmalquraCalendar) calendar.clone());
+
+ if (mDayPickerView != null) {
+ mDayPickerView.onChange();
+ }
+ }
+
+ /**
+ * @return The maximal date supported by this DatePicker. Null if it has not been set.
+ */
+ @SuppressWarnings("unused")
+ public UmmalquraCalendar getMaxDate() {
+ return mMaxDate;
+ }
+
+ /**
+ * Sets an array of dates which should be highlighted when the picker is drawn
+ *
+ * @param highlightedDays an Array of Calendar objects containing the dates to be highlighted
+ */
+ @SuppressWarnings("unused")
+ public void setHighlightedDays(UmmalquraCalendar[] highlightedDays) {
+ for (UmmalquraCalendar highlightedDay : highlightedDays) trimToMidnight(highlightedDay);
+ this.highlightedDays.addAll(Arrays.asList(highlightedDays));
+ if (mDayPickerView != null) mDayPickerView.onChange();
+ }
+
+ /**
+ * @return The list of dates, as Calendar Objects, which should be highlighted. null is no dates should be highlighted
+ */
+ @SuppressWarnings("unused")
+ public UmmalquraCalendar[] getHighlightedDays() {
+ if (highlightedDays.isEmpty()) return null;
+ UmmalquraCalendar[] output = highlightedDays.toArray(new UmmalquraCalendar[0]);
+ Arrays.sort(output);
+ return output;
+ }
+
+ @Override
+ public boolean isHighlighted(int year, int month, int day) {
+ UmmalquraCalendar date = new UmmalquraCalendar();
+ date.set(UmmalquraCalendar.YEAR, year);
+ date.set(UmmalquraCalendar.MONTH, month);
+ date.set(UmmalquraCalendar.DAY_OF_MONTH, day);
+ trimToMidnight(date);
+ return highlightedDays.contains(date);
+ }
+
+ /**
+ * Sets a list of days which are the only valid selections.
+ * Setting this value will take precedence over using setMinDate() and setMaxDate()
+ *
+ * @param selectableDays an Array of Calendar Objects containing the selectable dates
+ */
+ @SuppressWarnings("unused")
+ public void setSelectableDays(UmmalquraCalendar[] selectableDays) {
+ for (UmmalquraCalendar selectableDay : selectableDays) trimToMidnight(selectableDay);
+ this.selectableDays.addAll(Arrays.asList(selectableDays));
+ if (mDayPickerView != null) mDayPickerView.onChange();
+ }
+
+ /**
+ * @return an Array of Calendar objects containing the list with selectable items. null if no restriction is set
+ */
+ @SuppressWarnings("unused")
+ public UmmalquraCalendar[] getSelectableDays() {
+ return selectableDays.isEmpty() ? null : selectableDays.toArray(new UmmalquraCalendar[0]);
+ }
+
+ /**
+ * Sets a list of days that are not selectable in the picker
+ * Setting this value will take precedence over using setMinDate() and setMaxDate(), but stacks with setSelectableDays()
+ *
+ * @param disabledDays an Array of Calendar Objects containing the disabled dates
+ */
+ @SuppressWarnings("unused")
+ public void setDisabledDays(UmmalquraCalendar[] disabledDays) {
+ for (UmmalquraCalendar disabledDay : disabledDays) trimToMidnight(disabledDay);
+ this.disabledDays.addAll(Arrays.asList(disabledDays));
+ if (mDayPickerView != null) mDayPickerView.onChange();
+ }
+
+ /**
+ * @return an Array of Calendar objects containing the list of days that are not selectable. null if no restriction is set
+ */
+ @SuppressWarnings("unused")
+ public UmmalquraCalendar[] getDisabledDays() {
+ if (disabledDays.isEmpty()) return null;
+ UmmalquraCalendar[] output = disabledDays.toArray(new UmmalquraCalendar[0]);
+ Arrays.sort(output);
+ return output;
+ }
+
+ /**
+ * Set a title to be displayed instead of the weekday
+ *
+ * @param title String - The title to be displayed
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ /**
+ * Set the label for the Ok button (max 12 characters)
+ *
+ * @param okString A literal String to be used as the Ok button label
+ */
+ @SuppressWarnings("unused")
+ public void setOkText(String okString) {
+ mOkString = okString;
+ }
+
+ /**
+ * Set the label for the Ok button (max 12 characters)
+ *
+ * @param okResid A resource ID to be used as the Ok button label
+ */
+ @SuppressWarnings("unused")
+ public void setOkText(@StringRes int okResid) {
+ mOkString = null;
+ mOkResid = okResid;
+ }
+
+ /**
+ * Set the label for the Cancel button (max 12 characters)
+ *
+ * @param cancelString A literal String to be used as the Cancel button label
+ */
+ @SuppressWarnings("unused")
+ public void setCancelText(String cancelString) {
+ mCancelString = cancelString;
+ }
+
+ /**
+ * Set the label for the Cancel button (max 12 characters)
+ *
+ * @param cancelResid A resource ID to be used as the Cancel button label
+ */
+ @SuppressWarnings("unused")
+ public void setCancelText(@StringRes int cancelResid) {
+ mCancelString = null;
+ mCancelResid = cancelResid;
+ }
+
+ /**
+ * Set which layout version the picker should use
+ *
+ * @param version The version to use
+ */
+ public void setVersion(Version version) {
+ mVersion = version;
+ }
+
+ /**
+ * Set which timezone the picker should use
+ *
+ * @param timeZone The timezone to use
+ */
+ @SuppressWarnings("unused")
+ public void setTimeZone(TimeZone timeZone) {
+ mTimezone = timeZone;
+ mCalendar.setTimeZone(timeZone);
+ YEAR_FORMAT.setTimeZone(timeZone);
+ MONTH_FORMAT.setTimeZone(timeZone);
+ DAY_FORMAT.setTimeZone(timeZone);
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnDateSetListener(OnDateSetListener listener) {
+ mCallBack = listener;
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
+ mOnCancelListener = onCancelListener;
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) {
+ mOnDismissListener = onDismissListener;
+ }
+
+ // If the newly selected month / year does not contain the currently selected day number,
+ // change the selected day number to the last day of the selected month or year.
+ // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
+ // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
+ private void adjustDayInMonthIfNeeded(UmmalquraCalendar calendar) {
+ int day = calendar.get(UmmalquraCalendar.DAY_OF_MONTH);
+ int daysInMonth = calendar.getActualMaximum(UmmalquraCalendar.DAY_OF_MONTH);
+ if (day > daysInMonth) {
+ calendar.set(UmmalquraCalendar.DAY_OF_MONTH, daysInMonth);
+ }
+ setToNearestDate(calendar);
+ }
+
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if (v.getId() == R.id.mdtp_date_picker_year) {
+ setCurrentView(YEAR_VIEW);
+ } else if (v.getId() == R.id.mdtp_date_picker_month_and_day) {
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ }
+ }
+
+ @Override
+ public void onYearSelected(int year) {
+ mCalendar.set(UmmalquraCalendar.YEAR, year);
+ adjustDayInMonthIfNeeded(mCalendar);
+ updatePickers();
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ updateDisplay(true);
+ }
+
+ @Override
+ public void onDayOfMonthSelected(int year, int month, int day) {
+ mCalendar.set(UmmalquraCalendar.YEAR, year);
+ mCalendar.set(UmmalquraCalendar.MONTH, month);
+ mCalendar.set(UmmalquraCalendar.DAY_OF_MONTH, day);
+ updatePickers();
+ updateDisplay(true);
+ if (mAutoDismiss) {
+ notifyOnDateListener();
+ dismiss();
+ }
+ }
+
+ private void updatePickers() {
+ for (OnDateChangedListener listener : mListeners) listener.onDateChanged();
+ }
+
+
+ @Override
+ public MonthAdapter.CalendarDay getSelectedDay() {
+ return new MonthAdapter.CalendarDay(mCalendar, getTimeZone());
+ }
+
+ @Override
+ public UmmalquraCalendar getStartDate() {
+ if (!selectableDays.isEmpty()) return selectableDays.first();
+ if (mMinDate != null) return mMinDate;
+ UmmalquraCalendar output = new UmmalquraCalendar(getTimeZone(), getLocal());
+ output.set(UmmalquraCalendar.YEAR, mMinYear);
+ output.set(UmmalquraCalendar.DAY_OF_MONTH, 1);
+ output.set(UmmalquraCalendar.MONTH, UmmalquraCalendar.JANUARY);
+ return output;
+ }
+
+ @Override
+ public UmmalquraCalendar getEndDate() {
+ if (!selectableDays.isEmpty()) return selectableDays.last();
+ if (mMaxDate != null) return mMaxDate;
+ UmmalquraCalendar output = new UmmalquraCalendar(getTimeZone(), getLocal());
+ output.set(UmmalquraCalendar.YEAR, mMaxYear);
+ output.set(UmmalquraCalendar.DAY_OF_MONTH, 31);
+ output.set(UmmalquraCalendar.MONTH, UmmalquraCalendar.DECEMBER);
+ return output;
+ }
+
+ @Override
+ public int getMinYear() {
+ if (!selectableDays.isEmpty()) return selectableDays.first().get(UmmalquraCalendar.YEAR);
+ // Ensure no years can be selected outside of the given minimum date
+ return mMinDate != null && mMinDate.get(UmmalquraCalendar.YEAR) > mMinYear ? mMinDate.get(UmmalquraCalendar.YEAR) : mMinYear;
+ }
+
+ @Override
+ public int getMaxYear() {
+ if (!selectableDays.isEmpty()) return selectableDays.last().get(UmmalquraCalendar.YEAR);
+ // Ensure no years can be selected outside of the given maximum date
+ return mMaxDate != null && mMaxDate.get(UmmalquraCalendar.YEAR) < mMaxYear ? mMaxDate.get(UmmalquraCalendar.YEAR) : mMaxYear;
+ }
+
+ /**
+ * @return true if the specified year/month/day are within the selectable days or the range set by minDate and maxDate.
+ * If one or either have not been set, they are considered as Integer.MIN_VALUE and
+ * Integer.MAX_VALUE.
+ */
+ @Override
+ public boolean isOutOfRange(int year, int month, int day) {
+ UmmalquraCalendar date = new UmmalquraCalendar();
+ date.set(UmmalquraCalendar.YEAR, year);
+ date.set(UmmalquraCalendar.MONTH, month);
+ date.set(UmmalquraCalendar.DAY_OF_MONTH, day);
+ return isOutOfRange(date);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isOutOfRange(UmmalquraCalendar calendar) {
+ trimToMidnight(calendar);
+ return isDisabled(calendar) || !isSelectable(calendar);
+ }
+
+ private boolean isDisabled(UmmalquraCalendar c) {
+ return disabledDays.contains(trimToMidnight(c)) || isBeforeMin(c) || isAfterMax(c);
+ }
+
+ private boolean isSelectable(UmmalquraCalendar c) {
+ return selectableDays.isEmpty() || selectableDays.contains(trimToMidnight(c));
+ }
+
+ private boolean isBeforeMin(UmmalquraCalendar calendar) {
+ return mMinDate != null && calendar.before(mMinDate);
+ }
+
+ private boolean isAfterMax(UmmalquraCalendar calendar) {
+ return mMaxDate != null && calendar.after(mMaxDate);
+ }
+
+ private void setToNearestDate(UmmalquraCalendar calendar) {
+ if (!selectableDays.isEmpty()) {
+ UmmalquraCalendar newCalendar = null;
+ UmmalquraCalendar higher = selectableDays.ceiling(calendar);
+ UmmalquraCalendar lower = selectableDays.lower(calendar);
+
+ if (higher == null && lower != null) newCalendar = lower;
+ else if (lower == null && higher != null) newCalendar = higher;
+
+ if (newCalendar != null || higher == null) {
+ newCalendar = newCalendar == null ? calendar : newCalendar;
+ newCalendar.setTimeZone(getTimeZone());
+ calendar.setTimeInMillis(newCalendar.getTimeInMillis());
+ return;
+ }
+
+ long highDistance = Math.abs(higher.getTimeInMillis() - calendar.getTimeInMillis());
+ long lowDistance = Math.abs(calendar.getTimeInMillis() - lower.getTimeInMillis());
+
+ if (lowDistance < highDistance) calendar.setTimeInMillis(lower.getTimeInMillis());
+ else calendar.setTimeInMillis(higher.getTimeInMillis());
+
+ return;
+ }
+
+ if (!disabledDays.isEmpty()) {
+ UmmalquraCalendar forwardDate = (UmmalquraCalendar) calendar.clone();
+ UmmalquraCalendar backwardDate = (UmmalquraCalendar) calendar.clone();
+ while (isDisabled(forwardDate) && isDisabled(backwardDate)) {
+ forwardDate.add(UmmalquraCalendar.DAY_OF_MONTH, 1);
+ backwardDate.add(UmmalquraCalendar.DAY_OF_MONTH, -1);
+ }
+ if (!isDisabled(backwardDate)) {
+ calendar.setTimeInMillis(backwardDate.getTimeInMillis());
+ return;
+ }
+ if (!isDisabled(forwardDate)) {
+ calendar.setTimeInMillis(forwardDate.getTimeInMillis());
+ return;
+ }
+ }
+
+
+ if (isBeforeMin(calendar)) {
+ calendar.setTimeInMillis(mMinDate.getTimeInMillis());
+ return;
+ }
+
+ if (isAfterMax(calendar)) {
+ calendar.setTimeInMillis(mMaxDate.getTimeInMillis());
+ return;
+ }
+ }
+
+ /**
+ * Trims off all time information, effectively setting it to midnight
+ * Makes it easier to compare at just the day level
+ *
+ * @param calendar The Calendar object to trim
+ * @return The trimmed Calendar object
+ */
+ private UmmalquraCalendar trimToMidnight(UmmalquraCalendar calendar) {
+ calendar.set(UmmalquraCalendar.HOUR_OF_DAY, 0);
+ calendar.set(UmmalquraCalendar.MINUTE, 0);
+ calendar.set(UmmalquraCalendar.SECOND, 0);
+ calendar.set(UmmalquraCalendar.MILLISECOND, 0);
+ return calendar;
+ }
+
+ @Override
+ public int getFirstDayOfWeek() {
+ return mWeekStart;
+ }
+
+ @Override
+ public void registerOnDateChangedListener(OnDateChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void tryVibrate() {
+ if (mVibrate) mHapticFeedbackController.tryVibrate();
+ }
+
+ @Override
+ public TimeZone getTimeZone() {
+ return mTimezone == null ? TimeZone.getDefault() : mTimezone;
+ }
+
+ public static Locale getLocal() {
+ return Locale.getDefault();
+ }
+
+ public void notifyOnDateListener() {
+ if (mCallBack != null) {
+ mCallBack.onDateSet(HijriDatePickerDialog.this, mCalendar.get(UmmalquraCalendar.YEAR),
+ mCalendar.get(UmmalquraCalendar.MONTH), mCalendar.get(UmmalquraCalendar.DAY_OF_MONTH));
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/MonthAdapter.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/MonthAdapter.java
new file mode 100644
index 0000000..e6da4a2
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/MonthAdapter.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.BaseAdapter;
+
+import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
+import net.alhazmy13.hijridatepicker.date.hijri.MonthView.OnDayClickListener;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * An adapter for a list of {@link MonthView} items.
+ */
+public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
+
+ private static final String TAG = "SimpleMonthAdapter";
+
+ private final Context mContext;
+ protected final DatePickerController mController;
+
+ private CalendarDay mSelectedDay;
+
+ protected static int WEEK_7_OVERHANG_HEIGHT = 7;
+ protected static final int MONTHS_IN_YEAR = 12;
+
+ /**
+ * A convenience class to represent a specific date.
+ */
+ public static class CalendarDay {
+ private UmmalquraCalendar calendar;
+ int year;
+ int month;
+ int day;
+ TimeZone mTimeZone;
+
+ public CalendarDay(TimeZone timeZone) {
+ mTimeZone = timeZone;
+ setTime(System.currentTimeMillis());
+ }
+
+ public CalendarDay(long timeInMillis, TimeZone timeZone) {
+ mTimeZone = timeZone;
+ setTime(timeInMillis);
+ }
+
+ public CalendarDay(UmmalquraCalendar calendar, TimeZone timeZone) {
+ mTimeZone = timeZone;
+ year = calendar.get(UmmalquraCalendar.YEAR);
+ month = calendar.get(UmmalquraCalendar.MONTH);
+ day = calendar.get(UmmalquraCalendar.DAY_OF_MONTH);
+ }
+
+ public CalendarDay(int year, int month, int day) {
+ setDay(year, month, day);
+ }
+
+ public void set(CalendarDay date) {
+ year = date.year;
+ month = date.month;
+ day = date.day;
+ }
+
+ public void setDay(int year, int month, int day) {
+ this.year = year;
+ this.month = month;
+ this.day = day;
+ }
+
+ private void setTime(long timeInMillis) {
+ if (calendar == null) {
+ calendar = new UmmalquraCalendar(mTimeZone, Locale.getDefault());
+ }
+ calendar.setTimeInMillis(timeInMillis);
+ month = calendar.get(UmmalquraCalendar.MONTH);
+ year = calendar.get(UmmalquraCalendar.YEAR);
+ day = calendar.get(UmmalquraCalendar.DAY_OF_MONTH);
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public int getMonth() {
+ return month;
+ }
+
+ public int getDay() {
+ return day;
+ }
+ }
+
+ public MonthAdapter(Context context,
+ DatePickerController controller) {
+ mContext = context;
+ mController = controller;
+ init();
+ setSelectedDay(mController.getSelectedDay());
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param day The day to highlight
+ */
+ public void setSelectedDay(CalendarDay day) {
+ mSelectedDay = day;
+ notifyDataSetChanged();
+ }
+
+ @SuppressWarnings("unused")
+ public CalendarDay getSelectedDay() {
+ return mSelectedDay;
+ }
+
+ /**
+ * Set up the gesture detector and selected time
+ */
+ protected void init() {
+ mSelectedDay = new CalendarDay(System.currentTimeMillis(), mController.getTimeZone());
+ }
+
+ @Override
+ public int getCount() {
+ UmmalquraCalendar endDate = mController.getEndDate();
+ UmmalquraCalendar startDate = mController.getStartDate();
+ int endMonth = endDate.get(UmmalquraCalendar.YEAR) * MONTHS_IN_YEAR + endDate.get(UmmalquraCalendar.MONTH);
+ int startMonth = startDate.get(UmmalquraCalendar.YEAR) * MONTHS_IN_YEAR + startDate.get(UmmalquraCalendar.MONTH);
+ return endMonth - startMonth + 1;
+ //return ((mController.getMaxYear() - mController.getMinYear()) + 1) * MONTHS_IN_YEAR;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ @SuppressWarnings("unchecked")
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ MonthView v;
+ HashMap drawingParams = null;
+ if (convertView != null) {
+ v = (MonthView) convertView;
+ // We store the drawing parameters in the view so it can be recycled
+ drawingParams = (HashMap) v.getTag();
+ } else {
+ v = createMonthView(mContext);
+ // Set up the new view
+ LayoutParams params = new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ v.setLayoutParams(params);
+ v.setClickable(true);
+ v.setOnDayClickListener(this);
+ }
+ if (drawingParams == null) {
+ drawingParams = new HashMap<>();
+ }
+ drawingParams.clear();
+
+ final int month = (position + mController.getStartDate().get(UmmalquraCalendar.MONTH)) % MONTHS_IN_YEAR;
+ final int year = (position + mController.getStartDate().get(UmmalquraCalendar.MONTH)) / MONTHS_IN_YEAR + mController.getMinYear();
+
+ int selectedDay = -1;
+ if (isSelectedDayInMonth(year, month)) {
+ selectedDay = mSelectedDay.day;
+ }
+
+ // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
+ // height/number of weeks before being displayed.
+ v.reuse();
+
+ drawingParams.put(MonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
+ drawingParams.put(MonthView.VIEW_PARAMS_YEAR, year);
+ drawingParams.put(MonthView.VIEW_PARAMS_MONTH, month);
+ drawingParams.put(MonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
+ v.setMonthParams(drawingParams);
+ v.invalidate();
+ return v;
+ }
+
+ public abstract MonthView createMonthView(Context context);
+
+ private boolean isSelectedDayInMonth(int year, int month) {
+ return mSelectedDay.year == year && mSelectedDay.month == month;
+ }
+
+
+ @Override
+ public void onDayClick(MonthView view, CalendarDay day) {
+ if (day != null) {
+ onDayTapped(day);
+ }
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ protected void onDayTapped(CalendarDay day) {
+ mController.tryVibrate();
+ mController.onDayOfMonthSelected(day.year, day.month, day.day);
+ setSelectedDay(day);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/MonthView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/MonthView.java
new file mode 100644
index 0000000..d14e2e1
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/MonthView.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.TypefaceHelper;
+import net.alhazmy13.hijridatepicker.date.hijri.MonthAdapter.CalendarDay;
+
+import java.security.InvalidParameterException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A calendar-like view displaying a specified month and the appropriate selectable day numbers
+ * within the specified month.
+ */
+public abstract class MonthView extends View {
+ private static final String TAG = "MonthView";
+
+ /**
+ * These params can be passed into the view to control how it appears.
+ * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
+ * values are unlikely to fit most layouts correctly.
+ */
+ /**
+ * This sets the height of this week in pixels
+ */
+ public static final String VIEW_PARAMS_HEIGHT = "height";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week.
+ */
+ public static final String VIEW_PARAMS_MONTH = "month";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week.
+ */
+ public static final String VIEW_PARAMS_YEAR = "year";
+ /**
+ * This sets one of the days in this view as selected {@link UmmalquraCalendar#SUNDAY}
+ * through {@link UmmalquraCalendar#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
+ /**
+ * Which day the week should start on. {@link UmmalquraCalendar#SUNDAY} through
+ * {@link UmmalquraCalendar#SATURDAY}.
+ */
+ public static final String VIEW_PARAMS_WEEK_START = "week_start";
+ /**
+ * How many days to display at a time. Days will be displayed starting with
+ * {@link #mWeekStart}.
+ */
+ public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
+ /**
+ * Which month is currently in focus, as defined by {@link UmmalquraCalendar#MONTH}
+ * [0-11].
+ */
+ public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
+ /**
+ * If this month should display week numbers. false if 0, true otherwise.
+ */
+ public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
+
+ protected static int DEFAULT_HEIGHT = 32;
+ protected static int MIN_HEIGHT = 10;
+ protected static final int DEFAULT_SELECTED_DAY = -1;
+ protected static final int DEFAULT_WEEK_START = UmmalquraCalendar.SUNDAY;
+ protected static final int DEFAULT_NUM_DAYS = 7;
+ protected static final int DEFAULT_SHOW_WK_NUM = 0;
+ protected static final int DEFAULT_FOCUS_MONTH = -1;
+ protected static final int DEFAULT_NUM_ROWS = 6;
+ protected static final int MAX_NUM_ROWS = 6;
+
+ private static final int SELECTED_CIRCLE_ALPHA = 255;
+
+ protected static int DAY_SEPARATOR_WIDTH = 1;
+ protected static int MINI_DAY_NUMBER_TEXT_SIZE;
+ protected static int MONTH_LABEL_TEXT_SIZE;
+ protected static int MONTH_DAY_LABEL_TEXT_SIZE;
+ protected static int MONTH_HEADER_SIZE;
+ protected static int DAY_SELECTED_CIRCLE_SIZE;
+
+ // used for scaling to the device density
+ protected static float mScale = 0;
+
+ protected DatePickerController mController;
+
+ // affects the padding on the sides of this view
+ protected int mEdgePadding = 0;
+
+ private String mDayOfWeekTypeface;
+ private String mMonthTitleTypeface;
+
+ protected Paint mMonthNumPaint;
+ protected Paint mMonthTitlePaint;
+ protected Paint mSelectedCirclePaint;
+ protected Paint mMonthDayLabelPaint;
+
+ private final Formatter mFormatter;
+ private final StringBuilder mStringBuilder;
+
+ // The Julian day of the first day displayed by this item
+ protected int mFirstJulianDay = -1;
+ // The month of the first day in this week
+ protected int mFirstMonth = -1;
+ // The month of the last day in this week
+ protected int mLastMonth = -1;
+
+ protected int mMonth;
+
+ protected int mYear;
+ // Quick reference to the width of this view, matches parent
+ protected int mWidth;
+ // The height this view should draw at in pixels, set by height param
+ protected int mRowHeight = DEFAULT_HEIGHT;
+ // If this view contains the today
+ protected boolean mHasToday = false;
+ // Which day is selected [0-6] or -1 if no day is selected
+ protected int mSelectedDay = -1;
+ // Which day is today [0-6] or -1 if no day is today
+ protected int mToday = DEFAULT_SELECTED_DAY;
+ // Which day of the week to start on [0-6]
+ protected int mWeekStart = DEFAULT_WEEK_START;
+ // How many days to display
+ protected int mNumDays = DEFAULT_NUM_DAYS;
+ // The number of days + a spot for week number if it is displayed
+ protected int mNumCells = mNumDays;
+ // The left edge of the selected day
+ protected int mSelectedLeft = -1;
+ // The right edge of the selected day
+ protected int mSelectedRight = -1;
+
+ private final UmmalquraCalendar mCalendar;
+ protected final UmmalquraCalendar mDayLabelCalendar;
+ private final MonthViewTouchHelper mTouchHelper;
+
+ protected int mNumRows = DEFAULT_NUM_ROWS;
+
+ // Optional listener for handling day click actions
+ protected OnDayClickListener mOnDayClickListener;
+
+ // Whether to prevent setting the accessibility delegate
+ private boolean mLockAccessibilityDelegate;
+
+ protected int mDayTextColor;
+ protected int mSelectedDayTextColor;
+ protected int mMonthDayTextColor;
+ protected int mTodayNumberColor;
+ protected int mHighlightedDayTextColor;
+ protected int mDisabledDayTextColor;
+ protected int mMonthTitleColor;
+
+ public MonthView(Context context) {
+ this(context, null, null);
+ }
+
+ public MonthView(Context context, AttributeSet attr, DatePickerController controller) {
+ super(context, attr);
+ mController = controller;
+ Resources res = context.getResources();
+
+ mDayLabelCalendar = new UmmalquraCalendar(mController.getTimeZone(),Locale.getDefault());
+ mCalendar = new UmmalquraCalendar(mController.getTimeZone(),Locale.getDefault());
+
+ mDayOfWeekTypeface = res.getString(R.string.mdtp_day_of_week_label_typeface);
+ mMonthTitleTypeface = res.getString(R.string.mdtp_sans_serif);
+
+ boolean darkTheme = mController != null && mController.isThemeDark();
+ if(darkTheme) {
+ mDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_normal_dark_theme);
+ mMonthDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_month_day_dark_theme);
+ mDisabledDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled_dark_theme);
+ mHighlightedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_highlighted_dark_theme);
+ }
+ else {
+ mDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_normal);
+ mMonthDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_month_day);
+ mDisabledDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled);
+ mHighlightedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_highlighted);
+ }
+ mSelectedDayTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
+ mTodayNumberColor = mController.getAccentColor();
+ mMonthTitleColor = ContextCompat.getColor(context, R.color.mdtp_white);
+
+ mStringBuilder = new StringBuilder(50);
+ mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+
+ MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_day_number_size);
+ MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_label_size);
+ MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.mdtp_month_day_label_text_size);
+ MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.mdtp_month_list_item_header_height);
+ DAY_SELECTED_CIRCLE_SIZE = res
+ .getDimensionPixelSize(R.dimen.mdtp_day_number_select_circle_radius);
+
+ mRowHeight = (res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height)
+ - getMonthHeaderSize()) / MAX_NUM_ROWS;
+
+ // Set up accessibility components.
+ mTouchHelper = getMonthViewTouchHelper();
+ ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
+ ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mLockAccessibilityDelegate = true;
+
+ // Sets up any standard paints that will be used
+ initView();
+ }
+
+ public void setDatePickerController(DatePickerController controller) {
+ mController = controller;
+ }
+
+ protected MonthViewTouchHelper getMonthViewTouchHelper() {
+ return new MonthViewTouchHelper(this);
+ }
+
+ @Override
+ public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+ // Workaround for a JB MR1 issue where accessibility delegates on
+ // top-level ListView items are overwritten.
+ if (!mLockAccessibilityDelegate) {
+ super.setAccessibilityDelegate(delegate);
+ }
+ }
+
+ public void setOnDayClickListener(OnDayClickListener listener) {
+ mOnDayClickListener = listener;
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(@NonNull MotionEvent event) {
+ // First right-of-refusal goes the touch exploration helper.
+ if (mTouchHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ final int day = getDayFromLocation(event.getX(), event.getY());
+ if (day >= 0) {
+ onDayClick(day);
+ }
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Sets up the text and style properties for painting. Override this if you
+ * want to use a different paint.
+ */
+ protected void initView() {
+ mMonthTitlePaint = new Paint();
+ mMonthTitlePaint.setFakeBoldText(true);
+ mMonthTitlePaint.setAntiAlias(true);
+ mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
+ mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+ mMonthTitlePaint.setColor(mDayTextColor);
+ mMonthTitlePaint.setTextAlign(Align.CENTER);
+ mMonthTitlePaint.setStyle(Style.FILL);
+
+ mSelectedCirclePaint = new Paint();
+ mSelectedCirclePaint.setFakeBoldText(true);
+ mSelectedCirclePaint.setAntiAlias(true);
+ mSelectedCirclePaint.setColor(mTodayNumberColor);
+ mSelectedCirclePaint.setTextAlign(Align.CENTER);
+ mSelectedCirclePaint.setStyle(Style.FILL);
+ mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+
+ mMonthDayLabelPaint = new Paint();
+ mMonthDayLabelPaint.setAntiAlias(true);
+ mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
+ mMonthDayLabelPaint.setColor(mMonthDayTextColor);
+ mMonthDayLabelPaint.setTypeface(TypefaceHelper.get(getContext(),"Roboto-Medium"));
+ mMonthDayLabelPaint.setStyle(Style.FILL);
+ mMonthDayLabelPaint.setTextAlign(Align.CENTER);
+ mMonthDayLabelPaint.setFakeBoldText(true);
+
+ mMonthNumPaint = new Paint();
+ mMonthNumPaint.setAntiAlias(true);
+ mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
+ mMonthNumPaint.setStyle(Style.FILL);
+ mMonthNumPaint.setTextAlign(Align.CENTER);
+ mMonthNumPaint.setFakeBoldText(false);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawMonthTitle(canvas);
+ drawMonthDayLabels(canvas);
+ drawMonthNums(canvas);
+ }
+
+ private int mDayOfWeekStart = 0;
+
+ /**
+ * Sets all the parameters for displaying this week. The only required
+ * parameter is the week number. Other parameters have a default value and
+ * will only update if a new value is included, except for focus month,
+ * which will always default to no focus month if no value is passed in. See
+ * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
+ *
+ * @param params A map of the new parameters, see
+ * {@link #VIEW_PARAMS_HEIGHT}
+ */
+ public void setMonthParams(HashMap params) {
+ if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+ throw new InvalidParameterException("You must specify month and year for this view");
+ }
+ setTag(params);
+ // We keep the current value for any params not present
+ if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+ mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+ if (mRowHeight < MIN_HEIGHT) {
+ mRowHeight = MIN_HEIGHT;
+ }
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+ mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
+ }
+
+ // Allocate space for caching the day numbers and focus values
+ mMonth = params.get(VIEW_PARAMS_MONTH);
+ mYear = params.get(VIEW_PARAMS_YEAR);
+
+ // Figure out what day today is
+ //final Time today = new Time(Time.getCurrentTimezone());
+ //today.setToNow();
+ final UmmalquraCalendar today = new UmmalquraCalendar(mController.getTimeZone(),Locale.getDefault());
+ mHasToday = false;
+ mToday = -1;
+
+ mCalendar.set(UmmalquraCalendar.MONTH, mMonth);
+ mCalendar.set(UmmalquraCalendar.YEAR, mYear);
+ mCalendar.set(UmmalquraCalendar.DAY_OF_MONTH, 1);
+ mDayOfWeekStart = mCalendar.get(UmmalquraCalendar.DAY_OF_WEEK);
+
+ if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+ mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+ } else {
+ mWeekStart = mCalendar.getFirstDayOfWeek();
+ }
+
+ mNumCells = mCalendar.getActualMaximum(UmmalquraCalendar.DAY_OF_MONTH);
+ for (int i = 0; i < mNumCells; i++) {
+ final int day = i + 1;
+ if (sameDay(day, today)) {
+ mHasToday = true;
+ mToday = day;
+ }
+ }
+ mNumRows = calculateNumRows();
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ public void setSelectedDay(int day) {
+ mSelectedDay = day;
+ }
+
+ public void reuse() {
+ mNumRows = DEFAULT_NUM_ROWS;
+ requestLayout();
+ }
+
+ private int calculateNumRows() {
+ int offset = findDayOffset();
+ int dividend = (offset + mNumCells) / mNumDays;
+ int remainder = (offset + mNumCells) % mNumDays;
+ return (dividend + (remainder > 0 ? 1 : 0));
+ }
+
+ private boolean sameDay(int day, UmmalquraCalendar today) {
+ return mYear == today.get(UmmalquraCalendar.YEAR) &&
+ mMonth == today.get(UmmalquraCalendar.MONTH) &&
+ day == today.get(UmmalquraCalendar.DAY_OF_MONTH);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
+ + getMonthHeaderSize() + 5);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ public int getMonth() {
+ return mMonth;
+ }
+
+ public int getYear() {
+ return mYear;
+ }
+
+ /**
+ * A wrapper to the MonthHeaderSize to allow override it in children
+ */
+ protected int getMonthHeaderSize() {
+ return MONTH_HEADER_SIZE;
+ }
+
+ @NonNull
+ private String getMonthAndYearString() {
+ Locale locale = Locale.getDefault();
+ String pattern = "MMMM yyyy";
+
+ if(Build.VERSION.SDK_INT < 18) pattern = getContext().getResources().getString(R.string.mdtp_date_v1_monthyear);
+ else pattern = DateFormat.getBestDateTimePattern(locale, pattern);
+
+ SimpleDateFormat formatter = new SimpleDateFormat(pattern, locale);
+ formatter.setTimeZone(mController.getTimeZone());
+ formatter.applyLocalizedPattern(pattern);
+ mStringBuilder.setLength(0);
+ return mCalendar.getDisplayName(Calendar.MONTH,Calendar.LONG,locale)+" "+mCalendar.get(Calendar.YEAR);
+ }
+
+ protected void drawMonthTitle(Canvas canvas) {
+ int x = (mWidth + 2 * mEdgePadding) / 2;
+ int y = (getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE) / 2;
+ canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+ }
+
+ protected void drawMonthDayLabels(Canvas canvas) {
+ int y = getMonthHeaderSize() - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
+ int dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2);
+
+ for (int i = 0; i < mNumDays; i++) {
+ int x = (2 * i + 1) * dayWidthHalf + mEdgePadding;
+
+ int calendarDay = (i + mWeekStart) % mNumDays;
+ mDayLabelCalendar.set(UmmalquraCalendar.DAY_OF_WEEK, calendarDay);
+ String weekString = getWeekDayLabel(mDayLabelCalendar);
+ canvas.drawText(weekString, x, y, mMonthDayLabelPaint);
+ }
+ }
+
+ /**
+ * Draws the week and month day numbers for this week. Override this method
+ * if you need different placement.
+ *
+ * @param canvas The canvas to draw on
+ */
+ protected void drawMonthNums(Canvas canvas) {
+ int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
+ + getMonthHeaderSize();
+ final float dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2.0f);
+ int j = findDayOffset();
+ for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
+ final int x = (int)((2 * j + 1) * dayWidthHalf + mEdgePadding);
+
+ int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
+
+ final int startX = (int)(x - dayWidthHalf);
+ final int stopX = (int)(x + dayWidthHalf);
+ final int startY = (int)(y - yRelativeToDay);
+ final int stopY = (int)(startY + mRowHeight);
+
+ drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
+
+ j++;
+ if (j == mNumDays) {
+ j = 0;
+ y += mRowHeight;
+ }
+ }
+ }
+
+ /**
+ * This method should draw the month day. Implemented by sub-classes to allow customization.
+ *
+ * @param canvas The canvas to draw on
+ * @param year The year of this month day
+ * @param month The month of this month day
+ * @param day The day number of this month day
+ * @param x The default x position to draw the day number
+ * @param y The default y position to draw the day number
+ * @param startX The left boundary of the day number rect
+ * @param stopX The right boundary of the day number rect
+ * @param startY The top boundary of the day number rect
+ * @param stopY The bottom boundary of the day number rect
+ */
+ public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
+ int x, int y, int startX, int stopX, int startY, int stopY);
+
+ protected int findDayOffset() {
+ return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+ - mWeekStart;
+ }
+
+
+ /**
+ * Calculates the day that the given x position is in, accounting for week
+ * number. Returns the day or -1 if the position wasn't in a day.
+ *
+ * @param x The x position of the touch event
+ * @return The day number, or -1 if the position wasn't in a day
+ */
+ public int getDayFromLocation(float x, float y) {
+ final int day = getInternalDayFromLocation(x, y);
+ if (day < 1 || day > mNumCells) {
+ return -1;
+ }
+ return day;
+ }
+
+ /**
+ * Calculates the day that the given x position is in, accounting for week
+ * number.
+ *
+ * @param x The x position of the touch event
+ * @return The day number
+ */
+ protected int getInternalDayFromLocation(float x, float y) {
+ int dayStart = mEdgePadding;
+ if (x < dayStart || x > mWidth - mEdgePadding) {
+ return -1;
+ }
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ int row = (int) (y - getMonthHeaderSize()) / mRowHeight;
+ int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mEdgePadding));
+
+ int day = column - findDayOffset() + 1;
+ day += row * mNumDays;
+ return day;
+ }
+
+ /**
+ * Called when the user clicks on a day. Handles callbacks to the
+ * {@link OnDayClickListener} if one is set.
+ *
+ * If the day is out of the range set by minDate and/or maxDate, this is a no-op.
+ *
+ * @param day The day that was clicked
+ */
+ private void onDayClick(int day) {
+ // If the min / max date are set, only process the click if it's a valid selection.
+ if (mController.isOutOfRange(mYear, mMonth, day)) {
+ return;
+ }
+
+
+ if (mOnDayClickListener != null) {
+ mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
+ }
+
+ // This is a no-op if accessibility is turned off.
+ mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+
+ /**
+ * @param year
+ * @param month
+ * @param day
+ * @return true if the given date should be highlighted
+ */
+ protected boolean isHighlighted(int year, int month, int day) {
+ return mController.isHighlighted(year, month, day);
+ }
+
+ /**
+ * Return a 1 or 2 letter String for use as a weekday label
+ * @param day The day for which to generate a label
+ * @return The weekday label
+ */
+ private String getWeekDayLabel(UmmalquraCalendar day) {
+ Locale locale = Locale.getDefault();
+
+ // Localised short version of the string is not available on API < 18
+ if(Build.VERSION.SDK_INT < 18) {
+ String dayName = new SimpleDateFormat("E", locale).format(day.getTime());
+ String dayLabel = dayName.toUpperCase(locale).substring(0, 1);
+
+ // Chinese labels should be fetched right to left
+ if (locale.equals(Locale.CHINA) || locale.equals(Locale.CHINESE) || locale.equals(Locale.SIMPLIFIED_CHINESE) || locale.equals(Locale.TRADITIONAL_CHINESE)) {
+ int len = dayName.length();
+ dayLabel = dayName.substring(len -1, len);
+ }
+
+ // Most hebrew labels should select the second to last character
+ if (locale.getLanguage().equals("he") || locale.getLanguage().equals("iw")) {
+ if(mDayLabelCalendar.get(UmmalquraCalendar.DAY_OF_WEEK) != UmmalquraCalendar.SATURDAY) {
+ int len = dayName.length();
+ dayLabel = dayName.substring(len - 2, len - 1);
+ }
+ else {
+ // I know this is duplication, but it makes the code easier to grok by
+ // having all hebrew code in the same block
+ dayLabel = dayName.toUpperCase(locale).substring(0, 1);
+ }
+ }
+
+ // Catalan labels should be two digits in lowercase
+ if (locale.getLanguage().equals("ca"))
+ dayLabel = dayName.toLowerCase().substring(0,2);
+
+ // Correct single character label in Spanish is X
+ if (locale.getLanguage().equals("es") && day.get(UmmalquraCalendar.DAY_OF_WEEK) == UmmalquraCalendar.WEDNESDAY)
+ dayLabel = "X";
+
+ return dayLabel;
+ }
+ // Getting the short label is a one liner on API >= 18
+ return new SimpleDateFormat("EEEEE", locale).format(day.getTime());
+ }
+
+ /**
+ * @return The date that has accessibility focus, or {@code null} if no date
+ * has focus
+ */
+ public CalendarDay getAccessibilityFocus() {
+ final int day = mTouchHelper.getFocusedVirtualView();
+ if (day >= 0) {
+ return new CalendarDay(mYear, mMonth, day);
+ }
+ return null;
+ }
+
+ /**
+ * Clears accessibility focus within the view. No-op if the view does not
+ * contain accessibility focus.
+ */
+ public void clearAccessibilityFocus() {
+ mTouchHelper.clearFocusedVirtualView();
+ }
+
+ /**
+ * Attempts to restore accessibility focus to the specified date.
+ *
+ * @param day The date which should receive focus
+ * @return {@code false} if the date is not valid for this month view, or
+ * {@code true} if the date received focus
+ */
+ public boolean restoreAccessibilityFocus(CalendarDay day) {
+ if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
+ return false;
+ }
+ mTouchHelper.setFocusedVirtualView(day.day);
+ return true;
+ }
+
+ /**
+ * Provides a virtual view hierarchy for interfacing with an accessibility
+ * service.
+ */
+ protected class MonthViewTouchHelper extends ExploreByTouchHelper {
+ private static final String DATE_FORMAT = "dd MMMM yyyy";
+
+ private final Rect mTempRect = new Rect();
+ private final UmmalquraCalendar mTempCalendar = new UmmalquraCalendar(mController.getTimeZone(),Locale.getDefault());
+
+ public MonthViewTouchHelper(View host) {
+ super(host);
+ }
+
+ public void setFocusedVirtualView(int virtualViewId) {
+ getAccessibilityNodeProvider(MonthView.this).performAction(
+ virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ public void clearFocusedVirtualView() {
+ final int focusedVirtualView = getFocusedVirtualView();
+ if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
+ getAccessibilityNodeProvider(MonthView.this).performAction(
+ focusedVirtualView,
+ AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ null);
+ }
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ final int day = getDayFromLocation(x, y);
+ if (day >= 0) {
+ return day;
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List virtualViewIds) {
+ for (int day = 1; day <= mNumCells; day++) {
+ virtualViewIds.add(day);
+ }
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ event.setContentDescription(getItemDescription(virtualViewId));
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId,
+ AccessibilityNodeInfoCompat node) {
+ getItemBounds(virtualViewId, mTempRect);
+
+ node.setContentDescription(getItemDescription(virtualViewId));
+ node.setBoundsInParent(mTempRect);
+ node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+ if (virtualViewId == mSelectedDay) {
+ node.setSelected(true);
+ }
+
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK:
+ onDayClick(virtualViewId);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculates the bounding rectangle of a given time object.
+ *
+ * @param day The day to calculate bounds for
+ * @param rect The rectangle in which to store the bounds
+ */
+ protected void getItemBounds(int day, Rect rect) {
+ final int offsetX = mEdgePadding;
+ final int offsetY = getMonthHeaderSize();
+ final int cellHeight = mRowHeight;
+ final int cellWidth = ((mWidth - (2 * mEdgePadding)) / mNumDays);
+ final int index = ((day - 1) + findDayOffset());
+ final int row = (index / mNumDays);
+ final int column = (index % mNumDays);
+ final int x = (offsetX + (column * cellWidth));
+ final int y = (offsetY + (row * cellHeight));
+
+ rect.set(x, y, (x + cellWidth), (y + cellHeight));
+ }
+
+ /**
+ * Generates a description for a given time object. Since this
+ * description will be spoken, the components are ordered by descending
+ * specificity as DAY MONTH YEAR.
+ *
+ * @param day The day to generate a description for
+ * @return A description of the time object
+ */
+ protected CharSequence getItemDescription(int day) {
+ mTempCalendar.set(mYear, mMonth, day);
+ final CharSequence date = DateFormat.format(DATE_FORMAT,
+ mTempCalendar.getTimeInMillis());
+
+ if (day == mSelectedDay) {
+ return getContext().getString(R.string.mdtp_item_is_selected, date);
+ }
+
+ return date;
+ }
+ }
+
+ /**
+ * Handles callbacks when the user clicks on a time object.
+ */
+ public interface OnDayClickListener {
+ void onDayClick(MonthView view, CalendarDay day);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleDayPickerView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleDayPickerView.java
new file mode 100644
index 0000000..377e038
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleDayPickerView.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * A DayPickerView customized for {@link SimpleMonthAdapter}
+ */
+public class SimpleDayPickerView extends DayPickerView {
+
+ public SimpleDayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SimpleDayPickerView(Context context, DatePickerController controller) {
+ super(context, controller);
+ }
+
+ @Override
+ public MonthAdapter createMonthAdapter(Context context, DatePickerController controller) {
+ return new SimpleMonthAdapter(context, controller);
+ }
+
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleMonthAdapter.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleMonthAdapter.java
new file mode 100644
index 0000000..47f0e07
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleMonthAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+
+/**
+ * An adapter for a list of {@link SimpleMonthView} items.
+ */
+public class SimpleMonthAdapter extends MonthAdapter {
+
+ public SimpleMonthAdapter(Context context, DatePickerController controller) {
+ super(context, controller);
+ }
+
+ @Override
+ public MonthView createMonthView(Context context) {
+ final MonthView monthView = new SimpleMonthView(context, null, mController);
+ return monthView;
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleMonthView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleMonthView.java
new file mode 100644
index 0000000..b4d303c
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/SimpleMonthView.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+
+public class SimpleMonthView extends MonthView {
+
+ public SimpleMonthView(Context context, AttributeSet attr, DatePickerController controller) {
+ super(context, attr, controller);
+ }
+
+ @Override
+ public void drawMonthDay(Canvas canvas, int year, int month, int day,
+ int x, int y, int startX, int stopX, int startY, int stopY) {
+ if (mSelectedDay == day) {
+ canvas.drawCircle(x , y - (MINI_DAY_NUMBER_TEXT_SIZE / 3), DAY_SELECTED_CIRCLE_SIZE,
+ mSelectedCirclePaint);
+ }
+
+ if(isHighlighted(year, month, day)) {
+ mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
+ }
+ else {
+ mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
+ }
+
+ // If we have a mindate or maxdate, gray out the day number if it's outside the range.
+ if (mController.isOutOfRange(year, month, day)) {
+ mMonthNumPaint.setColor(mDisabledDayTextColor);
+ }
+ else if (mSelectedDay == day) {
+ mMonthNumPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
+ mMonthNumPaint.setColor(mSelectedDayTextColor);
+ } else if (mHasToday && mToday == day) {
+ mMonthNumPaint.setColor(mTodayNumberColor);
+ } else {
+ mMonthNumPaint.setColor(isHighlighted(year, month, day) ? mHighlightedDayTextColor : mDayTextColor);
+ }
+
+ canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/TextViewWithCircularIndicator.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/TextViewWithCircularIndicator.java
new file mode 100644
index 0000000..50ea3c6
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/TextViewWithCircularIndicator.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import net.alhazmy13.hijridatepicker.R;
+
+/**
+ * A text view which, when pressed or activated, displays a colored circle around the text.
+ */
+public class TextViewWithCircularIndicator extends TextView {
+
+ private static final int SELECTED_CIRCLE_ALPHA = 255;
+
+ Paint mCirclePaint = new Paint();
+
+ private int mCircleColor;
+ private final String mItemIsSelectedText;
+
+ private boolean mDrawCircle;
+
+ public TextViewWithCircularIndicator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mCircleColor = ContextCompat.getColor(context, R.color.mdtp_accent_color);
+ mItemIsSelectedText = context.getResources().getString(R.string.mdtp_item_is_selected);
+
+ init();
+ }
+
+ private void init() {
+ mCirclePaint.setFakeBoldText(true);
+ mCirclePaint.setAntiAlias(true);
+ mCirclePaint.setColor(mCircleColor);
+ mCirclePaint.setTextAlign(Align.CENTER);
+ mCirclePaint.setStyle(Style.FILL);
+ mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ }
+
+ public void setAccentColor(int color, boolean darkMode) {
+ mCircleColor = color;
+ mCirclePaint.setColor(mCircleColor);
+ setTextColor(createTextColor(color, darkMode));
+ }
+
+ /**
+ * Programmatically set the color state list (see mdtp_date_picker_year_selector)
+ * @param accentColor pressed state text color
+ * @param darkMode current theme mode
+ * @return ColorStateList with pressed state
+ */
+ private ColorStateList createTextColor(int accentColor, boolean darkMode) {
+ int[][] states = new int[][]{
+ new int[]{android.R.attr.state_pressed}, // pressed
+ new int[]{android.R.attr.state_selected}, // selected
+ new int[]{}
+ };
+ int[] colors = new int[]{
+ accentColor,
+ Color.WHITE,
+ darkMode ? Color.WHITE : Color.BLACK
+ };
+ return new ColorStateList(states, colors);
+ }
+
+ public void drawIndicator(boolean drawCircle) {
+ mDrawCircle = drawCircle;
+ }
+
+ @Override
+ public void onDraw(@NonNull Canvas canvas) {
+ if (mDrawCircle) {
+ final int width = getWidth();
+ final int height = getHeight();
+ int radius = Math.min(width, height) / 2;
+ canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
+ }
+ setSelected(mDrawCircle);
+ super.onDraw(canvas);
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ CharSequence itemText = getText();
+ if (mDrawCircle) {
+ return String.format(mItemIsSelectedText, itemText);
+ } else {
+ return itemText;
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/YearPickerView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/YearPickerView.java
new file mode 100644
index 0000000..71f0428
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/date/hijri/YearPickerView.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.date.hijri;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.StateListDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import net.alhazmy13.hijridatepicker.date.hijri.HijriDatePickerDialog.OnDateChangedListener;
+
+import net.alhazmy13.hijridatepicker.R;
+
+/**
+ * Displays a selectable list of years.
+ */
+public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
+ private static final String TAG = "YearPickerView";
+
+ private final DatePickerController mController;
+ private YearAdapter mAdapter;
+ private int mViewSize;
+ private int mChildSize;
+ private TextViewWithCircularIndicator mSelectedView;
+
+ public YearPickerView(Context context, DatePickerController controller) {
+ super(context);
+ mController = controller;
+ mController.registerOnDateChangedListener(this);
+ ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ setLayoutParams(frame);
+ Resources res = context.getResources();
+ mViewSize = res.getDimensionPixelOffset(R.dimen.mdtp_date_picker_view_animator_height);
+ mChildSize = res.getDimensionPixelOffset(R.dimen.mdtp_year_label_height);
+ setVerticalFadingEdgeEnabled(true);
+ setFadingEdgeLength(mChildSize / 3);
+ init();
+ setOnItemClickListener(this);
+ setSelector(new StateListDrawable());
+ setDividerHeight(0);
+ onDateChanged();
+ }
+
+ private void init() {
+ mAdapter = new YearAdapter(mController.getMinYear(), mController.getMaxYear());
+ setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ mController.tryVibrate();
+ TextViewWithCircularIndicator clickedView = (TextViewWithCircularIndicator) view;
+ if (clickedView != null) {
+ if (clickedView != mSelectedView) {
+ if (mSelectedView != null) {
+ mSelectedView.drawIndicator(false);
+ mSelectedView.requestLayout();
+ }
+ clickedView.drawIndicator(true);
+ clickedView.requestLayout();
+ mSelectedView = clickedView;
+ }
+ mController.onYearSelected(getYearFromTextView(clickedView));
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private static int getYearFromTextView(TextView view) {
+ return Integer.valueOf(view.getText().toString());
+ }
+
+ private final class YearAdapter extends BaseAdapter {
+ private final int mMinYear;
+ private final int mMaxYear;
+
+ YearAdapter(int minYear, int maxYear) {
+ if (minYear > maxYear) {
+ throw new IllegalArgumentException("minYear > maxYear");
+ }
+ mMinYear = minYear;
+ mMaxYear = maxYear;
+ }
+
+ @Override
+ public int getCount() {
+ return mMaxYear - mMinYear + 1;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mMinYear + position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextViewWithCircularIndicator v;
+ if (convertView != null) {
+ v = (TextViewWithCircularIndicator) convertView;
+ } else {
+ v = (TextViewWithCircularIndicator) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.mdtp_hijri_year_label_text_view, parent, false);
+ v.setAccentColor(mController.getAccentColor(), mController.isThemeDark());
+ }
+ int year = mMinYear + position;
+ boolean selected = mController.getSelectedDay().year == year;
+ v.setText(String.valueOf(year));
+ v.drawIndicator(selected);
+ v.requestLayout();
+ if (selected) {
+ mSelectedView = v;
+ }
+ return v;
+ }
+ }
+
+ public void postSetSelectionCentered(final int position) {
+ postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2);
+ }
+
+ public void postSetSelectionFromTop(final int position, final int offset) {
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSelectionFromTop(position, offset);
+ requestLayout();
+ }
+ });
+ }
+
+ public int getFirstPositionOffset() {
+ final View firstChild = getChildAt(0);
+ if (firstChild == null) {
+ return 0;
+ }
+ return firstChild.getTop();
+ }
+
+ @Override
+ public void onDateChanged() {
+ mAdapter.notifyDataSetChanged();
+ postSetSelectionCentered(mController.getSelectedDay().year - mController.getMinYear());
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ event.setFromIndex(0);
+ event.setToIndex(0);
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/time/AmPmCirclesView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/time/AmPmCirclesView.java
new file mode 100644
index 0000000..aa8d827
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/time/AmPmCirclesView.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.time;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.Paint.Align;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.view.View;
+
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.Utils;
+
+import java.text.DateFormatSymbols;
+
+/**
+ * Draw the two smaller AM and PM circles next to where the larger circle will be.
+ */
+public class AmPmCirclesView extends View {
+ private static final String TAG = "AmPmCirclesView";
+
+ // Alpha level for selected circle.
+ private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
+ private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
+
+ private final Paint mPaint = new Paint();
+ private int mSelectedAlpha;
+ private int mTouchedColor;
+ private int mUnselectedColor;
+ private int mAmPmTextColor;
+ private int mAmPmSelectedTextColor;
+ private int mAmPmDisabledTextColor;
+ private int mSelectedColor;
+ private float mCircleRadiusMultiplier;
+ private float mAmPmCircleRadiusMultiplier;
+ private String mAmText;
+ private String mPmText;
+ private boolean mAmDisabled;
+ private boolean mPmDisabled;
+ private boolean mIsInitialized;
+
+ private static final int AM = TimePickerDialog.AM;
+ private static final int PM = TimePickerDialog.PM;
+
+ private boolean mDrawValuesReady;
+ private int mAmPmCircleRadius;
+ private int mAmXCenter;
+ private int mPmXCenter;
+ private int mAmPmYCenter;
+ private int mAmOrPm;
+ private int mAmOrPmPressed;
+
+ public AmPmCirclesView(Context context) {
+ super(context);
+ mIsInitialized = false;
+ }
+
+ public void initialize(Context context, TimePickerController controller, int amOrPm) {
+ if (mIsInitialized) {
+ Log.e(TAG, "AmPmCirclesView may only be initialized once.");
+ return;
+ }
+
+ Resources res = context.getResources();
+
+ if (controller.isThemeDark()) {
+ mUnselectedColor = ContextCompat.getColor(context, R.color.mdtp_circle_background_dark_theme);
+ mAmPmTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
+ mAmPmDisabledTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled_dark_theme);
+ mSelectedAlpha = SELECTED_ALPHA_THEME_DARK;
+ } else {
+ mUnselectedColor = ContextCompat.getColor(context, R.color.mdtp_white);
+ mAmPmTextColor = ContextCompat.getColor(context, R.color.mdtp_ampm_text_color);
+ mAmPmDisabledTextColor = ContextCompat.getColor(context, R.color.mdtp_date_picker_text_disabled);
+ mSelectedAlpha = SELECTED_ALPHA;
+ }
+
+ mSelectedColor = controller.getAccentColor();
+ mTouchedColor = Utils.darkenColor(mSelectedColor);
+ mAmPmSelectedTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
+
+ String typefaceFamily = res.getString(R.string.mdtp_sans_serif);
+ Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL);
+ mPaint.setTypeface(tf);
+ mPaint.setAntiAlias(true);
+ mPaint.setTextAlign(Align.CENTER);
+
+ mCircleRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_circle_radius_multiplier));
+ mAmPmCircleRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
+ String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
+ mAmText = amPmTexts[0];
+ mPmText = amPmTexts[1];
+
+ mAmDisabled = controller.isAmDisabled();
+ mPmDisabled = controller.isPmDisabled();
+
+ setAmOrPm(amOrPm);
+ mAmOrPmPressed = -1;
+
+ mIsInitialized = true;
+ }
+
+ public void setAmOrPm(int amOrPm) {
+ mAmOrPm = amOrPm;
+ }
+
+ public void setAmOrPmPressed(int amOrPmPressed) {
+ mAmOrPmPressed = amOrPmPressed;
+ }
+
+ /**
+ * Calculate whether the coordinates are touching the AM or PM circle.
+ */
+ public int getIsTouchingAmOrPm(float xCoord, float yCoord) {
+ if (!mDrawValuesReady) {
+ return -1;
+ }
+
+ int squaredYDistance = (int) ((yCoord - mAmPmYCenter)*(yCoord - mAmPmYCenter));
+
+ int distanceToAmCenter =
+ (int) Math.sqrt((xCoord - mAmXCenter)*(xCoord - mAmXCenter) + squaredYDistance);
+ if (distanceToAmCenter <= mAmPmCircleRadius && !mAmDisabled) {
+ return AM;
+ }
+
+ int distanceToPmCenter =
+ (int) Math.sqrt((xCoord - mPmXCenter)*(xCoord - mPmXCenter) + squaredYDistance);
+ if (distanceToPmCenter <= mAmPmCircleRadius && !mPmDisabled) {
+ return PM;
+ }
+
+ // Neither was close enough.
+ return -1;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int viewWidth = getWidth();
+ if (viewWidth == 0 || !mIsInitialized) {
+ return;
+ }
+
+ if (!mDrawValuesReady) {
+ int layoutXCenter = getWidth() / 2;
+ int layoutYCenter = getHeight() / 2;
+ int circleRadius =
+ (int) (Math.min(layoutXCenter, layoutYCenter) * mCircleRadiusMultiplier);
+ mAmPmCircleRadius = (int) (circleRadius * mAmPmCircleRadiusMultiplier);
+ layoutYCenter += mAmPmCircleRadius*0.75;
+ int textSize = mAmPmCircleRadius * 3 / 4;
+ mPaint.setTextSize(textSize);
+
+ // Line up the vertical center of the AM/PM circles with the bottom of the main circle.
+ mAmPmYCenter = layoutYCenter - mAmPmCircleRadius / 2 + circleRadius;
+ // Line up the horizontal edges of the AM/PM circles with the horizontal edges
+ // of the main circle.
+ mAmXCenter = layoutXCenter - circleRadius + mAmPmCircleRadius;
+ mPmXCenter = layoutXCenter + circleRadius - mAmPmCircleRadius;
+
+ mDrawValuesReady = true;
+ }
+
+ // We'll need to draw either a lighter blue (for selection), a darker blue (for touching)
+ // or white (for not selected).
+ int amColor = mUnselectedColor;
+ int amAlpha = 255;
+ int amTextColor = mAmPmTextColor;
+ int pmColor = mUnselectedColor;
+ int pmAlpha = 255;
+ int pmTextColor = mAmPmTextColor;
+
+ if (mAmOrPm == AM) {
+ amColor = mSelectedColor;
+ amAlpha = mSelectedAlpha;
+ amTextColor = mAmPmSelectedTextColor;
+ } else if (mAmOrPm == PM) {
+ pmColor = mSelectedColor;
+ pmAlpha = mSelectedAlpha;
+ pmTextColor = mAmPmSelectedTextColor;
+ }
+ if (mAmOrPmPressed == AM) {
+ amColor = mTouchedColor;
+ amAlpha = mSelectedAlpha;
+ } else if (mAmOrPmPressed == PM) {
+ pmColor = mTouchedColor;
+ pmAlpha = mSelectedAlpha;
+ }
+ if (mAmDisabled) {
+ amColor = mUnselectedColor;
+ amTextColor = mAmPmDisabledTextColor;
+ }
+ if (mPmDisabled) {
+ pmColor = mUnselectedColor;
+ pmTextColor = mAmPmDisabledTextColor;
+ }
+
+ // Draw the two circles.
+ mPaint.setColor(amColor);
+ mPaint.setAlpha(amAlpha);
+ canvas.drawCircle(mAmXCenter, mAmPmYCenter, mAmPmCircleRadius, mPaint);
+ mPaint.setColor(pmColor);
+ mPaint.setAlpha(pmAlpha);
+ canvas.drawCircle(mPmXCenter, mAmPmYCenter, mAmPmCircleRadius, mPaint);
+
+ // Draw the AM/PM texts on top.
+ mPaint.setColor(amTextColor);
+ int textYCenter = mAmPmYCenter - (int) (mPaint.descent() + mPaint.ascent()) / 2;
+ canvas.drawText(mAmText, mAmXCenter, textYCenter, mPaint);
+ mPaint.setColor(pmTextColor);
+ canvas.drawText(mPmText, mPmXCenter, textYCenter, mPaint);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/time/CircleView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/time/CircleView.java
new file mode 100644
index 0000000..14c6ea4
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/time/CircleView.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.time;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.view.View;
+
+import net.alhazmy13.hijridatepicker.R;
+
+/**
+ * Draws a simple white circle on which the numbers will be drawn.
+ */
+public class CircleView extends View {
+ private static final String TAG = "CircleView";
+
+ private final Paint mPaint = new Paint();
+ private boolean mIs24HourMode;
+ private int mCircleColor;
+ private int mDotColor;
+ private float mCircleRadiusMultiplier;
+ private float mAmPmCircleRadiusMultiplier;
+ private boolean mIsInitialized;
+
+ private boolean mDrawValuesReady;
+ private int mXCenter;
+ private int mYCenter;
+ private int mCircleRadius;
+
+ public CircleView(Context context) {
+ super(context);
+
+ mIsInitialized = false;
+ }
+
+ public void initialize(Context context, TimePickerController controller) {
+ if (mIsInitialized) {
+ Log.e(TAG, "CircleView may only be initialized once.");
+ return;
+ }
+
+ Resources res = context.getResources();
+
+ int colorRes = controller.isThemeDark() ? R.color.mdtp_circle_background_dark_theme : R.color.mdtp_circle_color;
+ mCircleColor = ContextCompat.getColor(context, colorRes);
+ mDotColor = controller.getAccentColor();
+ mPaint.setAntiAlias(true);
+
+ mIs24HourMode = controller.is24HourMode();
+ if (mIs24HourMode || controller.getVersion() != TimePickerDialog.Version.VERSION_1) {
+ mCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode));
+ } else {
+ mCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_circle_radius_multiplier));
+ mAmPmCircleRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
+ }
+
+ mIsInitialized = true;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int viewWidth = getWidth();
+ if (viewWidth == 0 || !mIsInitialized) {
+ return;
+ }
+
+ if (!mDrawValuesReady) {
+ mXCenter = getWidth() / 2;
+ mYCenter = getHeight() / 2;
+ mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
+
+ if (!mIs24HourMode) {
+ // We'll need to draw the AM/PM circles, so the main circle will need to have
+ // a slightly higher center. To keep the entire view centered vertically, we'll
+ // have to push it up by half the radius of the AM/PM circles.
+ int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
+ mYCenter -= amPmCircleRadius*0.75;
+ }
+
+ mDrawValuesReady = true;
+ }
+
+ // Draw the white circle.
+ mPaint.setColor(mCircleColor);
+ canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaint);
+
+ // Draw a small black circle in the center.
+ mPaint.setColor(mDotColor);
+ canvas.drawCircle(mXCenter, mYCenter, 8, mPaint);
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialPickerLayout.java b/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialPickerLayout.java
new file mode 100644
index 0000000..0906233
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialPickerLayout.java
@@ -0,0 +1,1047 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.time;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+
+import net.alhazmy13.hijridatepicker.R;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * The primary layout to hold the circular picker, and the am/pm buttons. This view will measure
+ * itself to end up as a square. It also handles touches to be passed in to views that need to know
+ * when they'd been touched.
+ */
+public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
+ private static final String TAG = "RadialPickerLayout";
+
+ private final int TOUCH_SLOP;
+ private final int TAP_TIMEOUT;
+
+ private static final int VISIBLE_DEGREES_STEP_SIZE = 30;
+ private static final int HOUR_VALUE_TO_DEGREES_STEP_SIZE = VISIBLE_DEGREES_STEP_SIZE;
+ private static final int MINUTE_VALUE_TO_DEGREES_STEP_SIZE = 6;
+ private static final int SECOND_VALUE_TO_DEGREES_STEP_SIZE = 6;
+ private static final int HOUR_INDEX = TimePickerDialog.HOUR_INDEX;
+ private static final int MINUTE_INDEX = TimePickerDialog.MINUTE_INDEX;
+ private static final int SECOND_INDEX = TimePickerDialog.SECOND_INDEX;
+ private static final int AM = TimePickerDialog.AM;
+ private static final int PM = TimePickerDialog.PM;
+
+ private Timepoint mLastValueSelected;
+
+ private TimePickerController mController;
+ private OnValueSelectedListener mListener;
+ private boolean mTimeInitialized;
+ private Timepoint mCurrentTime;
+ private boolean mIs24HourMode;
+ private int mCurrentItemShowing;
+
+ private CircleView mCircleView;
+ private AmPmCirclesView mAmPmCirclesView;
+ private RadialTextsView mHourRadialTextsView;
+ private RadialTextsView mMinuteRadialTextsView;
+ private RadialTextsView mSecondRadialTextsView;
+ private RadialSelectorView mHourRadialSelectorView;
+ private RadialSelectorView mMinuteRadialSelectorView;
+ private RadialSelectorView mSecondRadialSelectorView;
+ private View mGrayBox;
+
+ private int[] mSnapPrefer30sMap;
+ private boolean mInputEnabled;
+ private int mIsTouchingAmOrPm = -1;
+ private boolean mDoingMove;
+ private boolean mDoingTouch;
+ private int mDownDegrees;
+ private float mDownX;
+ private float mDownY;
+ private AccessibilityManager mAccessibilityManager;
+
+ private AnimatorSet mTransition;
+ private Handler mHandler = new Handler();
+
+ public interface OnValueSelectedListener {
+ void onValueSelected(Timepoint newTime);
+ void enablePicker();
+ void advancePicker(int index);
+ }
+
+ public RadialPickerLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setOnTouchListener(this);
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ TOUCH_SLOP = vc.getScaledTouchSlop();
+ TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+ mDoingMove = false;
+
+ mCircleView = new CircleView(context);
+ addView(mCircleView);
+
+ mAmPmCirclesView = new AmPmCirclesView(context);
+ addView(mAmPmCirclesView);
+
+ mHourRadialSelectorView = new RadialSelectorView(context);
+ addView(mHourRadialSelectorView);
+ mMinuteRadialSelectorView = new RadialSelectorView(context);
+ addView(mMinuteRadialSelectorView);
+ mSecondRadialSelectorView = new RadialSelectorView(context);
+ addView(mSecondRadialSelectorView);
+
+ mHourRadialTextsView = new RadialTextsView(context);
+ addView(mHourRadialTextsView);
+ mMinuteRadialTextsView = new RadialTextsView(context);
+ addView(mMinuteRadialTextsView);
+ mSecondRadialTextsView = new RadialTextsView(context);
+ addView(mSecondRadialTextsView);
+
+ // Prepare mapping to snap touchable degrees to selectable degrees.
+ preparePrefer30sMap();
+
+ mLastValueSelected = null;
+
+ mInputEnabled = true;
+
+ mGrayBox = new View(context);
+ mGrayBox.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ mGrayBox.setBackgroundColor(ContextCompat.getColor(context, R.color.mdtp_transparent_black));
+ mGrayBox.setVisibility(View.INVISIBLE);
+ addView(mGrayBox);
+
+ mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ mTimeInitialized = false;
+ }
+
+ public void setOnValueSelectedListener(OnValueSelectedListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Initialize the Layout with starting values.
+ * @param context A context needed to inflate resources
+ * @param initialTime The initial selection of the Timepicker
+ * @param is24HourMode Indicates whether we should render in 24hour mode or with AM/PM selectors
+ */
+ public void initialize(Context context, TimePickerController timePickerController,
+ Timepoint initialTime, boolean is24HourMode) {
+ if (mTimeInitialized) {
+ Log.e(TAG, "Time has already been initialized.");
+ return;
+ }
+
+ mController = timePickerController;
+ mIs24HourMode = mAccessibilityManager.isTouchExplorationEnabled() || is24HourMode;
+
+ // Initialize the circle and AM/PM circles if applicable.
+ mCircleView.initialize(context, mController);
+ mCircleView.invalidate();
+ if (!mIs24HourMode && mController.getVersion() == TimePickerDialog.Version.VERSION_1) {
+ mAmPmCirclesView.initialize(context, mController, initialTime.isAM() ? AM : PM);
+ mAmPmCirclesView.invalidate();
+ }
+
+ // Create the selection validators
+ RadialTextsView.SelectionValidator secondValidator = new RadialTextsView.SelectionValidator() {
+ @Override
+ public boolean isValidSelection(int selection) {
+ Timepoint newTime = new Timepoint(mCurrentTime.getHour(), mCurrentTime.getMinute(), selection);
+ return !mController.isOutOfRange(newTime, SECOND_INDEX);
+ }
+ };
+ RadialTextsView.SelectionValidator minuteValidator = new RadialTextsView.SelectionValidator() {
+ @Override
+ public boolean isValidSelection(int selection) {
+ Timepoint newTime = new Timepoint(mCurrentTime.getHour(), selection, mCurrentTime.getSecond());
+ return !mController.isOutOfRange(newTime, MINUTE_INDEX);
+ }
+ };
+ RadialTextsView.SelectionValidator hourValidator = new RadialTextsView.SelectionValidator() {
+ @Override
+ public boolean isValidSelection(int selection) {
+ Timepoint newTime = new Timepoint(selection, mCurrentTime.getMinute(), mCurrentTime.getSecond());
+ if(!mIs24HourMode && getIsCurrentlyAmOrPm() == PM) newTime.setPM();
+ if(!mIs24HourMode && getIsCurrentlyAmOrPm() == AM) newTime.setAM();
+ return !mController.isOutOfRange(newTime, HOUR_INDEX);
+ }
+ };
+
+ // Initialize the hours and minutes numbers.
+ int[] hours = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+ int[] hours_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
+ int[] minutes = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
+ int[] seconds = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
+ String[] hoursTexts = new String[12];
+ String[] innerHoursTexts = new String[12];
+ String[] minutesTexts = new String[12];
+ String[] secondsTexts = new String[12];
+ for (int i = 0; i < 12; i++) {
+ hoursTexts[i] = is24HourMode?
+ String.format(Locale.getDefault(), "%02d", hours_24[i]) : String.format(Locale.getDefault(), "%d", hours[i]);
+ innerHoursTexts[i] = String.format(Locale.getDefault(), "%d", hours[i]);
+ minutesTexts[i] = String.format(Locale.getDefault(), "%02d", minutes[i]);
+ secondsTexts[i] = String.format(Locale.getDefault(), "%02d", seconds[i]);
+ }
+ mHourRadialTextsView.initialize(context,
+ hoursTexts, (is24HourMode ? innerHoursTexts : null), mController, hourValidator, true);
+ mHourRadialTextsView.setSelection(is24HourMode ? initialTime.getHour() : hours[initialTime.getHour() % 12]);
+ mHourRadialTextsView.invalidate();
+ mMinuteRadialTextsView.initialize(context, minutesTexts, null, mController, minuteValidator, false);
+ mMinuteRadialTextsView.setSelection(initialTime.getMinute());
+ mMinuteRadialTextsView.invalidate();
+ mSecondRadialTextsView.initialize(context, secondsTexts, null, mController, secondValidator, false);
+ mSecondRadialTextsView.setSelection(initialTime.getSecond());
+ mSecondRadialTextsView.invalidate();
+
+ // Initialize the currently-selected hour and minute.
+ mCurrentTime = initialTime;
+ int hourDegrees = (initialTime.getHour() % 12) * HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ mHourRadialSelectorView.initialize(context, mController, is24HourMode, true,
+ hourDegrees, isHourInnerCircle(initialTime.getHour()));
+ int minuteDegrees = initialTime.getMinute() * MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ mMinuteRadialSelectorView.initialize(context, mController, false, false,
+ minuteDegrees, false);
+ int secondDegrees = initialTime.getSecond() * SECOND_VALUE_TO_DEGREES_STEP_SIZE;
+ mSecondRadialSelectorView.initialize(context, mController, false, false,
+ secondDegrees, false);
+
+ mTimeInitialized = true;
+ }
+
+ public void setTime(Timepoint time) {
+ setItem(HOUR_INDEX, time);
+ }
+
+ /**
+ * Set either the hour, the minute or the second. Will set the internal value, and set the selection.
+ */
+ private void setItem(int index, Timepoint time) {
+ time = roundToValidTime(time, index);
+ mCurrentTime = time;
+ reselectSelector(time, false, index);
+ }
+
+ /**
+ * Check if a given hour appears in the outer circle or the inner circle
+ * @return true if the hour is in the inner circle, false if it's in the outer circle.
+ */
+ private boolean isHourInnerCircle(int hourOfDay) {
+ // We'll have the 00 hours on the outside circle.
+ return mIs24HourMode && (hourOfDay <= 12 && hourOfDay != 0);
+ }
+
+ public int getHours() {
+ return mCurrentTime.getHour();
+ }
+
+ public int getMinutes() {
+ return mCurrentTime.getMinute();
+ }
+
+ public int getSeconds() {
+ return mCurrentTime.getSecond();
+ }
+
+ public Timepoint getTime() {
+ return mCurrentTime;
+ }
+
+ /**
+ * If the hours are showing, return the current hour. If the minutes are showing, return the
+ * current minute.
+ */
+ private int getCurrentlyShowingValue() {
+ int currentIndex = getCurrentItemShowing();
+ switch(currentIndex) {
+ case HOUR_INDEX:
+ return mCurrentTime.getHour();
+ case MINUTE_INDEX:
+ return mCurrentTime.getMinute();
+ case SECOND_INDEX:
+ return mCurrentTime.getSecond();
+ default:
+ return -1;
+ }
+ }
+
+ public int getIsCurrentlyAmOrPm() {
+ if (mCurrentTime.isAM()) {
+ return AM;
+ } else if (mCurrentTime.isPM()) {
+ return PM;
+ }
+ return -1;
+ }
+
+ /**
+ * Set the internal value as either AM or PM, and update the AM/PM circle displays.
+ * @param amOrPm Integer representing AM of PM (use the supplied constants)
+ */
+ public void setAmOrPm(int amOrPm) {
+ mAmPmCirclesView.setAmOrPm(amOrPm);
+ mAmPmCirclesView.invalidate();
+ Timepoint newSelection = new Timepoint(mCurrentTime);
+ if(amOrPm == AM) newSelection.setAM();
+ else if(amOrPm == PM) newSelection.setPM();
+ newSelection = roundToValidTime(newSelection, HOUR_INDEX);
+ reselectSelector(newSelection, false, HOUR_INDEX);
+ mCurrentTime = newSelection;
+ mListener.onValueSelected(newSelection);
+ }
+
+ /**
+ * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
+ * selectable area to each of the 12 visible values, such that the ratio of space apportioned
+ * to a visible value : space apportioned to a non-visible value will be 14 : 4.
+ * E.g. the output of 30 degrees should have a higher range of input associated with it than
+ * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
+ * circle (5 on the minutes, 1 or 13 on the hours).
+ */
+ private void preparePrefer30sMap() {
+ // We'll split up the visible output and the non-visible output such that each visible
+ // output will correspond to a range of 14 associated input degrees, and each non-visible
+ // output will correspond to a range of 4 associate input degrees, so visible numbers
+ // are more than 3 times easier to get than non-visible numbers:
+ // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
+ //
+ // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
+ // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
+ // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
+ // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
+ // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
+ // ability to aggressively prefer the visible values by a factor of more than 3:1, which
+ // greatly contributes to the selectability of these values.
+
+ // Our input will be 0 through 360.
+ mSnapPrefer30sMap = new int[361];
+
+ // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
+ int snappedOutputDegrees = 0;
+ // Count of how many inputs we've designated to the specified output.
+ int count = 1;
+ // How many input we expect for a specified output. This will be 14 for output divisible
+ // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
+ // the caller can decide which they need.
+ int expectedCount = 8;
+ // Iterate through the input.
+ for (int degrees = 0; degrees < 361; degrees++) {
+ // Save the input-output mapping.
+ mSnapPrefer30sMap[degrees] = snappedOutputDegrees;
+ // If this is the last input for the specified output, calculate the next output and
+ // the next expected count.
+ if (count == expectedCount) {
+ snappedOutputDegrees += 6;
+ if (snappedOutputDegrees == 360) {
+ expectedCount = 7;
+ } else if (snappedOutputDegrees % 30 == 0) {
+ expectedCount = 14;
+ } else {
+ expectedCount = 4;
+ }
+ count = 1;
+ } else {
+ count++;
+ }
+ }
+ }
+
+ /**
+ * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
+ * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
+ * weighted heavier than the degrees corresponding to non-visible numbers.
+ * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
+ * mapping.
+ */
+ private int snapPrefer30s(int degrees) {
+ if (mSnapPrefer30sMap == null) {
+ return -1;
+ }
+ return mSnapPrefer30sMap[degrees];
+ }
+
+ /**
+ * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
+ * multiples of 30), where the input will be "snapped" to the closest visible degrees.
+ * @param degrees The input degrees
+ * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
+ * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
+ * strictly lower, and 0 to snap to the closer one.
+ * @return output degrees, will be a multiple of 30
+ */
+ private static int snapOnly30s(int degrees, int forceHigherOrLower) {
+ int stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ int floor = (degrees / stepSize) * stepSize;
+ int ceiling = floor + stepSize;
+ if (forceHigherOrLower == 1) {
+ degrees = ceiling;
+ } else if (forceHigherOrLower == -1) {
+ if (degrees == floor) {
+ floor -= stepSize;
+ }
+ degrees = floor;
+ } else {
+ if ((degrees - floor) < (ceiling - degrees)) {
+ degrees = floor;
+ } else {
+ degrees = ceiling;
+ }
+ }
+ return degrees;
+ }
+
+ /**
+ * Snap the input to a selectable value
+ * @param newSelection Timepoint - Time which should be rounded
+ * @param currentItemShowing int - The index of the current view
+ * @return Timepoint - the rounded value
+ */
+ private Timepoint roundToValidTime(Timepoint newSelection, int currentItemShowing) {
+ switch(currentItemShowing) {
+ case HOUR_INDEX:
+ newSelection = mController.roundToNearest(newSelection, Timepoint.TYPE.HOUR);
+ break;
+ case MINUTE_INDEX:
+ newSelection = mController.roundToNearest(newSelection, Timepoint.TYPE.MINUTE);
+ break;
+ case SECOND_INDEX:
+ newSelection = mController.roundToNearest(newSelection, Timepoint.TYPE.SECOND);
+ break;
+ default:
+ newSelection = mCurrentTime;
+ }
+ return newSelection;
+ }
+
+ /**
+ * For the currently showing view (either hours, minutes or seconds), re-calculate the position
+ * for the selector, and redraw it at that position. The text representing the currently
+ * selected value will be redrawn if required.
+ * @param newSelection Timpoint - Time which should be selected.
+ * @param forceDrawDot The dot in the circle will generally only be shown when the selection
+ * @param index The picker to use as a reference. Will be getCurrentItemShow() except when AM/PM is changed
+ * is on non-visible values, but use this to force the dot to be shown.
+ */
+ private void reselectSelector(Timepoint newSelection, boolean forceDrawDot, int index) {
+ switch(index) {
+ case HOUR_INDEX:
+ // The selection might have changed, recalculate the degrees and innerCircle values
+ int hour = newSelection.getHour();
+ boolean isInnerCircle = isHourInnerCircle(hour);
+ int degrees = (hour%12)*360/12;
+ if(!mIs24HourMode) hour = hour%12;
+ if(!mIs24HourMode && hour == 0) hour += 12;
+
+ mHourRadialSelectorView.setSelection(degrees, isInnerCircle, forceDrawDot);
+ mHourRadialTextsView.setSelection(hour);
+ // If we rounded the minutes, reposition the minuteSelector too.
+ if(newSelection.getMinute() != mCurrentTime.getMinute()) {
+ int minDegrees = newSelection.getMinute()*360/60;
+ mMinuteRadialSelectorView.setSelection(minDegrees, isInnerCircle, forceDrawDot);
+ mMinuteRadialTextsView.setSelection(newSelection.getMinute());
+ }
+ // If we rounded the seconds, reposition the secondSelector too.
+ if(newSelection.getSecond() != mCurrentTime.getSecond()) {
+ int secDegrees = newSelection.getSecond()*360/60;
+ mSecondRadialSelectorView.setSelection(secDegrees, isInnerCircle, forceDrawDot);
+ mSecondRadialTextsView.setSelection(newSelection.getSecond());
+ }
+ break;
+ case MINUTE_INDEX:
+ // The selection might have changed, recalculate the degrees
+ degrees = newSelection.getMinute()*360/60;
+
+ mMinuteRadialSelectorView.setSelection(degrees, false, forceDrawDot);
+ mMinuteRadialTextsView.setSelection(newSelection.getMinute());
+ // If we rounded the seconds, reposition the secondSelector too.
+ if(newSelection.getSecond() != mCurrentTime.getSecond()) {
+ int secDegrees = newSelection.getSecond()*360/60;
+ mSecondRadialSelectorView.setSelection(secDegrees, false, forceDrawDot);
+ mSecondRadialTextsView.setSelection(newSelection.getSecond());
+ }
+ break;
+ case SECOND_INDEX:
+ // The selection might have changed, recalculate the degrees
+ degrees = newSelection.getSecond()*360/60;
+ mSecondRadialSelectorView.setSelection(degrees, false, forceDrawDot);
+ mSecondRadialTextsView.setSelection(newSelection.getSecond());
+ }
+
+ // Invalidate the currently showing picker to force a redraw
+ switch(getCurrentItemShowing()) {
+ case HOUR_INDEX:
+ mHourRadialSelectorView.invalidate();
+ mHourRadialTextsView.invalidate();
+ break;
+ case MINUTE_INDEX:
+ mMinuteRadialSelectorView.invalidate();
+ mMinuteRadialTextsView.invalidate();
+ break;
+ case SECOND_INDEX:
+ mSecondRadialSelectorView.invalidate();
+ mSecondRadialTextsView.invalidate();
+ }
+ }
+
+ private Timepoint getTimeFromDegrees(int degrees, boolean isInnerCircle, boolean forceToVisibleValue) {
+ if (degrees == -1) {
+ return null;
+ }
+ int currentShowing = getCurrentItemShowing();
+
+ int stepSize;
+ boolean allowFineGrained = !forceToVisibleValue &&
+ (currentShowing == MINUTE_INDEX || currentShowing == SECOND_INDEX);
+ if (allowFineGrained) {
+ degrees = snapPrefer30s(degrees);
+ } else {
+ degrees = snapOnly30s(degrees, 0);
+ }
+
+ switch (currentShowing) {
+ case HOUR_INDEX:
+ stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ break;
+ case MINUTE_INDEX:
+ stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ break;
+ default:
+ stepSize = SECOND_VALUE_TO_DEGREES_STEP_SIZE;
+ }
+
+ if (currentShowing == HOUR_INDEX) {
+ if (mIs24HourMode) {
+ if (degrees == 0 && isInnerCircle) {
+ degrees = 360;
+ } else if (degrees == 360 && !isInnerCircle) {
+ degrees = 0;
+ }
+ } else if (degrees == 0) {
+ degrees = 360;
+ }
+ } else if (degrees == 360 && (currentShowing == MINUTE_INDEX || currentShowing == SECOND_INDEX)) {
+ degrees = 0;
+ }
+
+ int value = degrees / stepSize;
+
+ if (currentShowing == HOUR_INDEX && mIs24HourMode && !isInnerCircle && degrees != 0) {
+ value += 12;
+ }
+
+ Timepoint newSelection;
+ switch(currentShowing) {
+ case HOUR_INDEX:
+ int hour = value;
+ if(!mIs24HourMode && getIsCurrentlyAmOrPm() == PM && degrees != 360) hour += 12;
+ if(!mIs24HourMode && getIsCurrentlyAmOrPm() == AM && degrees == 360) hour = 0;
+ newSelection = new Timepoint(hour, mCurrentTime.getMinute(), mCurrentTime.getSecond());
+ break;
+ case MINUTE_INDEX:
+ newSelection = new Timepoint(mCurrentTime.getHour(), value, mCurrentTime.getSecond());
+ break;
+ case SECOND_INDEX:
+ newSelection = new Timepoint(mCurrentTime.getHour(), mCurrentTime.getMinute(), value);
+ break;
+ default:
+ newSelection = mCurrentTime;
+ }
+
+ return newSelection;
+ }
+
+ /**
+ * Calculate the degrees within the circle that corresponds to the specified coordinates, if
+ * the coordinates are within the range that will trigger a selection.
+ * @param pointX The x coordinate.
+ * @param pointY The y coordinate.
+ * @param forceLegal Force the selection to be legal, regardless of how far the coordinates are
+ * from the actual numbers.
+ * @param isInnerCircle If the selection may be in the inner circle, pass in a size-1 boolean
+ * array here, inside which the value will be true if the selection is in the inner circle,
+ * and false if in the outer circle.
+ * @return Degrees from 0 to 360, if the selection was within the legal range. -1 if not.
+ */
+ private int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
+ final Boolean[] isInnerCircle) {
+ switch(getCurrentItemShowing()) {
+ case HOUR_INDEX:
+ return mHourRadialSelectorView.getDegreesFromCoords(
+ pointX, pointY, forceLegal, isInnerCircle);
+ case MINUTE_INDEX:
+ return mMinuteRadialSelectorView.getDegreesFromCoords(
+ pointX, pointY, forceLegal, isInnerCircle);
+ case SECOND_INDEX:
+ return mSecondRadialSelectorView.getDegreesFromCoords(
+ pointX, pointY, forceLegal, isInnerCircle);
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Get the item (hours, minutes or seconds) that is currently showing.
+ */
+ public int getCurrentItemShowing() {
+ if (mCurrentItemShowing != HOUR_INDEX && mCurrentItemShowing != MINUTE_INDEX && mCurrentItemShowing != SECOND_INDEX) {
+ Log.e(TAG, "Current item showing was unfortunately set to " + mCurrentItemShowing);
+ return -1;
+ }
+ return mCurrentItemShowing;
+ }
+
+ /**
+ * Set either seconds, minutes or hours as showing.
+ * @param animate True to animate the transition, false to show with no animation.
+ */
+ public void setCurrentItemShowing(int index, boolean animate) {
+ if (index != HOUR_INDEX && index != MINUTE_INDEX && index != SECOND_INDEX) {
+ Log.e(TAG, "TimePicker does not support view at index "+index);
+ return;
+ }
+
+ int lastIndex = getCurrentItemShowing();
+ mCurrentItemShowing = index;
+ reselectSelector(getTime(), true, index);
+
+ if (animate && (index != lastIndex)) {
+ ObjectAnimator[] anims = new ObjectAnimator[4];
+ if (index == MINUTE_INDEX && lastIndex == HOUR_INDEX) {
+ anims[0] = mHourRadialTextsView.getDisappearAnimator();
+ anims[1] = mHourRadialSelectorView.getDisappearAnimator();
+ anims[2] = mMinuteRadialTextsView.getReappearAnimator();
+ anims[3] = mMinuteRadialSelectorView.getReappearAnimator();
+ } else if (index == HOUR_INDEX && lastIndex == MINUTE_INDEX){
+ anims[0] = mHourRadialTextsView.getReappearAnimator();
+ anims[1] = mHourRadialSelectorView.getReappearAnimator();
+ anims[2] = mMinuteRadialTextsView.getDisappearAnimator();
+ anims[3] = mMinuteRadialSelectorView.getDisappearAnimator();
+ } else if (index == MINUTE_INDEX && lastIndex == SECOND_INDEX) {
+ anims[0] = mSecondRadialTextsView.getDisappearAnimator();
+ anims[1] = mSecondRadialSelectorView.getDisappearAnimator();
+ anims[2] = mMinuteRadialTextsView.getReappearAnimator();
+ anims[3] = mMinuteRadialSelectorView.getReappearAnimator();
+ } else if (index == HOUR_INDEX && lastIndex == SECOND_INDEX) {
+ anims[0] = mSecondRadialTextsView.getDisappearAnimator();
+ anims[1] = mSecondRadialSelectorView.getDisappearAnimator();
+ anims[2] = mHourRadialTextsView.getReappearAnimator();
+ anims[3] = mHourRadialSelectorView.getReappearAnimator();
+ } else if (index == SECOND_INDEX && lastIndex == MINUTE_INDEX) {
+ anims[0] = mSecondRadialTextsView.getReappearAnimator();
+ anims[1] = mSecondRadialSelectorView.getReappearAnimator();
+ anims[2] = mMinuteRadialTextsView.getDisappearAnimator();
+ anims[3] = mMinuteRadialSelectorView.getDisappearAnimator();
+ } else if (index == SECOND_INDEX && lastIndex == HOUR_INDEX) {
+ anims[0] = mSecondRadialTextsView.getReappearAnimator();
+ anims[1] = mSecondRadialSelectorView.getReappearAnimator();
+ anims[2] = mHourRadialTextsView.getDisappearAnimator();
+ anims[3] = mHourRadialSelectorView.getDisappearAnimator();
+ }
+
+ if (anims[0] != null && anims[1] != null && anims[2] != null &&
+ anims[3] != null) {
+ if (mTransition != null && mTransition.isRunning()) {
+ mTransition.end();
+ }
+ mTransition = new AnimatorSet();
+ mTransition.playTogether(anims);
+ mTransition.start();
+ } else {
+ transitionWithoutAnimation(index);
+ }
+ } else {
+ transitionWithoutAnimation(index);
+ }
+ }
+
+ private void transitionWithoutAnimation(int index) {
+ int hourAlpha = (index == HOUR_INDEX) ? 1 : 0;
+ int minuteAlpha = (index == MINUTE_INDEX) ? 1 : 0;
+ int secondAlpha = (index == SECOND_INDEX) ? 1 : 0;
+ mHourRadialTextsView.setAlpha(hourAlpha);
+ mHourRadialSelectorView.setAlpha(hourAlpha);
+ mMinuteRadialTextsView.setAlpha(minuteAlpha);
+ mMinuteRadialSelectorView.setAlpha(minuteAlpha);
+ mSecondRadialTextsView.setAlpha(secondAlpha);
+ mSecondRadialSelectorView.setAlpha(secondAlpha);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ final float eventX = event.getX();
+ final float eventY = event.getY();
+ int degrees;
+ Timepoint value;
+ final Boolean[] isInnerCircle = new Boolean[1];
+ isInnerCircle[0] = false;
+
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (!mInputEnabled) {
+ return true;
+ }
+
+ mDownX = eventX;
+ mDownY = eventY;
+
+ mLastValueSelected = null;
+ mDoingMove = false;
+ mDoingTouch = true;
+ // If we're showing the AM/PM, check to see if the user is touching it.
+ if (!mIs24HourMode && mController.getVersion() == TimePickerDialog.Version.VERSION_1) {
+ mIsTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
+ } else {
+ mIsTouchingAmOrPm = -1;
+ }
+ if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
+ // If the touch is on AM or PM, set it as "touched" after the TAP_TIMEOUT
+ // in case the user moves their finger quickly.
+ mController.tryVibrate();
+ mDownDegrees = -1;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mAmPmCirclesView.setAmOrPmPressed(mIsTouchingAmOrPm);
+ mAmPmCirclesView.invalidate();
+ }
+ }, TAP_TIMEOUT);
+ } else {
+ // If we're in accessibility mode, force the touch to be legal. Otherwise,
+ // it will only register within the given touch target zone.
+ boolean forceLegal = mAccessibilityManager.isTouchExplorationEnabled();
+ // Calculate the degrees that is currently being touched.
+ mDownDegrees = getDegreesFromCoords(eventX, eventY, forceLegal, isInnerCircle);
+ Timepoint selectedTime = getTimeFromDegrees(mDownDegrees, isInnerCircle[0], false);
+ if(mController.isOutOfRange(selectedTime, getCurrentItemShowing())) mDownDegrees = -1;
+ if (mDownDegrees != -1) {
+ // If it's a legal touch, set that number as "selected" after the
+ // TAP_TIMEOUT in case the user moves their finger quickly.
+ mController.tryVibrate();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mDoingMove = true;
+ mLastValueSelected = getTimeFromDegrees(mDownDegrees, isInnerCircle[0],
+ false);
+ mLastValueSelected = roundToValidTime(mLastValueSelected, getCurrentItemShowing());
+ // Redraw
+ reselectSelector(mLastValueSelected, true, getCurrentItemShowing());
+ mListener.onValueSelected(mLastValueSelected);
+ }
+ }, TAP_TIMEOUT);
+ }
+ }
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ if (!mInputEnabled) {
+ // We shouldn't be in this state, because input is disabled.
+ Log.e(TAG, "Input was disabled, but received ACTION_MOVE.");
+ return true;
+ }
+
+ float dY = Math.abs(eventY - mDownY);
+ float dX = Math.abs(eventX - mDownX);
+
+ if (!mDoingMove && dX <= TOUCH_SLOP && dY <= TOUCH_SLOP) {
+ // Hasn't registered down yet, just slight, accidental movement of finger.
+ break;
+ }
+
+ // If we're in the middle of touching down on AM or PM, check if we still are.
+ // If so, no-op. If not, remove its pressed state. Either way, no need to check
+ // for touches on the other circle.
+ if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
+ mHandler.removeCallbacksAndMessages(null);
+ int isTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
+ if (isTouchingAmOrPm != mIsTouchingAmOrPm) {
+ mAmPmCirclesView.setAmOrPmPressed(-1);
+ mAmPmCirclesView.invalidate();
+ mIsTouchingAmOrPm = -1;
+ }
+ break;
+ }
+
+ if (mDownDegrees == -1) {
+ // Original down was illegal, so no movement will register.
+ break;
+ }
+
+ // We're doing a move along the circle, so move the selection as appropriate.
+ mDoingMove = true;
+ mHandler.removeCallbacksAndMessages(null);
+ degrees = getDegreesFromCoords(eventX, eventY, true, isInnerCircle);
+ if (degrees != -1) {
+ switch(getCurrentItemShowing()) {
+ case HOUR_INDEX:
+ value = mController.roundToNearest(
+ getTimeFromDegrees(degrees, isInnerCircle[0], false),
+ null
+ );
+ break;
+ case MINUTE_INDEX:
+ value = mController.roundToNearest(
+ getTimeFromDegrees(degrees, isInnerCircle[0], false),
+ Timepoint.TYPE.HOUR
+ );
+ break;
+ default:
+ value = mController.roundToNearest(
+ getTimeFromDegrees(degrees, isInnerCircle[0], false),
+ Timepoint.TYPE.MINUTE
+ );
+ break;
+ }
+ reselectSelector(value, true, getCurrentItemShowing());
+ if (value != null && (mLastValueSelected == null || !mLastValueSelected.equals(value))) {
+ mController.tryVibrate();
+ mLastValueSelected = value;
+ mListener.onValueSelected(value);
+ }
+ }
+ return true;
+ case MotionEvent.ACTION_UP:
+ if (!mInputEnabled) {
+ // If our touch input was disabled, tell the listener to re-enable us.
+ Log.d(TAG, "Input was disabled, but received ACTION_UP.");
+ mListener.enablePicker();
+ return true;
+ }
+
+ mHandler.removeCallbacksAndMessages(null);
+ mDoingTouch = false;
+
+ // If we're touching AM or PM, set it as selected, and tell the listener.
+ if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
+ int isTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
+ mAmPmCirclesView.setAmOrPmPressed(-1);
+ mAmPmCirclesView.invalidate();
+
+ if (isTouchingAmOrPm == mIsTouchingAmOrPm) {
+ mAmPmCirclesView.setAmOrPm(isTouchingAmOrPm);
+ if (getIsCurrentlyAmOrPm() != isTouchingAmOrPm) {
+ Timepoint newSelection = new Timepoint(mCurrentTime);
+ if(mIsTouchingAmOrPm == AM) newSelection.setAM();
+ else if(mIsTouchingAmOrPm == PM) newSelection.setPM();
+ newSelection = roundToValidTime(newSelection, HOUR_INDEX);
+ reselectSelector(newSelection, false, HOUR_INDEX);
+ mCurrentTime = newSelection;
+ mListener.onValueSelected(newSelection);
+
+ }
+ }
+ mIsTouchingAmOrPm = -1;
+ break;
+ }
+
+ // If we have a legal degrees selected, set the value and tell the listener.
+ if (mDownDegrees != -1) {
+ degrees = getDegreesFromCoords(eventX, eventY, mDoingMove, isInnerCircle);
+ if (degrees != -1) {
+ value = getTimeFromDegrees(degrees, isInnerCircle[0], !mDoingMove);
+ value = roundToValidTime(value, getCurrentItemShowing());
+ reselectSelector(value, false, getCurrentItemShowing());
+ mCurrentTime = value;
+ mListener.onValueSelected(value);
+ mListener.advancePicker(getCurrentItemShowing());
+ }
+ }
+ mDoingMove = false;
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Set touch input as enabled or disabled, for use with keyboard mode.
+ */
+ public boolean trySettingInputEnabled(boolean inputEnabled) {
+ if (mDoingTouch && !inputEnabled) {
+ // If we're trying to disable input, but we're in the middle of a touch event,
+ // we'll allow the touch event to continue before disabling input.
+ return false;
+ }
+
+ mInputEnabled = inputEnabled;
+ mGrayBox.setVisibility(inputEnabled? View.INVISIBLE : View.VISIBLE);
+ return true;
+ }
+
+ /**
+ * Necessary for accessibility, to ensure we support "scrolling" forward and backward
+ * in the circle.
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (Build.VERSION.SDK_INT >= 21) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ }
+ else if (Build.VERSION.SDK_INT >= 16) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ } else {
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ /**
+ * Announce the currently-selected time when launched.
+ */
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ // Clear the event's current text so that only the current time will be spoken.
+ event.getText().clear();
+ Calendar time = Calendar.getInstance();
+ time.set(Calendar.HOUR, getHours());
+ time.set(Calendar.MINUTE, getMinutes());
+ time.set(Calendar.SECOND, getSeconds());
+ long millis = time.getTimeInMillis();
+ int flags = DateUtils.FORMAT_SHOW_TIME;
+ if (mIs24HourMode) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ String timeString = DateUtils.formatDateTime(getContext(), millis, flags);
+ event.getText().add(timeString);
+ return true;
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ /**
+ * When scroll forward/backward events are received, jump the time to the higher/lower
+ * discrete, visible value on the circle.
+ */
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+
+ int changeMultiplier = 0;
+ int forward;
+ int backward;
+ if (Build.VERSION.SDK_INT >= 16) {
+ forward = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+ backward = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+ } else {
+ forward = AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
+ backward = AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
+ }
+ if (action == forward) {
+ changeMultiplier = 1;
+ } else if (action == backward) {
+ changeMultiplier = -1;
+ }
+ if (changeMultiplier != 0) {
+ int value = getCurrentlyShowingValue();
+ int stepSize = 0;
+ int currentItemShowing = getCurrentItemShowing();
+ if (currentItemShowing == HOUR_INDEX) {
+ stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ value %= 12;
+ } else if (currentItemShowing == MINUTE_INDEX) {
+ stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ } else if (currentItemShowing == SECOND_INDEX) {
+ stepSize = SECOND_VALUE_TO_DEGREES_STEP_SIZE;
+ }
+
+ int degrees = value * stepSize;
+ degrees = snapOnly30s(degrees, changeMultiplier);
+ value = degrees / stepSize;
+ int maxValue = 0;
+ int minValue = 0;
+ if (currentItemShowing == HOUR_INDEX) {
+ if (mIs24HourMode) {
+ maxValue = 23;
+ } else {
+ maxValue = 12;
+ minValue = 1;
+ }
+ } else {
+ maxValue = 55;
+ }
+ if (value > maxValue) {
+ // If we scrolled forward past the highest number, wrap around to the lowest.
+ value = minValue;
+ } else if (value < minValue) {
+ // If we scrolled backward past the lowest number, wrap around to the highest.
+ value = maxValue;
+ }
+
+ Timepoint newSelection;
+ switch(currentItemShowing) {
+ case HOUR_INDEX:
+ newSelection = new Timepoint(
+ value,
+ mCurrentTime.getMinute(),
+ mCurrentTime.getSecond()
+ );
+ break;
+ case MINUTE_INDEX:
+ newSelection = new Timepoint(
+ mCurrentTime.getHour(),
+ value,
+ mCurrentTime.getSecond()
+ );
+ break;
+ case SECOND_INDEX:
+ newSelection = new Timepoint(
+ mCurrentTime.getHour(),
+ mCurrentTime.getMinute(),
+ value
+ );
+ break;
+ default:
+ newSelection = mCurrentTime;
+ }
+
+ setItem(currentItemShowing, newSelection);
+ mListener.onValueSelected(newSelection);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialSelectorView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialSelectorView.java
new file mode 100644
index 0000000..489c452
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialSelectorView.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.time;
+
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.Log;
+import android.view.View;
+
+import net.alhazmy13.hijridatepicker.R;
+import net.alhazmy13.hijridatepicker.Utils;
+
+/**
+ * View to show what number is selected. This will draw a blue circle over the number, with a blue
+ * line coming from the center of the main circle to the edge of the blue selection.
+ */
+public class RadialSelectorView extends View {
+ private static final String TAG = "RadialSelectorView";
+
+ // Alpha level for selected circle.
+ private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
+ private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
+ // Alpha level for the line.
+ private static final int FULL_ALPHA = Utils.FULL_ALPHA;
+
+ private final Paint mPaint = new Paint();
+
+ private boolean mIsInitialized;
+ private boolean mDrawValuesReady;
+
+ private float mCircleRadiusMultiplier;
+ private float mAmPmCircleRadiusMultiplier;
+ private float mInnerNumbersRadiusMultiplier;
+ private float mOuterNumbersRadiusMultiplier;
+ private float mNumbersRadiusMultiplier;
+ private float mSelectionRadiusMultiplier;
+ private float mAnimationRadiusMultiplier;
+ private boolean mIs24HourMode;
+ private boolean mHasInnerCircle;
+ private int mSelectionAlpha;
+
+ private int mXCenter;
+ private int mYCenter;
+ private int mCircleRadius;
+ private float mTransitionMidRadiusMultiplier;
+ private float mTransitionEndRadiusMultiplier;
+ private int mLineLength;
+ private int mSelectionRadius;
+ private InvalidateUpdateListener mInvalidateUpdateListener;
+
+ private int mSelectionDegrees;
+ private double mSelectionRadians;
+ private boolean mForceDrawDot;
+
+ public RadialSelectorView(Context context) {
+ super(context);
+ mIsInitialized = false;
+ }
+
+ /**
+ * Initialize this selector with the state of the picker.
+ * @param context Current context.
+ * @param controller Structure containing the accentColor and the 24-hour mode, which will tell us
+ * whether the circle's center is moved up slightly to make room for the AM/PM circles.
+ * @param hasInnerCircle Whether we have both an inner and an outer circle of numbers
+ * that may be selected. Should be true for 24-hour mode in the hours circle.
+ * @param disappearsOut Whether the numbers' animation will have them disappearing out
+ * or disappearing in.
+ * @param selectionDegrees The initial degrees to be selected.
+ * @param isInnerCircle Whether the initial selection is in the inner or outer circle.
+ * Will be ignored when hasInnerCircle is false.
+ */
+ public void initialize(Context context, TimePickerController controller, boolean hasInnerCircle,
+ boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) {
+ if (mIsInitialized) {
+ Log.e(TAG, "This RadialSelectorView may only be initialized once.");
+ return;
+ }
+
+ Resources res = context.getResources();
+
+ int accentColor = controller.getAccentColor();
+ mPaint.setColor(accentColor);
+ mPaint.setAntiAlias(true);
+
+ mSelectionAlpha = controller.isThemeDark() ? SELECTED_ALPHA_THEME_DARK : SELECTED_ALPHA;
+
+ // Calculate values for the circle radius size.
+ mIs24HourMode = controller.is24HourMode();
+ if (mIs24HourMode || controller.getVersion() != TimePickerDialog.Version.VERSION_1) {
+ mCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode));
+ } else {
+ mCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_circle_radius_multiplier));
+ mAmPmCircleRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
+ }
+
+ // Calculate values for the radius size(s) of the numbers circle(s).
+ mHasInnerCircle = hasInnerCircle;
+ if (hasInnerCircle) {
+ mInnerNumbersRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_numbers_radius_multiplier_inner));
+ mOuterNumbersRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_numbers_radius_multiplier_outer));
+ } else {
+ mNumbersRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_numbers_radius_multiplier_normal));
+ }
+ mSelectionRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_selection_radius_multiplier));
+
+ // Calculate values for the transition mid-way states.
+ mAnimationRadiusMultiplier = 1;
+ mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
+ mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
+ mInvalidateUpdateListener = new InvalidateUpdateListener();
+
+ setSelection(selectionDegrees, isInnerCircle, false);
+ mIsInitialized = true;
+ }
+
+ /**
+ * Set the selection.
+ * @param selectionDegrees The degrees to be selected.
+ * @param isInnerCircle Whether the selection should be in the inner circle or outer. Will be
+ * ignored if hasInnerCircle was initialized to false.
+ * @param forceDrawDot Whether to force the dot in the center of the selection circle to be
+ * drawn. If false, the dot will be drawn only when the degrees is not a multiple of 30, i.e.
+ * the selection is not on a visible number.
+ */
+ public void setSelection(int selectionDegrees, boolean isInnerCircle, boolean forceDrawDot) {
+ mSelectionDegrees = selectionDegrees;
+ mSelectionRadians = selectionDegrees * Math.PI / 180;
+ mForceDrawDot = forceDrawDot;
+
+ if (mHasInnerCircle) {
+ if (isInnerCircle) {
+ mNumbersRadiusMultiplier = mInnerNumbersRadiusMultiplier;
+ } else {
+ mNumbersRadiusMultiplier = mOuterNumbersRadiusMultiplier;
+ }
+ }
+ }
+
+ /**
+ * Allows for smoother animations.
+ */
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /**
+ * Set the multiplier for the radius. Will be used during animations to move in/out.
+ */
+ public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
+ mAnimationRadiusMultiplier = animationRadiusMultiplier;
+ }
+
+ public int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
+ final Boolean[] isInnerCircle) {
+ if (!mDrawValuesReady) {
+ return -1;
+ }
+
+ double hypotenuse = Math.sqrt(
+ (pointY - mYCenter)*(pointY - mYCenter) +
+ (pointX - mXCenter)*(pointX - mXCenter));
+ // Check if we're outside the range
+ if (mHasInnerCircle) {
+ if (forceLegal) {
+ // If we're told to force the coordinates to be legal, we'll set the isInnerCircle
+ // boolean based based off whichever number the coordinates are closer to.
+ int innerNumberRadius = (int) (mCircleRadius * mInnerNumbersRadiusMultiplier);
+ int distanceToInnerNumber = (int) Math.abs(hypotenuse - innerNumberRadius);
+ int outerNumberRadius = (int) (mCircleRadius * mOuterNumbersRadiusMultiplier);
+ int distanceToOuterNumber = (int) Math.abs(hypotenuse - outerNumberRadius);
+
+ isInnerCircle[0] = (distanceToInnerNumber <= distanceToOuterNumber);
+ } else {
+ // Otherwise, if we're close enough to either number (with the space between the
+ // two allotted equally), set the isInnerCircle boolean as the closer one.
+ // appropriately, but otherwise return -1.
+ int minAllowedHypotenuseForInnerNumber =
+ (int) (mCircleRadius * mInnerNumbersRadiusMultiplier) - mSelectionRadius;
+ int maxAllowedHypotenuseForOuterNumber =
+ (int) (mCircleRadius * mOuterNumbersRadiusMultiplier) + mSelectionRadius;
+ int halfwayHypotenusePoint = (int) (mCircleRadius *
+ ((mOuterNumbersRadiusMultiplier + mInnerNumbersRadiusMultiplier) / 2));
+
+ if (hypotenuse >= minAllowedHypotenuseForInnerNumber &&
+ hypotenuse <= halfwayHypotenusePoint) {
+ isInnerCircle[0] = true;
+ } else if (hypotenuse <= maxAllowedHypotenuseForOuterNumber &&
+ hypotenuse >= halfwayHypotenusePoint) {
+ isInnerCircle[0] = false;
+ } else {
+ return -1;
+ }
+ }
+ } else {
+ // If there's just one circle, we'll need to return -1 if:
+ // we're not told to force the coordinates to be legal, and
+ // the coordinates' distance to the number is within the allowed distance.
+ if (!forceLegal) {
+ int distanceToNumber = (int) Math.abs(hypotenuse - mLineLength);
+ // The max allowed distance will be defined as the distance from the center of the
+ // number to the edge of the circle.
+ int maxAllowedDistance = (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier));
+ if (distanceToNumber > maxAllowedDistance) {
+ return -1;
+ }
+ }
+ }
+
+
+ float opposite = Math.abs(pointY - mYCenter);
+ double radians = Math.asin(opposite / hypotenuse);
+ int degrees = (int) (radians * 180 / Math.PI);
+
+ // Now we have to translate to the correct quadrant.
+ boolean rightSide = (pointX > mXCenter);
+ boolean topSide = (pointY < mYCenter);
+ if (rightSide && topSide) {
+ degrees = 90 - degrees;
+ } else if (rightSide && !topSide) {
+ degrees = 90 + degrees;
+ } else if (!rightSide && !topSide) {
+ degrees = 270 - degrees;
+ } else if (!rightSide && topSide) {
+ degrees = 270 + degrees;
+ }
+ return degrees;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int viewWidth = getWidth();
+ if (viewWidth == 0 || !mIsInitialized) {
+ return;
+ }
+
+ if (!mDrawValuesReady) {
+ mXCenter = getWidth() / 2;
+ mYCenter = getHeight() / 2;
+ mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
+
+ if (!mIs24HourMode) {
+ // We'll need to draw the AM/PM circles, so the main circle will need to have
+ // a slightly higher center. To keep the entire view centered vertically, we'll
+ // have to push it up by half the radius of the AM/PM circles.
+ int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
+ mYCenter -= amPmCircleRadius *0.75;
+ }
+
+ mSelectionRadius = (int) (mCircleRadius * mSelectionRadiusMultiplier);
+
+ mDrawValuesReady = true;
+ }
+
+ // Calculate the current radius at which to place the selection circle.
+ mLineLength = (int) (mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier);
+ int pointX = mXCenter + (int) (mLineLength * Math.sin(mSelectionRadians));
+ int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
+
+ // Draw the selection circle.
+ mPaint.setAlpha(mSelectionAlpha);
+ canvas.drawCircle(pointX, pointY, mSelectionRadius, mPaint);
+
+ if (mForceDrawDot | mSelectionDegrees % 30 != 0) {
+ // We're not on a direct tick (or we've been told to draw the dot anyway).
+ mPaint.setAlpha(FULL_ALPHA);
+ canvas.drawCircle(pointX, pointY, (mSelectionRadius * 2 / 7), mPaint);
+ } else {
+ // We're not drawing the dot, so shorten the line to only go as far as the edge of the
+ // selection circle.
+ int lineLength = mLineLength;
+ lineLength -= mSelectionRadius;
+ pointX = mXCenter + (int) (lineLength * Math.sin(mSelectionRadians));
+ pointY = mYCenter - (int) (lineLength * Math.cos(mSelectionRadians));
+ }
+
+ // Draw the line from the center of the circle.
+ mPaint.setAlpha(255);
+ mPaint.setStrokeWidth(3);
+ canvas.drawLine(mXCenter, mYCenter, pointX, pointY, mPaint);
+ }
+
+ public ObjectAnimator getDisappearAnimator() {
+ if (!mIsInitialized || !mDrawValuesReady) {
+ Log.e(TAG, "RadialSelectorView was not ready for animation.");
+ return null;
+ }
+
+ Keyframe kf0, kf1, kf2;
+ float midwayPoint = 0.2f;
+ int duration = 500;
+
+ kf0 = Keyframe.ofFloat(0f, 1);
+ kf1 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
+ kf2 = Keyframe.ofFloat(1f, mTransitionEndRadiusMultiplier);
+ PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
+ "animationRadiusMultiplier", kf0, kf1, kf2);
+
+ kf0 = Keyframe.ofFloat(0f, 1f);
+ kf1 = Keyframe.ofFloat(1f, 0f);
+ PropertyValuesHolder fadeOut = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1);
+
+ ObjectAnimator disappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
+ this, radiusDisappear, fadeOut).setDuration(duration);
+ disappearAnimator.addUpdateListener(mInvalidateUpdateListener);
+
+ return disappearAnimator;
+ }
+
+ public ObjectAnimator getReappearAnimator() {
+ if (!mIsInitialized || !mDrawValuesReady) {
+ Log.e(TAG, "RadialSelectorView was not ready for animation.");
+ return null;
+ }
+
+ Keyframe kf0, kf1, kf2, kf3;
+ float midwayPoint = 0.2f;
+ int duration = 500;
+
+ // The time points are half of what they would normally be, because this animation is
+ // staggered against the disappear so they happen seamlessly. The reappear starts
+ // halfway into the disappear.
+ float delayMultiplier = 0.25f;
+ float transitionDurationMultiplier = 1f;
+ float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
+ int totalDuration = (int) (duration * totalDurationMultiplier);
+ float delayPoint = (delayMultiplier * duration) / totalDuration;
+ midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
+
+ kf0 = Keyframe.ofFloat(0f, mTransitionEndRadiusMultiplier);
+ kf1 = Keyframe.ofFloat(delayPoint, mTransitionEndRadiusMultiplier);
+ kf2 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
+ kf3 = Keyframe.ofFloat(1f, 1);
+ PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
+ "animationRadiusMultiplier", kf0, kf1, kf2, kf3);
+
+ kf0 = Keyframe.ofFloat(0f, 0f);
+ kf1 = Keyframe.ofFloat(delayPoint, 0f);
+ kf2 = Keyframe.ofFloat(1f, 1f);
+ PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
+
+ ObjectAnimator reappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
+ this, radiusReappear, fadeIn).setDuration(totalDuration);
+ reappearAnimator.addUpdateListener(mInvalidateUpdateListener);
+ return reappearAnimator;
+ }
+
+ /**
+ * We'll need to invalidate during the animation.
+ */
+ private class InvalidateUpdateListener implements AnimatorUpdateListener {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ RadialSelectorView.this.invalidate();
+ }
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialTextsView.java b/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialTextsView.java
new file mode 100644
index 0000000..d441ab3
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/time/RadialTextsView.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.alhazmy13.hijridatepicker.time;
+
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.Paint.Align;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.view.View;
+
+import net.alhazmy13.hijridatepicker.R;
+
+/**
+ * A view to show a series of numbers in a circular pattern.
+ */
+public class RadialTextsView extends View {
+ private final static String TAG = "RadialTextsView";
+
+ private final Paint mPaint = new Paint();
+ private final Paint mSelectedPaint = new Paint();
+ private final Paint mInactivePaint = new Paint();
+
+ private boolean mDrawValuesReady;
+ private boolean mIsInitialized;
+
+ private int selection = -1;
+
+ private SelectionValidator mValidator;
+
+ private Typeface mTypefaceLight;
+ private Typeface mTypefaceRegular;
+ private String[] mTexts;
+ private String[] mInnerTexts;
+ private boolean mIs24HourMode;
+ private boolean mHasInnerCircle;
+ private float mCircleRadiusMultiplier;
+ private float mAmPmCircleRadiusMultiplier;
+ private float mNumbersRadiusMultiplier;
+ private float mInnerNumbersRadiusMultiplier;
+ private float mTextSizeMultiplier;
+ private float mInnerTextSizeMultiplier;
+
+ private int mXCenter;
+ private int mYCenter;
+ private float mCircleRadius;
+ private boolean mTextGridValuesDirty;
+ private float mTextSize;
+ private float mInnerTextSize;
+ private float[] mTextGridHeights;
+ private float[] mTextGridWidths;
+ private float[] mInnerTextGridHeights;
+ private float[] mInnerTextGridWidths;
+
+ private float mAnimationRadiusMultiplier;
+ private float mTransitionMidRadiusMultiplier;
+ private float mTransitionEndRadiusMultiplier;
+ ObjectAnimator mDisappearAnimator;
+ ObjectAnimator mReappearAnimator;
+ private InvalidateUpdateListener mInvalidateUpdateListener;
+
+ public RadialTextsView(Context context) {
+ super(context);
+ mIsInitialized = false;
+ }
+
+ public void initialize(Context context, String[] texts, String[] innerTexts,
+ TimePickerController controller, SelectionValidator validator, boolean disappearsOut) {
+ if (mIsInitialized) {
+ Log.e(TAG, "This RadialTextsView may only be initialized once.");
+ return;
+ }
+ Resources res = context.getResources();
+
+ // Set up the paint.
+ int textColorRes = controller.isThemeDark() ? R.color.mdtp_white : R.color.mdtp_numbers_text_color;
+ mPaint.setColor(ContextCompat.getColor(context, textColorRes));
+ String typefaceFamily = res.getString(R.string.mdtp_radial_numbers_typeface);
+ mTypefaceLight = Typeface.create(typefaceFamily, Typeface.NORMAL);
+ String typefaceFamilyRegular = res.getString(R.string.mdtp_sans_serif);
+ mTypefaceRegular = Typeface.create(typefaceFamilyRegular, Typeface.NORMAL);
+ mPaint.setAntiAlias(true);
+ mPaint.setTextAlign(Align.CENTER);
+
+ // Set up the selected paint
+ int selectedTextColor = ContextCompat.getColor(context, R.color.mdtp_white);
+ mSelectedPaint.setColor(selectedTextColor);
+ mSelectedPaint.setAntiAlias(true);
+ mSelectedPaint.setTextAlign(Align.CENTER);
+
+ // Set up the inactive paint
+ int inactiveColorRes = controller.isThemeDark() ? R.color.mdtp_date_picker_text_disabled_dark_theme
+ : R.color.mdtp_date_picker_text_disabled;
+ mInactivePaint.setColor(ContextCompat.getColor(context, inactiveColorRes));
+ mInactivePaint.setAntiAlias(true);
+ mInactivePaint.setTextAlign(Align.CENTER);
+
+ mTexts = texts;
+ mInnerTexts = innerTexts;
+ mIs24HourMode = controller.is24HourMode();
+ mHasInnerCircle = (innerTexts != null);
+
+ // Calculate the radius for the main circle.
+ if (mIs24HourMode || controller.getVersion() != TimePickerDialog.Version.VERSION_1) {
+ mCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_circle_radius_multiplier_24HourMode));
+ } else {
+ mCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_circle_radius_multiplier));
+ mAmPmCircleRadiusMultiplier =
+ Float.parseFloat(res.getString(R.string.mdtp_ampm_circle_radius_multiplier));
+ }
+
+ // Initialize the widths and heights of the grid, and calculate the values for the numbers.
+ mTextGridHeights = new float[7];
+ mTextGridWidths = new float[7];
+ if (mHasInnerCircle) {
+ mNumbersRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_numbers_radius_multiplier_outer));
+ mTextSizeMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_text_size_multiplier_outer));
+ mInnerNumbersRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_numbers_radius_multiplier_inner));
+ mInnerTextSizeMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_text_size_multiplier_inner));
+
+ mInnerTextGridHeights = new float[7];
+ mInnerTextGridWidths = new float[7];
+ } else {
+ mNumbersRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_numbers_radius_multiplier_normal));
+ mTextSizeMultiplier = Float.parseFloat(
+ res.getString(R.string.mdtp_text_size_multiplier_normal));
+ }
+
+ mAnimationRadiusMultiplier = 1;
+ mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
+ mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
+ mInvalidateUpdateListener = new InvalidateUpdateListener();
+
+ mValidator = validator;
+
+ mTextGridValuesDirty = true;
+ mIsInitialized = true;
+ }
+
+ /**
+ * Set the value of the selected text. Depending on the theme this will be rendered differently
+ * @param selection The text which is currently selected
+ */
+ protected void setSelection(int selection) {
+ this.selection = selection;
+ }
+
+ /**
+ * Allows for smoother animation.
+ */
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /**
+ * Used by the animation to move the numbers in and out.
+ */
+ @SuppressWarnings("unused")
+ public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
+ mAnimationRadiusMultiplier = animationRadiusMultiplier;
+ mTextGridValuesDirty = true;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int viewWidth = getWidth();
+ if (viewWidth == 0 || !mIsInitialized) {
+ return;
+ }
+
+ if (!mDrawValuesReady) {
+ mXCenter = getWidth() / 2;
+ mYCenter = getHeight() / 2;
+ mCircleRadius = Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier;
+ if (!mIs24HourMode) {
+ // We'll need to draw the AM/PM circles, so the main circle will need to have
+ // a slightly higher center. To keep the entire view centered vertically, we'll
+ // have to push it up by half the radius of the AM/PM circles.
+ float amPmCircleRadius = mCircleRadius * mAmPmCircleRadiusMultiplier;
+ mYCenter -= amPmCircleRadius *0.75;
+ }
+
+ mTextSize = mCircleRadius * mTextSizeMultiplier;
+ if (mHasInnerCircle) {
+ mInnerTextSize = mCircleRadius * mInnerTextSizeMultiplier;
+ }
+
+ // Because the text positions will be static, pre-render the animations.
+ renderAnimations();
+
+ mTextGridValuesDirty = true;
+ mDrawValuesReady = true;
+ }
+
+ // Calculate the text positions, but only if they've changed since the last onDraw.
+ if (mTextGridValuesDirty) {
+ float numbersRadius =
+ mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier;
+
+ // Calculate the positions for the 12 numbers in the main circle.
+ calculateGridSizes(numbersRadius, mXCenter, mYCenter,
+ mTextSize, mTextGridHeights, mTextGridWidths);
+ if (mHasInnerCircle) {
+ // If we have an inner circle, calculate those positions too.
+ float innerNumbersRadius =
+ mCircleRadius * mInnerNumbersRadiusMultiplier * mAnimationRadiusMultiplier;
+ calculateGridSizes(innerNumbersRadius, mXCenter, mYCenter,
+ mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
+ }
+ mTextGridValuesDirty = false;
+ }
+
+ // Draw the texts in the pre-calculated positions.
+ drawTexts(canvas, mTextSize, mTypefaceLight, mTexts, mTextGridWidths, mTextGridHeights);
+ if (mHasInnerCircle) {
+ drawTexts(canvas, mInnerTextSize, mTypefaceRegular, mInnerTexts,
+ mInnerTextGridWidths, mInnerTextGridHeights);
+ }
+ }
+
+ /**
+ * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
+ * drawn at based on the specified circle radius. Place the values in the textGridHeights and
+ * textGridWidths parameters.
+ */
+ private void calculateGridSizes(float numbersRadius, float xCenter, float yCenter,
+ float textSize, float[] textGridHeights, float[] textGridWidths) {
+ /*
+ * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
+ */
+ float offset1 = numbersRadius;
+ // cos(30) = a / r => r * cos(30) = a => r * √3/2 = a
+ float offset2 = numbersRadius * ((float) Math.sqrt(3)) / 2f;
+ // sin(30) = o / r => r * sin(30) = o => r / 2 = a
+ float offset3 = numbersRadius / 2f;
+ mPaint.setTextSize(textSize);
+ mSelectedPaint.setTextSize(textSize);
+ mInactivePaint.setTextSize(textSize);
+ // We'll need yTextBase to be slightly lower to account for the text's baseline.
+ yCenter -= (mPaint.descent() + mPaint.ascent()) / 2;
+
+ textGridHeights[0] = yCenter - offset1;
+ textGridWidths[0] = xCenter - offset1;
+ textGridHeights[1] = yCenter - offset2;
+ textGridWidths[1] = xCenter - offset2;
+ textGridHeights[2] = yCenter - offset3;
+ textGridWidths[2] = xCenter - offset3;
+ textGridHeights[3] = yCenter;
+ textGridWidths[3] = xCenter;
+ textGridHeights[4] = yCenter + offset3;
+ textGridWidths[4] = xCenter + offset3;
+ textGridHeights[5] = yCenter + offset2;
+ textGridWidths[5] = xCenter + offset2;
+ textGridHeights[6] = yCenter + offset1;
+ textGridWidths[6] = xCenter + offset1;
+ }
+
+ private Paint[] assignTextColors(String[] texts) {
+ Paint[] paints = new Paint[texts.length];
+ for(int i=0;i mTypedTimes;
+ private Node mLegalTimesTree;
+ private int mAmKeyCode;
+ private int mPmKeyCode;
+
+ // Accessibility strings.
+ private String mHourPickerDescription;
+ private String mSelectHours;
+ private String mMinutePickerDescription;
+ private String mSelectMinutes;
+ private String mSecondPickerDescription;
+ private String mSelectSeconds;
+
+ /**
+ * The callback interface used to indicate the user is done filling in
+ * the time (they clicked on the 'Set' button).
+ */
+ public interface OnTimeSetListener {
+
+ /**
+ * @param view The view associated with this listener.
+ * @param hourOfDay The hour that was set.
+ * @param minute The minute that was set.
+ * @param second The second that was set
+ */
+ void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second);
+ }
+
+ public TimePickerDialog() {
+ // Empty constructor required for dialog fragment.
+ }
+
+ public static TimePickerDialog newInstance(OnTimeSetListener callback,
+ int hourOfDay, int minute, int second, boolean is24HourMode) {
+ TimePickerDialog ret = new TimePickerDialog();
+ ret.initialize(callback, hourOfDay, minute, second, is24HourMode);
+ return ret;
+ }
+
+ public static TimePickerDialog newInstance(OnTimeSetListener callback,
+ int hourOfDay, int minute, boolean is24HourMode) {
+ return TimePickerDialog.newInstance(callback, hourOfDay, minute, 0, is24HourMode);
+ }
+
+ public void initialize(OnTimeSetListener callback,
+ int hourOfDay, int minute, int second, boolean is24HourMode) {
+ mCallback = callback;
+
+ mInitialTime = new Timepoint(hourOfDay, minute, second);
+ mIs24HourMode = is24HourMode;
+ mInKbMode = false;
+ mTitle = "";
+ mThemeDark = false;
+ mThemeDarkChanged = false;
+ mAccentColor = -1;
+ mVibrate = true;
+ mDismissOnPause = false;
+ mEnableSeconds = false;
+ mEnableMinutes = true;
+ mOkResid = R.string.mdtp_ok;
+ mOkColor = -1;
+ mCancelResid = R.string.mdtp_cancel;
+ mCancelColor = -1;
+ mVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? Version.VERSION_1 : Version.VERSION_2;
+ }
+
+ /**
+ * Set a title. NOTE: this will only take effect with the next onCreateView
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Set a dark or light theme. NOTE: this will only take effect for the next onCreateView.
+ */
+ public void setThemeDark(boolean dark) {
+ mThemeDark = dark;
+ mThemeDarkChanged = true;
+ }
+
+ /**
+ * Set the accent color of this dialog
+ * @param color the accent color you want
+ */
+ @SuppressWarnings("unused")
+ public void setAccentColor(String color) {
+ mAccentColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the accent color of this dialog
+ * @param color the accent color you want
+ */
+ public void setAccentColor(@ColorInt int color) {
+ mAccentColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Set the text color of the OK button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setOkColor(String color) {
+ mOkColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the text color of the OK button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setOkColor(@ColorInt int color) {
+ mOkColor = Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Set the text color of the Cancel button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setCancelColor(String color) {
+ mCancelColor = Color.parseColor(color);
+ }
+
+ /**
+ * Set the text color of the Cancel button
+ * @param color the color you want
+ */
+ @SuppressWarnings("unused")
+ public void setCancelColor(@ColorInt int color) {
+ mCancelColor= Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ @Override
+ public boolean isThemeDark() {
+ return mThemeDark;
+ }
+
+ @Override
+ public boolean is24HourMode() {
+ return mIs24HourMode;
+ }
+
+ @Override
+ public int getAccentColor() {
+ return mAccentColor;
+ }
+
+ /**
+ * Set whether the device should vibrate when touching fields
+ * @param vibrate true if the device should vibrate when touching a field
+ */
+ public void vibrate(boolean vibrate) {
+ mVibrate = vibrate;
+ }
+
+ /**
+ * Set whether the picker should dismiss itself when it's pausing or whether it should try to survive an orientation change
+ * @param dismissOnPause true if the picker should dismiss itself
+ */
+ public void dismissOnPause(boolean dismissOnPause) {
+ mDismissOnPause = dismissOnPause;
+ }
+
+ /**
+ * Set whether an additional picker for seconds should be shown
+ * Will enable minutes picker as well if seconds picker should be shown
+ * @param enableSeconds true if the seconds picker should be shown
+ */
+ public void enableSeconds(boolean enableSeconds) {
+ if (enableSeconds) mEnableMinutes = true;
+ mEnableSeconds = enableSeconds;
+ }
+
+ /**
+ * Set whether the picker for minutes should be shown
+ * Will disable seconds if minutes are disbled
+ * @param enableMinutes true if minutes picker should be shown
+ */
+ @SuppressWarnings("unused")
+ public void enableMinutes(boolean enableMinutes) {
+ if (!enableMinutes) mEnableSeconds = false;
+ mEnableMinutes = enableMinutes;
+ }
+ @SuppressWarnings("unused")
+ public void setMinTime(int hour, int minute, int second) {
+ setMinTime(new Timepoint(hour, minute, second));
+ }
+
+ public void setMinTime(Timepoint minTime) {
+ if(mMaxTime != null && minTime.compareTo(mMaxTime) > 0)
+ throw new IllegalArgumentException("Minimum time must be smaller than the maximum time");
+ mMinTime = minTime;
+ }
+
+ @SuppressWarnings("unused")
+ public void setMaxTime(int hour, int minute, int second) {
+ setMaxTime(new Timepoint(hour, minute, second));
+ }
+
+ public void setMaxTime(Timepoint maxTime) {
+ if(mMinTime != null && maxTime.compareTo(mMinTime) < 0)
+ throw new IllegalArgumentException("Maximum time must be greater than the minimum time");
+ mMaxTime = maxTime;
+ }
+
+ @SuppressWarnings("unused")
+ public void setSelectableTimes(Timepoint[] selectableTimes) {
+ mSelectableTimes = selectableTimes;
+ Arrays.sort(mSelectableTimes);
+ }
+
+ /**
+ * Set the interval for selectable times in the TimePickerDialog
+ * This is a convenience wrapper around setSelectableTimes
+ * The interval for all three time components can be set independently
+ * @param hourInterval The interval between 2 selectable hours ([1,24])
+ * @param minuteInterval The interval between 2 selectable minutes ([1,60])
+ * @param secondInterval The interval between 2 selectable seconds ([1,60])
+ */
+ public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval,
+ @IntRange(from=1, to=60) int minuteInterval,
+ @IntRange(from=1, to=60) int secondInterval) {
+ List timepoints = new ArrayList<>();
+
+ int hour = 0;
+ while (hour < 24) {
+ int minute = 0;
+ while (minute < 60) {
+ int second = 0;
+ while (second < 60) {
+ timepoints.add(new Timepoint(hour, minute, second));
+ second += secondInterval;
+ }
+ minute += minuteInterval;
+ }
+ hour += hourInterval;
+ }
+ setSelectableTimes(timepoints.toArray(new Timepoint[timepoints.size()]));
+ }
+
+ /**
+ * Set the interval for selectable times in the TimePickerDialog
+ * This is a convenience wrapper around setSelectableTimes
+ * The interval for all three time components can be set independently
+ * @param hourInterval The interval between 2 selectable hours ([1,24])
+ * @param minuteInterval The interval between 2 selectable minutes ([1,60])
+ */
+ public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval,
+ @IntRange(from=1, to=60) int minuteInterval) {
+ setTimeInterval(hourInterval, minuteInterval, 1);
+ }
+
+ /**
+ * Set the interval for selectable times in the TimePickerDialog
+ * This is a convenience wrapper around setSelectableTimes
+ * The interval for all three time components can be set independently
+ * @param hourInterval The interval between 2 selectable hours ([1,24])
+ */
+ @SuppressWarnings("unused")
+ public void setTimeInterval(@IntRange(from=1, to=24) int hourInterval) {
+ setTimeInterval(hourInterval, 1);
+ }
+
+ public void setOnTimeSetListener(OnTimeSetListener callback) {
+ mCallback = callback;
+ }
+
+ public void setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
+ mOnCancelListener = onCancelListener;
+ }
+
+ @SuppressWarnings("unused")
+ public void setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) {
+ mOnDismissListener = onDismissListener;
+ }
+
+ public void setStartTime(int hourOfDay, int minute, int second) {
+ mInitialTime = roundToNearest(new Timepoint(hourOfDay, minute, second));
+ mInKbMode = false;
+ }
+
+ @SuppressWarnings("unused")
+ public void setStartTime(int hourOfDay, int minute) {
+ setStartTime(hourOfDay, minute, 0);
+ }
+
+ /**
+ * Set the label for the Ok button (max 12 characters)
+ * @param okString A literal String to be used as the Ok button label
+ */
+ @SuppressWarnings("unused")
+ public void setOkText(String okString) {
+ mOkString = okString;
+ }
+
+ /**
+ * Set the label for the Ok button (max 12 characters)
+ * @param okResid A resource ID to be used as the Ok button label
+ */
+ @SuppressWarnings("unused")
+ public void setOkText(@StringRes int okResid) {
+ mOkString = null;
+ mOkResid = okResid;
+ }
+
+ /**
+ * Set the label for the Cancel button (max 12 characters)
+ * @param cancelString A literal String to be used as the Cancel button label
+ */
+ @SuppressWarnings("unused")
+ public void setCancelText(String cancelString) {
+ mCancelString = cancelString;
+ }
+
+ /**
+ * Set the label for the Cancel button (max 12 characters)
+ * @param cancelResid A resource ID to be used as the Cancel button label
+ */
+ @SuppressWarnings("unused")
+ public void setCancelText(@StringRes int cancelResid) {
+ mCancelString = null;
+ mCancelResid = cancelResid;
+ }
+
+ /**
+ * Set which layout version the picker should use
+ * @param version The version to use
+ */
+ public void setVersion(Version version) {
+ mVersion = version;
+ }
+
+ @Override
+ public Version getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_INITIAL_TIME)
+ && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
+ mInitialTime = savedInstanceState.getParcelable(KEY_INITIAL_TIME);
+ mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
+ mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE);
+ mTitle = savedInstanceState.getString(KEY_TITLE);
+ mThemeDark = savedInstanceState.getBoolean(KEY_THEME_DARK);
+ mThemeDarkChanged = savedInstanceState.getBoolean(KEY_THEME_DARK_CHANGED);
+ mAccentColor = savedInstanceState.getInt(KEY_ACCENT);
+ mVibrate = savedInstanceState.getBoolean(KEY_VIBRATE);
+ mDismissOnPause = savedInstanceState.getBoolean(KEY_DISMISS);
+ mSelectableTimes = (Timepoint[])savedInstanceState.getParcelableArray(KEY_SELECTABLE_TIMES);
+ mMinTime = savedInstanceState.getParcelable(KEY_MIN_TIME);
+ mMaxTime = savedInstanceState.getParcelable(KEY_MAX_TIME);
+ mEnableSeconds = savedInstanceState.getBoolean(KEY_ENABLE_SECONDS);
+ mEnableMinutes = savedInstanceState.getBoolean(KEY_ENABLE_MINUTES);
+ mOkResid = savedInstanceState.getInt(KEY_OK_RESID);
+ mOkString = savedInstanceState.getString(KEY_OK_STRING);
+ mOkColor = savedInstanceState.getInt(KEY_OK_COLOR);
+ mCancelResid = savedInstanceState.getInt(KEY_CANCEL_RESID);
+ mCancelString = savedInstanceState.getString(KEY_CANCEL_STRING);
+ mCancelColor = savedInstanceState.getInt(KEY_CANCEL_COLOR);
+ mVersion = (Version) savedInstanceState.getSerializable(KEY_VERSION);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ int viewRes = mVersion == Version.VERSION_1 ? R.layout.mdtp_time_picker_dialog : R.layout.mdtp_time_picker_dialog_v2;
+ View view = inflater.inflate(viewRes, container,false);
+ KeyboardListener keyboardListener = new KeyboardListener();
+ view.findViewById(R.id.mdtp_time_picker_dialog).setOnKeyListener(keyboardListener);
+
+ // If an accent color has not been set manually, get it from the context
+ if (mAccentColor == -1) {
+ mAccentColor = Utils.getAccentColorFromThemeIfAvailable(getActivity());
+ }
+
+ // if theme mode has not been set by java code, check if it is specified in Style.xml
+ if (!mThemeDarkChanged) {
+ mThemeDark = Utils.isDarkTheme(getActivity(), mThemeDark);
+ }
+
+ Resources res = getResources();
+ Context context = getActivity();
+ mHourPickerDescription = res.getString(R.string.mdtp_hour_picker_description);
+ mSelectHours = res.getString(R.string.mdtp_select_hours);
+ mMinutePickerDescription = res.getString(R.string.mdtp_minute_picker_description);
+ mSelectMinutes = res.getString(R.string.mdtp_select_minutes);
+ mSecondPickerDescription = res.getString(R.string.mdtp_second_picker_description);
+ mSelectSeconds = res.getString(R.string.mdtp_select_seconds);
+ mSelectedColor = ContextCompat.getColor(context, R.color.mdtp_white);
+ mUnselectedColor = ContextCompat.getColor(context, R.color.mdtp_accent_color_focused);
+
+ mHourView = (TextView) view.findViewById(R.id.mdtp_hours);
+ mHourView.setOnKeyListener(keyboardListener);
+ mHourSpaceView = (TextView) view.findViewById(R.id.mdtp_hour_space);
+ mMinuteSpaceView = (TextView) view.findViewById(R.id.mdtp_minutes_space);
+ mMinuteView = (TextView) view.findViewById(R.id.mdtp_minutes);
+ mMinuteView.setOnKeyListener(keyboardListener);
+ mSecondSpaceView = (TextView) view.findViewById(R.id.mdtp_seconds_space);
+ mSecondView = (TextView) view.findViewById(R.id.mdtp_seconds);
+ mSecondView.setOnKeyListener(keyboardListener);
+ mAmTextView = (TextView) view.findViewById(R.id.mdtp_am_label);
+ mAmTextView.setOnKeyListener(keyboardListener);
+ mPmTextView = (TextView) view.findViewById(R.id.mdtp_pm_label);
+ mPmTextView.setOnKeyListener(keyboardListener);
+ mAmPmLayout = view.findViewById(R.id.mdtp_ampm_layout);
+ String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
+ mAmText = amPmTexts[0];
+ mPmText = amPmTexts[1];
+
+ mHapticFeedbackController = new HapticFeedbackController(getActivity());
+
+ if(mTimePicker != null) {
+ mInitialTime = new Timepoint(mTimePicker.getHours(), mTimePicker.getMinutes(), mTimePicker.getSeconds());
+ }
+
+ mInitialTime = roundToNearest(mInitialTime);
+
+ mTimePicker = (RadialPickerLayout) view.findViewById(R.id.mdtp_time_picker);
+ mTimePicker.setOnValueSelectedListener(this);
+ mTimePicker.setOnKeyListener(keyboardListener);
+ mTimePicker.initialize(getActivity(), this, mInitialTime, mIs24HourMode);
+
+ int currentItemShowing = HOUR_INDEX;
+ if (savedInstanceState != null &&
+ savedInstanceState.containsKey(KEY_CURRENT_ITEM_SHOWING)) {
+ currentItemShowing = savedInstanceState.getInt(KEY_CURRENT_ITEM_SHOWING);
+ }
+ setCurrentItemShowing(currentItemShowing, false, true, true);
+ mTimePicker.invalidate();
+
+ mHourView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentItemShowing(HOUR_INDEX, true, false, true);
+ tryVibrate();
+ }
+ });
+ mMinuteView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentItemShowing(MINUTE_INDEX, true, false, true);
+ tryVibrate();
+ }
+ });
+ mSecondView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setCurrentItemShowing(SECOND_INDEX, true, false, true);
+ tryVibrate();
+ }
+ });
+
+ mOkButton = (Button) view.findViewById(R.id.mdtp_ok);
+ mOkButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mInKbMode && isTypedTimeFullyLegal()) {
+ finishKbMode(false);
+ } else {
+ tryVibrate();
+ }
+ notifyOnDateListener();
+ dismiss();
+ }
+ });
+ mOkButton.setOnKeyListener(keyboardListener);
+ mOkButton.setTypeface(TypefaceHelper.get(context, "Roboto-Medium"));
+ if(mOkString != null) mOkButton.setText(mOkString);
+ else mOkButton.setText(mOkResid);
+
+ mCancelButton = (Button) view.findViewById(R.id.mdtp_cancel);
+ mCancelButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if (getDialog() != null) getDialog().cancel();
+ }
+ });
+ mCancelButton.setTypeface(TypefaceHelper.get(context, "Roboto-Medium"));
+ if(mCancelString != null) mCancelButton.setText(mCancelString);
+ else mCancelButton.setText(mCancelResid);
+ mCancelButton.setVisibility(isCancelable() ? View.VISIBLE : View.GONE);
+
+ // Enable or disable the AM/PM view.
+ if (mIs24HourMode) {
+ mAmPmLayout.setVisibility(View.GONE);
+ } else {
+ OnClickListener listener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Don't do anything if either AM or PM are disabled
+ if (isAmDisabled() || isPmDisabled()) return;
+
+ tryVibrate();
+ int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
+ if (amOrPm == AM) {
+ amOrPm = PM;
+ } else if (amOrPm == PM) {
+ amOrPm = AM;
+ }
+ mTimePicker.setAmOrPm(amOrPm);
+ }
+ };
+ mAmTextView .setVisibility(View.GONE);
+ mPmTextView.setVisibility(View.VISIBLE);
+ mAmPmLayout.setOnClickListener(listener);
+ if (mVersion == Version.VERSION_2) {
+ mAmTextView.setText(mAmText);
+ mPmTextView.setText(mPmText);
+ mAmTextView.setVisibility(View.VISIBLE);
+ }
+ updateAmPmDisplay(mInitialTime.isAM() ? AM : PM);
+
+ }
+
+ // Disable seconds picker
+ if (!mEnableSeconds) {
+ mSecondView.setVisibility(View.GONE);
+ view.findViewById(R.id.mdtp_separator_seconds).setVisibility(View.GONE);
+ }
+
+ // Disable minutes picker
+ if (!mEnableMinutes) {
+ mMinuteSpaceView.setVisibility(View.GONE);
+ view.findViewById(R.id.mdtp_separator).setVisibility(View.GONE);
+ }
+
+ // Center stuff depending on what's visible
+ boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
+ // Landscape layout is radically different
+ if (isLandscape) {
+ if (!mEnableMinutes && !mEnableSeconds) {
+ // Just the hour
+ // Put the hour above the center
+ RelativeLayout.LayoutParams paramsHour = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsHour.addRule(RelativeLayout.ABOVE, R.id.mdtp_center_view);
+ paramsHour.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ mHourSpaceView.setLayoutParams(paramsHour);
+ if (mIs24HourMode) {
+ // Hour + Am/Pm indicator
+ // Put the am / pm indicator next to the hour
+ RelativeLayout.LayoutParams paramsAmPm = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsAmPm.addRule(RelativeLayout.RIGHT_OF, R.id.mdtp_hour_space);
+ mAmPmLayout.setLayoutParams(paramsAmPm);
+ }
+ } else if (!mEnableSeconds && mIs24HourMode) {
+ // Hour + Minutes
+ // Put the separator above the center
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsSeparator.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ paramsSeparator.addRule(RelativeLayout.ABOVE, R.id.mdtp_center_view);
+ TextView separatorView = (TextView) view.findViewById(R.id.mdtp_separator);
+ separatorView.setLayoutParams(paramsSeparator);
+ } else if (!mEnableSeconds) {
+ // Hour + Minutes + Am/Pm indicator
+ // Put separator above the center
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsSeparator.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ paramsSeparator.addRule(RelativeLayout.ABOVE, R.id.mdtp_center_view);
+ TextView separatorView = (TextView) view.findViewById(R.id.mdtp_separator);
+ separatorView.setLayoutParams(paramsSeparator);
+ // Put the am/pm indicator below the separator
+ RelativeLayout.LayoutParams paramsAmPm = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsAmPm.addRule(RelativeLayout.CENTER_IN_PARENT);
+ paramsAmPm.addRule(RelativeLayout.BELOW, R.id.mdtp_center_view);
+ mAmPmLayout.setLayoutParams(paramsAmPm);
+ } else if (mIs24HourMode) {
+ // Hour + Minutes + Seconds
+ // Put the separator above the center
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsSeparator.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ paramsSeparator.addRule(RelativeLayout.ABOVE, R.id.mdtp_seconds_space);
+ TextView separatorView = (TextView) view.findViewById(R.id.mdtp_separator);
+ separatorView.setLayoutParams(paramsSeparator);
+ // Center the seconds
+ RelativeLayout.LayoutParams paramsSeconds = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsSeconds.addRule(RelativeLayout.CENTER_IN_PARENT);
+ mSecondSpaceView.setLayoutParams(paramsSeconds);
+ } else {
+ // Hour + Minutes + Seconds + Am/Pm Indicator
+ // Put the seconds on the center
+ RelativeLayout.LayoutParams paramsSeconds = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsSeconds.addRule(RelativeLayout.CENTER_IN_PARENT);
+ mSecondSpaceView.setLayoutParams(paramsSeconds);
+ // Put the separator above the seconds
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsSeparator.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ paramsSeparator.addRule(RelativeLayout.ABOVE, R.id.mdtp_seconds_space);
+ TextView separatorView = (TextView) view.findViewById(R.id.mdtp_separator);
+ separatorView.setLayoutParams(paramsSeparator);
+ // Put the Am/Pm indicator below the seconds
+ RelativeLayout.LayoutParams paramsAmPm = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ paramsAmPm.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ paramsAmPm.addRule(RelativeLayout.BELOW, R.id.mdtp_seconds_space);
+ mAmPmLayout.setLayoutParams(paramsAmPm);
+ }
+ }
+ else if (mIs24HourMode && !mEnableSeconds && mEnableMinutes) {
+ // center first separator
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
+ );
+ paramsSeparator.addRule(RelativeLayout.CENTER_IN_PARENT);
+ TextView separatorView = (TextView) view.findViewById(R.id.mdtp_separator);
+ separatorView.setLayoutParams(paramsSeparator);
+ } else if (!mEnableMinutes && !mEnableSeconds) {
+ // center the hour
+ RelativeLayout.LayoutParams paramsHour = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
+ );
+ paramsHour.addRule(RelativeLayout.CENTER_IN_PARENT);
+ mHourSpaceView.setLayoutParams(paramsHour);
+
+ if (!mIs24HourMode) {
+ RelativeLayout.LayoutParams paramsAmPm = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
+ );
+ paramsAmPm.addRule(RelativeLayout.RIGHT_OF, R.id.mdtp_hour_space);
+ paramsAmPm.addRule(RelativeLayout.ALIGN_BASELINE, R.id.mdtp_hour_space);
+ mAmPmLayout.setLayoutParams(paramsAmPm);
+ }
+ } else if (mEnableSeconds) {
+ // link separator to minutes
+ final View separator = view.findViewById(R.id.mdtp_separator);
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
+ );
+ paramsSeparator.addRule(RelativeLayout.LEFT_OF, R.id.mdtp_minutes_space);
+ paramsSeparator.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
+ separator.setLayoutParams(paramsSeparator);
+
+ if (!mIs24HourMode) {
+ // center minutes
+ RelativeLayout.LayoutParams paramsMinutes = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
+ );
+ paramsMinutes.addRule(RelativeLayout.CENTER_IN_PARENT);
+ mMinuteSpaceView.setLayoutParams(paramsMinutes);
+ } else {
+ // move minutes to right of center
+ RelativeLayout.LayoutParams paramsMinutes = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
+ );
+ paramsMinutes.addRule(RelativeLayout.RIGHT_OF, R.id.mdtp_center_view);
+ mMinuteSpaceView.setLayoutParams(paramsMinutes);
+ }
+ }
+
+ mAllowAutoAdvance = true;
+ setHour(mInitialTime.getHour(), true);
+ setMinute(mInitialTime.getMinute());
+ setSecond(mInitialTime.getSecond());
+
+ // Set up for keyboard mode.
+ mDoublePlaceholderText = res.getString(R.string.mdtp_time_placeholder);
+ mDeletedKeyFormat = res.getString(R.string.mdtp_deleted_key);
+ mPlaceholderText = mDoublePlaceholderText.charAt(0);
+ mAmKeyCode = mPmKeyCode = -1;
+ generateLegalTimesTree();
+ if (mInKbMode) {
+ mTypedTimes = savedInstanceState.getIntegerArrayList(KEY_TYPED_TIMES);
+ tryStartingKbMode(-1);
+ mHourView.invalidate();
+ } else if (mTypedTimes == null) {
+ mTypedTimes = new ArrayList<>();
+ }
+
+ // Set the title (if any)
+ TextView timePickerHeader = (TextView) view.findViewById(R.id.mdtp_time_picker_header);
+ if (!mTitle.isEmpty()) {
+ timePickerHeader.setVisibility(TextView.VISIBLE);
+ timePickerHeader.setText(mTitle.toUpperCase(Locale.getDefault()));
+ }
+
+ // Set the theme at the end so that the initialize()s above don't counteract the theme.
+ timePickerHeader.setBackgroundColor(Utils.darkenColor(mAccentColor));
+ view.findViewById(R.id.mdtp_time_display_background).setBackgroundColor(mAccentColor);
+ view.findViewById(R.id.mdtp_time_display).setBackgroundColor(mAccentColor);
+
+ // Button text can have a different color
+ if (mOkColor != -1) mOkButton.setTextColor(mOkColor);
+ else mOkButton.setTextColor(mAccentColor);
+ if (mCancelColor != -1) mCancelButton.setTextColor(mCancelColor);
+ else mCancelButton.setTextColor(mAccentColor);
+
+ if(getDialog() == null) {
+ view.findViewById(R.id.mdtp_done_background).setVisibility(View.GONE);
+ }
+
+ int circleBackground = ContextCompat.getColor(context, R.color.mdtp_circle_background);
+ int backgroundColor = ContextCompat.getColor(context, R.color.mdtp_background_color);
+ int darkBackgroundColor = ContextCompat.getColor(context, R.color.mdtp_light_gray);
+ int lightGray = ContextCompat.getColor(context, R.color.mdtp_light_gray);
+
+ mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground);
+ view.findViewById(R.id.mdtp_time_picker_dialog).setBackgroundColor(mThemeDark ? darkBackgroundColor : backgroundColor);
+ return view;
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ ViewGroup viewGroup = (ViewGroup) getView();
+ if (viewGroup != null) {
+ viewGroup.removeAllViewsInLayout();
+ View view = onCreateView(getActivity().getLayoutInflater(), viewGroup, null);
+ viewGroup.addView(view);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mHapticFeedbackController.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHapticFeedbackController.stop();
+ if(mDismissOnPause) dismiss();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ if(mOnCancelListener != null) mOnCancelListener.onCancel(dialog);
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ if(mOnDismissListener != null) mOnDismissListener.onDismiss(dialog);
+ }
+
+ @Override
+ public void tryVibrate() {
+ if(mVibrate) mHapticFeedbackController.tryVibrate();
+ }
+
+ private void updateAmPmDisplay(int amOrPm) {
+ if (mVersion == Version.VERSION_2) {
+ if (amOrPm == AM) {
+ mAmTextView.setTextColor(mSelectedColor);
+ mPmTextView.setTextColor(mUnselectedColor);
+ Utils.tryAccessibilityAnnounce(mTimePicker, mAmText);
+ } else {
+ mAmTextView.setTextColor(mUnselectedColor);
+ mPmTextView.setTextColor(mSelectedColor);
+ Utils.tryAccessibilityAnnounce(mTimePicker, mPmText);
+ }
+ } else {
+ if (amOrPm == AM) {
+ mPmTextView.setText(mAmText);
+ Utils.tryAccessibilityAnnounce(mTimePicker, mAmText);
+ mPmTextView.setContentDescription(mAmText);
+ } else if (amOrPm == PM){
+ mPmTextView.setText(mPmText);
+ Utils.tryAccessibilityAnnounce(mTimePicker, mPmText);
+ mPmTextView.setContentDescription(mPmText);
+ } else {
+ mPmTextView.setText(mDoublePlaceholderText);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ if (mTimePicker != null) {
+ outState.putParcelable(KEY_INITIAL_TIME, mTimePicker.getTime());
+ outState.putBoolean(KEY_IS_24_HOUR_VIEW, mIs24HourMode);
+ outState.putInt(KEY_CURRENT_ITEM_SHOWING, mTimePicker.getCurrentItemShowing());
+ outState.putBoolean(KEY_IN_KB_MODE, mInKbMode);
+ if (mInKbMode) {
+ outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
+ }
+ outState.putString(KEY_TITLE, mTitle);
+ outState.putBoolean(KEY_THEME_DARK, mThemeDark);
+ outState.putBoolean(KEY_THEME_DARK_CHANGED, mThemeDarkChanged);
+ outState.putInt(KEY_ACCENT, mAccentColor);
+ outState.putBoolean(KEY_VIBRATE, mVibrate);
+ outState.putBoolean(KEY_DISMISS, mDismissOnPause);
+ outState.putParcelableArray(KEY_SELECTABLE_TIMES, mSelectableTimes);
+ outState.putParcelable(KEY_MIN_TIME, mMinTime);
+ outState.putParcelable(KEY_MAX_TIME, mMaxTime);
+ outState.putBoolean(KEY_ENABLE_SECONDS, mEnableSeconds);
+ outState.putBoolean(KEY_ENABLE_MINUTES, mEnableMinutes);
+ outState.putInt(KEY_OK_RESID, mOkResid);
+ outState.putString(KEY_OK_STRING, mOkString);
+ outState.putInt(KEY_OK_COLOR, mOkColor);
+ outState.putInt(KEY_CANCEL_RESID, mCancelResid);
+ outState.putString(KEY_CANCEL_STRING, mCancelString);
+ outState.putInt(KEY_CANCEL_COLOR, mCancelColor);
+ outState.putSerializable(KEY_VERSION, mVersion);
+ }
+ }
+
+ /**
+ * Called by the picker for updating the header display.
+ */
+ @Override
+ public void onValueSelected(Timepoint newValue) {
+ setHour(newValue.getHour(), false);
+ mTimePicker.setContentDescription(mHourPickerDescription + ": " + newValue.getHour());
+ setMinute(newValue.getMinute());
+ mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue.getMinute());
+ setSecond(newValue.getSecond());
+ mTimePicker.setContentDescription(mSecondPickerDescription + ": " + newValue.getSecond());
+ if(!mIs24HourMode) updateAmPmDisplay(newValue.isAM() ? AM : PM);
+ }
+
+ @Override
+ public void advancePicker(int index) {
+ if(!mAllowAutoAdvance) return;
+ if(index == HOUR_INDEX && mEnableMinutes) {
+ setCurrentItemShowing(MINUTE_INDEX, true, true, false);
+
+ String announcement = mSelectHours + ". " + mTimePicker.getMinutes();
+ Utils.tryAccessibilityAnnounce(mTimePicker, announcement);
+ } else if(index == MINUTE_INDEX && mEnableSeconds) {
+ setCurrentItemShowing(SECOND_INDEX, true, true, false);
+
+ String announcement = mSelectMinutes+". " + mTimePicker.getSeconds();
+ Utils.tryAccessibilityAnnounce(mTimePicker, announcement);
+ }
+ }
+
+ @Override
+ public void enablePicker() {
+ if(!isTypedTimeFullyLegal()) mTypedTimes.clear();
+ finishKbMode(true);
+ }
+
+ public boolean isOutOfRange(Timepoint current) {
+ if(mMinTime != null && mMinTime.compareTo(current) > 0) return true;
+
+ if(mMaxTime != null && mMaxTime.compareTo(current) < 0) return true;
+
+ if(mSelectableTimes != null) return !Arrays.asList(mSelectableTimes).contains(current);
+
+ return false;
+ }
+
+ @Override
+ public boolean isOutOfRange(Timepoint current, int index) {
+ if(current == null) return false;
+
+ if(index == HOUR_INDEX) {
+ if(mMinTime != null && mMinTime.getHour() > current.getHour()) return true;
+
+ if(mMaxTime != null && mMaxTime.getHour()+1 <= current.getHour()) return true;
+
+ if(mSelectableTimes != null) {
+ for(Timepoint t : mSelectableTimes) {
+ if(t.getHour() == current.getHour()) return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+ else if(index == MINUTE_INDEX) {
+ if(mMinTime != null) {
+ Timepoint roundedMin = new Timepoint(mMinTime.getHour(), mMinTime.getMinute());
+ if (roundedMin.compareTo(current) > 0) return true;
+ }
+
+ if(mMaxTime != null) {
+ Timepoint roundedMax = new Timepoint(mMaxTime.getHour(), mMaxTime.getMinute(), 59);
+ if (roundedMax.compareTo(current) < 0) return true;
+ }
+
+ if(mSelectableTimes != null) {
+ for(Timepoint t : mSelectableTimes) {
+ if(t.getHour() == current.getHour() && t.getMinute() == current.getMinute()) return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+ else return isOutOfRange(current);
+ }
+
+ @Override
+ public boolean isAmDisabled() {
+ Timepoint midday = new Timepoint(12);
+
+ if(mMinTime != null && mMinTime.compareTo(midday) > 0) return true;
+
+ if(mSelectableTimes != null) {
+ for(Timepoint t : mSelectableTimes) if(t.compareTo(midday) < 0) return false;
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isPmDisabled() {
+ Timepoint midday = new Timepoint(12);
+
+ if(mMaxTime != null && mMaxTime.compareTo(midday) < 0) return true;
+
+ if(mSelectableTimes != null) {
+ for(Timepoint t : mSelectableTimes) if(t.compareTo(midday) >= 0) return false;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Round a given Timepoint to the nearest valid Timepoint
+ * @param time Timepoint - The timepoint to round
+ * @return Timepoint - The nearest valid Timepoint
+ */
+ private Timepoint roundToNearest(@NonNull Timepoint time) {
+ return roundToNearest(time, null);
+ }
+
+ @Override
+ public Timepoint roundToNearest(@NonNull Timepoint time, @Nullable Timepoint.TYPE type) {
+
+ if(mMinTime != null && mMinTime.compareTo(time) > 0) return mMinTime;
+
+ if(mMaxTime != null && mMaxTime.compareTo(time) < 0) return mMaxTime;
+ if(mSelectableTimes != null) {
+ int currentDistance = Integer.MAX_VALUE;
+ Timepoint output = time;
+ for(Timepoint t : mSelectableTimes) {
+ Log.d("Timepoing", "" + type + " " + time + " compared to " + t.toString());
+ // type == null: no restrictions
+ // type == HOUR: do not change the hour
+ if (type == Timepoint.TYPE.HOUR && t.getHour() != time.getHour()) continue;
+ // type == MINUTE: do not change hour or minute
+ if (type == Timepoint.TYPE.MINUTE && t.getHour() != time.getHour() && t.getMinute() != time.getMinute()) continue;
+ // type == SECOND: cannot change anything, return input
+ if (type == Timepoint.TYPE.SECOND) return time;
+ Log.d("Timepoing", "Comparing");
+ int newDistance = Math.abs(t.compareTo(time));
+ if (newDistance < currentDistance) {
+ currentDistance = newDistance;
+ output = t;
+ }
+ else break;
+ }
+ return output;
+ }
+
+ return time;
+ }
+
+ private void setHour(int value, boolean announce) {
+ String format;
+ if (mIs24HourMode) {
+ format = "%02d";
+ } else {
+ format = "%d";
+ value = value % 12;
+ if (value == 0) {
+ value = 12;
+ }
+ }
+
+ CharSequence text = String.format(format, value);
+ mHourView.setText(text);
+ mHourSpaceView.setText(text);
+ if (announce) {
+ Utils.tryAccessibilityAnnounce(mTimePicker, text);
+ }
+ }
+
+ private void setMinute(int value) {
+ if (value == 60) {
+ value = 0;
+ }
+ CharSequence text = String.format(Locale.getDefault(), "%02d", value);
+ Utils.tryAccessibilityAnnounce(mTimePicker, text);
+ mMinuteView.setText(text);
+ mMinuteSpaceView.setText(text);
+ }
+
+ private void setSecond(int value) {
+ if(value == 60) {
+ value = 0;
+ }
+ CharSequence text = String.format(Locale.getDefault(), "%02d", value);
+ Utils.tryAccessibilityAnnounce(mTimePicker, text);
+ mSecondView.setText(text);
+ mSecondSpaceView.setText(text);
+ }
+
+ // Show either Hours or Minutes.
+ private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
+ boolean announce) {
+ mTimePicker.setCurrentItemShowing(index, animateCircle);
+
+ TextView labelToAnimate;
+ switch(index) {
+ case HOUR_INDEX:
+ int hours = mTimePicker.getHours();
+ if (!mIs24HourMode) {
+ hours = hours % 12;
+ }
+ mTimePicker.setContentDescription(mHourPickerDescription + ": " + hours);
+ if (announce) {
+ Utils.tryAccessibilityAnnounce(mTimePicker, mSelectHours);
+ }
+ labelToAnimate = mHourView;
+ break;
+ case MINUTE_INDEX:
+ int minutes = mTimePicker.getMinutes();
+ mTimePicker.setContentDescription(mMinutePickerDescription + ": " + minutes);
+ if (announce) {
+ Utils.tryAccessibilityAnnounce(mTimePicker, mSelectMinutes);
+ }
+ labelToAnimate = mMinuteView;
+ break;
+ default:
+ int seconds = mTimePicker.getSeconds();
+ mTimePicker.setContentDescription(mSecondPickerDescription + ": " + seconds);
+ if (announce) {
+ Utils.tryAccessibilityAnnounce(mTimePicker, mSelectSeconds);
+ }
+ labelToAnimate = mSecondView;
+ }
+
+ int hourColor = (index == HOUR_INDEX) ? mSelectedColor : mUnselectedColor;
+ int minuteColor = (index == MINUTE_INDEX) ? mSelectedColor : mUnselectedColor;
+ int secondColor = (index == SECOND_INDEX) ? mSelectedColor : mUnselectedColor;
+ mHourView.setTextColor(hourColor);
+ mMinuteView.setTextColor(minuteColor);
+ mSecondView.setTextColor(secondColor);
+
+ ObjectAnimator pulseAnimator = Utils.getPulseAnimator(labelToAnimate, 0.85f, 1.1f);
+ if (delayLabelAnimate) {
+ pulseAnimator.setStartDelay(PULSE_ANIMATOR_DELAY);
+ }
+ pulseAnimator.start();
+ }
+
+ /**
+ * For keyboard mode, processes key events.
+ * @param keyCode the pressed key.
+ * @return true if the key was successfully processed, false otherwise.
+ */
+ private boolean processKeyUp(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
+ if(isCancelable()) dismiss();
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+ if(mInKbMode) {
+ if (isTypedTimeFullyLegal()) {
+ finishKbMode(true);
+ }
+ return true;
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ if (mInKbMode) {
+ if (!isTypedTimeFullyLegal()) {
+ return true;
+ }
+ finishKbMode(false);
+ }
+ if (mCallback != null) {
+ mCallback.onTimeSet(this,
+ mTimePicker.getHours(), mTimePicker.getMinutes(), mTimePicker.getSeconds());
+ }
+ dismiss();
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+ if (mInKbMode) {
+ if (!mTypedTimes.isEmpty()) {
+ int deleted = deleteLastTypedKey();
+ String deletedKeyStr;
+ if (deleted == getAmOrPmKeyCode(AM)) {
+ deletedKeyStr = mAmText;
+ } else if (deleted == getAmOrPmKeyCode(PM)) {
+ deletedKeyStr = mPmText;
+ } else {
+ deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
+ }
+ Utils.tryAccessibilityAnnounce(mTimePicker,
+ String.format(mDeletedKeyFormat, deletedKeyStr));
+ updateDisplay(true);
+ }
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+ || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+ || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+ || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+ || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
+ || (!mIs24HourMode &&
+ (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
+ if (!mInKbMode) {
+ if (mTimePicker == null) {
+ // Something's wrong, because time picker should definitely not be null.
+ Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
+ return true;
+ }
+ mTypedTimes.clear();
+ tryStartingKbMode(keyCode);
+ return true;
+ }
+ // We're already in keyboard mode.
+ if (addKeyIfLegal(keyCode)) {
+ updateDisplay(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Try to start keyboard mode with the specified key, as long as the timepicker is not in the
+ * middle of a touch-event.
+ * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
+ * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
+ * key.
+ */
+ private void tryStartingKbMode(int keyCode) {
+ if (mTimePicker.trySettingInputEnabled(false) &&
+ (keyCode == -1 || addKeyIfLegal(keyCode))) {
+ mInKbMode = true;
+ mOkButton.setEnabled(false);
+ updateDisplay(false);
+ }
+ }
+
+ private boolean addKeyIfLegal(int keyCode) {
+ // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
+ // we'll need to see if AM/PM have been typed.
+ int textSize = 6;
+ if (mEnableMinutes && !mEnableSeconds) textSize = 4;
+ if (!mEnableMinutes && !mEnableSeconds) textSize = 2;
+ if ((mIs24HourMode && mTypedTimes.size() == textSize) ||
+ (!mIs24HourMode && isTypedTimeFullyLegal())) {
+ return false;
+ }
+
+ mTypedTimes.add(keyCode);
+ if (!isTypedTimeLegalSoFar()) {
+ deleteLastTypedKey();
+ return false;
+ }
+
+ int val = getValFromKeyCode(keyCode);
+ Utils.tryAccessibilityAnnounce(mTimePicker, String.format(Locale.getDefault(), "%d", val));
+ // Automatically fill in 0's if AM or PM was legally entered.
+ if (isTypedTimeFullyLegal()) {
+ if (!mIs24HourMode && mTypedTimes.size() <= (textSize - 1)) {
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ }
+ mOkButton.setEnabled(true);
+ }
+
+ return true;
+ }
+
+ /**
+ * Traverse the tree to see if the keys that have been typed so far are legal as is,
+ * or may become legal as more keys are typed (excluding backspace).
+ */
+ private boolean isTypedTimeLegalSoFar() {
+ Node node = mLegalTimesTree;
+ for (int keyCode : mTypedTimes) {
+ node = node.canReach(keyCode);
+ if (node == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check if the time that has been typed so far is completely legal, as is.
+ */
+ private boolean isTypedTimeFullyLegal() {
+ if (mIs24HourMode) {
+ // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
+ // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
+ int[] values = getEnteredTime(null);
+ return (values[0] >= 0 && values[1] >= 0 && values[1] < 60 && values[2] >= 0 && values[2] < 60);
+ } else {
+ // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
+ // legally added at specific times based on the tree's algorithm.
+ return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
+ mTypedTimes.contains(getAmOrPmKeyCode(PM)));
+ }
+ }
+
+ private int deleteLastTypedKey() {
+ int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
+ if (!isTypedTimeFullyLegal()) {
+ mOkButton.setEnabled(false);
+ }
+ return deleted;
+ }
+
+ /**
+ * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
+ * @param updateDisplays If true, update the displays with the relevant time.
+ */
+ private void finishKbMode(boolean updateDisplays) {
+ mInKbMode = false;
+ if (!mTypedTimes.isEmpty()) {
+ int values[] = getEnteredTime(null);
+ mTimePicker.setTime(new Timepoint(values[0], values[1], values[2]));
+ if (!mIs24HourMode) {
+ mTimePicker.setAmOrPm(values[3]);
+ }
+ mTypedTimes.clear();
+ }
+ if (updateDisplays) {
+ updateDisplay(false);
+ mTimePicker.trySettingInputEnabled(true);
+ }
+ }
+
+ /**
+ * Update the hours, minutes, seconds and AM/PM displays with the typed times. If the typedTimes
+ * is empty, either show an empty display (filled with the placeholder text), or update from the
+ * timepicker's values.
+ * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
+ * Otherwise, revert to the timepicker's values.
+ */
+ private void updateDisplay(boolean allowEmptyDisplay) {
+ if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
+ int hour = mTimePicker.getHours();
+ int minute = mTimePicker.getMinutes();
+ int second = mTimePicker.getSeconds();
+ setHour(hour, true);
+ setMinute(minute);
+ setSecond(second);
+ if (!mIs24HourMode) {
+ updateAmPmDisplay(hour < 12? AM : PM);
+ }
+ setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true);
+ mOkButton.setEnabled(true);
+ } else {
+ Boolean[] enteredZeros = {false, false, false};
+ int[] values = getEnteredTime(enteredZeros);
+ String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
+ String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
+ String secondFormat = (enteredZeros[1]) ? "%02d" : "%2d";
+ String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
+ String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
+ String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
+ String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
+ String secondStr = (values[2] == -1) ? mDoublePlaceholderText :
+ String.format(secondFormat, values[1]).replace(' ', mPlaceholderText);
+ mHourView.setText(hourStr);
+ mHourSpaceView.setText(hourStr);
+ mHourView.setTextColor(mUnselectedColor);
+ mMinuteView.setText(minuteStr);
+ mMinuteSpaceView.setText(minuteStr);
+ mMinuteView.setTextColor(mUnselectedColor);
+ mSecondView.setText(secondStr);
+ mSecondSpaceView.setText(secondStr);
+ mSecondView.setTextColor(mUnselectedColor);
+ if (!mIs24HourMode) {
+ updateAmPmDisplay(values[3]);
+ }
+ }
+ }
+
+ private static int getValFromKeyCode(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_0:
+ return 0;
+ case KeyEvent.KEYCODE_1:
+ return 1;
+ case KeyEvent.KEYCODE_2:
+ return 2;
+ case KeyEvent.KEYCODE_3:
+ return 3;
+ case KeyEvent.KEYCODE_4:
+ return 4;
+ case KeyEvent.KEYCODE_5:
+ return 5;
+ case KeyEvent.KEYCODE_6:
+ return 6;
+ case KeyEvent.KEYCODE_7:
+ return 7;
+ case KeyEvent.KEYCODE_8:
+ return 8;
+ case KeyEvent.KEYCODE_9:
+ return 9;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Get the currently-entered time, as integer values of the hours, minutes and seconds typed.
+ * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
+ * may then be used for the caller to know whether zeros had been explicitly entered as either
+ * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
+ * @return A size-3 int array. The first value will be the hours, the second value will be the
+ * minutes, and the third will be either TimePickerDialog.AM or TimePickerDialog.PM.
+ */
+ private int[] getEnteredTime(Boolean[] enteredZeros) {
+ int amOrPm = -1;
+ int startIndex = 1;
+ if (!mIs24HourMode && isTypedTimeFullyLegal()) {
+ int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
+ if (keyCode == getAmOrPmKeyCode(AM)) {
+ amOrPm = AM;
+ } else if (keyCode == getAmOrPmKeyCode(PM)){
+ amOrPm = PM;
+ }
+ startIndex = 2;
+ }
+ int minute = -1;
+ int hour = -1;
+ int second = 0;
+ int shift = mEnableSeconds ? 2 : 0;
+ for (int i = startIndex; i <= mTypedTimes.size(); i++) {
+ int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
+ if (mEnableSeconds) {
+ if (i == startIndex) {
+ second = val;
+ } else if (i == startIndex + 1) {
+ second += 10*val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[2] = true;
+ }
+ }
+ }
+ if (mEnableMinutes) {
+ if (i == startIndex + shift) {
+ minute = val;
+ } else if (i == startIndex + shift + 1) {
+ minute += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[1] = true;
+ }
+ } else if (i == startIndex + shift + 2) {
+ hour = val;
+ } else if (i == startIndex + shift + 3) {
+ hour += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[0] = true;
+ }
+ }
+ } else {
+ if (i == startIndex + shift) {
+ hour = val;
+ } else if (i == startIndex + shift + 1) {
+ hour += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[0] = true;
+ }
+ }
+ }
+ }
+
+ return new int[] {hour, minute, second, amOrPm};
+ }
+
+ /**
+ * Get the keycode value for AM and PM in the current language.
+ */
+ private int getAmOrPmKeyCode(int amOrPm) {
+ // Cache the codes.
+ if (mAmKeyCode == -1 || mPmKeyCode == -1) {
+ // Find the first character in the AM/PM text that is unique.
+ KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ char amChar;
+ char pmChar;
+ for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) {
+ amChar = mAmText.toLowerCase(Locale.getDefault()).charAt(i);
+ pmChar = mPmText.toLowerCase(Locale.getDefault()).charAt(i);
+ if (amChar != pmChar) {
+ KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar});
+ // There should be 4 events: a down and up for both AM and PM.
+ if (events != null && events.length == 4) {
+ mAmKeyCode = events[0].getKeyCode();
+ mPmKeyCode = events[2].getKeyCode();
+ } else {
+ Log.e(TAG, "Unable to find keycodes for AM and PM.");
+ }
+ break;
+ }
+ }
+ }
+ if (amOrPm == AM) {
+ return mAmKeyCode;
+ } else if (amOrPm == PM) {
+ return mPmKeyCode;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create a tree for deciding what keys can legally be typed.
+ */
+ private void generateLegalTimesTree() {
+ // Create a quick cache of numbers to their keycodes.
+ int k0 = KeyEvent.KEYCODE_0;
+ int k1 = KeyEvent.KEYCODE_1;
+ int k2 = KeyEvent.KEYCODE_2;
+ int k3 = KeyEvent.KEYCODE_3;
+ int k4 = KeyEvent.KEYCODE_4;
+ int k5 = KeyEvent.KEYCODE_5;
+ int k6 = KeyEvent.KEYCODE_6;
+ int k7 = KeyEvent.KEYCODE_7;
+ int k8 = KeyEvent.KEYCODE_8;
+ int k9 = KeyEvent.KEYCODE_9;
+
+ // The root of the tree doesn't contain any numbers.
+ mLegalTimesTree = new Node();
+
+ // In case we're only allowing hours
+ if (!mEnableMinutes && mIs24HourMode) {
+ // The first digit may be 0-1
+ Node firstDigit = new Node(k0, k1);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 0-1, the second digit may be 0-9
+ Node secondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ firstDigit.addChild(secondDigit);
+
+ // The first digit may be 2
+ firstDigit = new Node(k2);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 2, the second digit may be 0-3
+ secondDigit = new Node(k0, k1, k2, k3);
+ firstDigit.addChild(secondDigit);
+ return;
+ }
+ if (!mEnableMinutes && !mIs24HourMode) {
+ // We'll need to use the AM/PM node a lot.
+ // Set up AM and PM to respond to "a" and "p".
+ Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
+
+ // The first digit may be 1
+ Node firstDigit = new Node(k1);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // If the first digit is 1, the second one may be am/pm 1pm
+ firstDigit.addChild(ampm);
+ // If the first digit is 1, the second digit may be 0-2
+ Node secondDigit = new Node(k0, k1, k2);
+ firstDigit.addChild(secondDigit);
+ secondDigit.addChild(ampm);
+
+ // The first digit may be 2-9
+ firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ firstDigit.addChild(ampm);
+ return;
+ }
+
+ // In case minutes are allowed
+ if (mIs24HourMode) {
+ // We'll be re-using these nodes, so we'll save them.
+ Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
+ Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ // The first digit must be followed by the second digit.
+ minuteFirstDigit.addChild(minuteSecondDigit);
+
+ if (mEnableSeconds) {
+ Node secondsFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
+ Node secondsSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondsFirstDigit.addChild(secondsSecondDigit);
+
+ // Minutes can be followed by seconds.
+ minuteSecondDigit.addChild(secondsFirstDigit);
+ }
+
+ // The first digit may be 0-1.
+ Node firstDigit = new Node(k0, k1);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 0-1, the second digit may be 0-5.
+ Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
+ Node thirdDigit = new Node(k6, k7, k8, k9);
+ // The time must now be finished. E.g. 0:55, 1:08.
+ secondDigit.addChild(thirdDigit);
+
+ // When the first digit is 0-1, the second digit may be 6-9.
+ secondDigit = new Node(k6, k7, k8, k9);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // The first digit may be 2.
+ firstDigit = new Node(k2);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 2, the second digit may be 0-3.
+ secondDigit = new Node(k0, k1, k2, k3);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 2, the second digit may be 4-5.
+ secondDigit = new Node(k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
+ secondDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 3-9.
+ firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
+ firstDigit.addChild(minuteFirstDigit);
+ } else {
+ // We'll need to use the AM/PM node a lot.
+ // Set up AM and PM to respond to "a" and "p".
+ Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
+
+ // Seconds will be used a few times as well, if enabled.
+ Node secondsFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
+ Node secondsSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondsSecondDigit.addChild(ampm);
+ secondsFirstDigit.addChild(secondsSecondDigit);
+
+ // The first hour digit may be 1.
+ Node firstDigit = new Node(k1);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour times. E.g. 1pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 0-2.
+ Node secondDigit = new Node(k0, k1, k2);
+ firstDigit.addChild(secondDigit);
+ // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
+ secondDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
+ Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
+ secondDigit.addChild(thirdDigit);
+ // The time may be finished now. E.g. 1:02pm, 1:25am.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
+ // the fourth digit may be 0-9.
+ Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ thirdDigit.addChild(fourthDigit);
+ // The time must be finished now, when seconds are disabled. E.g. 10:49am, 12:40pm.
+ fourthDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
+ // and fourth digit is 0-9, we may add seconds if enabled.
+ if (mEnableSeconds) {
+ // The time must be finished now. E.g. 10:49:01am, 12:40:59pm.
+ fourthDigit.addChild(secondsFirstDigit);
+ }
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
+ thirdDigit = new Node(k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:08am, 1:26pm.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, and the third digit is 6-9,
+ // we may add seconds is enabled.
+ if (mEnableSeconds) {
+ // The time must be finished now. E.g. 1:08:01am, 1:26:59pm.
+ thirdDigit.addChild(secondsFirstDigit);
+ }
+
+ // When the first digit is 1, the second digit may be 3-5.
+ secondDigit = new Node(k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now if seconds are disabled. E.g. 1:39am, 1:50pm.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 3-5, and the third digit is 0-9,
+ // we may add seconds if enabled.
+ if (mEnableSeconds) {
+ // The time must be finished now. E.g. 1:39:01am, 1:50:59pm.
+ thirdDigit.addChild(secondsFirstDigit);
+ }
+
+ // The hour digit may be 2-9.
+ firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 2-9, the second digit may be 0-5.
+ secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 2:57am, 9:30pm.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 2-9, and the second digit is 0-5, and third digit is 0-9, we
+ // may add seconds if enabled.
+ if (mEnableSeconds) {
+ // The time must be finished now. E.g. 2:57:01am, 9:30:59pm.
+ thirdDigit.addChild(secondsFirstDigit);
+ }
+ }
+ }
+
+ /**
+ * Simple node class to be used for traversal to check for legal times.
+ * mLegalKeys represents the keys that can be typed to get to the node.
+ * mChildren are the children that can be reached from this node.
+ */
+ private static class Node {
+ private int[] mLegalKeys;
+ private ArrayList mChildren;
+
+ public Node(int... legalKeys) {
+ mLegalKeys = legalKeys;
+ mChildren = new ArrayList<>();
+ }
+
+ public void addChild(Node child) {
+ mChildren.add(child);
+ }
+
+ public boolean containsKey(int key) {
+ for (int legalKey : mLegalKeys) {
+ if (legalKey == key) return true;
+ }
+ return false;
+ }
+
+ public Node canReach(int key) {
+ if (mChildren == null) {
+ return null;
+ }
+ for (Node child : mChildren) {
+ if (child.containsKey(key)) {
+ return child;
+ }
+ }
+ return null;
+ }
+ }
+
+ private class KeyboardListener implements OnKeyListener {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ return processKeyUp(keyCode);
+ }
+ return false;
+ }
+ }
+
+ public void notifyOnDateListener() {
+ if (mCallback != null) {
+ mCallback.onTimeSet(this, mTimePicker.getHours(), mTimePicker.getMinutes(), mTimePicker.getSeconds());
+ }
+ }
+
+ public Timepoint getSelectedTime() {
+ return mTimePicker.getTime();
+ }
+}
diff --git a/library/src/main/java/net/alhazmy13/hijridatepicker/time/Timepoint.java b/library/src/main/java/net/alhazmy13/hijridatepicker/time/Timepoint.java
new file mode 100644
index 0000000..4c40e96
--- /dev/null
+++ b/library/src/main/java/net/alhazmy13/hijridatepicker/time/Timepoint.java
@@ -0,0 +1,131 @@
+package net.alhazmy13.hijridatepicker.time;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
+
+/**
+ * Simple utility class that represents a time in the day up to second precision
+ * The time input is expected to use 24 hour mode.
+ * Fields are modulo'd into their correct ranges.
+ * It does not handle timezones.
+ *
+ * Created by wdullaer on 13/10/15.
+ */
+public class Timepoint implements Parcelable, Comparable {
+ private int hour;
+ private int minute;
+ private int second;
+
+ public enum TYPE {
+ HOUR,
+ MINUTE,
+ SECOND
+ }
+
+ public Timepoint(Timepoint time) {
+ this(time.hour, time.minute, time.second);
+ }
+
+ public Timepoint(@IntRange(from=0, to=23) int hour,
+ @IntRange(from=0, to=59) int minute,
+ @IntRange(from=0, to=59) int second) {
+ this.hour = hour % 24;
+ this.minute = minute % 60;
+ this.second = second % 60;
+ }
+
+ public Timepoint(@IntRange(from=0, to=23) int hour,
+ @IntRange(from=0, to=59) int minute) {
+ this(hour, minute, 0);
+ }
+
+ public Timepoint(@IntRange(from=0, to=23) int hour) {
+ this(hour, 0);
+ }
+
+ public Timepoint(Parcel in) {
+ hour = in.readInt();
+ minute = in.readInt();
+ second = in.readInt();
+ }
+
+ @IntRange(from=0, to=23)
+ public int getHour() {
+ return hour;
+ }
+
+ @IntRange(from=0, to=59)
+ public int getMinute() {
+ return minute;
+ }
+
+ @IntRange(from=0, to=59)
+ public int getSecond() {
+ return second;
+ }
+
+ public boolean isAM() {
+ return hour < 12;
+ }
+
+ public boolean isPM() {
+ return hour >= 12 && hour < 24;
+ }
+
+ public void setAM() {
+ if(hour >= 12) hour = hour % 12;
+ }
+
+ public void setPM() {
+ if(hour < 12) hour = (hour + 12) % 24;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ try {
+ Timepoint other = (Timepoint) o;
+
+ return other.getHour() == hour &&
+ other.getMinute() == minute &&
+ other.getSecond() == second;
+ }
+ catch(ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public int compareTo(@NonNull Timepoint t) {
+ return (this.hour - t.hour)*3600 + (this.minute - t.minute)*60 + (this.second - t.second);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(hour);
+ out.writeInt(minute);
+ out.writeInt(second);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public Timepoint createFromParcel(Parcel in) {
+ return new Timepoint(in);
+ }
+
+ public Timepoint[] newArray(int size) {
+ return new Timepoint[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "" + hour + "h " + minute + "m " + second + "s";
+ }
+}
diff --git a/library/src/main/res/color/mdtp_date_picker_selector.xml b/library/src/main/res/color/mdtp_date_picker_selector.xml
new file mode 100644
index 0000000..15606d2
--- /dev/null
+++ b/library/src/main/res/color/mdtp_date_picker_selector.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/color/mdtp_date_picker_year_selector.xml b/library/src/main/res/color/mdtp_date_picker_year_selector.xml
new file mode 100644
index 0000000..eb2ce13
--- /dev/null
+++ b/library/src/main/res/color/mdtp_date_picker_year_selector.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/color/mdtp_done_text_color.xml b/library/src/main/res/color/mdtp_done_text_color.xml
new file mode 100644
index 0000000..7e99f97
--- /dev/null
+++ b/library/src/main/res/color/mdtp_done_text_color.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/color/mdtp_done_text_color_dark.xml b/library/src/main/res/color/mdtp_done_text_color_dark.xml
new file mode 100644
index 0000000..1c16f23
--- /dev/null
+++ b/library/src/main/res/color/mdtp_done_text_color_dark.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable-land-v19/mdtp_done_background_color.xml b/library/src/main/res/drawable-land-v19/mdtp_done_background_color.xml
new file mode 100644
index 0000000..696bd06
--- /dev/null
+++ b/library/src/main/res/drawable-land-v19/mdtp_done_background_color.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable-land/mdtp_done_background_color.xml b/library/src/main/res/drawable-land/mdtp_done_background_color.xml
new file mode 100644
index 0000000..48c51ff
--- /dev/null
+++ b/library/src/main/res/drawable-land/mdtp_done_background_color.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable-land/mdtp_done_background_color_dark.xml b/library/src/main/res/drawable-land/mdtp_done_background_color_dark.xml
new file mode 100644
index 0000000..1f426a0
--- /dev/null
+++ b/library/src/main/res/drawable-land/mdtp_done_background_color_dark.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable-v19/mdtp_done_background_color.xml b/library/src/main/res/drawable-v19/mdtp_done_background_color.xml
new file mode 100644
index 0000000..3ef7e41
--- /dev/null
+++ b/library/src/main/res/drawable-v19/mdtp_done_background_color.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable-v21/mdtp_material_button_background.xml b/library/src/main/res/drawable-v21/mdtp_material_button_background.xml
new file mode 100644
index 0000000..fbc1997
--- /dev/null
+++ b/library/src/main/res/drawable-v21/mdtp_material_button_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable-v21/mdtp_material_button_selected.xml b/library/src/main/res/drawable-v21/mdtp_material_button_selected.xml
new file mode 100644
index 0000000..9eaadcf
--- /dev/null
+++ b/library/src/main/res/drawable-v21/mdtp_material_button_selected.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/mdtp_done_background_color.xml b/library/src/main/res/drawable/mdtp_done_background_color.xml
new file mode 100644
index 0000000..f301fcc
--- /dev/null
+++ b/library/src/main/res/drawable/mdtp_done_background_color.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/mdtp_done_background_color_dark.xml b/library/src/main/res/drawable/mdtp_done_background_color_dark.xml
new file mode 100644
index 0000000..680ec82
--- /dev/null
+++ b/library/src/main/res/drawable/mdtp_done_background_color_dark.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/mdtp_material_button_background.xml b/library/src/main/res/drawable/mdtp_material_button_background.xml
new file mode 100644
index 0000000..f70c39c
--- /dev/null
+++ b/library/src/main/res/drawable/mdtp_material_button_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/mdtp_material_button_selected.xml b/library/src/main/res/drawable/mdtp_material_button_selected.xml
new file mode 100644
index 0000000..1733e2d
--- /dev/null
+++ b/library/src/main/res/drawable/mdtp_material_button_selected.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_date_picker_dialog.xml b/library/src/main/res/layout-land/mdtp_date_picker_dialog.xml
new file mode 100644
index 0000000..9264cba
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_date_picker_dialog.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_date_picker_dialog_v2.xml b/library/src/main/res/layout-land/mdtp_date_picker_dialog_v2.xml
new file mode 100644
index 0000000..23a94f6
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_date_picker_dialog_v2.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_date_picker_header_view_v2.xml b/library/src/main/res/layout-land/mdtp_date_picker_header_view_v2.xml
new file mode 100644
index 0000000..2105067
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_date_picker_header_view_v2.xml
@@ -0,0 +1,29 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_hijri_date_picker_dialog.xml b/library/src/main/res/layout-land/mdtp_hijri_date_picker_dialog.xml
new file mode 100644
index 0000000..e1e30d9
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_hijri_date_picker_dialog.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_hijri_date_picker_dialog_v2.xml b/library/src/main/res/layout-land/mdtp_hijri_date_picker_dialog_v2.xml
new file mode 100644
index 0000000..d3117fb
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_hijri_date_picker_dialog_v2.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_hijri_date_picker_header_view_v2.xml b/library/src/main/res/layout-land/mdtp_hijri_date_picker_header_view_v2.xml
new file mode 100644
index 0000000..d78e92e
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_hijri_date_picker_header_view_v2.xml
@@ -0,0 +1,29 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_time_header_label.xml b/library/src/main/res/layout-land/mdtp_time_header_label.xml
new file mode 100644
index 0000000..c378c35
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_time_header_label.xml
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/src/main/res/layout-land/mdtp_time_picker_dialog.xml b/library/src/main/res/layout-land/mdtp_time_picker_dialog.xml
new file mode 100644
index 0000000..4a02289
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_time_picker_dialog.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_time_picker_dialog_v2.xml b/library/src/main/res/layout-land/mdtp_time_picker_dialog_v2.xml
new file mode 100644
index 0000000..64a58a9
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_time_picker_dialog_v2.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-land/mdtp_time_title_view_v2.xml b/library/src/main/res/layout-land/mdtp_time_title_view_v2.xml
new file mode 100644
index 0000000..b817a48
--- /dev/null
+++ b/library/src/main/res/layout-land/mdtp_time_title_view_v2.xml
@@ -0,0 +1,29 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-sw600dp-land/mdtp_date_picker_dialog.xml b/library/src/main/res/layout-sw600dp-land/mdtp_date_picker_dialog.xml
new file mode 100644
index 0000000..9264cba
--- /dev/null
+++ b/library/src/main/res/layout-sw600dp-land/mdtp_date_picker_dialog.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-sw600dp-land/mdtp_hijri_date_picker_dialog.xml b/library/src/main/res/layout-sw600dp-land/mdtp_hijri_date_picker_dialog.xml
new file mode 100644
index 0000000..e1e30d9
--- /dev/null
+++ b/library/src/main/res/layout-sw600dp-land/mdtp_hijri_date_picker_dialog.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-sw600dp/mdtp_date_picker_dialog.xml b/library/src/main/res/layout-sw600dp/mdtp_date_picker_dialog.xml
new file mode 100644
index 0000000..687a48e
--- /dev/null
+++ b/library/src/main/res/layout-sw600dp/mdtp_date_picker_dialog.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-sw600dp/mdtp_hijri_date_picker_dialog.xml b/library/src/main/res/layout-sw600dp/mdtp_hijri_date_picker_dialog.xml
new file mode 100644
index 0000000..7caaa07
--- /dev/null
+++ b/library/src/main/res/layout-sw600dp/mdtp_hijri_date_picker_dialog.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-w270dp-h560dp/mdtp_date_picker_dialog.xml b/library/src/main/res/layout-w270dp-h560dp/mdtp_date_picker_dialog.xml
new file mode 100644
index 0000000..12e91ce
--- /dev/null
+++ b/library/src/main/res/layout-w270dp-h560dp/mdtp_date_picker_dialog.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout-w270dp-h560dp/mdtp_hijri_date_picker_dialog.xml b/library/src/main/res/layout-w270dp-h560dp/mdtp_hijri_date_picker_dialog.xml
new file mode 100644
index 0000000..25297a1
--- /dev/null
+++ b/library/src/main/res/layout-w270dp-h560dp/mdtp_hijri_date_picker_dialog.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_picker_dialog.xml b/library/src/main/res/layout/mdtp_date_picker_dialog.xml
new file mode 100644
index 0000000..47515f8
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_dialog.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_picker_dialog_v2.xml b/library/src/main/res/layout/mdtp_date_picker_dialog_v2.xml
new file mode 100644
index 0000000..4190ff9
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_dialog_v2.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_picker_header_view.xml b/library/src/main/res/layout/mdtp_date_picker_header_view.xml
new file mode 100644
index 0000000..5227b3c
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_header_view.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/library/src/main/res/layout/mdtp_date_picker_header_view_v2.xml b/library/src/main/res/layout/mdtp_date_picker_header_view_v2.xml
new file mode 100644
index 0000000..1ae8aeb
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_header_view_v2.xml
@@ -0,0 +1,28 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_picker_selected_date.xml b/library/src/main/res/layout/mdtp_date_picker_selected_date.xml
new file mode 100644
index 0000000..17cb157
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_selected_date.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml b/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml
new file mode 100644
index 0000000..3bf22ed
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_selected_date_v2.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_picker_view_animator.xml b/library/src/main/res/layout/mdtp_date_picker_view_animator.xml
new file mode 100644
index 0000000..588db43
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_picker_view_animator.xml
@@ -0,0 +1,23 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_date_time_picker_dialog.xml b/library/src/main/res/layout/mdtp_date_time_picker_dialog.xml
new file mode 100644
index 0000000..a26fe5a
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_date_time_picker_dialog.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_done_button.xml b/library/src/main/res/layout/mdtp_done_button.xml
new file mode 100644
index 0000000..17dad5f
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_done_button.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_dialog.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_dialog.xml
new file mode 100644
index 0000000..9ade7fd
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_dialog.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_dialog_v2.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_dialog_v2.xml
new file mode 100644
index 0000000..80210ec
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_dialog_v2.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_header_view.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_header_view.xml
new file mode 100644
index 0000000..877544b
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_header_view.xml
@@ -0,0 +1,26 @@
+
+
+
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_header_view_v2.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_header_view_v2.xml
new file mode 100644
index 0000000..101ffa3
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_header_view_v2.xml
@@ -0,0 +1,28 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_selected_date.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_selected_date.xml
new file mode 100644
index 0000000..b59ab28
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_selected_date.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_selected_date_v2.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_selected_date_v2.xml
new file mode 100644
index 0000000..76c9425
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_selected_date_v2.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_hijri_date_picker_view_animator.xml b/library/src/main/res/layout/mdtp_hijri_date_picker_view_animator.xml
new file mode 100644
index 0000000..1e991f0
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_date_picker_view_animator.xml
@@ -0,0 +1,23 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_hijri_year_label_text_view.xml b/library/src/main/res/layout/mdtp_hijri_year_label_text_view.xml
new file mode 100644
index 0000000..64ff6d5
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_hijri_year_label_text_view.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/library/src/main/res/layout/mdtp_time_header_label.xml b/library/src/main/res/layout/mdtp_time_header_label.xml
new file mode 100644
index 0000000..d5933a5
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_time_header_label.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/src/main/res/layout/mdtp_time_picker_dialog.xml b/library/src/main/res/layout/mdtp_time_picker_dialog.xml
new file mode 100644
index 0000000..9256f8a
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_time_picker_dialog.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/src/main/res/layout/mdtp_time_picker_dialog_v2.xml b/library/src/main/res/layout/mdtp_time_picker_dialog_v2.xml
new file mode 100644
index 0000000..c9b415e
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_time_picker_dialog_v2.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/src/main/res/layout/mdtp_time_title_view.xml b/library/src/main/res/layout/mdtp_time_title_view.xml
new file mode 100644
index 0000000..2f9237e
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_time_title_view.xml
@@ -0,0 +1,29 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_time_title_view_v2.xml b/library/src/main/res/layout/mdtp_time_title_view_v2.xml
new file mode 100644
index 0000000..12bf164
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_time_title_view_v2.xml
@@ -0,0 +1,29 @@
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/mdtp_year_label_text_view.xml b/library/src/main/res/layout/mdtp_year_label_text_view.xml
new file mode 100644
index 0000000..aff04ac
--- /dev/null
+++ b/library/src/main/res/layout/mdtp_year_label_text_view.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/library/src/main/res/values-af/strings.xml b/library/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..67741c9
--- /dev/null
+++ b/library/src/main/res/values-af/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Klaar"
+ "Ure se sirkelglyer"
+ "Minute se sirkelglyer"
+ "Kies ure"
+ "Kies minute"
+ "Maandrooster van dae"
+ "Jaarlys"
+ "Kies maand en dag"
+ "Kies jaar"
+ "%1$s gekies"
+ "%1$s uitgevee"
+ MMMM yyyy
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values-am/strings.xml b/library/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..0335410
--- /dev/null
+++ b/library/src/main/res/values-am/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "ተከናውኗል"
+ "የሰዓታት ክብ ተንሸራታች"
+ "የደቂቃዎች ክብ ተንሸራታች"
+ "ሰዓታትን ምረጥ"
+ "ደቂቃዎችን ምረጥ"
+ "የቀናት የወር ፍርግርግ"
+ "የዓመት ዝርዝር"
+ "ወር እና ቀን ይምረጡ"
+ "ዓመት ይምረጡ"
+ "%1$s ተመርጧል"
+ "%1$s ተሰርዟል"
+ MMMM yyyy
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values-ar/strings.xml b/library/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..2a75577
--- /dev/null
+++ b/library/src/main/res/values-ar/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "تم"
+ "شريط التمرير الدائري للساعات"
+ "شريط التمرير الدائري للدقائق"
+ "تحديد الساعات"
+ "تحديد الدقائق"
+ "شبكة الشهر مكونة من الأيام"
+ "قائمة الأعوام"
+ "تحديد الشهر واليوم"
+ "تحديد العام"
+ "تم تحديد %1$s"
+ "تم حذف %1$s"
+ MMMM yyyy
+ EEE، dd MMM
+
diff --git a/library/src/main/res/values-bg/strings.xml b/library/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..49bdddb
--- /dev/null
+++ b/library/src/main/res/values-bg/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Готово"
+ "Кръгов плъзгач за часовете"
+ "Кръгов плъзгач за минутите"
+ "Избиране на часове"
+ "Избиране на минути"
+ "Месечна таблица на дните"
+ "Списък на годините"
+ "Изберете месец и ден"
+ "Изберете година"
+ "Избрахте %1$s"
+ "Изтрихте %1$s"
+ MMMM yyyy \'г\'.
+ EEE, dd.MM
+
diff --git a/library/src/main/res/values-bn-rBD/strings.xml b/library/src/main/res/values-bn-rBD/strings.xml
new file mode 100644
index 0000000..b340775
--- /dev/null
+++ b/library/src/main/res/values-bn-rBD/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "সম্পন্ন হয়েছে"
+ "বৃত্তাকার ঘন্টা নির্বাচকের স্লাইডার"
+ "বৃত্তাকার মিনিট নির্বাচকের স্লাইডার"
+ "ঘন্টা নির্বাচন করুন"
+ "মিনিট নির্বাচন করুন"
+ "দিন দিয়ে সংগঠিত মাসের গ্রিড"
+ "বছরের তালিকা"
+ "মাস এবং দিন নির্বাচন করুন"
+ "বছর নির্বাচন করুন"
+ "%1$s নির্বাচন করা হয়েছে"
+ "%1$s মুছে ফেলা হয়েছে"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-ca/strings.xml b/library/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..ac89dff
--- /dev/null
+++ b/library/src/main/res/values-ca/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Fet"
+ "Control circular de les hores"
+ "Control circular dels minuts"
+ "Selecciona les hores"
+ "Selecciona els minuts"
+ "Graella mensual de dies"
+ "Llista anual"
+ "Selecciona un mes i un dia"
+ "Selecciona un any"
+ "%1$s seleccionat"
+ "%1$s suprimit"
+ LLLL \'de\' yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-cs/strings.xml b/library/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..7ee16cb
--- /dev/null
+++ b/library/src/main/res/values-cs/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Hotovo"
+ "Kruhový posuvník hodin"
+ "Kruhový posuvník minut"
+ "Zvolte hodiny"
+ "Zvolte minuty"
+ "Dny uspořádané po měsících"
+ "Seznam roků"
+ "Vyberte měsíc a den"
+ "Vyberte rok"
+ "Vybrána položka %1$s"
+ "%1$s smazáno"
+ LLLL yyyy
+ EEE dd. M.
+
diff --git a/library/src/main/res/values-da/strings.xml b/library/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..3fc59c5
--- /dev/null
+++ b/library/src/main/res/values-da/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Færdig"
+ "Cirkulær timevælger"
+ "Cirkulær minutvælger"
+ "Vælg timer"
+ "Vælg minutter"
+ "Månedsgitter med dage"
+ "Liste over år"
+ "Vælg måned og dag"
+ "Vælg år"
+ "%1$s valgt"
+ "%1$s er slettet"
+ MMMM yyyy
+ EEE dd. MMM
+
diff --git a/library/src/main/res/values-de/strings.xml b/library/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..7aec0b1
--- /dev/null
+++ b/library/src/main/res/values-de/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Fertig"
+ "Kreisförmiger Schieberegler für Stunden"
+ "Kreisförmiger Schieberegler für Minuten"
+ "Stunden auswählen"
+ "Minuten auswählen"
+ "Monatsraster mit einzelnen Tagen"
+ "Jahresliste"
+ "Monat und Tag auswählen"
+ "Jahr auswählen"
+ "%1$s ausgewählt"
+ "%1$s gelöscht"
+ MMMM yyyy
+ EEE, dd. MMM
+
diff --git a/library/src/main/res/values-el/strings.xml b/library/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..11d8e90
--- /dev/null
+++ b/library/src/main/res/values-el/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Τέλος"
+ "Κυκλικό ρυθμιστικό ωρών"
+ "Κυκλικό ρυθμιστικό λεπτών"
+ "Επιλέξτε ώρες"
+ "Επιλέξτε λεπτά"
+ "Πλέγμα ημερών του μήνα"
+ "Λίστα ετών"
+ "Επιλογή μήνα και ημέρας"
+ "Επιλογή έτους"
+ "Επιλέχτηκε το στοιχείο %1$s"
+ "%1$s διαγράφηκε"
+ LLLL yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-en-rGB/strings.xml b/library/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..f3fb653
--- /dev/null
+++ b/library/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Done"
+ "Hours circular slider"
+ "Minutes circular slider"
+ "Select hours"
+ "Select minutes"
+ "Month grid of days"
+ "Year list"
+ "Select month and day"
+ "Select year"
+ "%1$s selected"
+ "%1$s deleted"
+ MMMM yyyy
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values-en-rIN/strings.xml b/library/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..f3fb653
--- /dev/null
+++ b/library/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Done"
+ "Hours circular slider"
+ "Minutes circular slider"
+ "Select hours"
+ "Select minutes"
+ "Month grid of days"
+ "Year list"
+ "Select month and day"
+ "Select year"
+ "%1$s selected"
+ "%1$s deleted"
+ MMMM yyyy
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values-es-rUS/strings.xml b/library/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..240003a
--- /dev/null
+++ b/library/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Listo"
+ "Control deslizante circular de horas"
+ "Control deslizante circular de minutos"
+ "Seleccionar horas"
+ "Seleccionar minutos"
+ "Cuadrícula mensual de días"
+ "Lista de años"
+ "Seleccionar mes y día"
+ "Seleccionar año"
+ "%1$s seleccionado"
+ "%1$s borrado"
+ MMMM \'de\' yyyy
+ EEE dd \'de\' MMM
+
diff --git a/library/src/main/res/values-es/strings.xml b/library/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..12ce4bd
--- /dev/null
+++ b/library/src/main/res/values-es/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Listo"
+ "Control deslizante circular de horas"
+ "Control deslizante circular de minutos"
+ "Seleccionar horas"
+ "Seleccionar minutos"
+ "Cuadrícula mensual de días"
+ "Lista de años"
+ "Seleccionar mes y día"
+ "Seleccionar año"
+ "%1$s seleccionado"
+ "%1$s eliminado"
+ MMMM \'de\' yyyy
+ EEE dd \'de\' MMM
+
diff --git a/library/src/main/res/values-et-rEE/strings.xml b/library/src/main/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..624cf55
--- /dev/null
+++ b/library/src/main/res/values-et-rEE/strings.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ "Valmis"
+ "Ringikujuline tunniliugur"
+ "Ringikujuline minutiliugur"
+ "Tundide valimine"
+ "Minutite valimine"
+ "Päevad kuu ruudustikus"
+ "Aastate loend"
+ "Valige kuu ja päev"
+ "Valige aasta"
+ "%1$s on valitud"
+ "%1$s on kustutatud"
+
+ MMMM yyyy
+ EEE, dd. MMM
+
diff --git a/library/src/main/res/values-eu-rES/strings.xml b/library/src/main/res/values-eu-rES/strings.xml
new file mode 100644
index 0000000..2ae4671
--- /dev/null
+++ b/library/src/main/res/values-eu-rES/strings.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ "Eginda"
+ "Ordua aukeratzeko ikuspegi zirkularra"
+ "Minutuak aukeratzeko ikuspegi zirkularra"
+ "Hautatu orduak"
+ "Hautatu minutuak"
+ "Hilabete-ikuspegiko eguna aukeratzeko sareta"
+ "Urteen zerrenda"
+ "Hautatu hilabetea eta eguna"
+ "Hautatu urtea"
+ "%1$s hautatu da"
+ "%1$s ezabatu da"
+
+ yyyy(\'e\')\'ko\' MMMM
+ MMM dd, EEE
+
diff --git a/library/src/main/res/values-fa/strings.xml b/library/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..bcb8d94
--- /dev/null
+++ b/library/src/main/res/values-fa/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "انجام شد"
+ "لغزنده دایرهای ساعت"
+ "لغزنده دایرهای دقیقه"
+ "انتخاب ساعت"
+ "انتخاب دقیقه"
+ "روزهای ماه به صورت جدول"
+ "فهرست سالها"
+ "ماه و روز را انتخاب کنید"
+ "سال را انتخاب کنید"
+ "%1$s انتخاب شد"
+ "%1$s حذف شد"
+ MMMM yyyy
+ EEE dd LLL
+
diff --git a/library/src/main/res/values-fi/strings.xml b/library/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..c50ee08
--- /dev/null
+++ b/library/src/main/res/values-fi/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Valmis"
+ "Tuntien ympyränmuotoinen liukusäädin"
+ "Minuuttien ympyränmuotoinen liukusäädin"
+ "Valitse tunnit"
+ "Valitse minuutit"
+ "Päiväruudukko kuukausittain"
+ "Vuosiluettelo"
+ "Valitse kuukausi ja päivä"
+ "Valitse vuosi"
+ "%1$s on valittu"
+ "%1$s poistettiin"
+ LLLL yyyy
+ ccc dd. MMM
+
diff --git a/library/src/main/res/values-fr-rCA/strings.xml b/library/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..b87745f
--- /dev/null
+++ b/library/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Terminé"
+ "Curseur circulaire des heures"
+ "Curseur circulaire des minutes"
+ "Sélectionnez les heures"
+ "Sélectionnez les minutes"
+ "Calendrier mensuel sous forme de grille"
+ "Liste des années"
+ "Sélectionnez un mois et un jour"
+ "Sélectionnez une année"
+ "%1$s sélectionné"
+ "« %1$s » a été supprimé"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-fr/strings.xml b/library/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..37e2ffd
--- /dev/null
+++ b/library/src/main/res/values-fr/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "OK"
+ "Curseur circulaire des heures"
+ "Curseur circulaire des minutes"
+ "Sélectionner les heures"
+ "Sélectionner les minutes"
+ "Calendrier mensuel sous forme de grille"
+ "Liste des années"
+ "Sélectionnez un mois et un jour"
+ "Sélectionnez une année"
+ "%1$s sélectionné"
+ "\"%1$s\" supprimé"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-gl-rES/strings.xml b/library/src/main/res/values-gl-rES/strings.xml
new file mode 100644
index 0000000..8c747ee
--- /dev/null
+++ b/library/src/main/res/values-gl-rES/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Feito"
+ "Control de desprazamento circular das horas"
+ "Control de desprazamento circular dos minutos"
+ "Seleccionar horas"
+ "Seleccionar minutos"
+ "Grade mensual de días"
+ "Lista de anos"
+ "Seleccionar mes e día"
+ "Seleccionar ano"
+ "Seleccionouse %1$s"
+ "Eliminouse %1$s"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-h330dp/dimens.xml b/library/src/main/res/values-h330dp/dimens.xml
new file mode 100644
index 0000000..c2be45f
--- /dev/null
+++ b/library/src/main/res/values-h330dp/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 252dp
+ 300dip
+
diff --git a/library/src/main/res/values-hi/strings.xml b/library/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..d9ed568
--- /dev/null
+++ b/library/src/main/res/values-hi/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "पूर्ण"
+ "घंटो का चक्राकार स्लाइडर"
+ "मिनटों का चक्राकार स्लाइडर"
+ "घंटे चुनें"
+ "मिनट चुनें"
+ "दिनों की माह ग्रिड"
+ "वर्ष की सूची"
+ "माह और दिन चुनें"
+ "वर्ष चुनें"
+ "%1$s चयनित"
+ "%1$s को हटा दिया गया"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-hr/strings.xml b/library/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..6450c85
--- /dev/null
+++ b/library/src/main/res/values-hr/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Gotovo"
+ "Kružni klizač sati"
+ "Kružni klizač minuta"
+ "Odaberite sate"
+ "Odaberite minute"
+ "Mreža dana u mjesecu"
+ "Popis godina"
+ "Odaberite mjesec i dan"
+ "Odaberite godinu"
+ "Odabrana je stavka %1$s"
+ "Izbrisan je znak %1$s"
+ LLLL yyyy.
+ EEE, dd. MMM
+
diff --git a/library/src/main/res/values-hu/strings.xml b/library/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..416ef1d
--- /dev/null
+++ b/library/src/main/res/values-hu/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Kész"
+ "óra kör alakú csúszkája"
+ "perc kör alakú csúszkája"
+ "Óra kiválasztása"
+ "Perc kiválasztása"
+ "Napok havi leosztásban"
+ "Évek listája"
+ "Válassza ki a hónapot és a napot"
+ "Válassza ki az évet"
+ "%1$s kiválasztva"
+ "A(z) %1$s érték törölve"
+ yyyy. MMMM
+ MMM dd., EEE
+
diff --git a/library/src/main/res/values-hy-rAM/strings.xml b/library/src/main/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..fda9c8d
--- /dev/null
+++ b/library/src/main/res/values-hy-rAM/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Կատարված է"
+ "Ժամերի ընտրություն թվատախտակից"
+ "Րոպեների ընտրություն թվատախտակից"
+ "Ընտրեք ժամերը"
+ "Ընտրեք րոպեները"
+ "Ամսաթվի ընտրության պատուհան"
+ "Տարիների ցանկը"
+ "Ընտրեք ամիսն ու օրը"
+ "Ընտրեք տարին"
+ "%1$s ընտրված"
+ "%1$s ջնջված"
+ yyyyթ. LLLL
+ dd MMM, EEE
+
diff --git a/library/src/main/res/values-in/strings.xml b/library/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..044ab59
--- /dev/null
+++ b/library/src/main/res/values-in/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Selesai"
+ "Penggeser putar jam"
+ "Penggeser putar menit"
+ "Pilih jam"
+ "Pilih menit"
+ "Kisi hari pada bulan"
+ "Daftar tahun"
+ "Pilih bulan dan hari"
+ "Pilih tahun"
+ "%1$s dipilih"
+ "%1$s dihapus"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-is-rIS/strings.xml b/library/src/main/res/values-is-rIS/strings.xml
new file mode 100644
index 0000000..59b524a
--- /dev/null
+++ b/library/src/main/res/values-is-rIS/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Lokið"
+ "Valskífa fyrir klukkustundir"
+ "Valskífa fyrir mínútur"
+ "Velja klukkustundir"
+ "Velja mínútur"
+ "Mánaðartafla með dögum"
+ "Áralisti"
+ "Velja mánuð og dag"
+ "Velja ár"
+ "%1$s valið"
+ "%1$s eytt"
+ MMMM yyyy
+ EEE, dd. MMM
+
diff --git a/library/src/main/res/values-it/strings.xml b/library/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..c66a6c3
--- /dev/null
+++ b/library/src/main/res/values-it/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Fine"
+ "Dispositivo di scorrimento circolare per le ore"
+ "Dispositivo di scorrimento per i minuti"
+ "Seleziona le ore"
+ "Seleziona i minuti"
+ "Griglia del mese suddivisa per giorni"
+ "Elenco degli anni"
+ "Seleziona mese e giorno"
+ "Seleziona anno"
+ "Elemento selezionato: %1$s"
+ "%1$s eliminato"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-iw/strings.xml b/library/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..a7491b0
--- /dev/null
+++ b/library/src/main/res/values-iw/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "בוצע"
+ "מחוון שעות מעגלי"
+ "מחוון דקות מעגלי"
+ "בחר שעות"
+ "בחר דקות"
+ "בחירת ימים בחודש בתצוגת רשת"
+ "רשימת שנים"
+ "בחר חודש ויום"
+ "בחר שנה"
+ "בחרת %1$s"
+ "%1$s נמחק"
+ MMMM yyyy
+ EEE, dd בMMM
+
diff --git a/library/src/main/res/values-ja/strings.xml b/library/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..c453938
--- /dev/null
+++ b/library/src/main/res/values-ja/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "完了"
+ "円形スライダー(時)"
+ "円形スライダー(分)"
+ "時間を選択"
+ "分を選択"
+ "日グリッド(月別)"
+ "年リスト"
+ "月と日を選択"
+ "年を選択"
+ "%1$sを選択しました"
+ "%1$sを削除しました"
+ yyyy年M月
+ M月dd日(EEE)
+
diff --git a/library/src/main/res/values-ka-rGE/strings.xml b/library/src/main/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..88a565d
--- /dev/null
+++ b/library/src/main/res/values-ka-rGE/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "დასრულდა"
+ "წრიული სლაიდერის დამალვა"
+ "წუთების წრიული სლაიდერი"
+ "აირჩიეთ საათები"
+ "აირჩიეთ წუთები"
+ "დღეების ბადე თვეზე"
+ "წლის სია"
+ "აირჩიეთ თვე და რიცხვი"
+ "აირჩიეთ წელი"
+ "არჩეულია %1$s"
+ "%1$s წაიშალა"
+ MMMM, yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-kk-rKZ/strings.xml b/library/src/main/res/values-kk-rKZ/strings.xml
new file mode 100644
index 0000000..bb9d381
--- /dev/null
+++ b/library/src/main/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Орындалды"
+ "Сағаттардың дөңгелек жүгірткісі"
+ "Минуттардың дөңгелек жүгірткісі"
+ "Сағат таңдау"
+ "Минут таңдау"
+ "Күндердің айлық торы"
+ "Жылдар тізімі"
+ "Ай мен күнді таңдау"
+ "Жыл таңдау"
+ "%1$s таңдалды"
+ "%1$s жойылды"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-km-rKH/strings.xml b/library/src/main/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..997daa8
--- /dev/null
+++ b/library/src/main/res/values-km-rKH/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "ធ្វើរួច"
+ "គ្រាប់រំកិលរង្វង់ម៉ោង"
+ "គ្រាប់រំកិលរង្វង់នាទី"
+ "ជ្រើសម៉ោង"
+ "ជ្រើសនាទី"
+ "ក្រឡាចត្រង្គខែនៃថ្ងៃ"
+ "បញ្ជីឆ្នាំ"
+ "ជ្រើសខែ និងថ្ងៃ"
+ "ជ្រើសឆ្នាំ"
+ "បានជ្រើស %1$s"
+ "បានលុប %1$s"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-kn-rIN/strings.xml b/library/src/main/res/values-kn-rIN/strings.xml
new file mode 100644
index 0000000..88af111
--- /dev/null
+++ b/library/src/main/res/values-kn-rIN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "ಮುಗಿದಿದೆ"
+ "ಗಂಟೆಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"
+ "ನಿಮಿಷಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"
+ "ಗಂಟೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"
+ "ನಿಮಿಷಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"
+ "ದಿನಗಳ ತಿಂಗಳಿನ ಗ್ರಿಡ್"
+ "ವರ್ಷದ ಪಟ್ಟಿ"
+ "ತಿಂಗಳು ಮತ್ತು ದಿನವನ್ನು ಆಯ್ಕೆಮಾಡಿ"
+ "ವರ್ಷವನ್ನು ಆಯ್ಕೆಮಾಡಿ"
+ "%1$s ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"
+ "%1$s ಅಳಿಸಲಾಗಿದೆ"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-ko/strings.xml b/library/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..c251673
--- /dev/null
+++ b/library/src/main/res/values-ko/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "완료"
+ "시간 원형 슬라이더"
+ "분 원형 슬라이더"
+ "시간 선택"
+ "분 선택"
+ "월별 바둑판식 날짜 표시"
+ "년"
+ "월/일 선택"
+ "연도 선택"
+ "%1$s이(가) 선택됨"
+ "%1$s 삭제됨"
+ yyyy년 MMMM
+ MMM dd일 (EEE)
+
diff --git a/library/src/main/res/values-ky-rKG/strings.xml b/library/src/main/res/values-ky-rKG/strings.xml
new file mode 100644
index 0000000..4d257a6
--- /dev/null
+++ b/library/src/main/res/values-ky-rKG/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Аткарылды"
+ "Саат боюнча айлангыч"
+ "Мүнөт боюнча айлангыч"
+ "Саатты тандаңыз"
+ "Мүнөттөрдү тандаңыз"
+ "Айдын күндөрү"
+ "Жыл тизмеси"
+ "Ай жана күндү тандаңыз"
+ "Жылды тандаңыз"
+ "%1$s тандалды"
+ "%1$s жок кылынды"
+ yyyy-\'ж\'. MMMM
+ dd-MMM, EEE
+
diff --git a/library/src/main/res/values-land/dimens.xml b/library/src/main/res/values-land/dimens.xml
new file mode 100644
index 0000000..79ca190
--- /dev/null
+++ b/library/src/main/res/values-land/dimens.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ 200dip
+ 240dip
+
+ 30dp
+ 100dp
+ 30dp
+
+ 220dp
+ 170dp
+ 308dp
+ 220dp
+
+ 220dip
+ 270dip
+
diff --git a/library/src/main/res/values-lo-rLA/strings.xml b/library/src/main/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..3d372cc
--- /dev/null
+++ b/library/src/main/res/values-lo-rLA/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "ແລ້ວໆ"
+ "ໂຕໝຸນປັບຊົ່ວໂມງ"
+ "ໂຕໝຸນປັບນາທີ"
+ "ເລືອກຊົ່ວໂມງ"
+ "ເລືອກນາທີ"
+ "ຕາຕະລາງວັນທີ"
+ "ລາຍການປີ"
+ "ເລືອກເດືອນ ແລະ ວັນ"
+ "ເລືອກປີ"
+ "%1$s ຖືກເລືອກແລ້ວ"
+ "%1$s ຖືກລຶບແລ້ວ"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-lt/strings.xml b/library/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..1dbdfaa
--- /dev/null
+++ b/library/src/main/res/values-lt/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Atlikta"
+ "Apskritas valandų slankiklis"
+ "Apskritas minučių slankiklis"
+ "Pasirinkite valandas"
+ "Pasirinkite minutes"
+ "Mėnesio dienų tinklelis"
+ "Metų sąrašas"
+ "Pasirinkite mėnesį ir dieną"
+ "Pasirinkite metus"
+ "Elementas „%1$s“ pasirinktas"
+ "Ištrinta: %1$s"
+ yyyy MMMM
+ MMM dd, EEE
+
diff --git a/library/src/main/res/values-lv/strings.xml b/library/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..2de67c3
--- /dev/null
+++ b/library/src/main/res/values-lv/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Gatavs"
+ "Stundu apļveida slīdnis"
+ "Minūšu apļveida slīdnis"
+ "Atlasīt stundas"
+ "Atlasīt minūtes"
+ "Režģis ar mēneša dienām"
+ "Gadu saraksts"
+ "Atlasiet mēnesi un dienu"
+ "Atlasiet gadu"
+ "Atlasīts: %1$s"
+ "%1$s tika dzēsts"
+ yyyy. \'g\'. MMMM
+ EEE, dd. MMM
+
diff --git a/library/src/main/res/values-mk-rMK/strings.xml b/library/src/main/res/values-mk-rMK/strings.xml
new file mode 100644
index 0000000..e8a2573
--- /dev/null
+++ b/library/src/main/res/values-mk-rMK/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Готово"
+ "Приказ на часови во круг"
+ "Приказ на минути во круг"
+ "Избери часови"
+ "Избери минути"
+ "Месец со денови"
+ "Список со години"
+ "Избери месец и ден"
+ "Избери година"
+ "Избрано: %1$s"
+ "Избришано: %1$s"
+ MMMM yyyy \'г\'.
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-ml-rIN/strings.xml b/library/src/main/res/values-ml-rIN/strings.xml
new file mode 100644
index 0000000..03ff99c
--- /dev/null
+++ b/library/src/main/res/values-ml-rIN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "പൂർത്തിയായി"
+ "ചാക്രികമായി മണിക്കൂറുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"
+ "ചാക്രികമായി മിനിറ്റുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"
+ "മണിക്കൂർ തിരഞ്ഞെടുക്കുക"
+ "മിനിറ്റ് തിരഞ്ഞെടുക്കുക"
+ "മാസപ്രകാരമുള്ള ദിവസ ഗ്രിഡ്"
+ "വർഷങ്ങളുടെ ലിസ്റ്റ്"
+ "മാസവും ദിവസവും തിരഞ്ഞെടുക്കുക"
+ "വർഷം തിരഞ്ഞെടുക്കുക"
+ "%1$s തിരഞ്ഞെടുത്തു"
+ "%1$s ഇല്ലാതാക്കി"
+ yyyy MMMM
+ MMM dd, EEE
+
diff --git a/library/src/main/res/values-mn-rMN/strings.xml b/library/src/main/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..a088d4f
--- /dev/null
+++ b/library/src/main/res/values-mn-rMN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Дууссан"
+ "Цаг гүйлгэгч"
+ "Минут гүйлгэгч"
+ "Цаг сонгоно уу"
+ "Минут сонгоно уу"
+ "Өдрүүдийг сараар"
+ "Жилийн жагсаалт"
+ "Сар болон өдрийг сонгоно уу"
+ "Жилийг сонгоно уу"
+ "%1$s сонгогдсон"
+ "%1$s устсан"
+ yyyy MMMM
+ EEE MMM dd
+
diff --git a/library/src/main/res/values-mr-rIN/strings.xml b/library/src/main/res/values-mr-rIN/strings.xml
new file mode 100644
index 0000000..739c258
--- /dev/null
+++ b/library/src/main/res/values-mr-rIN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "पूर्ण झाले"
+ "तास परिपत्रक स्लायडर"
+ "मिनिटे परिपत्रक स्लायडर"
+ "तास निवडा"
+ "मिनिटे निवडा"
+ "दिवसांची महिना ग्रिड"
+ "वर्ष सूची"
+ "महिना आणि दिवस निवडा"
+ "वर्ष निवडा"
+ "%1$s निवडले"
+ "%1$s हटविली"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-ms-rMY/strings.xml b/library/src/main/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..b441128
--- /dev/null
+++ b/library/src/main/res/values-ms-rMY/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Selesai"
+ "Penggelangsar bulatan jam"
+ "Penggelangsar bulatan minit"
+ "Pilih jam"
+ "Pilih minit"
+ "Grid bulan hari"
+ "Senarai tahun"
+ "Pilih bulan dan hari"
+ "Pilih tahun"
+ "%1$s dipilih"
+ "%1$s dipadamkan"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-my-rMM/strings.xml b/library/src/main/res/values-my-rMM/strings.xml
new file mode 100644
index 0000000..8a39707
--- /dev/null
+++ b/library/src/main/res/values-my-rMM/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "လုပ်ပြီး"
+ "နာရီ ရွေးစရာ စက်ဝိုင်း"
+ "မိနစ် ရွေးစရာ စက်ဝိုင်း"
+ "နာရီများ ရွေးပါ"
+ "မိနစ်များ ရွေးပါ"
+ "လအလိုက် နေ့များ အကွက်"
+ "ခုနှစ် စာရင်း"
+ "လ နှင့် နေ့ ရွေးပါ"
+ "ခုနှစ်ကို ရွေးပါ"
+ "%1$s ရွေးထားပြီး"
+ "%1$s ကို ဖျက်ပြီး"
+ yyyy MMMM
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values-nb/strings.xml b/library/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..5a25974
--- /dev/null
+++ b/library/src/main/res/values-nb/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Ferdig"
+ "Sirkulær glidebryter for timer"
+ "Sirkulær glidebryter for minutter"
+ "Angi timer"
+ "Angi minutter"
+ "Månedsrutenett med dager"
+ "Årsliste"
+ "Velg måneden og dagen"
+ "Velg året"
+ "%1$s er valgt"
+ "%1$s er slettet"
+ MMMM yyyy
+ EEE dd. MMM
+
diff --git a/library/src/main/res/values-ne-rNP/strings.xml b/library/src/main/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..ae90f1a
--- /dev/null
+++ b/library/src/main/res/values-ne-rNP/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "सम्पन्न भयो"
+ "घण्टा गोलाकार स्लाइडर"
+ "मिनेट गोलाकार स्लाइडर"
+ "घण्टा चयन गर्नुहोस्"
+ "मिनेट चयन गर्नुहोस्"
+ "दिनहरुको महिना ग्रिड"
+ "वर्ष सूची"
+ "महिना र दिन चयन गर्नुहोस्"
+ "वर्ष चयन गर्नुहोस्"
+ "%1$s चयन गरियो"
+ "%1$s हटाइयो"
+ yyyy MMMM
+ MMM dd, EEE
+
diff --git a/library/src/main/res/values-nl/strings.xml b/library/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..ebb042c
--- /dev/null
+++ b/library/src/main/res/values-nl/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Gereed"
+ "Ronde schuifregelaar voor uren"
+ "Ronde schuifregelaar voor minuten"
+ "Uren selecteren"
+ "Minuten selecteren"
+ "Maandraster van dagen"
+ "Jaarlijst"
+ "Maand en dag selecteren"
+ "Jaar selecteren"
+ "%1$s geselecteerd"
+ "%1$s verwijderd"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-pl/strings.xml b/library/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..fa6cdba
--- /dev/null
+++ b/library/src/main/res/values-pl/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Gotowe"
+ "Kołowy suwak godzin"
+ "Kołowy suwak minut"
+ "Wybierz godziny"
+ "Wybierz minuty"
+ "Siatka miesięczna z dniami"
+ "Lista lat"
+ "Wybierz miesiąc i dzień"
+ "Wybierz rok"
+ "Wybrałeś %1$s"
+ "%1$s usunięte"
+ LLLL yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-pt-rPT/strings.xml b/library/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..d2a3887
--- /dev/null
+++ b/library/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Concluído"
+ "Controlo de deslize circular das horas"
+ "Controlo de deslize circular dos minutos"
+ "Selecionar horas"
+ "Selecionar minutos"
+ "Grelha de dias do mês"
+ "Lista de anos"
+ "Selecionar mês e dia"
+ "Selecionar ano"
+ "%1$s selecionado"
+ "%1$s eliminado"
+ MMMM \'de\' yyyy
+ EEE, dd \'de\' MMM
+
diff --git a/library/src/main/res/values-pt/strings.xml b/library/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..f1ed733
--- /dev/null
+++ b/library/src/main/res/values-pt/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Concluído"
+ "Controle deslizante circular das horas"
+ "Controle deslizante circular dos minutos"
+ "Selecione as horas"
+ "Selecione os minutos"
+ "Grade mensal de dias"
+ "Lista de anos"
+ "Selecione o mês e o dia"
+ "Selecione o ano"
+ "%1$s selecionado"
+ "%1$s excluído"
+ MMMM \'de\' yyyy
+ EEE, dd \'de\' MMM
+
diff --git a/library/src/main/res/values-ro/strings.xml b/library/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..7e90c05
--- /dev/null
+++ b/library/src/main/res/values-ro/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Terminat"
+ "Indicator circular ore"
+ "Indicator circular minute"
+ "Selectați orele"
+ "Selectați minutele"
+ "Afișare pe luni"
+ "Listă de ani"
+ "Selectați luna și ziua"
+ "Selectați anul"
+ "%1$s selectat"
+ "%1$s a fost șters"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-ru/strings.xml b/library/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..2d0077e
--- /dev/null
+++ b/library/src/main/res/values-ru/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Готово"
+ "Выбор часов на циферблате"
+ "Выбор минут на циферблате"
+ "Выберите часы"
+ "Выберите минуты"
+ "Окно выбора даты"
+ "Меню выбора года"
+ "Выберите месяц и день"
+ "Выберите год"
+ "Выбран элемент %1$s"
+ "Цифра %1$s удалена"
+ LLLL yyyy
+ ccc, dd MMM
+
diff --git a/library/src/main/res/values-si-rLK/strings.xml b/library/src/main/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..cf70a25
--- /dev/null
+++ b/library/src/main/res/values-si-rLK/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "අවසන්"
+ "පැය කවාකාර සර්පනය"
+ "මිනිත්තු කවාකාර සර්පනය"
+ "පැය තෝරන්න"
+ "මිනිත්තු තෝරන්න"
+ "දින ජාලයකින් මාසය"
+ "වසර ලැයිස්තුව"
+ "මාසය සහ දිනය තෝරන්න"
+ "වසර තෝරන්න"
+ "%1$s තෝරාගෙන ඇත"
+ "%1$s මකා දමන ලදි"
+ yyyy MMMM
+ MMM dd EEE
+
diff --git a/library/src/main/res/values-sk/strings.xml b/library/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..404c1ee
--- /dev/null
+++ b/library/src/main/res/values-sk/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Hotovo"
+ "Kruhový posúvač hodín"
+ "Kruhový posúvač minút"
+ "Vyberte hodiny"
+ "Vyberte minúty"
+ "Tabuľka dní v mesiaci"
+ "Zoznam rokov"
+ "Vyberte mesiac a deň"
+ "Vyberte rok"
+ "Bola vybratá položka %1$s"
+ "%1$s odstránené"
+ LLLL yyyy
+ EEE, dd. MMM.
+
diff --git a/library/src/main/res/values-sl/strings.xml b/library/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..8525f3a
--- /dev/null
+++ b/library/src/main/res/values-sl/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Dokončano"
+ "Okrogli drsnik za ure"
+ "Okrogli drsnik za minute"
+ "Izberite ure"
+ "Izberite minute"
+ "Mesečna mreža dni"
+ "Seznam let"
+ "Izberite mesec in dan"
+ "Izberite leto"
+ "Izbrano: %1$s"
+ "%1$s je izbrisana"
+ MMMM yyyy
+ EEE, dd. MMM
+
diff --git a/library/src/main/res/values-sr/strings.xml b/library/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..b5729bb
--- /dev/null
+++ b/library/src/main/res/values-sr/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Готово"
+ "Кружни клизач за сате"
+ "Кружни клизач за минуте"
+ "Изабери сате"
+ "Изабери минуте"
+ "Приказ дана у месецу у виду мреже"
+ "Листа година"
+ "Изаберите месец и дан"
+ "Изаберите годину"
+ "Ставка %1$s је изабрана"
+ "Избрисали сте %1$s"
+ MMMM yyyy.
+ EEE dd. MMM
+
diff --git a/library/src/main/res/values-sv/strings.xml b/library/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..a19afb9
--- /dev/null
+++ b/library/src/main/res/values-sv/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Klar"
+ "Cirkelreglage för timmar"
+ "Cirkelreglage för minuter"
+ "Välj timmar"
+ "Välj minuter"
+ "Rutnät för månad"
+ "Lista över år"
+ "Välj månad och dag"
+ "Välj år"
+ "%1$s har markerats"
+ "%1$s har tagits bort"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-sw/strings.xml b/library/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..fb40ab8
--- /dev/null
+++ b/library/src/main/res/values-sw/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Nimemaliza"
+ "Kitelezi cha mviringo cha saa"
+ "Kitelezi cha mviringo cha dakika"
+ "Chagua saa"
+ "Chagua dakika"
+ "Gridi ya mwezi ya siku"
+ "Orodha ya miaka"
+ "Chagua mwezi na siku"
+ "Chagua mwaka"
+ "%1$s kimechaguliwa"
+ "%1$s kimefutwa"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-sw600dp-land/dimens.xml b/library/src/main/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..89bd897
--- /dev/null
+++ b/library/src/main/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ 45dp
+ 14dp
+ 220dp
+ 456dp
+ 16dp
+ 64dp
+ 32dp
+ 16dp
+ 6dip
+ 5dip
+ 96dip
+ 48dip
+ 400dip
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values-sw600dp/dimens.xml b/library/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..a63001f
--- /dev/null
+++ b/library/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+ 400dp
+ 400dp
+ 360dp
+ 45dp
+ 75dp
+ 20dp
+ 20dp
+ 36dp
+ 45dp
+ 150dp
+ 45dp
+ 96dp
+ 33dp
+ 18dp
+ 18dp
+
+ 18sp
+ 16sp
+ 14sp
+ 12sp
+
+ 76dp
+ 38dp
+ -50dp
+ 16dp
+ 6dip
+ 5dip
+ 120dip
+ 60dip
+ 48dip
+ 24dip
+ 400dip
+ 450dp
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values-ta-rIN/strings.xml b/library/src/main/res/values-ta-rIN/strings.xml
new file mode 100644
index 0000000..6c4086f
--- /dev/null
+++ b/library/src/main/res/values-ta-rIN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "முடிந்தது"
+ "மணிநேர வட்ட வடிவ ஸ்லைடர்"
+ "நிமிடத்திற்கான வட்டவடிவ ஸ்லைடர்"
+ "மணிநேரத்தைத் தேர்ந்தெடுக்கவும்"
+ "நிமிடத்தைத் தேர்ந்தெடுக்கவும்"
+ "நாட்களின் மாதக் கட்டம்"
+ "ஆண்டு பட்டியல்"
+ "மாதம் மற்றும் தேதியைத் தேர்ந்தெடுக்கவும்"
+ "ஆண்டைத் தேர்ந்தெடுக்கவும்"
+ "%1$s தேர்ந்தெடுக்கப்பட்டது"
+ "%1$s நீக்கப்பட்டது"
+ MMMM yyyy
+ MMM dd, EEE
+
diff --git a/library/src/main/res/values-te-rIN/strings.xml b/library/src/main/res/values-te-rIN/strings.xml
new file mode 100644
index 0000000..3bf787d
--- /dev/null
+++ b/library/src/main/res/values-te-rIN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "పూర్తయింది"
+ "గంటల వృత్తాకార స్లయిడర్"
+ "నిమిషాల వృత్తాకార స్లయిడర్"
+ "గంటలను ఎంచుకోండి"
+ "నిమిషాలను ఎంచుకోండి"
+ "రోజుల యొక్క నెల గ్రిడ్"
+ "సంవత్సర జాబితా"
+ "నెల మరియు రోజును ఎంచుకోండి"
+ "సంవత్సరాన్ని ఎంచుకోండి"
+ "%1$s ఎంచుకోబడింది"
+ "%1$s తొలగించబడింది"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-th/strings.xml b/library/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..ad048ed
--- /dev/null
+++ b/library/src/main/res/values-th/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "เสร็จสิ้น"
+ "ตัวเลื่อนหมุนระบุชั่วโมง"
+ "ตัวเลื่อนหมุนระบุนาที"
+ "เลือกชั่วโมง"
+ "เลือกนาที"
+ "ตารางวันที่ของเดือน"
+ "รายการปี"
+ "เลือกเดือนและวัน"
+ "เลือกปี"
+ "เลือก %1$s"
+ "ลบ %1$s แล้ว"
+ MMMM yyyy
+ EEE dd MMM
+
diff --git a/library/src/main/res/values-tl/strings.xml b/library/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..9966dfb
--- /dev/null
+++ b/library/src/main/res/values-tl/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Tapos na"
+ "Pabilog na slider ng mga oras"
+ "Pabilog na slider ng mga minuto"
+ "Pumili ng mga oras"
+ "Pumili ng mga minuto"
+ "Grid ng mga araw ayon sa buwan"
+ "Listahan ng taon"
+ "Pumili ng buwan at araw"
+ "Pumili ng taon"
+ "Napili ang %1$s"
+ "Tinanggal ang %1$s"
+ MMMM yyyy
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values-tr/strings.xml b/library/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..4490de3
--- /dev/null
+++ b/library/src/main/res/values-tr/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Bitti"
+ "Saatler için kaydırma çemberi"
+ "Dakikalar için kaydırma çemberi"
+ "Saat seçin"
+ "Dakika seçin"
+ "Ayın günleri tablosu"
+ "Yıl listesi"
+ "Ayı ve günü seçin"
+ "Yılı seçin"
+ "%1$s seçildi"
+ "%1$s silindi"
+ MMMM yyyy
+ dd MMMM EEE
+
diff --git a/library/src/main/res/values-uk/strings.xml b/library/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..3c2d1ce
--- /dev/null
+++ b/library/src/main/res/values-uk/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Готово"
+ "Вибір годин на циферблаті"
+ "Вибір хвилин на циферблаті"
+ "Вибрати години"
+ "Вибрати хвилини"
+ "Дні місяця – ескізи"
+ "Роки – список"
+ "Виберіть місяць і день"
+ "Виберіть рік"
+ "Вибрано: %1$s"
+ "%1$s видалено"
+ LLLL yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-ur-rPK/strings.xml b/library/src/main/res/values-ur-rPK/strings.xml
new file mode 100644
index 0000000..5ca48c7
--- /dev/null
+++ b/library/src/main/res/values-ur-rPK/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "ہوگیا"
+ "گھنٹوں کا سرکلر سلائیڈر"
+ "منٹ کا سرکلر سلائیڈر"
+ "گھنٹے منتخب کریں"
+ "منٹ منتخب کریں"
+ "دنوں کا ماہ کا گرڈ"
+ "سال کی فہرست"
+ "مہینہ اور دن منتخب کریں"
+ "سال منتخب کریں"
+ "%1$s منتخب ہوگیا"
+ "%1$s حذف ہوگیا"
+ MMMM yyyy
+ EEE، dd MMM
+
diff --git a/library/src/main/res/values-uz-rUZ/strings.xml b/library/src/main/res/values-uz-rUZ/strings.xml
new file mode 100644
index 0000000..d352512
--- /dev/null
+++ b/library/src/main/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Tayyor"
+ "Soat ko‘rsatkichli doira"
+ "Daqiqa ko‘rsatkichli doira"
+ "Soatni tanlash"
+ "Daqiqani tanlash"
+ "Oy kunlari jadvali"
+ "Yillar ro‘yxati"
+ "Oy va kunni tanlash"
+ "Yilni tanlash"
+ "%1$s tanlandi"
+ "%1$s o‘chirildi"
+ yyyy MMMM
+ MMM dd, EEE
+
diff --git a/library/src/main/res/values-v16/strings.xml b/library/src/main/res/values-v16/strings.xml
new file mode 100644
index 0000000..55bb88e
--- /dev/null
+++ b/library/src/main/res/values-v16/strings.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ sans-serif-light
+
diff --git a/library/src/main/res/values-v16/styles.xml b/library/src/main/res/values-v16/styles.xml
new file mode 100644
index 0000000..0f2bfca
--- /dev/null
+++ b/library/src/main/res/values-v16/styles.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values-vi/strings.xml b/library/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..3412b01
--- /dev/null
+++ b/library/src/main/res/values-vi/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Xong"
+ "Thanh trượt giờ hình tròn"
+ "Thanh trượt phút hình tròn"
+ "Chọn giờ"
+ "Chọn phút"
+ "Lưới ngày theo tháng"
+ "Danh sách năm"
+ "Chọn tháng và ngày"
+ "Chọn năm"
+ "Đã chọn %1$s"
+ "Đã xóa %1$s"
+ MMMM yyyy
+ EEE, dd MMM
+
diff --git a/library/src/main/res/values-w270dp-h560dp/dimens.xml b/library/src/main/res/values-w270dp-h560dp/dimens.xml
new file mode 100644
index 0000000..89ff8f7
--- /dev/null
+++ b/library/src/main/res/values-w270dp-h560dp/dimens.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ 230dp
+ 190dp
+ 30dp
+ 100dp
+ 30dp
+
\ No newline at end of file
diff --git a/library/src/main/res/values-w560dp-land/dimens.xml b/library/src/main/res/values-w560dp-land/dimens.xml
new file mode 100644
index 0000000..682a72a
--- /dev/null
+++ b/library/src/main/res/values-w560dp-land/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 270dp
+ 270dip
+ 270dip
+
diff --git a/library/src/main/res/values-zh-rCN/strings.xml b/library/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..2b7e800
--- /dev/null
+++ b/library/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "完成"
+ "小时转盘"
+ "分钟转盘"
+ "选择小时"
+ "选择分钟"
+ "按月份划分的日期网格"
+ "年份列表"
+ "选择月份和日期"
+ "选择年份"
+ "已选择 %1$s"
+ "已删除 %1$s"
+ yyyy年M月
+ M月dd日EEE
+
diff --git a/library/src/main/res/values-zh-rHK/strings.xml b/library/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..f8ad956
--- /dev/null
+++ b/library/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "完成"
+ "小時環形滑桿"
+ "分鐘環形滑桿"
+ "選取小時"
+ "選取分鐘"
+ "日期網格 (按月顯示)"
+ "年份清單"
+ "選取月份和日期"
+ "選取年份"
+ "已選取%1$s"
+ "已刪除 %1$s"
+ yyyy年M月
+ M月dd日EEE
+
diff --git a/library/src/main/res/values-zh-rTW/strings.xml b/library/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..c173244
--- /dev/null
+++ b/library/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "完成"
+ "小時數循環滑桿"
+ "分鐘數循環滑桿"
+ "選取小時數"
+ "選取分鐘數"
+ "日期網格 (按月顯示)"
+ "年份清單"
+ "選取月份和日期"
+ "選取年份"
+ "已選取 %1$s"
+ "已刪除 %1$s"
+ yyyy年M月
+ M月dd日EEE
+
diff --git a/library/src/main/res/values-zu/strings.xml b/library/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..adc0219
--- /dev/null
+++ b/library/src/main/res/values-zu/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ "Kwenziwe"
+ "Amahora weslayidi esiyindingilizi"
+ "Amaminithi weslayidi esiyindingilizi"
+ "Khetha amahora"
+ "Khetha amaminithi"
+ "Igridi yenyanga yezinsuku"
+ "Uhlu lonyaka"
+ "Khetha inyanga nosuku"
+ "Khetha unyaka"
+ "I-%1$s ekhethiwe"
+ "I-%1$s isusiwe"
+ MMMM yyyy
+ EEE, MMM dd
+
diff --git a/library/src/main/res/values/colors.xml b/library/src/main/res/values/colors.xml
new file mode 100644
index 0000000..5c8a5dc
--- /dev/null
+++ b/library/src/main/res/values/colors.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ #ffffff
+ @color/mdtp_white
+ @color/mdtp_background_color
+ #f5f5f5
+ #cccccc
+ @color/mdtp_numbers_text_color
+ #ff212121
+ #cccccc
+ #ff212121
+
+ #7f000000
+ #009688
+ #76ffffff
+ #33999999
+ #00796b
+ #ff212121
+ @color/mdtp_date_picker_text_normal
+ #ccc
+ #767676
+
+
+ @color/mdtp_light_gray
+ @color/mdtp_white
+ #b4b4b4
+ #767676
+ @color/mdtp_date_picker_text_normal_dark_theme
+
+ @color/mdtp_accent_color_dark
+ @android:color/white
+ #ffd1d2d4
+
+ @color/mdtp_accent_color
+ #33969696
+
+
+ #525252
+
+
+ @color/mdtp_accent_color
+ @color/mdtp_accent_color_dark
+ #424242
+ #323232
+ #808080
+ #ffffff
+ #888888
+ #bfbfbf
+
diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..52fd0a4
--- /dev/null
+++ b/library/src/main/res/values/dimens.xml
@@ -0,0 +1,76 @@
+
+
+
+
+ 0.82
+ 0.85
+ 0.16
+ 0.22
+ 0.81
+ 0.60
+ 0.83
+ 0.12
+ 0.11
+ 0.08
+
+ 54dp
+ 30dp
+ -12dp
+ -30dp
+ 16dp
+ 14sp
+ 6dip
+ 4dip
+ 96dip
+ 48dip
+ 48dip
+ 24dip
+ 270dip
+ 300dip
+
+ 270dp
+ @dimen/mdtp_date_picker_component_width
+ -2dp
+ 30dp
+ 9dp
+ 155dp
+ 140dp
+ 252dp
+ 42dp
+
+ 56dp
+ 12sp
+ 16dp
+ 45dp
+ 25dp
+ 70dp
+ 25dp
+ 12dp
+ 12dp
+ 14sp
+ 12sp
+ 64dp
+ 22dp
+
+ 30sp
+ 16sp
+
+ 48dp
+ 14sp
+ 64dp
+ 8dp
+
\ No newline at end of file
diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1b6ebab
--- /dev/null
+++ b/library/src/main/res/values/strings.xml
@@ -0,0 +1,91 @@
+
+
+
+
+ MMMM yyyy
+
+ EEE, MMM dd
+
+
+ Done
+ @android:string/ok
+ @android:string/cancel
+
+ Hours circular slider
+
+ Minutes circular slider
+
+ Seconds circular slider
+
+ Select hours
+
+ Select minutes
+
+ Select seconds
+
+ Date
+ Time
+
+
+ Month grid of days
+
+ Year list
+
+ Select month and day
+
+ Select year
+
+ %1$s selected
+
+ %1$s deleted
+
+
+ 00
+
+ :
+
+
+ sans-serif
+
+ sans-serif
+
+
+ sans-serif
+
+ am
+ pm
+
diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml
new file mode 100644
index 0000000..356d7cd
--- /dev/null
+++ b/library/src/main/res/values/styles.xml
@@ -0,0 +1,59 @@
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/.gitignore b/sample/.gitignore
similarity index 100%
rename from app/.gitignore
rename to sample/.gitignore
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 0000000..79992c0
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+
+ defaultConfig {
+ minSdkVersion 17
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0.1"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ lintOptions {
+ abortOnError false
+ }
+
+
+}
+
+dependencies {
+ compile project(':library')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:25.2.0'
+ compile 'com.android.support:design:25.2.0'
+ compile 'com.android.support:support-v13:25.2.0'
+}
diff --git a/example/proguard-rules.pro b/sample/proguard-rules.pro
similarity index 90%
rename from example/proguard-rules.pro
rename to sample/proguard-rules.pro
index ff3561b..9062564 100644
--- a/example/proguard-rules.pro
+++ b/sample/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
-# in /Users/Alhazmy13/Library/Android/sdk/tools/proguard/proguard-android.txt
+# in /home/wdullaer/Downloads/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
diff --git a/example/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
similarity index 74%
rename from example/src/main/AndroidManifest.xml
rename to sample/src/main/AndroidManifest.xml
index eaf02e1..72bce7e 100644
--- a/example/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
+ package="net.alhazmy13.hijridatepickerexample" >
+ android:name="net.alhazmy13.hijridatepickerexample.MainActivity"
+ android:label="@string/title_activity_main" >
-
diff --git a/sample/src/main/java/net/alhazmy13/hijridatepickerexample/GregorianDatePickerFragment.java b/sample/src/main/java/net/alhazmy13/hijridatepickerexample/GregorianDatePickerFragment.java
new file mode 100644
index 0000000..de02b59
--- /dev/null
+++ b/sample/src/main/java/net/alhazmy13/hijridatepickerexample/GregorianDatePickerFragment.java
@@ -0,0 +1,119 @@
+package net.alhazmy13.hijridatepickerexample;
+
+import android.app.Fragment;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+
+
+import net.alhazmy13.hijridatepicker.date.gregorian.GregorianDatePickerDialog;
+
+import java.util.Calendar;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class GregorianDatePickerFragment extends Fragment implements GregorianDatePickerDialog.OnDateSetListener {
+
+ private TextView dateTextView;
+ private CheckBox modeDarkDate;
+ private CheckBox modeCustomAccentDate;
+ private CheckBox vibrateDate;
+ private CheckBox dismissDate;
+ private CheckBox titleDate;
+ private CheckBox showYearFirst;
+ private CheckBox showVersion2;
+ private CheckBox limitSelectableDays;
+ private CheckBox highlightDays;
+
+ public GregorianDatePickerFragment() {
+ // Required empty public constructor
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.datepicker_layout, container, false);
+
+ // Find our View instances
+ dateTextView = (TextView) view.findViewById(R.id.date_textview);
+ Button dateButton = (Button) view.findViewById(R.id.date_button);
+ modeDarkDate = (CheckBox) view.findViewById(R.id.mode_dark_date);
+ modeCustomAccentDate = (CheckBox) view.findViewById(R.id.mode_custom_accent_date);
+ vibrateDate = (CheckBox) view.findViewById(R.id.vibrate_date);
+ dismissDate = (CheckBox) view.findViewById(R.id.dismiss_date);
+ titleDate = (CheckBox) view.findViewById(R.id.title_date);
+ showYearFirst = (CheckBox) view.findViewById(R.id.show_year_first);
+ showVersion2 = (CheckBox) view.findViewById(R.id.show_version_2);
+ limitSelectableDays = (CheckBox) view.findViewById(R.id.limit_dates);
+ highlightDays = (CheckBox) view.findViewById(R.id.highlight_dates);
+
+ // Show a datepicker when the dateButton is clicked
+ dateButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Calendar now = Calendar.getInstance();
+ GregorianDatePickerDialog dpd = GregorianDatePickerDialog.newInstance(
+ GregorianDatePickerFragment.this,
+ now.get(Calendar.YEAR),
+ now.get(Calendar.MONTH),
+ now.get(Calendar.DAY_OF_MONTH)
+ );
+ dpd.setThemeDark(modeDarkDate.isChecked());
+ dpd.vibrate(vibrateDate.isChecked());
+ dpd.dismissOnPause(dismissDate.isChecked());
+ dpd.showYearPickerFirst(showYearFirst.isChecked());
+ dpd.setVersion(showVersion2.isChecked() ? GregorianDatePickerDialog.Version.VERSION_2 : GregorianDatePickerDialog.Version.VERSION_1);
+ if (modeCustomAccentDate.isChecked()) {
+ dpd.setAccentColor(Color.parseColor("#009688"));
+ }
+ if (titleDate.isChecked()) {
+ dpd.setTitle("DatePicker Title");
+ }
+ if (highlightDays.isChecked()) {
+ Calendar date1 = Calendar.getInstance();
+ Calendar date2 = Calendar.getInstance();
+ date2.add(Calendar.WEEK_OF_MONTH, -1);
+ Calendar date3 = Calendar.getInstance();
+ date3.add(Calendar.WEEK_OF_MONTH, 1);
+ Calendar[] days = {date1, date2, date3};
+ dpd.setHighlightedDays(days);
+ }
+ if (limitSelectableDays.isChecked()) {
+ Calendar[] days = new Calendar[13];
+ for (int i = -6; i < 7; i++) {
+ Calendar day = Calendar.getInstance();
+ day.add(Calendar.DAY_OF_MONTH, i * 2);
+ days[i + 6] = day;
+ }
+// dpd.setSelectableDays(days);
+ }
+ dpd.show(getFragmentManager(), "Datepickerdialog");
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ GregorianDatePickerDialog dpd = (GregorianDatePickerDialog) getFragmentManager().findFragmentByTag("Datepickerdialog");
+ if(dpd != null) dpd.setOnDateSetListener(this);
+ }
+
+
+
+ @Override
+ public void onDateSet(GregorianDatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
+ String date = "You picked the following date: "+dayOfMonth+"/"+(++monthOfYear)+"/"+year;
+ dateTextView.setText(date);
+ }
+}
diff --git a/sample/src/main/java/net/alhazmy13/hijridatepickerexample/HijriDatePickerFragment.java b/sample/src/main/java/net/alhazmy13/hijridatepickerexample/HijriDatePickerFragment.java
new file mode 100644
index 0000000..1c74552
--- /dev/null
+++ b/sample/src/main/java/net/alhazmy13/hijridatepickerexample/HijriDatePickerFragment.java
@@ -0,0 +1,118 @@
+package net.alhazmy13.hijridatepickerexample;
+
+import android.app.Fragment;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.github.msarhan.ummalqura.calendar.UmmalquraCalendar;
+import net.alhazmy13.hijridatepicker.date.hijri.HijriDatePickerDialog;
+
+import java.util.Calendar;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class HijriDatePickerFragment extends Fragment implements HijriDatePickerDialog.OnDateSetListener {
+
+ private TextView dateTextView;
+ private CheckBox modeDarkDate;
+ private CheckBox modeCustomAccentDate;
+ private CheckBox vibrateDate;
+ private CheckBox dismissDate;
+ private CheckBox titleDate;
+ private CheckBox showYearFirst;
+ private CheckBox showVersion2;
+ private CheckBox limitSelectableDays;
+ private CheckBox highlightDays;
+
+ public HijriDatePickerFragment() {
+ // Required empty public constructor
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.datepicker_layout, container, false);
+
+ // Find our View instances
+ dateTextView = (TextView) view.findViewById(R.id.date_textview);
+ Button dateButton = (Button) view.findViewById(R.id.date_button);
+ modeDarkDate = (CheckBox) view.findViewById(R.id.mode_dark_date);
+ modeCustomAccentDate = (CheckBox) view.findViewById(R.id.mode_custom_accent_date);
+ vibrateDate = (CheckBox) view.findViewById(R.id.vibrate_date);
+ dismissDate = (CheckBox) view.findViewById(R.id.dismiss_date);
+ titleDate = (CheckBox) view.findViewById(R.id.title_date);
+ showYearFirst = (CheckBox) view.findViewById(R.id.show_year_first);
+ showVersion2 = (CheckBox) view.findViewById(R.id.show_version_2);
+ limitSelectableDays = (CheckBox) view.findViewById(R.id.limit_dates);
+ highlightDays = (CheckBox) view.findViewById(R.id.highlight_dates);
+
+ // Show a datepicker when the dateButton is clicked
+ dateButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ UmmalquraCalendar now = new UmmalquraCalendar();
+ HijriDatePickerDialog dpd = HijriDatePickerDialog.newInstance(
+ HijriDatePickerFragment.this,
+ now.get(Calendar.YEAR),
+ now.get(Calendar.MONTH),
+ now.get(Calendar.DAY_OF_MONTH)
+ );
+ dpd.setThemeDark(modeDarkDate.isChecked());
+ dpd.vibrate(vibrateDate.isChecked());
+ dpd.dismissOnPause(dismissDate.isChecked());
+ dpd.showYearPickerFirst(showYearFirst.isChecked());
+ dpd.setVersion(showVersion2.isChecked() ? HijriDatePickerDialog.Version.VERSION_2 : HijriDatePickerDialog.Version.VERSION_1);
+ if (modeCustomAccentDate.isChecked()) {
+ dpd.setAccentColor(Color.parseColor("#009688"));
+ }
+ if (titleDate.isChecked()) {
+ dpd.setTitle("DatePicker Title");
+ }
+ if (highlightDays.isChecked()) {
+ UmmalquraCalendar date1 = new UmmalquraCalendar();
+ UmmalquraCalendar date2 = new UmmalquraCalendar();
+ date2.add(Calendar.WEEK_OF_MONTH, -1);
+ UmmalquraCalendar date3 = new UmmalquraCalendar();
+ date3.add(Calendar.WEEK_OF_MONTH, 1);
+ UmmalquraCalendar[] days = {date1, date2, date3};
+ dpd.setHighlightedDays(days);
+ }
+ if (limitSelectableDays.isChecked()) {
+ UmmalquraCalendar[] days = new UmmalquraCalendar[13];
+ for (int i = -6; i < 7; i++) {
+ UmmalquraCalendar day = new UmmalquraCalendar();
+ day.add(Calendar.DAY_OF_MONTH, i * 2);
+ days[i + 6] = day;
+ }
+// dpd.setSelectableDays(days);
+ }
+ dpd.show(getFragmentManager(), "Datepickerdialog");
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ HijriDatePickerDialog dpd = (HijriDatePickerDialog) getFragmentManager().findFragmentByTag("Datepickerdialog");
+ if(dpd != null) dpd.setOnDateSetListener(this);
+ }
+
+
+
+ @Override
+ public void onDateSet(HijriDatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
+ String date = "You picked the following date: "+dayOfMonth+"/"+(++monthOfYear)+"/"+year;
+ dateTextView.setText(date);
+ }
+}
diff --git a/sample/src/main/java/net/alhazmy13/hijridatepickerexample/MainActivity.java b/sample/src/main/java/net/alhazmy13/hijridatepickerexample/MainActivity.java
new file mode 100644
index 0000000..04d8441
--- /dev/null
+++ b/sample/src/main/java/net/alhazmy13/hijridatepickerexample/MainActivity.java
@@ -0,0 +1,77 @@
+package net.alhazmy13.hijridatepickerexample;
+
+import android.os.Bundle;
+import android.support.design.widget.TabLayout;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.support.v13.app.FragmentPagerAdapter;
+
+public class MainActivity extends AppCompatActivity
+{
+ ViewPager viewPager;
+ PickerAdapter adapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ adapter = new PickerAdapter(getFragmentManager());
+ viewPager = (ViewPager) findViewById(R.id.pager);
+ viewPager.setAdapter(adapter);
+
+ setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
+ TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
+ tabLayout.setupWithViewPager(viewPager);
+ for(int i=0;i
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/datepicker_layout.xml b/sample/src/main/res/layout/datepicker_layout.xml
new file mode 100644
index 0000000..fb6eeb0
--- /dev/null
+++ b/sample/src/main/res/layout/datepicker_layout.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/timepicker_layout.xml b/sample/src/main/res/layout/timepicker_layout.xml
new file mode 100644
index 0000000..4644390
--- /dev/null
+++ b/sample/src/main/res/layout/timepicker_layout.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to sample/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to sample/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to sample/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/sample/src/main/res/values-w820dp/dimens.xml
similarity index 100%
rename from app/src/main/res/values-w820dp/dimens.xml
rename to sample/src/main/res/values-w820dp/dimens.xml
diff --git a/app/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml
similarity index 100%
rename from app/src/main/res/values/dimens.xml
rename to sample/src/main/res/values/dimens.xml
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d94a1b4
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,36 @@
+
+
+
+ Material DateTimePicker
+ DateTimePicker Example
+ The time you picked will be shown here.
+ The date you picked will be shown here.
+ The date and time you picked will be shown here.
+ Pick Time
+ Pick Date
+ Pick Date+Time
+ Use 24 hour mode
+ Use dark theme
+ Use custom accent
+ Vibrate on touch
+ Dismiss on pause (eg: orientation change)
+ Show a title
+ Use marshmallow layout
+ Show the year picker first
+ Show seconds picker
+ Show minutes picker
+ Limit selectable times
+ Limit selectable dates
+ Disable specific dates
+ Highlight certain dates
+ Settings
+
+ Time
+ Date
+ Date+Time
+
+
+ Hello blank fragment
+ Hijri
+
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..3090e03
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 48a0e9d..77c36d0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app', ':libary', ':example'
+include ':library', ':sample'