├── .gitignore ├── .travis.yml ├── CONTRIBUTING ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── chinese-locale-daynames.png ├── compact-calendar-customised-indicators.png ├── compact-calendar-demo.gif ├── compact-calendar-view-example-multi-events.png ├── compact-calendar-view-example.png └── compact_calendar_animation.gif ├── library ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── sundeepk │ │ │ └── compactcalendarview │ │ │ ├── AnimationHandler.java │ │ │ ├── AnimationListener.java │ │ │ ├── AnimatorListener.java │ │ │ ├── CollapsingAnimation.java │ │ │ ├── CompactCalendarController.java │ │ │ ├── CompactCalendarView.java │ │ │ ├── Events.java │ │ │ ├── EventsContainer.java │ │ │ ├── WeekUtils.java │ │ │ ├── comparators │ │ │ └── EventComparator.java │ │ │ └── domain │ │ │ └── Event.java │ └── res │ │ └── values │ │ └── attrs.xml │ └── test │ └── java │ └── com │ └── github │ └── sundeepk │ └── compactcalendarview │ ├── CompactCalendarControllerTest.java │ ├── CompactCalendarHelper.java │ ├── EventsContainerTest.java │ └── WeekUtilsTest.java ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── screenshots │ ├── sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToJan.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarch.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarchRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForFeb.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForFebRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsDifferentColorsForCurrentSelectedDay.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsEventIndicatorsBelowHighlightedDayIndicators.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsEventsRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsFillLargeIndicatorForEventsWhenDrawEventsBelowDayIndicatorsIsTrue.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsFridayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsFridayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsIndicatorsBelowCurrentSelectedDayWithLargeHeight.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsMondayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsMondayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsSaturdayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsSaturdayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsSundayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsSundayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsThursdayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsThursdayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsTuesdayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsTuesdayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsWedAsFirstDayWithFrenchLocaleRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayOfMonth.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayOfMonthRtl.png │ ├── sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayWithFrenchLocale.png │ ├── sundeepk.github.com.sample.ApplicationTest_testOnDayClickListenerIsCalledWhenLocaleIsFranceWithWedAsFirstDayOFWeek.png │ └── sundeepk.github.com.sample.ApplicationTest_testWhenShouldSelectFirstDayOfMonthOnScrollIsFalseItDoesNotSelectFIrstDayOfMonth.png └── src │ ├── androidTest │ └── java │ │ └── sundeepk │ │ └── github │ │ └── com │ │ └── sample │ │ ├── ApplicationTest.java │ │ └── TestRunner.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── sundeepk │ │ └── github │ │ └── com │ │ └── sample │ │ ├── CompactCalendarTab.java │ │ ├── MainActivity.java │ │ ├── SlidingTabLayout.java │ │ ├── SlidingTabStrip.java │ │ ├── Tab2.java │ │ └── ViewPagerAdapter.java │ └── res │ ├── layout │ ├── activity_main.xml │ ├── main_tab.xml │ ├── tab_2.xml │ └── tool_bar.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Android ### 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | 32 | ### Intellij ### 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 34 | 35 | /*.iml 36 | 37 | ## Directory-based project format: 38 | .idea/ 39 | # if you remove the above rule, at least ignore the follwing: 40 | 41 | # User-specific stuff: 42 | # .idea/workspace.xml 43 | # .idea/tasks.xml 44 | # .idea/dictionaries 45 | 46 | # Sensitive or high-churn files: 47 | # .idea/dataSources.ids 48 | # .idea/dataSources.xml 49 | # .idea/sqlDataSources.xml 50 | # .idea/dynamic.xml 51 | # .idea/uiDesigner.xml 52 | 53 | # Gradle: 54 | # .idea/gradle.xml 55 | # .idea/libraries 56 | 57 | # Mongo Explorer plugin: 58 | # .idea/mongoSettings.xml 59 | 60 | ## File-based project format: 61 | *.ipr 62 | *.iws 63 | 64 | ## Plugin-specific files: 65 | 66 | # IntelliJ 67 | out/ 68 | 69 | # mpeltonen/sbt-idea plugin 70 | .idea_modules/ 71 | 72 | # JIRA plugin 73 | atlassian-ide-plugin.xml 74 | 75 | # Crashlytics plugin (for Android Studio and IntelliJ) 76 | com_crashlytics_export_strings.xml 77 | 78 | 79 | ### OSX ### 80 | .DS_Store 81 | .AppleDouble 82 | .LSOverride 83 | 84 | # Icon must end with two \r 85 | Icon 86 | 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear on external disk 92 | .Spotlight-V100 93 | .Trashes 94 | 95 | # Directories potentially created on remote AFP share 96 | .AppleDB 97 | .AppleDesktop 98 | Network Trash Folder 99 | Temporary Items 100 | .apdisk 101 | 102 | 103 | ### Windows ### 104 | # Windows image file caches 105 | Thumbs.db 106 | ehthumbs.db 107 | 108 | # Folder config file 109 | Desktop.ini 110 | 111 | # Recycle Bin used on file shares 112 | $RECYCLE.BIN/ 113 | 114 | # Windows Installer files 115 | *.cab 116 | *.msi 117 | *.msm 118 | *.msp 119 | 120 | # Windows shortcuts 121 | *.lnk 122 | 123 | 124 | ### Gradle ### 125 | .gradle 126 | build/ 127 | 128 | # Ignore Gradle GUI config 129 | gradle-app.setting 130 | *.iml 131 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: required 3 | env: 4 | global: 5 | - ADB_INSTALL_TIMEOUT=15 # 8 minutes (2 minutes by default) 6 | 7 | jdk: 8 | - oraclejdk8 9 | 10 | cache: 11 | directories: 12 | 13 | # Gradle dependencies 14 | - $HOME/.gradle/caches/ 15 | - $HOME/.gradle/wrapper/ 16 | 17 | # python 18 | - $HOME/.cache/pip/ 19 | 20 | android: 21 | components: 22 | - tools 23 | - build-tools-27.0.3 24 | - android-27 25 | - android-22 26 | - extra-google-google_play_services 27 | - extra-android-support 28 | - extra 29 | 30 | before_install: 31 | - sudo pip install Pillow 32 | - yes | sdkmanager tools 33 | - yes | sdkmanager --licenses 34 | - sdkmanager 'system-images;android-22;default;armeabi-v7a' 35 | 36 | before_script: 37 | - echo no | avdmanager create avd --force -n testCompactCalendarEmulator -k "system-images;android-22;default;armeabi-v7a" 38 | - mksdcard -l sdcard 100M sdcard 39 | - $ANDROID_HOME/emulator/emulator -avd testCompactCalendarEmulator -no-audio -no-window -sdcard sdcard & 40 | - android-wait-for-emulator 41 | - adb shell settings put global window_animation_scale 0 & 42 | - adb shell settings put global transition_animation_scale 0 & 43 | - adb shell settings put global animator_duration_scale 0 & 44 | - adb shell input keyevent 82 & 45 | 46 | script: 47 | - ./gradlew verifyMode screenshotTests -Pcom.android.build.threadPoolSize=1 -Dorg.gradle.parallel=false -Dorg.gradle.jvmargs="-Xms1024m -Xmx1024m" -Dorg.gradle.daemon=false 48 | - ./gradlew test 49 | 50 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Please raise an issue of the requirement so that a discussion can take before any code is written, even if you intend to raise a pull request. Please see setup for testing. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 SundeepK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CompactCalendarView [![Build Status](https://travis-ci.org/SundeepK/CompactCalendarView.svg?branch=master)](https://travis-ci.org/SundeepK/CompactCalendarView) 2 | CompactCalendarView is a simple calendar view which provides scrolling between months. It's based on Java's Date and Calendar classes. It provides a simple api to query for dates and listeners for specific events. For example, when the calendar has scrolled to a new month or a day has been selected. 3 | Still under active development. 4 | 5 | 6 | 7 | # Contributing 8 | Please raise an issue of the requirement so that a discussion can take before any code is written, even if you intend to raise a pull request. Please see setup for testing. 9 | 10 | # Testing 11 | CompactCalendarView makes use of screenshot-tests-for-android (https://github.com/facebook/screenshot-tests-for-android). This is for UI testing. Since screenshot-tests-for-android takes screenshots, we need a way to ensure images can be reproduced consistently. To do this, a specific emulator is used to run tests. 12 | Unfortunately, an older emulator is used for now. New pull requests which change functionality some how should aim to create new screenshot tests or unit tests if possible. To run this locally, run the below commands: 13 | 14 | Pre-requisite (Also refer to .travis.yml): 15 | - Python 16 | - Python pillow installed 17 | - Install android-19 (can be done through android sdk manager or command line). 18 | 19 | Android 19 emulator is used because it seems to be a fast enough on travis-ci and because x86 emulators are not supported on travis-ci. Newer android version is possible but build times will increase. 20 | 21 | Install the abi and accept: 22 | ```bash 23 | $ $ANDROID_HOME/tools/bin/sdkmanager 'system-images;android-22;default;armeabi-v7a' 24 | ``` 25 | 26 | Create the emulator: 27 | ```bash 28 | $ echo no | $ANDROID_HOME/tools/bin/avdmanager create avd --force -n testCompactCalendarEmulator -k "system-images;android-22;default;armeabi-v7a" 29 | ``` 30 | 31 | Create sd card (creating in current dir): 32 | Any problems with sdcard are best solved by deleting and trying again 33 | ```bash 34 | $ mksdcard -l sdcard 100M sdcard 35 | ``` 36 | 37 | Run emulator (with out audio and window): 38 | ```bash 39 | $ $ANDROID_HOME/emulator/emulator -avd testCompactCalendarEmulator -no-audio -no-window -sdcard sdcard & 40 | ``` 41 | 42 | Run emulator and watch(with audio and window): 43 | ```bash 44 | $ $ANDROID_HOME/emulator/emulator -avd testCompactCalendarEmulator -sdcard sdcard 45 | ``` 46 | 47 | Running the tests to verify that the current tests pass and to check which tests are not producing the same screenshot: 48 | ```bash 49 | $ ./gradlew verifyMode screenshotTests 50 | ``` 51 | 52 | To generate new screenshots if new tests have been added: 53 | ```bash 54 | $ ./gradlew recordMode screenshotTests 55 | ``` 56 | 57 | Run the unit tests like below: 58 | ```bash 59 | $ ./gradlew test 60 | ``` 61 | 62 | ## Android studio emulator 63 | It's possible to test using android studio emulator. However, it must be android 19 and and 480x800 screen resolution. One example is the Nexus S emulator. Just start the emulator and execute the gradle commands to run the tests. Emulator should be found automatically. 64 | 65 | # Open/Close animations 66 | The library supports opening/closing with or without animations. 67 | 68 | ![ScreenShot](https://github.com/SundeepK/CompactCalendarView/blob/master/images/compact_calendar_animation.gif) 69 | 70 | # Example usage 71 | It is possible to change the appearance of the view via a few properties. This includes the background color, text color, textsize color of the current day and the color of the first day of the month. 72 | 73 | 74 | ```xml 75 | 90 | 91 | ``` 92 | 93 | Please see Sample app for full example. 94 | 95 | ```java 96 | // ... code omitted for brevity 97 | @Override 98 | protected void onCreate(Bundle savedInstanceState) { 99 | super.onCreate(savedInstanceState); 100 | setContentView(R.layout.activity_main); 101 | final CompactCalendarView compactCalendarView = (CompactCalendarView) findViewById(R.id.compactcalendar_view); 102 | // Set first day of week to Monday, defaults to Monday so calling setFirstDayOfWeek is not necessary 103 | // Use constants provided by Java Calendar class 104 | compactCalendarView.setFirstDayOfWeek(Calendar.MONDAY); 105 | 106 | // Add event 1 on Sun, 07 Jun 2015 18:20:51 GMT 107 | Event ev1 = new Event(Color.GREEN, 1433701251000L, "Some extra data that I want to store."); 108 | compactCalendar.addEvent(ev1); 109 | 110 | // Added event 2 GMT: Sun, 07 Jun 2015 19:10:51 GMT 111 | Event ev2 = new Event(Color.GREEN, 1433704251000L); 112 | compactCalendar.addEvent(ev2); 113 | 114 | // Query for events on Sun, 07 Jun 2015 GMT. 115 | // Time is not relevant when querying for events, since events are returned by day. 116 | // So you can pass in any arbitary DateTime and you will receive all events for that day. 117 | List events = compactCalendar.getEvents(1433701251000L); // can also take a Date object 118 | 119 | // events has size 2 with the 2 events inserted previously 120 | Log.d(TAG, "Events: " + events); 121 | 122 | // define a listener to receive callbacks when certain events happen. 123 | compactCalendarView.setListener(new CompactCalendarView.CompactCalendarViewListener() { 124 | @Override 125 | public void onDayClick(Date dateClicked) { 126 | List events = compactCalendarView.getEvents(dateClicked); 127 | Log.d(TAG, "Day was clicked: " + dateClicked + " with events " + events); 128 | } 129 | 130 | @Override 131 | public void onMonthScroll(Date firstDayOfNewMonth) { 132 | Log.d(TAG, "Month was scrolled to: " + firstDayOfNewMonth); 133 | } 134 | }); 135 | } 136 | 137 | ``` 138 | 139 | You can modify indicators using a preset of styles, below is an example, but few other combinations are also possible: 140 | 141 | ![ScreenShot](https://github.com/SundeepK/CompactCalendarView/blob/master/images/compact-calendar-customised-indicators.png) 142 | 143 | Note that the calendar makes no attempt to de-duplicate events for the same exact DateTime. This is something that you must handle your self if it is important to your use case. 144 | 145 | # Locale specific settings 146 | It's possible to set the locale so that weekday column names are automatically set by the calendar. 147 | ```java 148 | CompactCalendarView compactCalendarView = (CompactCalendarView) findViewById(R.id.compactcalendar_view); 149 | compactCalendarView.setLocale(Locale.CHINESE); 150 | compactCalendarView.setUseThreeLetterAbbreviation(true); 151 | ``` 152 | 153 | 154 | 155 | ```gradle 156 | dependencies { 157 | compile 'com.github.sundeepk:compact-calendar-view:3.0.0' 158 | } 159 | ``` 160 | 161 | ``` 162 | The MIT License (MIT) 163 | 164 | Copyright (c) [2018] [Sundeepk] 165 | 166 | Permission is hereby granted, free of charge, to any person obtaining a copy 167 | of this software and associated documentation files (the "Software"), to deal 168 | in the Software without restriction, including without limitation the rights 169 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 170 | copies of the Software, and to permit persons to whom the Software is 171 | furnished to do so, subject to the following conditions: 172 | 173 | The above copyright notice and this permission notice shall be included in all 174 | copies or substantial portions of the Software. 175 | 176 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 177 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 178 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 179 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 180 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 181 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 182 | SOFTWARE. 183 | ``` 184 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.1.2' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Tue Aug 23 13:10:40 BST 2016 16 | systemProp.http.proxyPort=80 17 | POM_LICENCE_URL=http\://opensource.org/licenses/MIT 18 | VERSION_NAME=3.0.0 19 | VERSION_CODE=3.0.0 20 | POM_DEVELOPER_ID=sundeepk 21 | POM_SCM_DEV_CONNECTION=scm\:git@github.com\:SundeepK/CompactCalendarView.git 22 | POM_SCM_URL=https\://github.com/SundeepK/CompactCalendarView 23 | GROUP=com.github.sundeepk 24 | POM_LICENCE_DIST=repo 25 | POM_DEVELOPER_NAME=Sundeep Kahlon 26 | systemProp.http.nonProxyHosts=localhost,127.0.01 27 | POM_URL=https\://github.com/SundeepK/CompactCalendarView 28 | POM_SCM_CONNECTION=scm\:git@github.com\:SundeepK/CompactCalendarView.git 29 | POM_LICENCE_NAME=The MIT License (MIT) 30 | POM_DESCRIPTION=An android library which provides a compact calender view 31 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 28 11:47:28 BST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/chinese-locale-daynames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/images/chinese-locale-daynames.png -------------------------------------------------------------------------------- /images/compact-calendar-customised-indicators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/images/compact-calendar-customised-indicators.png -------------------------------------------------------------------------------- /images/compact-calendar-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/images/compact-calendar-demo.gif -------------------------------------------------------------------------------- /images/compact-calendar-view-example-multi-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/images/compact-calendar-view-example-multi-events.png -------------------------------------------------------------------------------- /images/compact-calendar-view-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/images/compact-calendar-view-example.png -------------------------------------------------------------------------------- /images/compact_calendar_animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/images/compact_calendar_animation.gif -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'idea' 3 | 4 | idea { 5 | module { 6 | downloadJavadoc = false 7 | 8 | downloadSources = true 9 | } 10 | } 11 | 12 | repositories { 13 | google() 14 | jcenter() 15 | mavenCentral() 16 | } 17 | 18 | 19 | android { 20 | compileSdkVersion 27 21 | buildToolsVersion '27.0.3' 22 | 23 | defaultConfig { 24 | minSdkVersion 11 25 | targetSdkVersion 27 26 | versionCode 1 27 | versionName "1.0" 28 | } 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | api fileTree(include: ['*.jar'], dir: 'libs') 39 | api 'com.android.support:appcompat-v7:27.1.1' 40 | //mockito dependencies 41 | testImplementation 'junit:junit:4.12' 42 | testImplementation 'org.mockito:mockito-core:2.15.0' 43 | } 44 | 45 | //apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' 46 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=CompactCalendarView Library 2 | POM_ARTIFACT_ID=compact-calendar-view 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/sundeep/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/AnimationHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | 4 | import android.animation.Animator; 5 | import android.animation.ValueAnimator; 6 | import android.support.annotation.NonNull; 7 | import android.view.animation.AccelerateDecelerateInterpolator; 8 | import android.view.animation.Animation; 9 | import android.view.animation.OvershootInterpolator; 10 | 11 | class AnimationHandler { 12 | 13 | private static final int HEIGHT_ANIM_DURATION_MILLIS = 650; 14 | private static final int INDICATOR_ANIM_DURATION_MILLIS = 600; 15 | private boolean isAnimating = false; 16 | private CompactCalendarController compactCalendarController; 17 | private CompactCalendarView compactCalendarView; 18 | private CompactCalendarView.CompactCalendarAnimationListener compactCalendarAnimationListener; 19 | 20 | AnimationHandler(CompactCalendarController compactCalendarController, CompactCalendarView compactCalendarView) { 21 | this.compactCalendarController = compactCalendarController; 22 | this.compactCalendarView = compactCalendarView; 23 | } 24 | 25 | void setCompactCalendarAnimationListener(CompactCalendarView.CompactCalendarAnimationListener compactCalendarAnimationListener){ 26 | this.compactCalendarAnimationListener = compactCalendarAnimationListener; 27 | } 28 | 29 | void openCalendar() { 30 | if (isAnimating) { 31 | return; 32 | } 33 | isAnimating = true; 34 | Animation heightAnim = getCollapsingAnimation(true); 35 | heightAnim.setDuration(HEIGHT_ANIM_DURATION_MILLIS); 36 | heightAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 37 | compactCalendarController.setAnimationStatus(CompactCalendarController.EXPAND_COLLAPSE_CALENDAR); 38 | setUpAnimationLisForOpen(heightAnim); 39 | compactCalendarView.getLayoutParams().height = 0; 40 | compactCalendarView.requestLayout(); 41 | compactCalendarView.startAnimation(heightAnim); 42 | } 43 | 44 | void closeCalendar() { 45 | if (isAnimating) { 46 | return; 47 | } 48 | isAnimating = true; 49 | Animation heightAnim = getCollapsingAnimation(false); 50 | heightAnim.setDuration(HEIGHT_ANIM_DURATION_MILLIS); 51 | heightAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 52 | setUpAnimationLisForClose(heightAnim); 53 | compactCalendarController.setAnimationStatus(CompactCalendarController.EXPAND_COLLAPSE_CALENDAR); 54 | compactCalendarView.getLayoutParams().height = compactCalendarView.getHeight(); 55 | compactCalendarView.requestLayout(); 56 | compactCalendarView.startAnimation(heightAnim); 57 | } 58 | 59 | void openCalendarWithAnimation() { 60 | if (isAnimating) { 61 | return; 62 | } 63 | isAnimating = true; 64 | final Animator indicatorAnim = getIndicatorAnimator(1f, compactCalendarController.getDayIndicatorRadius()); 65 | final Animation heightAnim = getExposeCollapsingAnimation(true); 66 | compactCalendarView.getLayoutParams().height = 0; 67 | compactCalendarView.requestLayout(); 68 | setUpAnimationLisForExposeOpen(indicatorAnim, heightAnim); 69 | compactCalendarView.startAnimation(heightAnim); 70 | } 71 | 72 | void closeCalendarWithAnimation() { 73 | if (isAnimating) { 74 | return; 75 | } 76 | isAnimating = true; 77 | final Animator indicatorAnim = getIndicatorAnimator(compactCalendarController.getDayIndicatorRadius(), 1f); 78 | final Animation heightAnim = getExposeCollapsingAnimation(false); 79 | compactCalendarView.getLayoutParams().height = compactCalendarView.getHeight(); 80 | compactCalendarView.requestLayout(); 81 | setUpAnimationLisForExposeClose(indicatorAnim, heightAnim); 82 | compactCalendarView.startAnimation(heightAnim); 83 | } 84 | 85 | private void setUpAnimationLisForExposeOpen(final Animator indicatorAnim, Animation heightAnim) { 86 | heightAnim.setAnimationListener(new AnimationListener() { 87 | @Override 88 | public void onAnimationStart(Animation animation) { 89 | compactCalendarController.setAnimationStatus(CompactCalendarController.EXPOSE_CALENDAR_ANIMATION); 90 | } 91 | 92 | @Override 93 | public void onAnimationEnd(Animation animation) { 94 | indicatorAnim.start(); 95 | } 96 | }); 97 | indicatorAnim.addListener(new AnimatorListener() { 98 | @Override 99 | public void onAnimationStart(Animator animation) { 100 | compactCalendarController.setAnimationStatus(CompactCalendarController.ANIMATE_INDICATORS); 101 | } 102 | 103 | @Override 104 | public void onAnimationEnd(Animator animation) { 105 | compactCalendarController.setAnimationStatus(CompactCalendarController.IDLE); 106 | onOpen(); 107 | isAnimating = false; 108 | } 109 | }); 110 | } 111 | 112 | private void setUpAnimationLisForExposeClose(final Animator indicatorAnim, Animation heightAnim) { 113 | heightAnim.setAnimationListener(new AnimationListener() { 114 | @Override 115 | public void onAnimationStart(Animation animation) { 116 | compactCalendarController.setAnimationStatus(CompactCalendarController.EXPOSE_CALENDAR_ANIMATION); 117 | indicatorAnim.start(); 118 | } 119 | 120 | @Override 121 | public void onAnimationEnd(Animation animation) { 122 | compactCalendarController.setAnimationStatus(CompactCalendarController.IDLE); 123 | onClose(); 124 | isAnimating = false; 125 | } 126 | }); 127 | indicatorAnim.addListener(new AnimatorListener() { 128 | @Override 129 | public void onAnimationStart(Animator animation) { 130 | compactCalendarController.setAnimationStatus(CompactCalendarController.ANIMATE_INDICATORS); 131 | } 132 | 133 | @Override 134 | public void onAnimationEnd(Animator animation) { 135 | } 136 | }); 137 | } 138 | 139 | @NonNull 140 | private Animation getExposeCollapsingAnimation(final boolean isCollapsing) { 141 | Animation heightAnim = getCollapsingAnimation(isCollapsing); 142 | heightAnim.setDuration(HEIGHT_ANIM_DURATION_MILLIS); 143 | heightAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 144 | return heightAnim; 145 | } 146 | 147 | @NonNull 148 | private Animation getCollapsingAnimation(boolean isCollapsing) { 149 | return new CollapsingAnimation(compactCalendarView, compactCalendarController, compactCalendarController.getTargetHeight(), getTargetGrowRadius(), isCollapsing); 150 | } 151 | 152 | @NonNull 153 | private Animator getIndicatorAnimator(float from, float to) { 154 | ValueAnimator animIndicator = ValueAnimator.ofFloat(from, to); 155 | animIndicator.setDuration(INDICATOR_ANIM_DURATION_MILLIS); 156 | animIndicator.setInterpolator(new OvershootInterpolator()); 157 | animIndicator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 158 | @Override 159 | public void onAnimationUpdate(ValueAnimator animation) { 160 | compactCalendarController.setGrowFactorIndicator((Float) animation.getAnimatedValue()); 161 | compactCalendarView.invalidate(); 162 | } 163 | }); 164 | return animIndicator; 165 | } 166 | 167 | private int getTargetGrowRadius() { 168 | int heightSq = compactCalendarController.getTargetHeight() * compactCalendarController.getTargetHeight(); 169 | int widthSq = compactCalendarController.getWidth() * compactCalendarController.getWidth(); 170 | return (int) (0.5 * Math.sqrt(heightSq + widthSq)); 171 | } 172 | 173 | private void onOpen() { 174 | if (compactCalendarAnimationListener != null) { 175 | compactCalendarAnimationListener.onOpened(); 176 | } 177 | } 178 | 179 | private void onClose() { 180 | if (compactCalendarAnimationListener != null) { 181 | compactCalendarAnimationListener.onClosed(); 182 | } 183 | } 184 | 185 | private void setUpAnimationLisForOpen(Animation openAnimation) { 186 | openAnimation.setAnimationListener(new AnimationListener() { 187 | @Override 188 | public void onAnimationEnd(Animation animation) { 189 | super.onAnimationEnd(animation); 190 | onOpen(); 191 | isAnimating = false; 192 | } 193 | }); 194 | } 195 | 196 | private void setUpAnimationLisForClose(Animation openAnimation) { 197 | openAnimation.setAnimationListener(new AnimationListener() { 198 | @Override 199 | public void onAnimationEnd(Animation animation) { 200 | super.onAnimationEnd(animation); 201 | onClose(); 202 | isAnimating = false; 203 | } 204 | }); 205 | } 206 | 207 | public boolean isAnimating() { 208 | return isAnimating; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/AnimationListener.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | 4 | import android.view.animation.Animation; 5 | 6 | public abstract class AnimationListener implements Animation.AnimationListener{ 7 | 8 | @Override 9 | public void onAnimationStart(Animation animation) { 10 | 11 | } 12 | 13 | @Override 14 | public void onAnimationEnd(Animation animation) { 15 | 16 | } 17 | 18 | @Override 19 | public void onAnimationRepeat(Animation animation) { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/AnimatorListener.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | 4 | import android.animation.Animator; 5 | 6 | public abstract class AnimatorListener implements Animator.AnimatorListener{ 7 | 8 | @Override 9 | public void onAnimationStart(Animator animation) { 10 | } 11 | 12 | @Override 13 | public void onAnimationEnd(Animator animation) { 14 | 15 | } 16 | 17 | @Override 18 | public void onAnimationCancel(Animator animation) { 19 | } 20 | 21 | @Override 22 | public void onAnimationRepeat(Animator animation) { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/CollapsingAnimation.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | 4 | import android.view.animation.Animation; 5 | import android.view.animation.Transformation; 6 | 7 | class CollapsingAnimation extends Animation { 8 | private final int targetHeight; 9 | private final CompactCalendarView view; 10 | private int targetGrowRadius; 11 | private final boolean down; 12 | private CompactCalendarController compactCalendarController; 13 | 14 | public CollapsingAnimation(CompactCalendarView view, CompactCalendarController compactCalendarController, int targetHeight, int targetGrowRadius, boolean down) { 15 | this.view = view; 16 | this.compactCalendarController = compactCalendarController; 17 | this.targetHeight = targetHeight; 18 | this.targetGrowRadius = targetGrowRadius; 19 | this.down = down; 20 | } 21 | 22 | @Override 23 | protected void applyTransformation(float interpolatedTime, Transformation t) { 24 | float grow = 0; 25 | int newHeight; 26 | if (down) { 27 | newHeight = (int) (targetHeight * interpolatedTime); 28 | grow = (interpolatedTime * (targetGrowRadius * 2)); 29 | } else { 30 | float progress = 1 - interpolatedTime; 31 | newHeight = (int) (targetHeight * progress); 32 | grow = (progress * (targetGrowRadius * 2)); 33 | } 34 | compactCalendarController.setGrowProgress(grow); 35 | view.getLayoutParams().height = newHeight; 36 | view.requestLayout(); 37 | 38 | } 39 | 40 | @Override 41 | public void initialize(int width, int height, int parentWidth, 42 | int parentHeight) { 43 | super.initialize(width, height, parentWidth, parentHeight); 44 | } 45 | 46 | @Override 47 | public boolean willChangeBounds() { 48 | return true; 49 | } 50 | } -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/CompactCalendarView.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.support.v4.view.GestureDetectorCompat; 9 | import android.util.AttributeSet; 10 | import android.view.GestureDetector; 11 | import android.view.MotionEvent; 12 | import android.view.VelocityTracker; 13 | import android.view.View; 14 | import android.widget.OverScroller; 15 | 16 | import com.github.sundeepk.compactcalendarview.domain.Event; 17 | 18 | import java.util.Calendar; 19 | import java.util.Date; 20 | import java.util.List; 21 | import java.util.Locale; 22 | import java.util.TimeZone; 23 | 24 | public class CompactCalendarView extends View { 25 | 26 | public static final int FILL_LARGE_INDICATOR = 1; 27 | public static final int NO_FILL_LARGE_INDICATOR = 2; 28 | public static final int SMALL_INDICATOR = 3; 29 | 30 | private final AnimationHandler animationHandler; 31 | private CompactCalendarController compactCalendarController; 32 | private GestureDetectorCompat gestureDetector; 33 | private boolean horizontalScrollEnabled = true; 34 | 35 | public interface CompactCalendarViewListener { 36 | public void onDayClick(Date dateClicked); 37 | public void onMonthScroll(Date firstDayOfNewMonth); 38 | } 39 | 40 | public interface CompactCalendarAnimationListener { 41 | public void onOpened(); 42 | public void onClosed(); 43 | } 44 | 45 | private final GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { 46 | @Override 47 | public void onLongPress(MotionEvent e) { 48 | } 49 | 50 | @Override 51 | public boolean onSingleTapUp(MotionEvent e) { 52 | compactCalendarController.onSingleTapUp(e); 53 | invalidate(); 54 | return super.onSingleTapUp(e); 55 | } 56 | 57 | @Override 58 | public boolean onDown(MotionEvent e) { 59 | return true; 60 | } 61 | 62 | @Override 63 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 64 | return true; 65 | } 66 | 67 | @Override 68 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 69 | if(horizontalScrollEnabled) { 70 | if (Math.abs(distanceX) > 0) { 71 | getParent().requestDisallowInterceptTouchEvent(true); 72 | 73 | compactCalendarController.onScroll(e1, e2, distanceX, distanceY); 74 | invalidate(); 75 | return true; 76 | } 77 | } 78 | 79 | return false; 80 | } 81 | }; 82 | 83 | public CompactCalendarView(Context context) { 84 | this(context, null); 85 | } 86 | 87 | public CompactCalendarView(Context context, AttributeSet attrs) { 88 | this(context, attrs, 0); 89 | } 90 | 91 | public CompactCalendarView(Context context, AttributeSet attrs, int defStyleAttr) { 92 | super(context, attrs, defStyleAttr); 93 | compactCalendarController = new CompactCalendarController(new Paint(), new OverScroller(getContext()), 94 | new Rect(), attrs, getContext(), Color.argb(255, 233, 84, 81), 95 | Color.argb(255, 64, 64, 64), Color.argb(255, 219, 219, 219), VelocityTracker.obtain(), 96 | Color.argb(255, 100, 68, 65), new EventsContainer(Calendar.getInstance()), 97 | Locale.getDefault(), TimeZone.getDefault()); 98 | gestureDetector = new GestureDetectorCompat(getContext(), gestureListener); 99 | animationHandler = new AnimationHandler(compactCalendarController, this); 100 | } 101 | 102 | public void setAnimationListener(CompactCalendarAnimationListener compactCalendarAnimationListener){ 103 | animationHandler.setCompactCalendarAnimationListener(compactCalendarAnimationListener); 104 | } 105 | 106 | /* 107 | Use a custom locale for compact calendar and reinitialise the view. 108 | */ 109 | public void setLocale(TimeZone timeZone, Locale locale){ 110 | compactCalendarController.setLocale(timeZone, locale); 111 | invalidate(); 112 | } 113 | 114 | /* 115 | Compact calendar will use the locale to determine the abbreviation to use as the day column names. 116 | The default is to use the default locale and to abbreviate the day names to one character. 117 | Setting this to true will displace the short weekday string provided by java. 118 | */ 119 | public void setUseThreeLetterAbbreviation(boolean useThreeLetterAbbreviation){ 120 | compactCalendarController.setUseWeekDayAbbreviation(useThreeLetterAbbreviation); 121 | invalidate(); 122 | } 123 | 124 | public void setCalendarBackgroundColor(final int calenderBackgroundColor) { 125 | compactCalendarController.setCalenderBackgroundColor(calenderBackgroundColor); 126 | invalidate(); 127 | } 128 | 129 | /* 130 | Sets the name for each day of the week. No attempt is made to adjust width or text size based on the length of each day name. 131 | Works best with 3-4 characters for each day. 132 | */ 133 | public void setDayColumnNames(String[] dayColumnNames){ 134 | compactCalendarController.setDayColumnNames(dayColumnNames); 135 | } 136 | 137 | public void setFirstDayOfWeek(int dayOfWeek){ 138 | compactCalendarController.setFirstDayOfWeek(dayOfWeek); 139 | invalidate(); 140 | } 141 | 142 | public void setCurrentSelectedDayBackgroundColor(int currentSelectedDayBackgroundColor) { 143 | compactCalendarController.setCurrentSelectedDayBackgroundColor(currentSelectedDayBackgroundColor); 144 | invalidate(); 145 | } 146 | 147 | public void setCurrentDayBackgroundColor(int currentDayBackgroundColor) { 148 | compactCalendarController.setCurrentDayBackgroundColor(currentDayBackgroundColor); 149 | invalidate(); 150 | } 151 | 152 | public int getHeightPerDay(){ 153 | return compactCalendarController.getHeightPerDay(); 154 | } 155 | 156 | public void setListener(CompactCalendarViewListener listener){ 157 | compactCalendarController.setListener(listener); 158 | } 159 | 160 | public Date getFirstDayOfCurrentMonth(){ 161 | return compactCalendarController.getFirstDayOfCurrentMonth(); 162 | } 163 | 164 | public void shouldDrawIndicatorsBelowSelectedDays(boolean shouldDrawIndicatorsBelowSelectedDays){ 165 | compactCalendarController.shouldDrawIndicatorsBelowSelectedDays(shouldDrawIndicatorsBelowSelectedDays); 166 | } 167 | 168 | public void setCurrentDate(Date dateTimeMonth){ 169 | compactCalendarController.setCurrentDate(dateTimeMonth); 170 | invalidate(); 171 | } 172 | 173 | public int getWeekNumberForCurrentMonth(){ 174 | return compactCalendarController.getWeekNumberForCurrentMonth(); 175 | } 176 | 177 | public void setShouldDrawDaysHeader(boolean shouldDrawDaysHeader){ 178 | compactCalendarController.setShouldDrawDaysHeader(shouldDrawDaysHeader); 179 | } 180 | 181 | public void setCurrentSelectedDayTextColor(int currentSelectedDayTextColor) { 182 | compactCalendarController.setCurrentSelectedDayTextColor(currentSelectedDayTextColor); 183 | } 184 | 185 | public void setCurrentDayTextColor(int currentDayTextColor) { 186 | compactCalendarController.setCurrentDayTextColor(currentDayTextColor); 187 | } 188 | 189 | /** 190 | * see {@link #addEvent(Event, boolean)} when adding single events to control if calendar should redraw 191 | * or {@link #addEvents(java.util.List)} when adding multiple events 192 | * @param event 193 | */ 194 | public void addEvent(Event event){ 195 | addEvent(event, true); 196 | } 197 | 198 | /** 199 | * Adds an event to be drawn as an indicator in the calendar. 200 | * If adding multiple events see {@link #addEvents(List)}} method. 201 | * @param event to be added to the calendar 202 | * @param shouldInvalidate true if the view should invalidate 203 | */ 204 | public void addEvent(Event event, boolean shouldInvalidate){ 205 | compactCalendarController.addEvent(event); 206 | if(shouldInvalidate){ 207 | invalidate(); 208 | } 209 | } 210 | 211 | /** 212 | * Adds multiple events to the calendar and invalidates the view once all events are added. 213 | */ 214 | public void addEvents(List events){ 215 | compactCalendarController.addEvents(events); 216 | invalidate(); 217 | } 218 | 219 | /** 220 | * Fetches the events for the date passed in 221 | * @param date 222 | * @return 223 | */ 224 | public List getEvents(Date date){ 225 | return compactCalendarController.getCalendarEventsFor(date.getTime()); 226 | } 227 | 228 | /** 229 | * Fetches the events for the epochMillis passed in 230 | * @param epochMillis 231 | * @return 232 | */ 233 | public List getEvents(long epochMillis){ 234 | return compactCalendarController.getCalendarEventsFor(epochMillis); 235 | } 236 | 237 | /** 238 | * Fetches the events for the month of the epochMillis passed in and returns a sorted list of events 239 | * @param epochMillis 240 | * @return 241 | */ 242 | public List getEventsForMonth(long epochMillis){ 243 | return compactCalendarController.getCalendarEventsForMonth(epochMillis); 244 | } 245 | 246 | /** 247 | * Fetches the events for the month of the date passed in and returns a sorted list of events 248 | * @param date 249 | * @return 250 | */ 251 | public List getEventsForMonth(Date date){ 252 | return compactCalendarController.getCalendarEventsForMonth(date.getTime()); 253 | } 254 | 255 | /** 256 | * Remove the event associated with the Date passed in 257 | * @param date 258 | */ 259 | public void removeEvents(Date date){ 260 | compactCalendarController.removeEventsFor(date.getTime()); 261 | } 262 | 263 | public void removeEvents(long epochMillis){ 264 | compactCalendarController.removeEventsFor(epochMillis); 265 | } 266 | 267 | /** 268 | * see {@link #removeEvent(Event, boolean)} when removing single events to control if calendar should redraw 269 | * or {@link #removeEvents(java.util.List)} (java.util.List)} when removing multiple events 270 | * @param event 271 | */ 272 | public void removeEvent(Event event){ 273 | removeEvent(event, true); 274 | } 275 | 276 | /** 277 | * Removes an event from the calendar. 278 | * If removing multiple events see {@link #removeEvents(List)} 279 | * 280 | * @param event event to remove from the calendar 281 | * @param shouldInvalidate true if the view should invalidate 282 | */ 283 | public void removeEvent(Event event, boolean shouldInvalidate){ 284 | compactCalendarController.removeEvent(event); 285 | if(shouldInvalidate){ 286 | invalidate(); 287 | } 288 | } 289 | 290 | /** 291 | * Removes multiple events from the calendar and invalidates the view once all events are added. 292 | */ 293 | public void removeEvents(List events){ 294 | compactCalendarController.removeEvents(events); 295 | invalidate(); 296 | } 297 | 298 | /** 299 | * Clears all Events from the calendar. 300 | */ 301 | public void removeAllEvents() { 302 | compactCalendarController.removeAllEvents(); 303 | invalidate(); 304 | } 305 | 306 | public void setIsRtl(boolean isRtl) { 307 | compactCalendarController.setIsRtl(isRtl); 308 | } 309 | 310 | public void shouldSelectFirstDayOfMonthOnScroll(boolean shouldSelectFirstDayOfMonthOnScroll){ 311 | compactCalendarController.setShouldSelectFirstDayOfMonthOnScroll(shouldSelectFirstDayOfMonthOnScroll); 312 | } 313 | 314 | public void setCurrentSelectedDayIndicatorStyle(final int currentSelectedDayIndicatorStyle){ 315 | compactCalendarController.setCurrentSelectedDayIndicatorStyle(currentSelectedDayIndicatorStyle); 316 | invalidate(); 317 | } 318 | 319 | public void setCurrentDayIndicatorStyle(final int currentDayIndicatorStyle){ 320 | compactCalendarController.setCurrentDayIndicatorStyle(currentDayIndicatorStyle); 321 | invalidate(); 322 | } 323 | 324 | public void setEventIndicatorStyle(final int eventIndicatorStyle){ 325 | compactCalendarController.setEventIndicatorStyle(eventIndicatorStyle); 326 | invalidate(); 327 | } 328 | 329 | private void checkTargetHeight() { 330 | if (compactCalendarController.getTargetHeight() <= 0) { 331 | throw new IllegalStateException("Target height must be set in xml properties in order to expand/collapse CompactCalendar."); 332 | } 333 | } 334 | 335 | public void displayOtherMonthDays(boolean displayOtherMonthDays){ 336 | compactCalendarController.setDisplayOtherMonthDays(displayOtherMonthDays); 337 | invalidate(); 338 | } 339 | 340 | public void setTargetHeight(int targetHeight){ 341 | compactCalendarController.setTargetHeight(targetHeight); 342 | checkTargetHeight(); 343 | } 344 | 345 | public void showCalendar(){ 346 | checkTargetHeight(); 347 | animationHandler.openCalendar(); 348 | } 349 | 350 | public void hideCalendar(){ 351 | checkTargetHeight(); 352 | animationHandler.closeCalendar(); 353 | } 354 | 355 | public void showCalendarWithAnimation(){ 356 | checkTargetHeight(); 357 | animationHandler.openCalendarWithAnimation(); 358 | } 359 | 360 | public void hideCalendarWithAnimation(){ 361 | checkTargetHeight(); 362 | animationHandler.closeCalendarWithAnimation(); 363 | } 364 | 365 | /** 366 | * Moves the calendar to the right. This will show the next month when {@link #setIsRtl(boolean)} 367 | * is set to false. If in rtl mode, it will show the previous month. 368 | */ 369 | public void scrollRight(){ 370 | compactCalendarController.scrollRight(); 371 | invalidate(); 372 | } 373 | 374 | /** 375 | * Moves the calendar to the left. This will show the previous month when {@link #setIsRtl(boolean)} 376 | * is set to false. If in rtl mode, it will show the next month. 377 | */ 378 | public void scrollLeft(){ 379 | compactCalendarController.scrollLeft(); 380 | invalidate(); 381 | } 382 | 383 | public boolean isAnimating(){ 384 | return animationHandler.isAnimating(); 385 | } 386 | 387 | @Override 388 | protected void onMeasure(int parentWidth, int parentHeight) { 389 | super.onMeasure(parentWidth, parentHeight); 390 | int width = MeasureSpec.getSize(parentWidth); 391 | int height = MeasureSpec.getSize(parentHeight); 392 | if(width > 0 && height > 0) { 393 | compactCalendarController.onMeasure(width, height, getPaddingRight(), getPaddingLeft()); 394 | } 395 | setMeasuredDimension(width, height); 396 | } 397 | 398 | @Override 399 | protected void onDraw(Canvas canvas) { 400 | compactCalendarController.onDraw(canvas); 401 | } 402 | 403 | @Override 404 | public void computeScroll() { 405 | super.computeScroll(); 406 | if(compactCalendarController.computeScroll()){ 407 | invalidate(); 408 | } 409 | } 410 | 411 | public void shouldScrollMonth(boolean enableHorizontalScroll){ 412 | this.horizontalScrollEnabled = enableHorizontalScroll; 413 | } 414 | 415 | public boolean onTouchEvent(MotionEvent event) { 416 | if (horizontalScrollEnabled) { 417 | compactCalendarController.onTouch(event); 418 | invalidate(); 419 | } 420 | 421 | // on touch action finished (CANCEL or UP), we re-allow the parent container to intercept touch events (scroll inside ViewPager + RecyclerView issue #82) 422 | if((event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) && horizontalScrollEnabled) { 423 | getParent().requestDisallowInterceptTouchEvent(false); 424 | } 425 | 426 | // always allow gestureDetector to detect onSingleTap and scroll events 427 | return gestureDetector.onTouchEvent(event); 428 | } 429 | 430 | @Override 431 | public boolean canScrollHorizontally(int direction) { 432 | if (this.getVisibility() == View.GONE) { 433 | return false; 434 | } 435 | // Prevents ViewPager from scrolling horizontally by announcing that (issue #82) 436 | return this.horizontalScrollEnabled; 437 | } 438 | 439 | } 440 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/Events.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import com.github.sundeepk.compactcalendarview.domain.Event; 4 | 5 | import java.util.List; 6 | 7 | class Events { 8 | 9 | private final List events; 10 | private final long timeInMillis; 11 | 12 | Events(long timeInMillis, List events) { 13 | this.timeInMillis = timeInMillis; 14 | this.events = events; 15 | } 16 | 17 | long getTimeInMillis() { 18 | return timeInMillis; 19 | } 20 | 21 | List getEvents() { 22 | return events; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (o == null || getClass() != o.getClass()) return false; 29 | 30 | Events event = (Events) o; 31 | 32 | if (timeInMillis != event.timeInMillis) return false; 33 | if (events != null ? !events.equals(event.events) : event.events != null) return false; 34 | 35 | return true; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | int result = events != null ? events.hashCode() : 0; 41 | result = 31 * result + (int) (timeInMillis ^ (timeInMillis >>> 32)); 42 | return result; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "Events{" + 48 | "events=" + events + 49 | ", timeInMillis=" + timeInMillis + 50 | '}'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/EventsContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import com.github.sundeepk.compactcalendarview.comparators.EventComparator; 4 | import com.github.sundeepk.compactcalendarview.domain.Event; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Calendar; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.HashMap; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class EventsContainer { 16 | 17 | private Map> eventsByMonthAndYearMap = new HashMap<>(); 18 | private Comparator eventsComparator = new EventComparator(); 19 | private Calendar eventsCalendar; 20 | 21 | public EventsContainer(Calendar eventsCalendar) { 22 | this.eventsCalendar = eventsCalendar; 23 | } 24 | 25 | void addEvent(Event event) { 26 | eventsCalendar.setTimeInMillis(event.getTimeInMillis()); 27 | String key = getKeyForCalendarEvent(eventsCalendar); 28 | List eventsForMonth = eventsByMonthAndYearMap.get(key); 29 | if (eventsForMonth == null) { 30 | eventsForMonth = new ArrayList<>(); 31 | } 32 | Events eventsForTargetDay = getEventDayEvent(event.getTimeInMillis()); 33 | if (eventsForTargetDay == null) { 34 | List events = new ArrayList<>(); 35 | events.add(event); 36 | eventsForMonth.add(new Events(event.getTimeInMillis(), events)); 37 | } else { 38 | eventsForTargetDay.getEvents().add(event); 39 | } 40 | eventsByMonthAndYearMap.put(key, eventsForMonth); 41 | } 42 | 43 | void removeAllEvents() { 44 | eventsByMonthAndYearMap.clear(); 45 | } 46 | 47 | void addEvents(List events) { 48 | int count = events.size(); 49 | for (int i = 0; i < count; i++) { 50 | addEvent(events.get(i)); 51 | } 52 | } 53 | 54 | List getEventsFor(long epochMillis) { 55 | Events events = getEventDayEvent(epochMillis); 56 | if (events == null) { 57 | return new ArrayList<>(); 58 | } else { 59 | return events.getEvents(); 60 | } 61 | } 62 | 63 | List getEventsForMonthAndYear(int month, int year){ 64 | return eventsByMonthAndYearMap.get(year + "_" + month); 65 | } 66 | 67 | List getEventsForMonth(long eventTimeInMillis){ 68 | eventsCalendar.setTimeInMillis(eventTimeInMillis); 69 | String keyForCalendarEvent = getKeyForCalendarEvent(eventsCalendar); 70 | List events = eventsByMonthAndYearMap.get(keyForCalendarEvent); 71 | List allEventsForMonth = new ArrayList<>(); 72 | if (events != null) { 73 | for(Events eve : events){ 74 | if (eve != null) { 75 | allEventsForMonth.addAll(eve.getEvents()); 76 | } 77 | } 78 | } 79 | Collections.sort(allEventsForMonth, eventsComparator); 80 | return allEventsForMonth; 81 | } 82 | 83 | private Events getEventDayEvent(long eventTimeInMillis){ 84 | eventsCalendar.setTimeInMillis(eventTimeInMillis); 85 | int dayInMonth = eventsCalendar.get(Calendar.DAY_OF_MONTH); 86 | String keyForCalendarEvent = getKeyForCalendarEvent(eventsCalendar); 87 | List eventsForMonthsAndYear = eventsByMonthAndYearMap.get(keyForCalendarEvent); 88 | if (eventsForMonthsAndYear != null) { 89 | for (Events events : eventsForMonthsAndYear) { 90 | eventsCalendar.setTimeInMillis(events.getTimeInMillis()); 91 | int dayInMonthFromCache = eventsCalendar.get(Calendar.DAY_OF_MONTH); 92 | if (dayInMonthFromCache == dayInMonth) { 93 | return events; 94 | } 95 | } 96 | } 97 | return null; 98 | } 99 | 100 | void removeEventByEpochMillis(long epochMillis) { 101 | eventsCalendar.setTimeInMillis(epochMillis); 102 | int dayInMonth = eventsCalendar.get(Calendar.DAY_OF_MONTH); 103 | String key = getKeyForCalendarEvent(eventsCalendar); 104 | List eventsForMonthAndYear = eventsByMonthAndYearMap.get(key); 105 | if (eventsForMonthAndYear != null) { 106 | Iterator calendarDayEventIterator = eventsForMonthAndYear.iterator(); 107 | while (calendarDayEventIterator.hasNext()) { 108 | Events next = calendarDayEventIterator.next(); 109 | eventsCalendar.setTimeInMillis(next.getTimeInMillis()); 110 | int dayInMonthFromCache = eventsCalendar.get(Calendar.DAY_OF_MONTH); 111 | if (dayInMonthFromCache == dayInMonth) { 112 | calendarDayEventIterator.remove(); 113 | break; 114 | } 115 | } 116 | if (eventsForMonthAndYear.isEmpty()) { 117 | eventsByMonthAndYearMap.remove(key); 118 | } 119 | } 120 | } 121 | 122 | void removeEvent(Event event) { 123 | eventsCalendar.setTimeInMillis(event.getTimeInMillis()); 124 | String key = getKeyForCalendarEvent(eventsCalendar); 125 | List eventsForMonthAndYear = eventsByMonthAndYearMap.get(key); 126 | if (eventsForMonthAndYear != null) { 127 | Iterator eventsForMonthYrItr = eventsForMonthAndYear.iterator(); 128 | while(eventsForMonthYrItr.hasNext()) { 129 | Events events = eventsForMonthYrItr.next(); 130 | int indexOfEvent = events.getEvents().indexOf(event); 131 | if (indexOfEvent >= 0) { 132 | if (events.getEvents().size() == 1) { 133 | eventsForMonthYrItr.remove(); 134 | } else { 135 | events.getEvents().remove(indexOfEvent); 136 | } 137 | break; 138 | } 139 | } 140 | if (eventsForMonthAndYear.isEmpty()) { 141 | eventsByMonthAndYearMap.remove(key); 142 | } 143 | } 144 | } 145 | 146 | void removeEvents(List events) { 147 | int count = events.size(); 148 | for (int i = 0; i < count; i++) { 149 | removeEvent(events.get(i)); 150 | } 151 | } 152 | 153 | //E.g. 4 2016 becomes 2016_4 154 | private String getKeyForCalendarEvent(Calendar cal) { 155 | return cal.get(Calendar.YEAR) + "_" + cal.get(Calendar.MONTH); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/WeekUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import java.text.DateFormatSymbols; 4 | import java.util.Arrays; 5 | import java.util.Locale; 6 | 7 | public class WeekUtils { 8 | 9 | static String[] getWeekdayNames(Locale locale, int day, boolean useThreeLetterAbbreviation){ 10 | DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale); 11 | String[] dayNames = dateFormatSymbols.getShortWeekdays(); 12 | if (dayNames == null) { 13 | throw new IllegalStateException("Unable to determine weekday names from default locale"); 14 | } 15 | if (dayNames.length != 8) { 16 | throw new IllegalStateException("Expected weekday names from default locale to be of size 7 but: " 17 | + Arrays.toString(dayNames) + " with size " + dayNames.length + " was returned."); 18 | } 19 | 20 | String[] weekDayNames = new String[7]; 21 | String[] weekDaysFromSunday = {dayNames[1], dayNames[2], dayNames[3], dayNames[4], dayNames[5], dayNames[6], dayNames[7]}; 22 | for (int currentDay = day - 1, i = 0; i <= 6; i++, currentDay++) { 23 | currentDay = currentDay >= 7 ? 0 : currentDay; 24 | weekDayNames[i] = weekDaysFromSunday[currentDay]; 25 | } 26 | 27 | if (!useThreeLetterAbbreviation) { 28 | for (int i = 0; i < weekDayNames.length; i++) { 29 | weekDayNames[i] = weekDayNames[i].substring(0, 1); 30 | } 31 | } 32 | 33 | return weekDayNames; 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/comparators/EventComparator.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview.comparators; 2 | 3 | import com.github.sundeepk.compactcalendarview.domain.Event; 4 | 5 | import java.util.Comparator; 6 | 7 | public class EventComparator implements Comparator { 8 | 9 | @Override 10 | public int compare(Event lhs, Event rhs) { 11 | return lhs.getTimeInMillis() < rhs.getTimeInMillis() ? -1 : lhs.getTimeInMillis() == rhs.getTimeInMillis() ? 0 : 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/github/sundeepk/compactcalendarview/domain/Event.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview.domain; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | public class Event { 6 | 7 | private int color; 8 | private long timeInMillis; 9 | private Object data; 10 | 11 | public Event(int color, long timeInMillis) { 12 | this.color = color; 13 | this.timeInMillis = timeInMillis; 14 | } 15 | 16 | public Event(int color, long timeInMillis, Object data) { 17 | this.color = color; 18 | this.timeInMillis = timeInMillis; 19 | this.data = data; 20 | } 21 | 22 | public int getColor() { 23 | return color; 24 | } 25 | 26 | public long getTimeInMillis() { 27 | return timeInMillis; 28 | } 29 | 30 | @Nullable 31 | public Object getData() { 32 | return data; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | 40 | Event event = (Event) o; 41 | 42 | if (color != event.color) return false; 43 | if (timeInMillis != event.timeInMillis) return false; 44 | if (data != null ? !data.equals(event.data) : event.data != null) return false; 45 | 46 | return true; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = color; 52 | result = 31 * result + (int) (timeInMillis ^ (timeInMillis >>> 32)); 53 | result = 31 * result + (data != null ? data.hashCode() : 0); 54 | return result; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "Event{" + 60 | "color=" + color + 61 | ", timeInMillis=" + timeInMillis + 62 | ", data=" + data + 63 | '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/src/test/java/com/github/sundeepk/compactcalendarview/CompactCalendarControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Rect; 6 | import android.view.MotionEvent; 7 | import android.view.VelocityTracker; 8 | import android.widget.OverScroller; 9 | 10 | import com.github.sundeepk.compactcalendarview.domain.Event; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.mockito.InOrder; 16 | import org.mockito.Mock; 17 | import org.mockito.junit.MockitoJUnitRunner; 18 | 19 | import java.text.DateFormatSymbols; 20 | import java.util.Calendar; 21 | import java.util.Date; 22 | import java.util.List; 23 | import java.util.Locale; 24 | import java.util.TimeZone; 25 | 26 | import static com.github.sundeepk.compactcalendarview.CompactCalendarHelper.getDayEventWith2EventsPerDay; 27 | import static com.github.sundeepk.compactcalendarview.CompactCalendarHelper.getDayEventWithMultipleEventsPerDay; 28 | import static com.github.sundeepk.compactcalendarview.CompactCalendarHelper.getEvents; 29 | import static org.junit.Assert.assertArrayEquals; 30 | import static org.junit.Assert.assertEquals; 31 | import static org.mockito.Matchers.anyFloat; 32 | import static org.mockito.Matchers.eq; 33 | import static org.mockito.Mockito.inOrder; 34 | import static org.mockito.Mockito.reset; 35 | import static org.mockito.Mockito.times; 36 | import static org.mockito.Mockito.verify; 37 | import static org.mockito.Mockito.verifyNoMoreInteractions; 38 | import static org.mockito.Mockito.when; 39 | 40 | @RunWith(MockitoJUnitRunner.class) 41 | public class CompactCalendarControllerTest { 42 | 43 | @Mock private Paint paint; 44 | @Mock private OverScroller overScroller; 45 | @Mock private Canvas canvas; 46 | @Mock private Rect rect; 47 | @Mock private Calendar calendar; 48 | @Mock private MotionEvent motionEvent; 49 | @Mock private VelocityTracker velocityTracker; 50 | @Mock private EventsContainer eventsContainer; 51 | 52 | private static final String[] dayColumnNames = {"M", "T", "W", "T", "F", "S", "S"}; 53 | 54 | CompactCalendarController underTest; 55 | 56 | @Before 57 | public void setUp(){ 58 | Locale.setDefault(Locale.ENGLISH); 59 | TimeZone.setDefault(TimeZone.getTimeZone("GMT")); 60 | 61 | when(velocityTracker.getXVelocity()).thenReturn(-200f); 62 | underTest = 63 | new CompactCalendarController(paint, overScroller, rect, null, null, 0, 0, 0, velocityTracker, 0, eventsContainer, Locale.getDefault(), TimeZone.getDefault()); 64 | } 65 | 66 | @Test(expected=IllegalArgumentException.class) 67 | public void testItThrowsWhenDayColumnsIsNotLengthSeven(){ 68 | String[] dayNames = {"Mon", "Tue", "Wed", "Thur", "Fri"}; 69 | underTest.setDayColumnNames(dayNames); 70 | } 71 | 72 | @Test 73 | public void testManualScrollAndGestureScrollPlayNicelyTogether(){ 74 | //Set width of view so that scrolling will return a correct value 75 | underTest.onMeasure(720, 1080, 0, 0); 76 | 77 | Calendar cal = Calendar.getInstance(); 78 | 79 | //Sun, 08 Feb 2015 00:00:00 GMT 80 | underTest.setCurrentDate(new Date(setTimeToMidnightAndGet(cal, 1423353600000L))); 81 | 82 | underTest.scrollRight(); 83 | 84 | //Sun, 01 Mar 2015 00:00:00 GMT - expected 85 | assertEquals(new Date(setTimeToMidnightAndGet(cal, 1425168000000L)), underTest.getFirstDayOfCurrentMonth()); 86 | 87 | when(motionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP); 88 | 89 | //Scroll enough to push calender to next month 90 | underTest.onScroll(motionEvent, motionEvent, 600, 0); 91 | underTest.onDraw(canvas); 92 | underTest.onTouch(motionEvent); 93 | 94 | //Wed, 01 Apr 2015 00:00:00 GMT 95 | assertEquals(new Date(setTimeToMidnightAndGet(cal, 1427846400000L)), underTest.getFirstDayOfCurrentMonth()); 96 | } 97 | 98 | @Test 99 | public void testItScrollsToNextMonth(){ 100 | //Sun, 08 Feb 2015 00:00:00 GMT 101 | underTest.setCurrentDate(new Date(1423353600000L)); 102 | 103 | underTest.scrollRight(); 104 | Date actualDate = underTest.getFirstDayOfCurrentMonth(); 105 | 106 | //Sun, 01 Mar 2015 00:00:00 GMT - expected 107 | assertEquals(new Date(1425168000000L), actualDate); 108 | } 109 | 110 | @Test 111 | public void testItScrollsToPreviousMonth(){ 112 | //Sun, 08 Feb 2015 00:00:00 GMT 113 | underTest.setCurrentDate(new Date(1423353600000L)); 114 | 115 | underTest.scrollLeft(); 116 | Date actualDate = underTest.getFirstDayOfCurrentMonth(); 117 | 118 | // Thu, 01 Jan 2015 00:00:00 GMT - expected 119 | assertEquals(new Date(1420070400000L), actualDate); 120 | } 121 | 122 | @Test 123 | public void testItScrollsToNextMonthWhenRtl(){ 124 | //Sun, 08 Feb 2015 00:00:00 GMT 125 | underTest.setCurrentDate(new Date(1423353600000L)); 126 | underTest.setIsRtl(true); 127 | 128 | underTest.scrollRight(); 129 | Date actualDate = underTest.getFirstDayOfCurrentMonth(); 130 | 131 | // Thu, 01 Jan 2015 00:00:00 GMT - expected 132 | assertEquals(new Date(1420070400000L), actualDate); 133 | } 134 | 135 | @Test 136 | public void testItScrollsToPreviousMonthWhenRtl(){ 137 | //Sun, 08 Feb 2015 00:00:00 GMT 138 | underTest.setCurrentDate(new Date(1423353600000L)); 139 | underTest.setIsRtl(true); 140 | 141 | underTest.scrollLeft(); 142 | Date actualDate = underTest.getFirstDayOfCurrentMonth(); 143 | 144 | //Sun, 01 Mar 2015 00:00:00 GMT - expected 145 | assertEquals(new Date(1425168000000L), actualDate); 146 | } 147 | 148 | @Test 149 | public void testItSetsDayColumns(){ 150 | //simulate Feb month 151 | when(calendar.get(Calendar.DAY_OF_WEEK)).thenReturn(1); 152 | when(calendar.get(Calendar.MONTH)).thenReturn(1); 153 | when(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)).thenReturn(28); 154 | 155 | String[] dayNames = {"Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"}; 156 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 157 | underTest.setDayColumnNames(dayNames); 158 | underTest.drawMonth(canvas, calendar, 0); 159 | 160 | InOrder inOrder = inOrder(canvas); 161 | inOrder.verify(canvas).drawText(eq("Mon"), anyFloat(), anyFloat(), eq(paint)); 162 | inOrder.verify(canvas).drawText(eq("Tue"), anyFloat(), anyFloat(), eq(paint)); 163 | inOrder.verify(canvas).drawText(eq("Wed"), anyFloat(), anyFloat(), eq(paint)); 164 | inOrder.verify(canvas).drawText(eq("Thur"), anyFloat(), anyFloat(), eq(paint)); 165 | inOrder.verify(canvas).drawText(eq("Fri"), anyFloat(), anyFloat(), eq(paint)); 166 | inOrder.verify(canvas).drawText(eq("Sat"), anyFloat(), anyFloat(), eq(paint)); 167 | inOrder.verify(canvas).drawText(eq("Sun"), anyFloat(), anyFloat(), eq(paint)); 168 | } 169 | 170 | @Test 171 | public void testListenerIsCalledOnMonthScroll(){ 172 | //Sun, 01 Mar 2015 00:00:00 GMT 173 | Date expectedDateOnScroll = new Date(1425168000000L); 174 | 175 | when(motionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP); 176 | 177 | //Set width of view so that scrolling will return a correct value 178 | underTest.onMeasure(720, 1080, 0, 0); 179 | 180 | //Sun, 08 Feb 2015 00:00:00 GMT 181 | underTest.setCurrentDate(new Date(1423353600000L)); 182 | 183 | //Scroll enough to push calender to next month 184 | underTest.onScroll(motionEvent, motionEvent, 600, 0); 185 | underTest.onDraw(canvas); 186 | underTest.onTouch(motionEvent); 187 | assertEquals(expectedDateOnScroll, underTest.getFirstDayOfCurrentMonth()); 188 | } 189 | 190 | @Test 191 | public void testItAbbreviatesDayNames(){ 192 | //simulate Feb month 193 | when(calendar.get(Calendar.DAY_OF_WEEK)).thenReturn(1); 194 | when(calendar.get(Calendar.MONTH)).thenReturn(1); 195 | when(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)).thenReturn(28); 196 | 197 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 198 | underTest.setLocale(TimeZone.getTimeZone("Europe/Paris"), Locale.FRANCE); 199 | reset(canvas); //reset because invalidate is called 200 | underTest.setUseWeekDayAbbreviation(true); 201 | reset(canvas); //reset because invalidate is called 202 | underTest.drawMonth(canvas, calendar, 0); 203 | 204 | DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(Locale.FRANCE); 205 | String[] dayNames = dateFormatSymbols.getShortWeekdays(); 206 | 207 | InOrder inOrder = inOrder(canvas); 208 | inOrder.verify(canvas).drawText(eq(dayNames[2]), anyFloat(), anyFloat(), eq(paint)); 209 | inOrder.verify(canvas).drawText(eq(dayNames[3]), anyFloat(), anyFloat(), eq(paint)); 210 | inOrder.verify(canvas).drawText(eq(dayNames[4]), anyFloat(), anyFloat(), eq(paint)); 211 | inOrder.verify(canvas).drawText(eq(dayNames[5]), anyFloat(), anyFloat(), eq(paint)); 212 | inOrder.verify(canvas).drawText(eq(dayNames[6]), anyFloat(), anyFloat(), eq(paint)); 213 | inOrder.verify(canvas).drawText(eq(dayNames[7]), anyFloat(), anyFloat(), eq(paint)); 214 | inOrder.verify(canvas).drawText(eq(dayNames[1]), anyFloat(), anyFloat(), eq(paint)); 215 | } 216 | 217 | @Test 218 | public void testItReturnsFirstDayOfMonthAfterDateHasBeenSet(){ 219 | //Sun, 01 Feb 2015 00:00:00 GMT 220 | Date expectedDate = new Date(1422748800000L); 221 | 222 | //Sun, 08 Feb 2015 00:00:00 GMT 223 | underTest.setCurrentDate(new Date(1423353600000L)); 224 | 225 | Date actualDate = underTest.getFirstDayOfCurrentMonth(); 226 | assertEquals(expectedDate, actualDate); 227 | } 228 | 229 | @Test 230 | public void testItReturnsFirstDayOfMonth(){ 231 | Calendar currentCalender = Calendar.getInstance(); 232 | currentCalender.set(Calendar.DAY_OF_MONTH, 1); 233 | currentCalender.set(Calendar.HOUR_OF_DAY, 0); 234 | currentCalender.set(Calendar.MINUTE, 0); 235 | currentCalender.set(Calendar.SECOND, 0); 236 | currentCalender.set(Calendar.MILLISECOND, 0); 237 | Date expectFirstDayOfMonth = currentCalender.getTime(); 238 | 239 | Date actualDate = underTest.getFirstDayOfCurrentMonth(); 240 | 241 | assertEquals(expectFirstDayOfMonth, actualDate); 242 | } 243 | 244 | @Test 245 | public void testItDrawsSundayAsFirstDay(){ 246 | //simulate Feb month 247 | when(calendar.get(Calendar.DAY_OF_WEEK)).thenReturn(1); 248 | when(calendar.get(Calendar.MONTH)).thenReturn(1); 249 | when(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)).thenReturn(28); 250 | 251 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 252 | underTest.setUseWeekDayAbbreviation(true); 253 | underTest.setFirstDayOfWeek(Calendar.SUNDAY); 254 | underTest.drawMonth(canvas, calendar, 0); 255 | 256 | InOrder inOrder = inOrder(canvas); 257 | inOrder.verify(canvas).drawText(eq("Sun"), anyFloat(), anyFloat(), eq(paint)); 258 | inOrder.verify(canvas).drawText(eq("Mon"), anyFloat(), anyFloat(), eq(paint)); 259 | inOrder.verify(canvas).drawText(eq("Tue"), anyFloat(), anyFloat(), eq(paint)); 260 | inOrder.verify(canvas).drawText(eq("Wed"), anyFloat(), anyFloat(), eq(paint)); 261 | inOrder.verify(canvas).drawText(eq("Thu"), anyFloat(), anyFloat(), eq(paint)); 262 | inOrder.verify(canvas).drawText(eq("Fri"), anyFloat(), anyFloat(), eq(paint)); 263 | inOrder.verify(canvas).drawText(eq("Sat"), anyFloat(), anyFloat(), eq(paint)); 264 | } 265 | 266 | @Test 267 | public void testItDrawsFirstLetterOfEachDay(){ 268 | //simulate Feb month 269 | when(calendar.get(Calendar.DAY_OF_WEEK)).thenReturn(1); 270 | when(calendar.get(Calendar.MONTH)).thenReturn(1); 271 | when(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)).thenReturn(28); 272 | 273 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 274 | underTest.drawMonth(canvas, calendar, 0); 275 | 276 | InOrder inOrder = inOrder(canvas); 277 | inOrder.verify(canvas).drawText(eq("M"), anyFloat(), anyFloat(), eq(paint)); 278 | inOrder.verify(canvas).drawText(eq("T"), anyFloat(), anyFloat(), eq(paint)); 279 | inOrder.verify(canvas).drawText(eq("W"), anyFloat(), anyFloat(), eq(paint)); 280 | inOrder.verify(canvas).drawText(eq("T"), anyFloat(), anyFloat(), eq(paint)); 281 | inOrder.verify(canvas).drawText(eq("F"), anyFloat(), anyFloat(), eq(paint)); 282 | inOrder.verify(canvas).drawText(eq("S"), anyFloat(), anyFloat(), eq(paint)); 283 | inOrder.verify(canvas).drawText(eq("S"), anyFloat(), anyFloat(), eq(paint)); 284 | } 285 | 286 | @Test 287 | public void testItDrawsDaysOnCalender(){ 288 | //simulate Feb month 289 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 290 | when(calendar.get(Calendar.DAY_OF_WEEK)).thenReturn(1); 291 | when(calendar.get(Calendar.MONTH)).thenReturn(1); 292 | when(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)).thenReturn(28); 293 | 294 | underTest.drawMonth(canvas, calendar, 0); 295 | 296 | for(int dayColumn = 0, dayRow = 0; dayColumn <= 6; dayRow++){ 297 | if(dayRow == 7){ 298 | dayRow = 0; 299 | if(dayColumn <= 6){ 300 | dayColumn++; 301 | } 302 | } 303 | if(dayColumn == dayColumnNames.length){ 304 | break; 305 | } 306 | if(dayColumn == 0){ 307 | verify(canvas).drawText(eq(dayColumnNames[dayColumn]), anyFloat(), anyFloat(), eq(paint)); 308 | }else{ 309 | int day = ((dayRow - 1) * 7 + dayColumn + 1) - 6; 310 | if( day > 0 && day <= 28){ 311 | verify(canvas).drawText(eq(String.valueOf(day)), anyFloat(), anyFloat(), eq(paint)); 312 | } 313 | } 314 | } 315 | } 316 | 317 | @Test 318 | public void testItDrawsEventDaysOnCalendar(){ 319 | //Sun, 07 Jun 2015 18:20:51 GMT 320 | //get 30 events in total 321 | int numberOfDaysWithEvents = 30; 322 | List events = getEvents(0, numberOfDaysWithEvents, 1433701251000L); 323 | when(eventsContainer.getEventsForMonthAndYear(5, 2015)).thenReturn(events); 324 | when(calendar.get(Calendar.MONTH)).thenReturn(5); 325 | when(calendar.get(Calendar.YEAR)).thenReturn(2015); 326 | 327 | underTest.shouldDrawIndicatorsBelowSelectedDays(true); // always draw events, even on current day 328 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 329 | underTest.drawEvents(canvas, calendar, 0); 330 | 331 | //draw events for every day with an event 332 | verify(canvas, times(numberOfDaysWithEvents)).drawCircle(anyFloat(), anyFloat(), anyFloat(), eq(paint)); 333 | } 334 | 335 | @Test 336 | public void testItDrawsMultipleEventDaysOnCalendar(){ 337 | //Sun, 07 Jun 2015 18:20:51 GMT 338 | //get 60 events in total 339 | int numberOfDaysWithEvents = 30; 340 | List events = getDayEventWith2EventsPerDay(0, numberOfDaysWithEvents, 1433701251000L); 341 | when(eventsContainer.getEventsForMonthAndYear(5, 2015)).thenReturn(events); 342 | when(calendar.get(Calendar.MONTH)).thenReturn(5); 343 | when(calendar.get(Calendar.YEAR)).thenReturn(2015); 344 | 345 | underTest.shouldDrawIndicatorsBelowSelectedDays(true); // always draw events, even on current day 346 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 347 | underTest.drawEvents(canvas, calendar, 0); 348 | 349 | //draw 2 events per day 350 | verify(canvas, times(numberOfDaysWithEvents * 2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), eq(paint)); 351 | } 352 | 353 | @Test 354 | public void testItDrawsMultipleEventDaysOnCalendarWithPlusIndicator(){ 355 | //Sun, 07 Jun 2015 18:20:51 GMT 356 | //get 120 events in total but only draw 3 event indicators per a day 357 | int numberOfDaysWithEvents = 30; 358 | List events = getDayEventWithMultipleEventsPerDay(0, numberOfDaysWithEvents, 1433701251000L); 359 | when(eventsContainer.getEventsForMonthAndYear(5, 2015)).thenReturn(events); 360 | when(calendar.get(Calendar.MONTH)).thenReturn(5); 361 | when(calendar.get(Calendar.YEAR)).thenReturn(2015); 362 | 363 | underTest.shouldDrawIndicatorsBelowSelectedDays(true); // always draw events, even on current day 364 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 365 | underTest.drawEvents(canvas, calendar, 0); 366 | 367 | //draw 2 events per day because we don't draw more than 3 indicators 368 | verify(canvas, times(numberOfDaysWithEvents * 2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), eq(paint)); 369 | 370 | //draw event indicator with lines 371 | // 2 calls for each plus event indicator since it takes 2 draw calls to make a plus sign 372 | verify(canvas, times(numberOfDaysWithEvents * 2)).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), eq(paint)); 373 | } 374 | 375 | @Test 376 | public void testItDrawsEventDaysOnCalendarForCurrentMonth(){ 377 | Calendar todayCalendar = Calendar.getInstance(); 378 | int numberOfDaysInMonth = todayCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); 379 | int todayMonth = todayCalendar.get(Calendar.MONTH); 380 | int todayYear = todayCalendar.get(Calendar.YEAR); 381 | 382 | //get events for every day in the month 383 | List events = getEvents(0, numberOfDaysInMonth, todayCalendar.getTimeInMillis()); 384 | when(eventsContainer.getEventsForMonthAndYear(todayMonth, todayYear)).thenReturn(events); 385 | when(calendar.get(Calendar.MONTH)).thenReturn(todayMonth); 386 | when(calendar.get(Calendar.YEAR)).thenReturn(todayYear); 387 | 388 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 389 | underTest.drawEvents(canvas, calendar, 0); 390 | 391 | //draw events for every day except the current day -- selected day is also the current day 392 | verify(canvas, times(numberOfDaysInMonth - 1)).drawCircle(anyFloat(), anyFloat(), anyFloat(), eq(paint)); 393 | } 394 | 395 | @Test 396 | public void testItDrawsEventDaysOnCalendarWithSelectedDay(){ 397 | //Sun, 07 Jun 2015 18:20:51 GMT 398 | long selectedDayTimestamp = 1433701251000L; 399 | //get 30 events in total 400 | int numberOfDaysWithEvents = 30; 401 | List events = getEvents(0, numberOfDaysWithEvents, selectedDayTimestamp); 402 | when(eventsContainer.getEventsForMonthAndYear(5, 2015)).thenReturn(events); 403 | when(calendar.get(Calendar.MONTH)).thenReturn(5); 404 | when(calendar.get(Calendar.YEAR)).thenReturn(2015); 405 | 406 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 407 | // Selects first day of the month 408 | underTest.setCurrentDate(new Date(selectedDayTimestamp)); 409 | underTest.drawEvents(canvas, calendar, 0); 410 | 411 | //draw events for every day except the selected day 412 | verify(canvas, times(numberOfDaysWithEvents - 1)).drawCircle(anyFloat(), anyFloat(), anyFloat(), eq(paint)); 413 | } 414 | 415 | @Test 416 | public void testItDrawsEventDaysOnCalendarForCurrentMonthWithSelectedDay(){ 417 | Calendar todayCalendar = Calendar.getInstance(); 418 | int numberOfDaysInMonth = todayCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); 419 | int todayMonth = todayCalendar.get(Calendar.MONTH); 420 | int todayYear = todayCalendar.get(Calendar.YEAR); 421 | 422 | //get events for every day in the month 423 | List events = getEvents(0, numberOfDaysInMonth, todayCalendar.getTimeInMillis()); 424 | when(eventsContainer.getEventsForMonthAndYear(todayMonth, todayYear)).thenReturn(events); 425 | when(calendar.get(Calendar.MONTH)).thenReturn(todayMonth); 426 | when(calendar.get(Calendar.YEAR)).thenReturn(todayYear); 427 | 428 | // sets either 1st day or 2nd day so that there are always 2 days selected 429 | int dayOfMonth = todayCalendar.get(Calendar.DAY_OF_MONTH); 430 | if (dayOfMonth == 1) { 431 | todayCalendar.set(Calendar.DAY_OF_MONTH, 2); 432 | } else { 433 | todayCalendar.set(Calendar.DAY_OF_MONTH, 1); 434 | } 435 | todayCalendar.set(Calendar.HOUR_OF_DAY, 0); 436 | todayCalendar.set(Calendar.MINUTE, 0); 437 | todayCalendar.set(Calendar.SECOND, 0); 438 | todayCalendar.set(Calendar.MILLISECOND, 0); 439 | underTest.setCurrentDate(todayCalendar.getTime()); 440 | 441 | underTest.setGrowProgress(1000); //set grow progress so that it simulates the calendar being open 442 | underTest.drawEvents(canvas, calendar, 0); 443 | 444 | //draw events for every day except the current day and the selected day 445 | verify(canvas, times(numberOfDaysInMonth - 2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), eq(paint)); 446 | } 447 | 448 | @Test 449 | public void testItAddsEvent(){ 450 | Event event = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L).get(0); 451 | underTest.addEvent(event); 452 | verify(eventsContainer).addEvent(event); 453 | verifyNoMoreInteractions(eventsContainer); 454 | } 455 | 456 | @Test 457 | public void testItAddsEvents(){ 458 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 459 | underTest.addEvents(events); 460 | verify(eventsContainer).addEvents(events); 461 | verifyNoMoreInteractions(eventsContainer); 462 | } 463 | 464 | @Test 465 | public void testItRemovesEvent(){ 466 | Event event = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L).get(0); 467 | underTest.removeEvent(event); 468 | verify(eventsContainer).removeEvent(event); 469 | verifyNoMoreInteractions(eventsContainer); 470 | } 471 | 472 | @Test 473 | public void testItRemovesEvents(){ 474 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 475 | underTest.removeEvents(events); 476 | verify(eventsContainer).removeEvents(events); 477 | verifyNoMoreInteractions(eventsContainer); 478 | } 479 | 480 | @Test 481 | public void testItGetCalendarEventsForADate(){ 482 | underTest.getCalendarEventsFor(1433701251000L); 483 | verify(eventsContainer).getEventsFor(1433701251000L); 484 | verifyNoMoreInteractions(eventsContainer); 485 | } 486 | 487 | @Test 488 | public void testItRemovesCalendarEventsForADate(){ 489 | underTest.removeEventsFor(1433701251000L); 490 | verify(eventsContainer).removeEventByEpochMillis(1433701251000L); 491 | verifyNoMoreInteractions(eventsContainer); 492 | } 493 | 494 | @Test 495 | public void testItRemovesAllEvents(){ 496 | underTest.removeAllEvents(); 497 | verify(eventsContainer).removeAllEvents(); 498 | verifyNoMoreInteractions(eventsContainer); 499 | } 500 | 501 | @Test(expected=IllegalArgumentException.class) 502 | public void testItThrowsWhenZeroIsUsedAsFirstDayOfWeek(){ 503 | underTest.setFirstDayOfWeek(0); 504 | } 505 | 506 | @Test(expected=IllegalArgumentException.class) 507 | public void testItThrowsWhenValuesGreaterThanSevenIsUsedAsFirstDayOfWeek(){ 508 | underTest.setFirstDayOfWeek(8); 509 | } 510 | 511 | @Test 512 | public void testItGetsDayOfWeekWhenSundayIsFirstDayOfWeek(){ 513 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 514 | // Sunday as first day means Saturday is last day of week 515 | // first index corresponds to Sunday and last is Saturday 516 | int[] expectedDaysOfWeekOrder = {0,1,2,3,4,5,6}; 517 | int[] actualDaysOfWeekOrder = new int[7]; 518 | Calendar calendar = Calendar.getInstance(); 519 | underTest.setFirstDayOfWeek(Calendar.SUNDAY); 520 | for (int day = 1; day <= 7 ; day++){ 521 | calendar.set(Calendar.DAY_OF_WEEK, day); 522 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 523 | } 524 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 525 | } 526 | 527 | @Test 528 | public void testItGetsDayOfWeekWhenMondayIsFirstDayOfWeek(){ 529 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 530 | // Monday as first day means Sunday is last day of week 531 | // first index corresponds to Sunday and last is Saturday 532 | int[] expectedDaysOfWeekOrder = {6,0,1,2,3,4,5}; 533 | int[] actualDaysOfWeekOrder = new int[7]; 534 | Calendar calendar = Calendar.getInstance(); 535 | underTest.setFirstDayOfWeek(Calendar.MONDAY); 536 | for (int day = 1; day <= 7 ; day++){ 537 | calendar.set(Calendar.DAY_OF_WEEK, day); 538 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 539 | } 540 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 541 | } 542 | 543 | @Test 544 | public void testItGetsDayOfWeekWhenTuesdayIsFirstDayOfWeek(){ 545 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 546 | // Tuesday as first day means Monday is last day of week 547 | // first index corresponds to Sunday and last is Saturday 548 | int[] expectedDaysOfWeekOrder = {5,6,0,1,2,3,4}; 549 | int[] actualDaysOfWeekOrder = new int[7]; 550 | Calendar calendar = Calendar.getInstance(); 551 | underTest.setFirstDayOfWeek(Calendar.TUESDAY); 552 | for (int day = 1; day <= 7 ; day++){ 553 | calendar.set(Calendar.DAY_OF_WEEK, day); 554 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 555 | } 556 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 557 | } 558 | 559 | @Test 560 | public void testItGetsDayOfWeekWhenWednesdayIsFirstDayOfWeek(){ 561 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 562 | // Wednesday as first day means Tuesday is last day of week 563 | // first index corresponds to Sunday and last is Saturday 564 | int[] expectedDaysOfWeekOrder = {4,5,6,0,1,2,3}; 565 | int[] actualDaysOfWeekOrder = new int[7]; 566 | Calendar calendar = Calendar.getInstance(); 567 | underTest.setFirstDayOfWeek(Calendar.WEDNESDAY); 568 | for (int day = 1; day <= 7 ; day++){ 569 | calendar.set(Calendar.DAY_OF_WEEK, day); 570 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 571 | } 572 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 573 | } 574 | 575 | @Test 576 | public void testItGetsDayOfWeekWhenThursdayIsFirstDayOfWeek(){ 577 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 578 | // Thursday as first day means Wednesday is last day of week 579 | // first index corresponds to Sunday and last is Saturday 580 | int[] expectedDaysOfWeekOrder = {3,4,5,6,0,1,2}; 581 | int[] actualDaysOfWeekOrder = new int[7]; 582 | Calendar calendar = Calendar.getInstance(); 583 | underTest.setFirstDayOfWeek(Calendar.THURSDAY); 584 | for (int day = 1; day <= 7 ; day++){ 585 | calendar.set(Calendar.DAY_OF_WEEK, day); 586 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 587 | } 588 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 589 | } 590 | 591 | @Test 592 | public void testItGetsDayOfWeekWhenFridayIsFirstDayOfWeek(){ 593 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 594 | // Friday as first day means Wednesday is last day of week 595 | // first index corresponds to Sunday and last is Saturday 596 | int[] expectedDaysOfWeekOrder = {2,3,4,5,6,0,1}; 597 | int[] actualDaysOfWeekOrder = new int[7]; 598 | Calendar calendar = Calendar.getInstance(); 599 | underTest.setFirstDayOfWeek(Calendar.FRIDAY); 600 | for (int day = 1; day <= 7 ; day++){ 601 | calendar.set(Calendar.DAY_OF_WEEK, day); 602 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 603 | } 604 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 605 | } 606 | 607 | @Test 608 | public void testItGetsDayOfWeekWhenSaturdayIsFirstDayOfWeek(){ 609 | // zero based indexes used internally so instead of returning range of 1-7 it returns 0-6 610 | // Saturday as first day means Friday is last day of week 611 | // first index corresponds to Sunday and last is Saturday 612 | int[] expectedDaysOfWeekOrder = {1,2,3,4,5,6,0}; 613 | int[] actualDaysOfWeekOrder = new int[7]; 614 | Calendar calendar = Calendar.getInstance(); 615 | underTest.setFirstDayOfWeek(Calendar.SATURDAY); 616 | for (int day = 1; day <= 7 ; day++){ 617 | calendar.set(Calendar.DAY_OF_WEEK, day); 618 | actualDaysOfWeekOrder[day - 1] = underTest.getDayOfWeek(calendar); 619 | } 620 | assertArrayEquals(expectedDaysOfWeekOrder, actualDaysOfWeekOrder); 621 | } 622 | 623 | private long setTimeToMidnightAndGet(Calendar cal, long epoch) { 624 | cal.setTime(new Date(epoch)); 625 | cal.set(Calendar.HOUR_OF_DAY, 0); 626 | cal.set(Calendar.MINUTE, 0); 627 | cal.set(Calendar.SECOND, 0); 628 | cal.set(Calendar.MILLISECOND, 0); 629 | return cal.getTimeInMillis(); 630 | } 631 | } 632 | -------------------------------------------------------------------------------- /library/src/test/java/com/github/sundeepk/compactcalendarview/CompactCalendarHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.github.sundeepk.compactcalendarview.domain.Event; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Calendar; 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Locale; 14 | import java.util.Map; 15 | 16 | public class CompactCalendarHelper { 17 | 18 | public static List getOneEventPerDayForMonth(int start, int days, long timeStamp) { 19 | return getOneEventPerDayForMonth(start, days, timeStamp, Color.BLUE); 20 | } 21 | 22 | //generate one event per a day for a month 23 | public static List getOneEventPerDayForMonth(int start, int days, long timeStamp, int color) { 24 | Calendar currentCalender = Calendar.getInstance(Locale.getDefault()); 25 | List events = new ArrayList<>(); 26 | for(int i = start; i < days; i++){ 27 | setDateTime(timeStamp, currentCalender, i); 28 | events.add(new Event(color, currentCalender.getTimeInMillis())); 29 | } 30 | return events; 31 | } 32 | 33 | public static List getEvents(int start, int days, long timeStamp) { 34 | Calendar currentCalender = Calendar.getInstance(Locale.getDefault()); 35 | List events = new ArrayList<>(); 36 | for(int i = start; i < days; i++){ 37 | setDateTime(timeStamp, currentCalender, i); 38 | List eventList = new ArrayList<>(); 39 | eventList.add(new Event(Color.BLUE, currentCalender.getTimeInMillis())); 40 | Events eventsObject = new Events(currentCalender.getTimeInMillis(), eventList); 41 | events.add(eventsObject); 42 | } 43 | return events; 44 | } 45 | 46 | 47 | public static List getDayEventWith2EventsPerDay(int start, int days, long timeStamp) { 48 | Calendar currentCalender = Calendar.getInstance(Locale.getDefault()); 49 | List events = new ArrayList<>(); 50 | for(int i = start; i < days; i++){ 51 | setDateTime(timeStamp, currentCalender, i); 52 | List eventList = new ArrayList<>(); 53 | eventList.add(new Event(Color.BLUE, currentCalender.getTimeInMillis())); 54 | eventList.add(new Event(Color.RED, currentCalender.getTimeInMillis() + 3600 * 1000)); 55 | Events eventsObject = new Events(currentCalender.getTimeInMillis(), eventList); 56 | events.add(eventsObject); 57 | } 58 | return events; 59 | } 60 | 61 | public static List getDayEventWithMultipleEventsPerDay(int start, int days, long timeStamp) { 62 | Calendar currentCalender = Calendar.getInstance(Locale.getDefault()); 63 | List events = new ArrayList<>(); 64 | for(int i = start; i < days; i++){ 65 | setDateTime(timeStamp, currentCalender, i); 66 | List eventsList = Arrays.asList(new Event(Color.BLUE, currentCalender.getTimeInMillis()), 67 | new Event(Color.RED, currentCalender.getTimeInMillis() + 3600 * 1000), 68 | new Event(Color.RED, currentCalender.getTimeInMillis() + (3600 * 2) * 1000), 69 | new Event(Color.RED, currentCalender.getTimeInMillis() + (3600 * 3) * 1000)); 70 | Events eventsObject = new Events(currentCalender.getTimeInMillis(), eventsList); 71 | events.add(eventsObject); 72 | } 73 | return events; 74 | } 75 | 76 | public static Map> getMultipleEventsForEachDayAsMap(int start, int days, long timeStamp) { 77 | Calendar currentCalender = Calendar.getInstance(Locale.getDefault()); 78 | Map> epochMillisToEvents = new HashMap<>(); 79 | for(int i = start; i < days; i++){ 80 | setDateTime(timeStamp, currentCalender, i); 81 | List eventList = new ArrayList<>(); 82 | List events = Arrays.asList(new Event(Color.BLUE, currentCalender.getTimeInMillis()), 83 | new Event(Color.RED, currentCalender.getTimeInMillis() + 3600 * 1000), 84 | new Event(Color.RED, currentCalender.getTimeInMillis() + (3600 * 2) * 1000), 85 | new Event(Color.RED, currentCalender.getTimeInMillis() + (3600 * 3) * 1000)); 86 | eventList.addAll(events); 87 | epochMillisToEvents.put(currentCalender.getTimeInMillis(), eventList); 88 | } 89 | return epochMillisToEvents; 90 | } 91 | 92 | public static void setDateTime(long timeStamp, Calendar currentCalender, int i) { 93 | currentCalender.setTimeInMillis(timeStamp); 94 | currentCalender.set(Calendar.DATE, 1); 95 | currentCalender.set(Calendar.HOUR_OF_DAY, 0); 96 | currentCalender.set(Calendar.MINUTE, 0); 97 | currentCalender.set(Calendar.SECOND, 0); 98 | currentCalender.set(Calendar.MILLISECOND, 0); 99 | currentCalender.add(Calendar.DATE, i); 100 | } 101 | 102 | public static long setTimeToMidnightAndGet(Calendar cal, long epoch) { 103 | cal.setTime(new Date(epoch)); 104 | cal.set(Calendar.HOUR_OF_DAY, 0); 105 | cal.set(Calendar.MINUTE, 0); 106 | cal.set(Calendar.SECOND, 0); 107 | cal.set(Calendar.MILLISECOND, 0); 108 | return cal.getTimeInMillis(); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /library/src/test/java/com/github/sundeepk/compactcalendarview/EventsContainerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.github.sundeepk.compactcalendarview.domain.Event; 6 | 7 | import junit.framework.Assert; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.Calendar; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.Locale; 18 | import java.util.Map; 19 | import java.util.Random; 20 | import java.util.TimeZone; 21 | 22 | import static com.github.sundeepk.compactcalendarview.CompactCalendarHelper.getMultipleEventsForEachDayAsMap; 23 | import static com.github.sundeepk.compactcalendarview.CompactCalendarHelper.setTimeToMidnightAndGet; 24 | import static org.junit.Assert.assertEquals; 25 | import static org.junit.Assert.assertNotNull; 26 | import static org.junit.Assert.assertNull; 27 | import static org.junit.Assert.assertTrue; 28 | 29 | public class EventsContainerTest { 30 | 31 | private EventsContainer underTest; 32 | 33 | @Before 34 | public void setUp(){ 35 | Locale.setDefault(Locale.ENGLISH); 36 | TimeZone.setDefault(TimeZone.getTimeZone("GMT")); 37 | 38 | underTest = new EventsContainer(Calendar.getInstance()); 39 | } 40 | 41 | @Test 42 | public void testItRemovesAllEvents(){ 43 | //Sun, 01 Feb 2015 00:00:00 GMT 44 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1422748800000L); 45 | for(Event event : events){ 46 | underTest.addEvent(event); 47 | } 48 | 49 | underTest.removeAllEvents(); 50 | 51 | List actualEvents = underTest.getEventsFor(1422748800000L); 52 | Assert.assertEquals(new ArrayList(), actualEvents); 53 | } 54 | 55 | @Test 56 | public void testItAddsAndGetsEvents(){ 57 | //Sun, 01 Feb 2015 00:00:00 GMT 58 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1422748800000L); 59 | for(Event event : events){ 60 | underTest.addEvent(event); 61 | } 62 | 63 | events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 28, 1422748800000L); 64 | 65 | List actualEvents = underTest.getEventsFor(1422748800000L); 66 | Assert.assertEquals(events.get(0), actualEvents.get(0)); 67 | } 68 | 69 | @Test 70 | public void testItAddsEventsUsingList(){ 71 | //Sun, 01 Feb 2015 00:00:00 GMT 72 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1422748800000L); 73 | 74 | underTest.addEvents(events); 75 | 76 | events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 28, 1422748800000L); 77 | 78 | List actualEvents = underTest.getEventsFor(1422748800000L); 79 | Assert.assertEquals(1, actualEvents.size()); 80 | Assert.assertEquals(events.get(0), actualEvents.get(0)); 81 | } 82 | 83 | @Test 84 | public void testItRemovesEvents(){ 85 | //Sun, 01 Feb 2015 00:00:00 GMT 86 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1422748800000L); 87 | for(Event event : events){ 88 | underTest.addEvent(event); 89 | } 90 | 91 | underTest.removeEvent(events.get(0)); 92 | underTest.removeEvent(events.get(1)); 93 | underTest.removeEvent(events.get(5)); 94 | underTest.removeEvent(events.get(20)); 95 | 96 | List expectedEvents = CompactCalendarHelper.getOneEventPerDayForMonth(0, 28, 1422748800000L); 97 | expectedEvents.remove(events.get(0)); 98 | expectedEvents.remove(events.get(1)); 99 | expectedEvents.remove(events.get(5)); 100 | expectedEvents.remove(events.get(20)); 101 | 102 | for (Event e : expectedEvents) { 103 | List actualEvents = underTest.getEventsFor(e.getTimeInMillis()); 104 | Assert.assertEquals(1, actualEvents.size()); 105 | Assert.assertEquals(e, actualEvents.get(0)); 106 | } 107 | } 108 | 109 | @Test 110 | public void testItRemovesEventFromCacheIfEmpty(){ 111 | //Sun, 01 Feb 2015 00:00:00 GMT 112 | Event event = new Event(Color.BLUE, 1422748800001L); 113 | Event event2 = new Event(Color.BLUE, 1442758800000L); 114 | underTest.addEvent(event); 115 | underTest.addEvent(event2); 116 | 117 | assertEquals(event, underTest.getEventsForMonthAndYear(1, 2015).get(0).getEvents().get(0)); 118 | 119 | underTest.removeEvent(event); 120 | 121 | assertNull(underTest.getEventsForMonthAndYear(1, 2015)); 122 | assertEquals(Arrays.asList(event2), underTest.getEventsFor(1442758800000L)); 123 | } 124 | 125 | @Test 126 | public void testItRemovesEventFromCacheIfEmptyUsingEpoch(){ 127 | //Sun, 01 Feb 2015 00:00:00 GMT 128 | Event event = new Event(Color.BLUE, 1422748800001L); 129 | Event event2 = new Event(Color.BLUE, 1442758800000L); 130 | underTest.addEvent(event); 131 | underTest.addEvent(event2); 132 | 133 | assertEquals(event, underTest.getEventsForMonthAndYear(1, 2015).get(0).getEvents().get(0)); 134 | 135 | underTest.removeEventByEpochMillis(1422748800001L); 136 | 137 | assertNull(underTest.getEventsForMonthAndYear(1, 2015)); 138 | assertEquals(Arrays.asList(event2), underTest.getEventsFor(1442758800000L)); 139 | } 140 | 141 | @Test 142 | public void testItDoesNotInterfereWithOtherEventsWhenRemovingUnknownEvent(){ 143 | //Sun, 01 Feb 2015 00:00:00 GMT 144 | List expectedEvents = CompactCalendarHelper.getOneEventPerDayForMonth(0, 28, 1422748800000L); 145 | 146 | underTest.addEvents(expectedEvents); 147 | 148 | //Sun, 20 September 2015 14:20:00 GMT 149 | underTest.removeEvent(new Event(Color.BLUE, 1442758800000L)); 150 | 151 | List actualEvents = underTest.getEventsForMonth(1422748800000L); 152 | List empty = underTest.getEventsForMonth(1442758800000L); 153 | 154 | assertEquals(empty, new ArrayList()); 155 | assertEquals(expectedEvents, actualEvents); 156 | } 157 | 158 | @Test 159 | public void testItGetsMultipleEventsThatWereAddedForADay(){ 160 | //Add 3 events per a day for Feb starting from Sun, 01 Feb 2015 00:00:00 GMT 161 | Map> events = getMultipleEventsForEachDayAsMap(0, 30, 1422748800000L); 162 | for(Map.Entry> entry : events.entrySet()){ 163 | for (Event event: entry.getValue()) { 164 | underTest.addEvent(event); 165 | } 166 | } 167 | 168 | //if multiple events were added for every day, then check that all events are present by day 169 | for(Map.Entry> entry : events.entrySet()){ 170 | List actualEvents = underTest.getEventsFor(entry.getKey()); 171 | Assert.assertEquals(entry.getValue(), actualEvents); 172 | } 173 | } 174 | 175 | @Test 176 | public void testItRemovesEventsUsingList(){ 177 | //Sun, 01 Feb 2015 00:00:00 GMT 178 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1422748800000L); 179 | for(Event event : events){ 180 | underTest.addEvent(event); 181 | } 182 | 183 | underTest.removeEvents(Arrays.asList(events.get(0), events.get(1), events.get(5), events.get(20))); 184 | 185 | List expectedEvents = CompactCalendarHelper.getOneEventPerDayForMonth(0, 28, 1422748800000L); 186 | expectedEvents.removeAll(Arrays.asList(events.get(0), events.get(1), events.get(5), events.get(20))); 187 | 188 | for (Event e : expectedEvents) { 189 | List actualEvents = underTest.getEventsFor(e.getTimeInMillis()); 190 | Assert.assertEquals(1, actualEvents.size()); 191 | Assert.assertEquals(e, actualEvents.get(0)); 192 | } 193 | } 194 | 195 | @Test 196 | public void testItGetsEventsForSpecificDay(){ 197 | //Sun, 07 Jun 2015 18:20:51 GMT 198 | //get 30 events in total 199 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 200 | for(Event event : events){ 201 | underTest.addEvent(event); 202 | } 203 | 204 | //Wed, 24 Aug 2016 09:21:09 GMT 205 | //get 30 events in total 206 | List events2 = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1472030469000L); 207 | for(Event event : events2){ 208 | underTest.addEvent(event); 209 | } 210 | 211 | //Sun, 07 Jun 2015 18:20:51 GMT 212 | List calendarDayEvents = underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 213 | assertNotNull(calendarDayEvents); 214 | //Assert 6th item since it will represent Sun, 07 Jun 2015 which is the day that we queried for 215 | assertEquals(1, calendarDayEvents.size()); 216 | assertEquals(events.get(6), calendarDayEvents.get(0)); 217 | } 218 | 219 | @Test 220 | public void testItGetsEventsForMonth(){ 221 | //Sun, 07 Jun 2015 18:20:51 GMT 222 | //get 30 events in total 223 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 224 | 225 | //Wed, 06 Jul 2016 13:37:32 GMT 226 | List events2 = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1467812256000L); 227 | 228 | //give a random ordering to elements 229 | Collections.shuffle(events, new Random()); 230 | Collections.shuffle(events2, new Random()); 231 | 232 | for(Event event : events){ 233 | underTest.addEvent(event); 234 | } 235 | 236 | for(Event event : events2){ 237 | underTest.addEvent(event); 238 | } 239 | 240 | //Sun, 07 Jun 2015 18:20:51 GMT 241 | List calendarDayEvents = underTest.getEventsForMonth(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 242 | assertNotNull(calendarDayEvents); 243 | //Assert 6th item since it will represent Sun, 07 Jun 2015 which is the day that we queried for 244 | //Check that events are sorted as expected 245 | assertEquals(CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L), calendarDayEvents); 246 | } 247 | 248 | @Test 249 | public void testItReturnsEmptyForMonthWithNotEvents(){ 250 | //Sun, 07 Jun 2015 18:20:51 GMT 251 | //get 30 events in total 252 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 253 | underTest.addEvents(events); 254 | 255 | //Fri, 07 Aug 2015 12:09:59 GMT 256 | List calendarDayEvents = underTest.getEventsForMonth(setTimeToMidnightAndGet(Calendar.getInstance(), 1438949399000L)); 257 | assertEquals(new ArrayList(), calendarDayEvents); 258 | } 259 | 260 | 261 | @Test 262 | public void testItRemovesEventByDate(){ 263 | //Sun, 07 Jun 2015 18:20:51 GMT 264 | //get 30 events in total 265 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 266 | for(Event event : events){ 267 | underTest.addEvent(event); 268 | } 269 | 270 | assertEquals(1, underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)).size()); 271 | assertEquals(events.get(6), underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)).get(0)); 272 | 273 | 274 | //Sun, 07 Jun 2015 18:20:51 GMT 275 | underTest.removeEventByEpochMillis(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 276 | //Remove 6th item since it will represent Sun, 07 Jun 2015 which is the day that was removed 277 | events.remove(6); 278 | assertEquals(0, underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)).size()); 279 | } 280 | 281 | @Test 282 | public void testItUpdatesEvents(){ 283 | //Sun, 07 Jun 2015 18:20:51 GMT 284 | //get 30 events in total 285 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 286 | for(Event event : events){ 287 | underTest.addEvent(event); 288 | } 289 | 290 | //Sun, 07 Jun 2015 18:20:51 GMT 291 | List calendarDayEvents = underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 292 | assertNotNull(calendarDayEvents); 293 | //Assert 6th item since it will represent Sun, 07 Jun 2015 which is the day that we queried for 294 | assertEquals(events.get(6), calendarDayEvents.get(0)); 295 | 296 | //Add a random event Sun, 07 Jun 2015 21:24:21 GMT 297 | Event updateItem = new Event(Color.GREEN, 1433712261000L); 298 | calendarDayEvents.add(updateItem); 299 | 300 | //Query again Sun, 07 Jun 2015 18:20:51 GMT to make sure list is updated 301 | List calendarDayEvents2 = underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 302 | assertTrue(calendarDayEvents2.contains(updateItem)); 303 | } 304 | 305 | @Test 306 | public void testItAddsEventsToExistingList(){ 307 | //Sun, 07 Jun 2015 18:20:51 GMT 308 | //get 30 events in total 309 | List events = CompactCalendarHelper.getOneEventPerDayForMonth(0, 30, 1433701251000L); 310 | underTest.addEvents(events); 311 | 312 | //Sun, 07 Jun 2015 18:20:51 GMT 313 | List calendarDayEvents = underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 314 | //Assert 6th item since it will represent Sun, 07 Jun 2015 which is the day that we queried for 315 | assertEquals(events.get(6), calendarDayEvents.get(0)); 316 | 317 | //add event in Sun, 07 Jun 2015 18:20:51 GMT for same day, making total 2 events for same day now 318 | Event extraEventAdded = new Event(Color.GREEN, 1433701251000L); 319 | underTest.addEvent(extraEventAdded); 320 | 321 | //Sun, 07 Jun 2015 18:20:51 GMT 322 | List calendarDayEvents2 = underTest.getEventsFor(setTimeToMidnightAndGet(Calendar.getInstance(), 1433701251000L)); 323 | 324 | assertNotNull(calendarDayEvents2); 325 | //Assert 6th item since it will represent Sun, 07 Jun 2015 which is the day that we queried for 326 | assertEquals(2, calendarDayEvents2.size()); 327 | assertEquals(events.get(6), calendarDayEvents2.get(0)); 328 | assertEquals(extraEventAdded, calendarDayEvents2.get(1)); 329 | } 330 | 331 | } 332 | -------------------------------------------------------------------------------- /library/src/test/java/com/github/sundeepk/compactcalendarview/WeekUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.sundeepk.compactcalendarview; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Calendar; 6 | import java.util.Locale; 7 | 8 | import static org.junit.Assert.assertArrayEquals; 9 | 10 | public class WeekUtilsTest { 11 | 12 | @Test 13 | public void itShouldReturnCorrectWeekDaysWhenSundayIsFirstDay(){ 14 | String[] expectedWeekDays = {"S", "M", "T", "W", "T", "F", "S"}; 15 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.SUNDAY, false); 16 | assertArrayEquals(expectedWeekDays, actualWeekDays); 17 | } 18 | 19 | @Test 20 | public void itShouldReturnCorrectWeekDaysWhenMondayIsFirstDay(){ 21 | String[] expectedWeekDays = {"M", "T", "W", "T", "F", "S", "S"}; 22 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.MONDAY, false); 23 | assertArrayEquals(expectedWeekDays, actualWeekDays); 24 | } 25 | 26 | @Test 27 | public void itShouldReturnCorrectWeekDaysWhenTuesdayIsFirstDay(){ 28 | String[] expectedWeekDays = {"T", "W", "T", "F", "S", "S", "M"}; 29 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.TUESDAY, false); 30 | assertArrayEquals(expectedWeekDays, actualWeekDays); 31 | } 32 | 33 | @Test 34 | public void itShouldReturnCorrectWeekDaysWhenWednesdayIsFirstDay(){ 35 | String[] expectedWeekDays = {"W", "T", "F", "S", "S", "M", "T"}; 36 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.WEDNESDAY, false); 37 | assertArrayEquals(expectedWeekDays, actualWeekDays); 38 | } 39 | 40 | @Test 41 | public void itShouldReturnCorrectWeekDaysWhenThursdayIsFirstDay(){ 42 | String[] expectedWeekDays = {"T", "F", "S", "S", "M", "T", "W"}; 43 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.THURSDAY, false); 44 | assertArrayEquals(expectedWeekDays, actualWeekDays); 45 | } 46 | 47 | @Test 48 | public void itShouldReturnCorrectWeekDaysWhenFridayIsFirstDay(){ 49 | String[] expectedWeekDays = {"F", "S", "S", "M", "T", "W", "T"}; 50 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.FRIDAY, false); 51 | assertArrayEquals(expectedWeekDays, actualWeekDays); 52 | } 53 | 54 | @Test 55 | public void itShouldReturnCorrectWeekDaysWhenSaturdayIsFirstDay(){ 56 | String[] expectedWeekDays = {"S", "S", "M", "T", "W", "T", "F"}; 57 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.SATURDAY, false); 58 | assertArrayEquals(expectedWeekDays, actualWeekDays); 59 | } 60 | 61 | @Test 62 | public void itShouldReturnCorrectWeekDaysWhenSundayIsFirstDayWith3Letters(){ 63 | String[] expectedWeekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 64 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.SUNDAY, true); 65 | assertArrayEquals(expectedWeekDays, actualWeekDays); 66 | } 67 | 68 | @Test 69 | public void itShouldReturnCorrectWeekDaysWhenMondayIsFirstDayWith3Letters(){ 70 | String[] expectedWeekDays = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; 71 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.MONDAY, true); 72 | assertArrayEquals(expectedWeekDays, actualWeekDays); 73 | } 74 | 75 | @Test 76 | public void itShouldReturnCorrectWeekDaysWhenTuesdayIsFirstDayWith3Letters(){ 77 | String[] expectedWeekDays = {"Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"}; 78 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.TUESDAY, true); 79 | assertArrayEquals(expectedWeekDays, actualWeekDays); 80 | } 81 | 82 | @Test 83 | public void itShouldReturnCorrectWeekDaysWhenWednesdayIsFirstDayWith3Letters(){ 84 | String[] expectedWeekDays = {"Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue"}; 85 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.WEDNESDAY, true); 86 | assertArrayEquals(expectedWeekDays, actualWeekDays); 87 | } 88 | 89 | @Test 90 | public void itShouldReturnCorrectWeekDaysWhenThursdayIsFirstDayWith3Letters(){ 91 | String[] expectedWeekDays = {"Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed"}; 92 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.THURSDAY, true); 93 | assertArrayEquals(expectedWeekDays, actualWeekDays); 94 | } 95 | 96 | @Test 97 | public void itShouldReturnCorrectWeekDaysWhenFridayIsFirstDayWith3Letters(){ 98 | String[] expectedWeekDays = {"Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"}; 99 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.FRIDAY, true); 100 | assertArrayEquals(expectedWeekDays, actualWeekDays); 101 | } 102 | 103 | @Test 104 | public void itShouldReturnCorrectWeekDaysWhenSaturdayIsFirstDayWith3Letters(){ 105 | String[] expectedWeekDays = {"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"}; 106 | String[] actualWeekDays = WeekUtils.getWeekdayNames(Locale.ENGLISH, Calendar.SATURDAY, true); 107 | assertArrayEquals(expectedWeekDays, actualWeekDays); 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | mavenLocal() 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.2' 11 | classpath 'com.facebook.testing.screenshot:plugin:0.6.0' 12 | } 13 | } 14 | 15 | apply plugin: 'com.android.application' 16 | apply plugin: 'com.facebook.testing.screenshot' 17 | 18 | android { 19 | compileSdkVersion 27 20 | buildToolsVersion '27.0.3' 21 | 22 | defaultConfig { 23 | applicationId "sundeepk.github.com.sample" 24 | minSdkVersion 16 25 | targetSdkVersion 27 26 | versionCode 1 27 | versionName "1.0" 28 | testInstrumentationRunner "sundeepk.github.com.sample.TestRunner" 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | } 37 | 38 | configurations.all { 39 | resolutionStrategy.force 'com.android.support:support-annotations:27.1.1' 40 | } 41 | 42 | dependencies { 43 | api fileTree(include: ['*.jar'], dir: 'libs') 44 | api 'com.android.support:appcompat-v7:27.1.1' 45 | api 'com.android.support:support-v4:27.1.1' 46 | api project(':library') 47 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 48 | androidTestImplementation 'com.azimolabs.conditionwatcher:conditionwatcher:0.1' 49 | androidTestImplementation 'com.android.support.test:rules:1.0.2' 50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 51 | androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.0' 52 | androidTestImplementation 'com.google.dexmaker:dexmaker:1.0' 53 | androidTestImplementation 'org.mockito:mockito-core:1.10.17' 54 | androidTestImplementation 'com.android.support:support-annotations:27.1.1' 55 | 56 | } 57 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/sundeep/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToJan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToJan.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarch.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarchRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarchRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForFeb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForFeb.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForFebRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDisplaysDaysFromOtherMonthsForFebRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsDifferentColorsForCurrentSelectedDay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsDifferentColorsForCurrentSelectedDay.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsEventIndicatorsBelowHighlightedDayIndicators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsEventIndicatorsBelowHighlightedDayIndicators.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsEventsRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsEventsRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsFillLargeIndicatorForEventsWhenDrawEventsBelowDayIndicatorsIsTrue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsFillLargeIndicatorForEventsWhenDrawEventsBelowDayIndicatorsIsTrue.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsFridayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsFridayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsFridayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsFridayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsIndicatorsBelowCurrentSelectedDayWithLargeHeight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsIndicatorsBelowCurrentSelectedDayWithLargeHeight.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsMondayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsMondayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsMondayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsMondayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSaturdayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSaturdayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSaturdayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSaturdayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSundayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSundayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSundayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsSundayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsThursdayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsThursdayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsThursdayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsThursdayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsTuesdayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsTuesdayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsTuesdayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsTuesdayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWedAsFirstDayWithFrenchLocaleRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWedAsFirstDayWithFrenchLocaleRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayOfMonth.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayOfMonthRtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayOfMonthRtl.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayWithFrenchLocale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testItDrawsWednesdayAsFirstDayWithFrenchLocale.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testOnDayClickListenerIsCalledWhenLocaleIsFranceWithWedAsFirstDayOFWeek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testOnDayClickListenerIsCalledWhenLocaleIsFranceWithWedAsFirstDayOFWeek.png -------------------------------------------------------------------------------- /sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testWhenShouldSelectFirstDayOfMonthOnScrollIsFalseItDoesNotSelectFIrstDayOfMonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SundeepK/CompactCalendarView/e74e0eeb913744bdcaea6e9df53268441f9dbf8d/sample/screenshots/sundeepk.github.com.sample.ApplicationTest_testWhenShouldSelectFirstDayOfMonthOnScrollIsFalseItDoesNotSelectFIrstDayOfMonth.png -------------------------------------------------------------------------------- /sample/src/androidTest/java/sundeepk/github/com/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.support.test.espresso.ViewAction; 7 | import android.support.test.espresso.action.CoordinatesProvider; 8 | import android.support.test.espresso.action.GeneralClickAction; 9 | import android.support.test.espresso.action.GeneralSwipeAction; 10 | import android.support.test.espresso.action.Press; 11 | import android.support.test.espresso.action.Swipe; 12 | import android.support.test.espresso.action.Tap; 13 | import android.support.test.rule.ActivityTestRule; 14 | import android.support.test.runner.AndroidJUnit4; 15 | import android.support.v7.app.ActionBar; 16 | import android.util.DisplayMetrics; 17 | import android.util.Log; 18 | import android.util.TypedValue; 19 | import android.view.View; 20 | 21 | import com.azimolabs.conditionwatcher.ConditionWatcher; 22 | import com.azimolabs.conditionwatcher.Instruction; 23 | import com.facebook.testing.screenshot.Screenshot; 24 | import com.facebook.testing.screenshot.ViewHelpers; 25 | import com.github.sundeepk.compactcalendarview.CompactCalendarView; 26 | import com.github.sundeepk.compactcalendarview.CompactCalendarView.CompactCalendarAnimationListener; 27 | import com.github.sundeepk.compactcalendarview.domain.Event; 28 | 29 | import org.junit.Before; 30 | import org.junit.Rule; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | 34 | import java.text.SimpleDateFormat; 35 | import java.util.ArrayList; 36 | import java.util.Arrays; 37 | import java.util.Calendar; 38 | import java.util.Date; 39 | import java.util.GregorianCalendar; 40 | import java.util.List; 41 | import java.util.Locale; 42 | import java.util.TimeZone; 43 | 44 | import static android.support.test.InstrumentationRegistry.getInstrumentation; 45 | import static android.support.test.espresso.Espresso.onView; 46 | import static android.support.test.espresso.action.ViewActions.click; 47 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 48 | import static com.github.sundeepk.compactcalendarview.CompactCalendarView.CompactCalendarViewListener; 49 | import static com.github.sundeepk.compactcalendarview.CompactCalendarView.FILL_LARGE_INDICATOR; 50 | import static org.junit.Assert.assertEquals; 51 | import static org.mockito.Mockito.mock; 52 | import static org.mockito.Mockito.verify; 53 | import static org.mockito.Mockito.verifyNoMoreInteractions; 54 | 55 | @RunWith(AndroidJUnit4.class) 56 | public class ApplicationTest { 57 | 58 | private static final String APPLICATION_TEST_TAG = "ApplicationTest"; 59 | 60 | private SimpleDateFormat dateFormatForMonth; 61 | private CompactCalendarView compactCalendarView; 62 | 63 | @Rule 64 | public ActivityTestRule activityRule = new ActivityTestRule<>(MainActivity.class); 65 | 66 | private View mainContent; 67 | private int onClosedCallCount = 0; 68 | private int onOpenedCallCount = 0; 69 | 70 | @Before 71 | public void setUp() throws Exception { 72 | Locale.setDefault(Locale.ENGLISH); 73 | TimeZone.setDefault(TimeZone.getTimeZone("GMT")); 74 | dateFormatForMonth = new SimpleDateFormat("MMM - yyyy", Locale.getDefault()); 75 | compactCalendarView = (CompactCalendarView) activityRule.getActivity().findViewById(R.id.compactcalendar_view); 76 | mainContent = activityRule.getActivity().findViewById(R.id.parent); 77 | onClosedCallCount = 0; 78 | onOpenedCallCount = 0; 79 | } 80 | 81 | @Test 82 | public void testItDrawsEventsRtl(){ 83 | Calendar currentCalender = Calendar.getInstance(); 84 | currentCalender.set(Calendar.DAY_OF_MONTH, 1); 85 | currentCalender.set(Calendar.ERA, GregorianCalendar.AD); 86 | currentCalender.set(Calendar.YEAR, 2015); 87 | currentCalender.set(Calendar.MONTH, Calendar.MARCH); 88 | 89 | compactCalendarView.setIsRtl(true); 90 | //Sun, 08 Feb 2015 00:00:00 GMT 91 | setDate(new Date(1423353600000L)); 92 | addEvents(Calendar.FEBRUARY, 2015); 93 | addEvents(Calendar.MARCH, 2015); 94 | scrollCalendarBackwardsBy(1); 95 | 96 | assertEquals(getEventsFor(Calendar.MARCH, 2015), compactCalendarView.getEventsForMonth(currentCalender.getTime())); 97 | 98 | syncToolbarDate(); 99 | 100 | takeScreenShot(); 101 | } 102 | 103 | @Test 104 | public void testItDrawsEventIndicatorsBelowHighlightedDayIndicators(){ 105 | setDrawEventsBelowDayIndicators(true); 106 | setDate(new Date(1423094400000L)); 107 | addEvents(Calendar.FEBRUARY, 2015); 108 | takeScreenShot(); 109 | 110 | } 111 | 112 | @Test 113 | public void testItDrawsFillLargeIndicatorForEventsWhenDrawEventsBelowDayIndicatorsIsTrue() { 114 | // test to make sure calendar does not draw event indicators below highlighted days 115 | // when the style is FILL_LARGE_INDICATOR 116 | //Sun, 08 Feb 2015 00:00:00 GMT 117 | setDrawEventsBelowDayIndicators(true); 118 | setDate(new Date(1423353600000L)); 119 | addEvents(Calendar.FEBRUARY, 2015); 120 | onView(withId(R.id.compactcalendar_view)).perform(clickXY(60, 150)); 121 | setIndicatorType(FILL_LARGE_INDICATOR, FILL_LARGE_INDICATOR, FILL_LARGE_INDICATOR); 122 | takeScreenShot(); 123 | } 124 | 125 | @Test 126 | public void testItDrawsIndicatorsBelowCurrentSelectedDayWithLargeHeight() { 127 | // test to make sure calendar does not draw event indicators below highlighted days 128 | //Sun, 08 Feb 2015 00:00:00 GMT 129 | setHeight(400); 130 | setDrawEventsBelowDayIndicators(true); 131 | setDate(new Date(1423353600000L)); 132 | addEvents(Calendar.FEBRUARY, 2015); 133 | onView(withId(R.id.compactcalendar_view)).perform(clickXY(60, 120)); 134 | takeScreenShot(800); 135 | } 136 | 137 | @Test 138 | public void testItDisplaysDaysFromOtherMonthsForFeb(){ 139 | //Sun, 08 Feb 2015 00:00:00 GMT 140 | setDate(new Date(1423353600000L)); 141 | setShouldDrawDaysFromOtherMonths(true); 142 | takeScreenShot(); 143 | } 144 | 145 | @Test 146 | public void testItDisplaysDaysFromOtherMonthsForFebRtl(){ 147 | compactCalendarView.setIsRtl(true); 148 | //Sun, 08 Feb 2015 00:00:00 GMT 149 | setDate(new Date(1423353600000L)); 150 | setShouldDrawDaysFromOtherMonths(true); 151 | takeScreenShot(); 152 | } 153 | 154 | @Test 155 | public void testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarch(){ 156 | //Sun, 08 Feb 2015 00:00:00 GMT 157 | setDate(new Date(1423353600000L)); 158 | setShouldDrawDaysFromOtherMonths(true); 159 | scrollCalendarForwardBy(1); 160 | takeScreenShot(); 161 | } 162 | 163 | @Test 164 | public void testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToMarchRtl(){ 165 | compactCalendarView.setIsRtl(true); 166 | //Sun, 08 Feb 2015 00:00:00 GMT 167 | setDate(new Date(1423353600000L)); 168 | setShouldDrawDaysFromOtherMonths(true); 169 | scrollCalendarBackwardsBy(1); 170 | takeScreenShot(); 171 | } 172 | 173 | @Test 174 | public void testItDisplaysDaysFromOtherMonthsForAfterScrollingFromFebToJan(){ 175 | //Sun, 08 Feb 2015 00:00:00 GMT 176 | setShouldDrawDaysFromOtherMonths(true); 177 | setDate(new Date(1423353600000L)); 178 | getInstrumentation().waitForIdleSync(); 179 | scrollCalendarBackwardsBy(1); 180 | takeScreenShot(); 181 | } 182 | 183 | 184 | @Test 185 | public void testItDrawsSundayAsFirstDayOfMonthRtl(){ 186 | compactCalendarView.setIsRtl(true); 187 | //Sun, 08 Feb 2015 00:00:00 GMT 188 | setDate(new Date(1423353600000L)); 189 | setFirstDayOfWeek(Calendar.SUNDAY); 190 | takeScreenShot(); 191 | } 192 | 193 | @Test 194 | public void testItDrawsMondayAsFirstDayOfMonthRtl(){ 195 | compactCalendarView.setIsRtl(true); 196 | // defaults to Monday 197 | //Sun, 08 Feb 2015 00:00:00 GMT 198 | setDate(new Date(1423353600000L)); 199 | takeScreenShot(); 200 | } 201 | 202 | @Test 203 | public void testItDrawsTuesdayAsFirstDayOfMonthRtl(){ 204 | compactCalendarView.setIsRtl(true); 205 | //Sun, 08 Feb 2015 00:00:00 GMT 206 | setDate(new Date(1423353600000L)); 207 | setFirstDayOfWeek(Calendar.TUESDAY); 208 | takeScreenShot(); 209 | } 210 | 211 | @Test 212 | public void testItDrawsWednesdayAsFirstDayOfMonthRtl(){ 213 | compactCalendarView.setIsRtl(true); 214 | //Sun, 08 Feb 2015 00:00:00 GMT 215 | setDate(new Date(1423353600000L)); 216 | setFirstDayOfWeek(Calendar.WEDNESDAY); 217 | takeScreenShot(); 218 | } 219 | 220 | @Test 221 | public void testItDrawsThursdayAsFirstDayOfMonthRtl(){ 222 | compactCalendarView.setIsRtl(true); 223 | //Sun, 08 Feb 2015 00:00:00 GMT 224 | setDate(new Date(1423353600000L)); 225 | setFirstDayOfWeek(Calendar.THURSDAY); 226 | takeScreenShot(); 227 | } 228 | 229 | @Test 230 | public void testItDrawsFridayAsFirstDayOfMonthRtl(){ 231 | compactCalendarView.setIsRtl(true); 232 | //Sun, 08 Feb 2015 00:00:00 GMT 233 | setDate(new Date(1423353600000L)); 234 | setFirstDayOfWeek(Calendar.FRIDAY); 235 | takeScreenShot(); 236 | } 237 | 238 | @Test 239 | public void testItDrawsSaturdayAsFirstDayOfMonthRtl(){ 240 | compactCalendarView.setIsRtl(true); 241 | //Sun, 08 Feb 2015 00:00:00 GMT 242 | setDate(new Date(1423353600000L)); 243 | setFirstDayOfWeek(Calendar.SATURDAY); 244 | takeScreenShot(); 245 | } 246 | 247 | @Test 248 | public void testItDrawsWedAsFirstDayWithFrenchLocaleRtl(){ 249 | compactCalendarView.setIsRtl(true); 250 | //Sun, 08 Feb 2015 00:00:00 GMT 251 | setDate(new Date(1423353600000L)); 252 | setFirstDayOfWeek(Calendar.WEDNESDAY); 253 | onView(withId(R.id.set_locale)).perform(clickXY(0, 0)); 254 | setUseThreeLetterAbbreviation(true); 255 | takeScreenShot(); 256 | } 257 | 258 | 259 | @Test 260 | public void testItDrawsSundayAsFirstDayOfMonth(){ 261 | //Sun, 08 Feb 2015 00:00:00 GMT 262 | setDate(new Date(1423353600000L)); 263 | setFirstDayOfWeek(Calendar.SUNDAY); 264 | takeScreenShot(); 265 | } 266 | 267 | @Test 268 | public void testItDrawsMondayAsFirstDayOfMonth(){ 269 | // defaults to Monday 270 | //Sun, 08 Feb 2015 00:00:00 GMT 271 | setDate(new Date(1423353600000L)); 272 | takeScreenShot(); 273 | } 274 | 275 | @Test 276 | public void testItDrawsTuesdayAsFirstDayOfMonth(){ 277 | //Sun, 08 Feb 2015 00:00:00 GMT 278 | setDate(new Date(1423353600000L)); 279 | setFirstDayOfWeek(Calendar.TUESDAY); 280 | takeScreenShot(); 281 | } 282 | 283 | @Test 284 | public void testItDrawsWednesdayAsFirstDayOfMonth(){ 285 | //Sun, 08 Feb 2015 00:00:00 GMT 286 | setDate(new Date(1423353600000L)); 287 | setFirstDayOfWeek(Calendar.WEDNESDAY); 288 | takeScreenShot(); 289 | } 290 | 291 | @Test 292 | public void testItDrawsThursdayAsFirstDayOfMonth(){ 293 | //Sun, 08 Feb 2015 00:00:00 GMT 294 | setDate(new Date(1423353600000L)); 295 | setFirstDayOfWeek(Calendar.THURSDAY); 296 | takeScreenShot(); 297 | } 298 | 299 | @Test 300 | public void testItDrawsFridayAsFirstDayOfMonth(){ 301 | //Sun, 08 Feb 2015 00:00:00 GMT 302 | setDate(new Date(1423353600000L)); 303 | setFirstDayOfWeek(Calendar.FRIDAY); 304 | takeScreenShot(); 305 | } 306 | 307 | @Test 308 | public void testItDrawsSaturdayAsFirstDayOfMonth(){ 309 | //Sun, 08 Feb 2015 00:00:00 GMT 310 | setDate(new Date(1423353600000L)); 311 | setFirstDayOfWeek(Calendar.SATURDAY); 312 | takeScreenShot(); 313 | } 314 | 315 | @Test 316 | public void testItDrawsWednesdayAsFirstDayWithFrenchLocale(){ 317 | //Sun, 08 Feb 2015 00:00:00 GMT 318 | setDate(new Date(1423353600000L)); 319 | setFirstDayOfWeek(Calendar.WEDNESDAY); 320 | onView(withId(R.id.set_locale)).perform(clickXY(0, 0)); 321 | setUseThreeLetterAbbreviation(true); 322 | takeScreenShot(); 323 | } 324 | 325 | @Test 326 | public void testOnDayClickListenerIsCalledWhenLocaleIsFranceWithWedAsFirstDayOFWeek(){ 327 | CompactCalendarViewListener listener = mock(CompactCalendarViewListener.class); 328 | compactCalendarView.setListener(listener); 329 | 330 | Locale locale = Locale.FRANCE; 331 | TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); 332 | Calendar instance = Calendar.getInstance(timeZone, locale); 333 | // Thu, 05 Feb 2015 12:00:00 GMT - then set to midnight 334 | instance.setTimeInMillis(1423137600000L); 335 | instance.set(Calendar.HOUR_OF_DAY, 0); 336 | instance.set(Calendar.MINUTE, 0); 337 | instance.set(Calendar.SECOND, 0); 338 | instance.set(Calendar.MILLISECOND, 0); 339 | 340 | //Sun, 08 Feb 2015 00:00:00 GMT 341 | setDate(new Date(1423353600000L)); 342 | setFirstDayOfWeek(Calendar.WEDNESDAY); 343 | onView(withId(R.id.set_locale)).perform(clickXY(0, 0)); 344 | onView(withId(R.id.compactcalendar_view)).perform(clickXY(60, 100)); 345 | 346 | //Thr, 05 Feb 2015 00:00:00 GMT - expected 347 | verify(listener).onDayClick(instance.getTime()); 348 | verifyNoMoreInteractions(listener); 349 | takeScreenShot(); 350 | } 351 | 352 | @Test 353 | public void testOnDayClickListenerIsCalled(){ 354 | CompactCalendarViewListener listener = mock(CompactCalendarViewListener.class); 355 | compactCalendarView.setListener(listener); 356 | 357 | Calendar instance = Calendar.getInstance(); 358 | // Thu, 03 Feb 2015 12:00:00 GMT - then set to midnight 359 | instance.setTimeInMillis(1422921600000L); 360 | instance.set(Calendar.HOUR_OF_DAY, 0); 361 | instance.set(Calendar.MINUTE, 0); 362 | instance.set(Calendar.SECOND, 0); 363 | instance.set(Calendar.MILLISECOND, 0); 364 | 365 | //Sun, 08 Feb 2015 00:00:00 GMT 366 | setDate(new Date(1423353600000L)); 367 | onView(withId(R.id.compactcalendar_view)).perform(clickXY(60, 100)); 368 | 369 | //Thr, 03 Feb 2015 00:00:00 GMT - expected 370 | verify(listener).onDayClick(instance.getTime()); 371 | verifyNoMoreInteractions(listener); 372 | } 373 | 374 | @Test 375 | public void testOnDayClickListenerIsCalledInRtl(){ 376 | CompactCalendarViewListener listener = mock(CompactCalendarViewListener.class); 377 | compactCalendarView.setListener(listener); 378 | compactCalendarView.setIsRtl(true); 379 | 380 | Calendar instance = Calendar.getInstance(); 381 | // Thu, 07 Feb 2015 12:00:00 GMT - then set to midnight 382 | instance.setTimeInMillis(1423267200000L); 383 | instance.set(Calendar.HOUR_OF_DAY, 0); 384 | instance.set(Calendar.MINUTE, 0); 385 | instance.set(Calendar.SECOND, 0); 386 | instance.set(Calendar.MILLISECOND, 0); 387 | 388 | //Sun, 08 Feb 2015 00:00:00 GMT 389 | setDate(new Date(1423353600000L)); 390 | onView(withId(R.id.compactcalendar_view)).perform(clickXY(60, 100)); 391 | 392 | //Thr, 07 Feb 2015 00:00:00 GMT - expected 393 | verify(listener).onDayClick(instance.getTime()); 394 | verifyNoMoreInteractions(listener); 395 | } 396 | 397 | // Using mocks for listener causes espresso to throw an error because the callback is called from within animation handler. 398 | // Maybe a problem with espresso, for now manually check count. 399 | @Test 400 | public void testOpenedAndClosedListerCalledForExposeAnimationCalendar() throws Throwable { 401 | // calendar is opened by default. 402 | CompactCalendarAnimationListener listener = new CompactCalendarAnimationListener() { 403 | @Override 404 | public void onOpened() { 405 | onOpenedCallCount++; 406 | } 407 | 408 | @Override 409 | public void onClosed() { 410 | onClosedCallCount++; 411 | } 412 | }; 413 | compactCalendarView.setAnimationListener(listener); 414 | 415 | //Sun, 08 Feb 2015 00:00:00 GMT 416 | setDate(new Date(1423353600000L)); 417 | onView(withId(R.id.show_with_animation_calendar)).perform(click()); 418 | onView(withId(R.id.show_with_animation_calendar)).perform(click()); 419 | 420 | waitForAnimationFinish(); 421 | 422 | assertEquals(onClosedCallCount, 1); 423 | assertEquals(onOpenedCallCount, 1); 424 | } 425 | 426 | // Using mocks for listener causes espresso to throw an error because the callback is called from within animation handler. 427 | // Maybe a problem with espresso, for now manually check count. 428 | @Test 429 | public void testOpenedAndClosedListerCalledForCalendar() throws Throwable { 430 | // calendar is opened by default. 431 | CompactCalendarAnimationListener listener = new CompactCalendarAnimationListener() { 432 | @Override 433 | public void onOpened() { 434 | onOpenedCallCount = onOpenedCallCount + 1; 435 | } 436 | 437 | @Override 438 | public void onClosed() { 439 | onClosedCallCount++; 440 | } 441 | }; 442 | compactCalendarView.setAnimationListener(listener); 443 | 444 | //Sun, 08 Feb 2015 00:00:00 GMT 445 | setDate(new Date(1423353600000L)); 446 | onView(withId(R.id.slide_calendar)).perform(click()); 447 | onView(withId(R.id.slide_calendar)).perform(click()); 448 | 449 | waitForAnimationFinish(); 450 | 451 | assertEquals(onClosedCallCount, 1); 452 | assertEquals(onOpenedCallCount, 1); 453 | } 454 | 455 | private void waitForAnimationFinish() throws Exception { 456 | ConditionWatcher.waitForCondition(new Instruction() { 457 | @Override 458 | public String getDescription() { 459 | return null; 460 | } 461 | 462 | @Override 463 | public boolean checkCondition() { 464 | return !compactCalendarView.isAnimating(); 465 | } 466 | }); 467 | } 468 | 469 | @Test 470 | public void testItDoesNotThrowNullPointerWhenNoAnimationListenerIsSet() throws Throwable { 471 | //Sun, 08 Feb 2015 00:00:00 GMT 472 | compactCalendarView.setAnimationListener(null); 473 | setDate(new Date(1423353600000L)); 474 | onView(withId(R.id.show_with_animation_calendar)).perform(click()); 475 | onView(withId(R.id.slide_calendar)).perform(click()); 476 | } 477 | 478 | @Test 479 | public void testItDrawsDifferentColorsForCurrentSelectedDay(){ 480 | //Sun, 08 Feb 2015 00:00:00 GMT 481 | setDate(new Date(1423353600000L)); 482 | compactCalendarView.setCurrentDayTextColor(Color.BLACK); 483 | compactCalendarView.setCurrentSelectedDayTextColor(Color.BLUE); 484 | takeScreenShot(); 485 | } 486 | 487 | @Test 488 | public void testWhenShouldSelectFirstDayOfMonthOnScrollIsFalseItDoesNotSelectFIrstDayOfMonth() { 489 | compactCalendarView.shouldSelectFirstDayOfMonthOnScroll(false); 490 | setDate(new Date(1423353600000L)); 491 | scrollCalendarForwardBy(1); 492 | takeScreenShot(); 493 | } 494 | 495 | // Nasty hack to get the toolbar to update the current month 496 | // TODO sample code should be refactored to do this 497 | private void syncToolbarDate(){ 498 | getInstrumentation().runOnMainSync(new Runnable() { 499 | @Override 500 | public void run() { 501 | ActionBar toolbar = activityRule.getActivity().getSupportActionBar(); 502 | toolbar.setTitle(dateFormatForMonth.format(compactCalendarView.getFirstDayOfCurrentMonth())); 503 | } 504 | }); 505 | } 506 | 507 | private void setFirstDayOfWeek(final int dayOfWeek) { 508 | getInstrumentation().runOnMainSync(new Runnable() { 509 | @Override 510 | public void run() { 511 | compactCalendarView.setFirstDayOfWeek(dayOfWeek); 512 | } 513 | }); 514 | } 515 | 516 | private void setUseThreeLetterAbbreviation(final boolean useThreeLetterAbbreviation) { 517 | getInstrumentation().runOnMainSync(new Runnable() { 518 | @Override 519 | public void run() { 520 | compactCalendarView.setUseThreeLetterAbbreviation(useThreeLetterAbbreviation); 521 | } 522 | }); 523 | } 524 | 525 | private void setShouldDrawDaysFromOtherMonths(final boolean shouldDrawEventsBelowDayIndicators) { 526 | getInstrumentation().runOnMainSync(new Runnable() { 527 | @Override 528 | public void run() { 529 | compactCalendarView.displayOtherMonthDays(shouldDrawEventsBelowDayIndicators); 530 | } 531 | }); 532 | } 533 | 534 | private void setDrawEventsBelowDayIndicators(final boolean shouldDrawEventsBelowDayIndicators) { 535 | getInstrumentation().runOnMainSync(new Runnable() { 536 | @Override 537 | public void run() { 538 | compactCalendarView.shouldDrawIndicatorsBelowSelectedDays(shouldDrawEventsBelowDayIndicators); 539 | } 540 | }); 541 | } 542 | 543 | private void setIndicatorType(final int currentSelectedDayStyle, final int eventStyle, final int currentDayStyle) { 544 | getInstrumentation().runOnMainSync(new Runnable() { 545 | @Override 546 | public void run() { 547 | compactCalendarView.setCurrentSelectedDayIndicatorStyle(currentSelectedDayStyle); 548 | compactCalendarView.setEventIndicatorStyle(eventStyle); 549 | compactCalendarView.setCurrentDayIndicatorStyle(currentDayStyle); 550 | } 551 | }); 552 | } 553 | 554 | private void capture(final String name) { 555 | getInstrumentation().runOnMainSync(new Runnable() { 556 | @Override 557 | public void run() { 558 | compactCalendarView.requestLayout(); 559 | ViewHelpers.setupView(mainContent) 560 | .setExactHeightPx(mainContent.getHeight()) 561 | .setExactWidthPx(mainContent.getWidth()) 562 | .layout(); 563 | safeSleep(200); 564 | Screenshot.snap(mainContent) 565 | .setName(name) 566 | .record(); 567 | } 568 | }); 569 | } 570 | 571 | private void setDate(final Date date) { 572 | getInstrumentation().runOnMainSync(new Runnable() { 573 | @Override 574 | public void run() { 575 | compactCalendarView.setCurrentDate(date); 576 | ActionBar toolbar = activityRule.getActivity().getSupportActionBar(); 577 | toolbar.setTitle(dateFormatForMonth.format(compactCalendarView.getFirstDayOfCurrentMonth())); 578 | } 579 | }); 580 | } 581 | 582 | private void shouldSelectFirstDayOfMonthOnScroll(final boolean shouldSelectFirstDay) { 583 | getInstrumentation().runOnMainSync(new Runnable() { 584 | @Override 585 | public void run() { 586 | compactCalendarView.shouldSelectFirstDayOfMonthOnScroll(shouldSelectFirstDay); 587 | ActionBar toolbar = activityRule.getActivity().getSupportActionBar(); 588 | toolbar.setTitle(dateFormatForMonth.format(compactCalendarView.getFirstDayOfCurrentMonth())); 589 | } 590 | }); 591 | } 592 | 593 | public ViewAction clickXY(final float x, final float y){ 594 | final DisplayMetrics dm = activityRule.getActivity().getResources().getDisplayMetrics() ; 595 | final float spX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, x, dm); 596 | final float spY = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, y, dm); 597 | return new GeneralClickAction( 598 | Tap.SINGLE, 599 | new CoordinatesProvider() { 600 | @Override 601 | public float[] calculateCoordinates(View view) { 602 | 603 | final int[] screenPos = new int[2]; 604 | view.getLocationOnScreen(screenPos); 605 | 606 | final float screenX = screenPos[0] + spX; 607 | final float screenY = screenPos[1] + spY; 608 | float[] coordinates = {screenX, screenY}; 609 | 610 | return coordinates; 611 | } 612 | }, 613 | Press.FINGER); 614 | } 615 | 616 | public ViewAction scroll(final int startX, final int startY, final int endX, final int endY){ 617 | final DisplayMetrics dm = activityRule.getActivity().getResources().getDisplayMetrics() ; 618 | final float spStartX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, startX, dm); 619 | final float spStartY = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, startY, dm); 620 | final float spEndX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, endX, dm); 621 | final float spEndY = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, endY, dm); 622 | return new GeneralSwipeAction( 623 | Swipe.FAST, 624 | new CoordinatesProvider() { 625 | @Override 626 | public float[] calculateCoordinates(View view) { 627 | 628 | final int[] screenPos = new int[2]; 629 | view.getLocationOnScreen(screenPos); 630 | 631 | final float screenX = screenPos[0] + spStartX; 632 | final float screenY = screenPos[1] + spStartY; 633 | float[] coordinates = {screenX, screenY}; 634 | 635 | return coordinates; 636 | } 637 | }, 638 | new CoordinatesProvider() { 639 | @Override 640 | public float[] calculateCoordinates(View view) { 641 | 642 | final int[] screenPos = new int[2]; 643 | view.getLocationOnScreen(screenPos); 644 | 645 | final float screenX = screenPos[0] + spEndX; 646 | final float screenY = screenPos[1] + spEndY; 647 | float[] coordinates = {screenX, screenY}; 648 | 649 | return coordinates; 650 | } 651 | }, 652 | Press.FINGER); 653 | } 654 | 655 | private void addEvents(final int month, final int year) { 656 | Context context = compactCalendarView.getContext(); 657 | ((Activity) context).runOnUiThread(new Runnable() { 658 | @Override 659 | public void run() { 660 | compactCalendarView.addEvents(getEventsFor(month, year)); 661 | } 662 | }); 663 | } 664 | 665 | private List getEventsFor(final int month, final int year){ 666 | Calendar currentCalender = Calendar.getInstance(); 667 | currentCalender.setTime(new Date()); 668 | currentCalender.set(Calendar.DAY_OF_MONTH, 1); 669 | Date firstDayOfMonth = currentCalender.getTime(); 670 | List events = new ArrayList<>(); 671 | for (int i = 0; i < 6; i++) { 672 | currentCalender.setTime(firstDayOfMonth); 673 | if (month > -1) { 674 | currentCalender.set(Calendar.MONTH, month); 675 | } 676 | if (year > -1) { 677 | currentCalender.set(Calendar.ERA, GregorianCalendar.AD); 678 | currentCalender.set(Calendar.YEAR, year); 679 | } 680 | currentCalender.add(Calendar.DATE, i); 681 | setToMidnight(currentCalender); 682 | long timeInMillis = currentCalender.getTimeInMillis(); 683 | events.addAll(getEvents(timeInMillis, i)); 684 | } 685 | return events; 686 | } 687 | 688 | private List getEvents(long timeInMillis, int day) { 689 | if (day < 2) { 690 | return Arrays.asList(new Event(Color.argb(255, 169, 68, 65), timeInMillis, "Event at " + new Date(timeInMillis))); 691 | } else if ( day > 2 && day <= 4) { 692 | return Arrays.asList( 693 | new Event(Color.argb(255, 169, 68, 65), timeInMillis, "Event at " + new Date(timeInMillis)), 694 | new Event(Color.argb(255, 100, 68, 65), timeInMillis, "Event 2 at " + new Date(timeInMillis))); 695 | } else { 696 | return Arrays.asList( 697 | new Event(Color.argb(255, 169, 68, 65), timeInMillis, "Event at " + new Date(timeInMillis) ), 698 | new Event(Color.argb(255, 100, 68, 65), timeInMillis, "Event 2 at " + new Date(timeInMillis)), 699 | new Event(Color.argb(255, 70, 68, 65), timeInMillis, "Event 3 at " + new Date(timeInMillis))); 700 | } 701 | } 702 | 703 | private void setToMidnight(Calendar calendar) { 704 | calendar.set(Calendar.HOUR_OF_DAY, 0); 705 | calendar.set(Calendar.MINUTE, 0); 706 | calendar.set(Calendar.SECOND, 0); 707 | calendar.set(Calendar.MILLISECOND, 0); 708 | } 709 | 710 | public void setHeight(final float height) { 711 | final Context context = compactCalendarView.getContext(); 712 | ((Activity) context).runOnUiThread(new Runnable() { 713 | @Override 714 | public void run() { 715 | int newHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, context.getResources().getDisplayMetrics()); 716 | compactCalendarView.getLayoutParams().height = newHeight; 717 | compactCalendarView.setTargetHeight(newHeight); 718 | compactCalendarView.requestLayout(); 719 | compactCalendarView.invalidate(); 720 | } 721 | }); 722 | } 723 | 724 | private void scrollCalendarForwardBy(int months) { 725 | for (int i =0; i < months; i++) { 726 | onView(withId(R.id.compactcalendar_view)).perform(scroll(100, 100, -200, 0)); 727 | safeSleep(); 728 | } 729 | } 730 | 731 | private void scrollCalendarBackwardsBy(int months) { 732 | for (int i =0; i < months; i++) { 733 | onView(withId(R.id.compactcalendar_view)).perform(scroll(100, 10, 300, 0)); 734 | safeSleep(); 735 | } 736 | } 737 | 738 | private void safeSleep() { 739 | safeSleep(500); 740 | } 741 | 742 | private void safeSleep(int i) { 743 | try { 744 | Thread.sleep(i); 745 | } catch (InterruptedException e) { 746 | Log.e(APPLICATION_TEST_TAG, "Error occurred while sleeping.", e); 747 | } 748 | } 749 | 750 | private void takeScreenShot(final int height) { 751 | activityRule.getActivity().runOnUiThread(new Runnable() { 752 | @Override 753 | public void run() { 754 | ViewHelpers.setupView(mainContent) 755 | .setExactHeightDp(height) 756 | .setExactWidthPx(mainContent.getWidth()) 757 | .layout(); 758 | } 759 | }); 760 | 761 | Screenshot.snap(mainContent) 762 | .record(); 763 | } 764 | 765 | private void takeScreenShot() { 766 | takeScreenShot(600); 767 | } 768 | } -------------------------------------------------------------------------------- /sample/src/androidTest/java/sundeepk/github/com/sample/TestRunner.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.test.runner.AndroidJUnitRunner; 5 | 6 | import com.facebook.testing.screenshot.ScreenshotRunner; 7 | 8 | public class TestRunner extends AndroidJUnitRunner { 9 | @Override 10 | public void onCreate(Bundle args) { 11 | ScreenshotRunner.onCreate(this, args); 12 | super.onCreate(args); 13 | } 14 | 15 | @Override 16 | public void finish(int resultCode, Bundle results) { 17 | ScreenshotRunner.onDestroy(); 18 | super.finish(resultCode, results); 19 | } 20 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/java/sundeepk/github/com/sample/CompactCalendarTab.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | import android.graphics.Color; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.Fragment; 9 | import android.support.v7.app.ActionBar; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.ViewTreeObserver; 16 | import android.widget.ArrayAdapter; 17 | import android.widget.Button; 18 | import android.widget.ListView; 19 | import android.widget.RelativeLayout; 20 | 21 | import com.github.sundeepk.compactcalendarview.CompactCalendarView; 22 | import com.github.sundeepk.compactcalendarview.domain.Event; 23 | 24 | import java.text.SimpleDateFormat; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.Calendar; 28 | import java.util.Date; 29 | import java.util.GregorianCalendar; 30 | import java.util.List; 31 | import java.util.Locale; 32 | import java.util.TimeZone; 33 | 34 | public class CompactCalendarTab extends Fragment { 35 | 36 | private static final String TAG = "MainActivity"; 37 | private Calendar currentCalender = Calendar.getInstance(Locale.getDefault()); 38 | private SimpleDateFormat dateFormatForDisplaying = new SimpleDateFormat("dd-M-yyyy hh:mm:ss a", Locale.getDefault()); 39 | private SimpleDateFormat dateFormatForMonth = new SimpleDateFormat("MMM - yyyy", Locale.getDefault()); 40 | private boolean shouldShow = false; 41 | private CompactCalendarView compactCalendarView; 42 | private ActionBar toolbar; 43 | 44 | @Override 45 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 46 | View mainTabView = inflater.inflate(R.layout.main_tab,container,false); 47 | 48 | final List mutableBookings = new ArrayList<>(); 49 | 50 | final ListView bookingsListView = mainTabView.findViewById(R.id.bookings_listview); 51 | final Button showPreviousMonthBut = mainTabView.findViewById(R.id.prev_button); 52 | final Button showNextMonthBut = mainTabView.findViewById(R.id.next_button); 53 | final Button slideCalendarBut = mainTabView.findViewById(R.id.slide_calendar); 54 | final Button showCalendarWithAnimationBut = mainTabView.findViewById(R.id.show_with_animation_calendar); 55 | final Button setLocaleBut = mainTabView.findViewById(R.id.set_locale); 56 | final Button removeAllEventsBut = mainTabView.findViewById(R.id.remove_all_events); 57 | 58 | final ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, mutableBookings); 59 | bookingsListView.setAdapter(adapter); 60 | compactCalendarView = mainTabView.findViewById(R.id.compactcalendar_view); 61 | 62 | // below allows you to configure color for the current day in the month 63 | // compactCalendarView.setCurrentDayBackgroundColor(getResources().getColor(R.color.black)); 64 | // below allows you to configure colors for the current day the user has selected 65 | // compactCalendarView.setCurrentSelectedDayBackgroundColor(getResources().getColor(R.color.dark_red)); 66 | compactCalendarView.setUseThreeLetterAbbreviation(false); 67 | compactCalendarView.setFirstDayOfWeek(Calendar.MONDAY); 68 | compactCalendarView.setIsRtl(false); 69 | compactCalendarView.displayOtherMonthDays(false); 70 | //compactCalendarView.setIsRtl(true); 71 | loadEvents(); 72 | loadEventsForYear(2017); 73 | compactCalendarView.invalidate(); 74 | 75 | logEventsByMonth(compactCalendarView); 76 | 77 | // below line will display Sunday as the first day of the week 78 | // compactCalendarView.setShouldShowMondayAsFirstDay(false); 79 | 80 | // disable scrolling calendar 81 | // compactCalendarView.shouldScrollMonth(false); 82 | 83 | // show days from other months as greyed out days 84 | // compactCalendarView.displayOtherMonthDays(true); 85 | 86 | // show Sunday as first day of month 87 | // compactCalendarView.setShouldShowMondayAsFirstDay(false); 88 | 89 | //set initial title 90 | toolbar = ((AppCompatActivity) getActivity()).getSupportActionBar(); 91 | toolbar.setTitle(dateFormatForMonth.format(compactCalendarView.getFirstDayOfCurrentMonth())); 92 | 93 | //set title on calendar scroll 94 | compactCalendarView.setListener(new CompactCalendarView.CompactCalendarViewListener() { 95 | @Override 96 | public void onDayClick(Date dateClicked) { 97 | toolbar.setTitle(dateFormatForMonth.format(dateClicked)); 98 | List bookingsFromMap = compactCalendarView.getEvents(dateClicked); 99 | Log.d(TAG, "inside onclick " + dateFormatForDisplaying.format(dateClicked)); 100 | if (bookingsFromMap != null) { 101 | Log.d(TAG, bookingsFromMap.toString()); 102 | mutableBookings.clear(); 103 | for (Event booking : bookingsFromMap) { 104 | mutableBookings.add((String) booking.getData()); 105 | } 106 | adapter.notifyDataSetChanged(); 107 | } 108 | 109 | } 110 | 111 | @Override 112 | public void onMonthScroll(Date firstDayOfNewMonth) { 113 | toolbar.setTitle(dateFormatForMonth.format(firstDayOfNewMonth)); 114 | } 115 | }); 116 | 117 | showPreviousMonthBut.setOnClickListener(new View.OnClickListener() { 118 | @Override 119 | public void onClick(View v) { 120 | compactCalendarView.scrollLeft(); 121 | } 122 | }); 123 | 124 | showNextMonthBut.setOnClickListener(new View.OnClickListener() { 125 | @Override 126 | public void onClick(View v) { 127 | compactCalendarView.scrollRight(); 128 | } 129 | }); 130 | 131 | final View.OnClickListener showCalendarOnClickLis = getCalendarShowLis(); 132 | slideCalendarBut.setOnClickListener(showCalendarOnClickLis); 133 | 134 | final View.OnClickListener exposeCalendarListener = getCalendarExposeLis(); 135 | showCalendarWithAnimationBut.setOnClickListener(exposeCalendarListener); 136 | 137 | compactCalendarView.setAnimationListener(new CompactCalendarView.CompactCalendarAnimationListener() { 138 | @Override 139 | public void onOpened() { 140 | } 141 | 142 | @Override 143 | public void onClosed() { 144 | } 145 | }); 146 | 147 | setLocaleBut.setOnClickListener(new View.OnClickListener() { 148 | @Override 149 | public void onClick(View v) { 150 | Locale locale = Locale.FRANCE; 151 | dateFormatForDisplaying = new SimpleDateFormat("dd-M-yyyy hh:mm:ss a", locale); 152 | TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); 153 | dateFormatForDisplaying.setTimeZone(timeZone); 154 | dateFormatForMonth.setTimeZone(timeZone); 155 | compactCalendarView.setLocale(timeZone, locale); 156 | compactCalendarView.setUseThreeLetterAbbreviation(false); 157 | loadEvents(); 158 | loadEventsForYear(2017); 159 | logEventsByMonth(compactCalendarView); 160 | 161 | } 162 | }); 163 | 164 | removeAllEventsBut.setOnClickListener(new View.OnClickListener() { 165 | @Override 166 | public void onClick(View v) { 167 | compactCalendarView.removeAllEvents(); 168 | } 169 | }); 170 | 171 | 172 | // uncomment below to show indicators above small indicator events 173 | // compactCalendarView.shouldDrawIndicatorsBelowSelectedDays(true); 174 | 175 | // uncomment below to open onCreate 176 | //openCalendarOnCreate(v); 177 | 178 | return mainTabView; 179 | } 180 | 181 | @NonNull 182 | private View.OnClickListener getCalendarShowLis() { 183 | return new View.OnClickListener() { 184 | @Override 185 | public void onClick(View v) { 186 | if (!compactCalendarView.isAnimating()) { 187 | if (shouldShow) { 188 | compactCalendarView.showCalendar(); 189 | } else { 190 | compactCalendarView.hideCalendar(); 191 | } 192 | shouldShow = !shouldShow; 193 | } 194 | } 195 | }; 196 | } 197 | 198 | @NonNull 199 | private View.OnClickListener getCalendarExposeLis() { 200 | return new View.OnClickListener() { 201 | @Override 202 | public void onClick(View v) { 203 | if (!compactCalendarView.isAnimating()) { 204 | if (shouldShow) { 205 | compactCalendarView.showCalendarWithAnimation(); 206 | } else { 207 | compactCalendarView.hideCalendarWithAnimation(); 208 | } 209 | shouldShow = !shouldShow; 210 | } 211 | } 212 | }; 213 | } 214 | 215 | private void openCalendarOnCreate(View v) { 216 | final RelativeLayout layout = v.findViewById(R.id.main_content); 217 | ViewTreeObserver vto = layout.getViewTreeObserver(); 218 | vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 219 | @Override 220 | public void onGlobalLayout() { 221 | if (Build.VERSION.SDK_INT < 16) { 222 | layout.getViewTreeObserver().removeGlobalOnLayoutListener(this); 223 | } else { 224 | layout.getViewTreeObserver().removeOnGlobalLayoutListener(this); 225 | } 226 | compactCalendarView.showCalendarWithAnimation(); 227 | } 228 | }); 229 | } 230 | 231 | @Override 232 | public void onResume() { 233 | super.onResume(); 234 | toolbar.setTitle(dateFormatForMonth.format(compactCalendarView.getFirstDayOfCurrentMonth())); 235 | // Set to current day on resume to set calendar to latest day 236 | // toolbar.setTitle(dateFormatForMonth.format(new Date())); 237 | } 238 | 239 | private void loadEvents() { 240 | addEvents(-1, -1); 241 | addEvents(Calendar.DECEMBER, -1); 242 | addEvents(Calendar.AUGUST, -1); 243 | } 244 | 245 | private void loadEventsForYear(int year) { 246 | addEvents(Calendar.DECEMBER, year); 247 | addEvents(Calendar.AUGUST, year); 248 | } 249 | 250 | private void logEventsByMonth(CompactCalendarView compactCalendarView) { 251 | currentCalender.setTime(new Date()); 252 | currentCalender.set(Calendar.DAY_OF_MONTH, 1); 253 | currentCalender.set(Calendar.MONTH, Calendar.AUGUST); 254 | List dates = new ArrayList<>(); 255 | for (Event e : compactCalendarView.getEventsForMonth(new Date())) { 256 | dates.add(dateFormatForDisplaying.format(e.getTimeInMillis())); 257 | } 258 | Log.d(TAG, "Events for Aug with simple date formatter: " + dates); 259 | Log.d(TAG, "Events for Aug month using default local and timezone: " + compactCalendarView.getEventsForMonth(currentCalender.getTime())); 260 | } 261 | 262 | private void addEvents(int month, int year) { 263 | currentCalender.setTime(new Date()); 264 | currentCalender.set(Calendar.DAY_OF_MONTH, 1); 265 | Date firstDayOfMonth = currentCalender.getTime(); 266 | for (int i = 0; i < 6; i++) { 267 | currentCalender.setTime(firstDayOfMonth); 268 | if (month > -1) { 269 | currentCalender.set(Calendar.MONTH, month); 270 | } 271 | if (year > -1) { 272 | currentCalender.set(Calendar.ERA, GregorianCalendar.AD); 273 | currentCalender.set(Calendar.YEAR, year); 274 | } 275 | currentCalender.add(Calendar.DATE, i); 276 | setToMidnight(currentCalender); 277 | long timeInMillis = currentCalender.getTimeInMillis(); 278 | 279 | List events = getEvents(timeInMillis, i); 280 | 281 | compactCalendarView.addEvents(events); 282 | } 283 | } 284 | 285 | private List getEvents(long timeInMillis, int day) { 286 | if (day < 2) { 287 | return Arrays.asList(new Event(Color.argb(255, 169, 68, 65), timeInMillis, "Event at " + new Date(timeInMillis))); 288 | } else if ( day > 2 && day <= 4) { 289 | return Arrays.asList( 290 | new Event(Color.argb(255, 169, 68, 65), timeInMillis, "Event at " + new Date(timeInMillis)), 291 | new Event(Color.argb(255, 100, 68, 65), timeInMillis, "Event 2 at " + new Date(timeInMillis))); 292 | } else { 293 | return Arrays.asList( 294 | new Event(Color.argb(255, 169, 68, 65), timeInMillis, "Event at " + new Date(timeInMillis) ), 295 | new Event(Color.argb(255, 100, 68, 65), timeInMillis, "Event 2 at " + new Date(timeInMillis)), 296 | new Event(Color.argb(255, 70, 68, 65), timeInMillis, "Event 3 at " + new Date(timeInMillis))); 297 | } 298 | } 299 | 300 | private void setToMidnight(Calendar calendar) { 301 | calendar.set(Calendar.HOUR_OF_DAY, 0); 302 | calendar.set(Calendar.MINUTE, 0); 303 | calendar.set(Calendar.SECOND, 0); 304 | calendar.set(Calendar.MILLISECOND, 0); 305 | } 306 | } -------------------------------------------------------------------------------- /sample/src/main/java/sundeepk/github/com/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.view.ViewPager; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | private Toolbar toolbar; 14 | private ViewPager pager; 15 | private ViewPagerAdapter adapter; 16 | private SlidingTabLayout tabs; 17 | private CharSequence titles[]= {"Home","Events"}; 18 | private int numberOfTabs = 2; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | toolbar = (Toolbar) findViewById(R.id.tool_bar); 26 | setSupportActionBar(toolbar); 27 | getSupportActionBar().setDisplayShowTitleEnabled(true); 28 | 29 | // Creating The ViewPagerAdapter and Passing Fragment Manager, Titles fot the Tabs and Number Of Tabs. 30 | adapter = new ViewPagerAdapter(getSupportFragmentManager(), titles, numberOfTabs); 31 | 32 | // Assigning ViewPager View and setting the adapter 33 | pager = (ViewPager) findViewById(R.id.pager); 34 | pager.setAdapter(adapter); 35 | 36 | // Assiging the Sliding Tab Layout View 37 | tabs = (SlidingTabLayout) findViewById(R.id.tabs); 38 | tabs.setDistributeEvenly(true); // To make the Tabs Fixed set this true, This makes the tabs Space Evenly in Available width 39 | 40 | // Setting Custom Color for the Scroll bar indicator of the Tab View 41 | tabs.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() { 42 | @Override 43 | public int getIndicatorColor(int position) { 44 | return getResources().getColor(R.color.black); 45 | } 46 | }); 47 | 48 | // Setting the ViewPager For the SlidingTabsLayout 49 | tabs.setViewPager(pager); 50 | } 51 | 52 | @Override 53 | public boolean onCreateOptionsMenu(Menu menu) { 54 | // Inflate the menu; this adds items to the action bar if it is present. 55 | getMenuInflater().inflate(R.menu.menu_main, menu); 56 | return true; 57 | } 58 | 59 | @Override 60 | public boolean onOptionsItemSelected(MenuItem item) { 61 | // Handle action bar item clicks here. The action bar will 62 | // automatically handle clicks on the Home/Up button, so long 63 | // as you specify a parent activity in AndroidManifest.xml. 64 | int id = item.getItemId(); 65 | 66 | //noinspection SimplifiableIfStatement 67 | if (id == R.id.action_settings) { 68 | return true; 69 | } 70 | 71 | return super.onOptionsItemSelected(item); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /sample/src/main/java/sundeepk/github/com/sample/SlidingTabLayout.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | /* 3 | * Copyright 2014 Google Inc. All rights reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import android.content.Context; 19 | import android.graphics.Typeface; 20 | import android.support.v4.view.PagerAdapter; 21 | import android.support.v4.view.ViewPager; 22 | import android.util.AttributeSet; 23 | import android.util.SparseArray; 24 | import android.util.TypedValue; 25 | import android.view.Gravity; 26 | import android.view.LayoutInflater; 27 | import android.view.View; 28 | import android.view.ViewGroup; 29 | import android.widget.HorizontalScrollView; 30 | import android.widget.LinearLayout; 31 | import android.widget.TextView; 32 | 33 | /** 34 | * To be used with ViewPager to provide a tab indicator component which give constant feedback as to 35 | * the user's scroll progress. 36 | *

37 | * To use the component, simply add it to your view hierarchy. Then in your 38 | * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call 39 | * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. 40 | *

41 | * The colors can be customized in two ways. The first and simplest is to provide an array of colors 42 | * via {@link #setSelectedIndicatorColors(int...)}. The 43 | * alternative is via the {@link TabColorizer} interface which provides you complete control over 44 | * which color is used for any individual position. 45 | *

46 | * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, 47 | * providing the layout ID of your custom layout. 48 | */ 49 | public class SlidingTabLayout extends HorizontalScrollView { 50 | /** 51 | * Allows complete control over the colors drawn in the tab layout. Set with 52 | * {@link #setCustomTabColorizer(TabColorizer)}. 53 | */ 54 | public interface TabColorizer { 55 | 56 | /** 57 | * @return return the color of the indicator used when {@code position} is selected. 58 | */ 59 | int getIndicatorColor(int position); 60 | 61 | } 62 | 63 | private static final int TITLE_OFFSET_DIPS = 24; 64 | private static final int TAB_VIEW_PADDING_DIPS = 16; 65 | private static final int TAB_VIEW_TEXT_SIZE_SP = 12; 66 | 67 | private int mTitleOffset; 68 | 69 | private int mTabViewLayoutId; 70 | private int mTabViewTextViewId; 71 | private boolean mDistributeEvenly; 72 | 73 | private ViewPager mViewPager; 74 | private SparseArray mContentDescriptions = new SparseArray(); 75 | private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; 76 | 77 | private final SlidingTabStrip mTabStrip; 78 | 79 | public SlidingTabLayout(Context context) { 80 | this(context, null); 81 | } 82 | 83 | public SlidingTabLayout(Context context, AttributeSet attrs) { 84 | this(context, attrs, 0); 85 | } 86 | 87 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { 88 | super(context, attrs, defStyle); 89 | 90 | // Disable the Scroll Bar 91 | setHorizontalScrollBarEnabled(false); 92 | // Make sure that the Tab Strips fills this View 93 | setFillViewport(true); 94 | 95 | mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); 96 | 97 | mTabStrip = new SlidingTabStrip(context); 98 | addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 99 | } 100 | 101 | /** 102 | * Set the custom {@link TabColorizer} to be used. 103 | *

104 | * If you only require simple custmisation then you can use 105 | * {@link #setSelectedIndicatorColors(int...)} to achieve 106 | * similar effects. 107 | */ 108 | public void setCustomTabColorizer(TabColorizer tabColorizer) { 109 | mTabStrip.setCustomTabColorizer(tabColorizer); 110 | } 111 | 112 | public void setDistributeEvenly(boolean distributeEvenly) { 113 | mDistributeEvenly = distributeEvenly; 114 | } 115 | 116 | /** 117 | * Sets the colors to be used for indicating the selected tab. These colors are treated as a 118 | * circular array. Providing one color will mean that all tabs are indicated with the same color. 119 | */ 120 | public void setSelectedIndicatorColors(int... colors) { 121 | mTabStrip.setSelectedIndicatorColors(colors); 122 | } 123 | 124 | /** 125 | * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are 126 | * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so 127 | * that the layout can update it's scroll position correctly. 128 | * 129 | * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) 130 | */ 131 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { 132 | mViewPagerPageChangeListener = listener; 133 | } 134 | 135 | /** 136 | * Set the custom layout to be inflated for the tab views. 137 | * 138 | * @param layoutResId Layout id to be inflated 139 | * @param textViewId id of the {@link TextView} in the inflated view 140 | */ 141 | public void setCustomTabView(int layoutResId, int textViewId) { 142 | mTabViewLayoutId = layoutResId; 143 | mTabViewTextViewId = textViewId; 144 | } 145 | 146 | /** 147 | * Sets the associated view pager. Note that the assumption here is that the pager content 148 | * (number of tabs and tab titles) does not change after this call has been made. 149 | */ 150 | public void setViewPager(ViewPager viewPager) { 151 | mTabStrip.removeAllViews(); 152 | 153 | mViewPager = viewPager; 154 | if (viewPager != null) { 155 | viewPager.setOnPageChangeListener(new InternalViewPagerListener()); 156 | populateTabStrip(); 157 | } 158 | } 159 | 160 | /** 161 | * Create a default view to be used for tabs. This is called if a custom tab view is not set via 162 | * {@link #setCustomTabView(int, int)}. 163 | */ 164 | protected TextView createDefaultTabView(Context context) { 165 | TextView textView = new TextView(context); 166 | textView.setGravity(Gravity.CENTER); 167 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); 168 | textView.setTypeface(Typeface.DEFAULT_BOLD); 169 | textView.setLayoutParams(new LinearLayout.LayoutParams( 170 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 171 | 172 | TypedValue outValue = new TypedValue(); 173 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, 174 | outValue, true); 175 | textView.setBackgroundResource(outValue.resourceId); 176 | textView.setAllCaps(true); 177 | 178 | int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); 179 | textView.setPadding(padding, padding, padding, padding); 180 | 181 | return textView; 182 | } 183 | 184 | private void populateTabStrip() { 185 | final PagerAdapter adapter = mViewPager.getAdapter(); 186 | final OnClickListener tabClickListener = new TabClickListener(); 187 | 188 | for (int i = 0; i < adapter.getCount(); i++) { 189 | View tabView = null; 190 | TextView tabTitleView = null; 191 | 192 | if (mTabViewLayoutId != 0) { 193 | // If there is a custom tab view layout id set, try and inflate it 194 | tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, 195 | false); 196 | tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); 197 | } 198 | 199 | if (tabView == null) { 200 | tabView = createDefaultTabView(getContext()); 201 | } 202 | 203 | if (tabTitleView == null && TextView.class.isInstance(tabView)) { 204 | tabTitleView = (TextView) tabView; 205 | } 206 | 207 | if (mDistributeEvenly) { 208 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams(); 209 | lp.width = 0; 210 | lp.weight = 1; 211 | } 212 | 213 | tabTitleView.setText(adapter.getPageTitle(i)); 214 | tabView.setOnClickListener(tabClickListener); 215 | String desc = mContentDescriptions.get(i, null); 216 | if (desc != null) { 217 | tabView.setContentDescription(desc); 218 | } 219 | 220 | mTabStrip.addView(tabView); 221 | if (i == mViewPager.getCurrentItem()) { 222 | tabView.setSelected(true); 223 | } 224 | } 225 | } 226 | 227 | public void setContentDescription(int i, String desc) { 228 | mContentDescriptions.put(i, desc); 229 | } 230 | 231 | @Override 232 | protected void onAttachedToWindow() { 233 | super.onAttachedToWindow(); 234 | 235 | if (mViewPager != null) { 236 | scrollToTab(mViewPager.getCurrentItem(), 0); 237 | } 238 | } 239 | 240 | private void scrollToTab(int tabIndex, int positionOffset) { 241 | final int tabStripChildCount = mTabStrip.getChildCount(); 242 | if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { 243 | return; 244 | } 245 | 246 | View selectedChild = mTabStrip.getChildAt(tabIndex); 247 | if (selectedChild != null) { 248 | int targetScrollX = selectedChild.getLeft() + positionOffset; 249 | 250 | if (tabIndex > 0 || positionOffset > 0) { 251 | // If we're not at the first child and are mid-scroll, make sure we obey the offset 252 | targetScrollX -= mTitleOffset; 253 | } 254 | 255 | scrollTo(targetScrollX, 0); 256 | } 257 | } 258 | 259 | private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { 260 | private int mScrollState; 261 | 262 | @Override 263 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 264 | int tabStripChildCount = mTabStrip.getChildCount(); 265 | if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { 266 | return; 267 | } 268 | 269 | mTabStrip.onViewPagerPageChanged(position, positionOffset); 270 | 271 | View selectedTitle = mTabStrip.getChildAt(position); 272 | int extraOffset = (selectedTitle != null) 273 | ? (int) (positionOffset * selectedTitle.getWidth()) 274 | : 0; 275 | scrollToTab(position, extraOffset); 276 | 277 | if (mViewPagerPageChangeListener != null) { 278 | mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, 279 | positionOffsetPixels); 280 | } 281 | } 282 | 283 | @Override 284 | public void onPageScrollStateChanged(int state) { 285 | mScrollState = state; 286 | 287 | if (mViewPagerPageChangeListener != null) { 288 | mViewPagerPageChangeListener.onPageScrollStateChanged(state); 289 | } 290 | } 291 | 292 | @Override 293 | public void onPageSelected(int position) { 294 | if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 295 | mTabStrip.onViewPagerPageChanged(position, 0f); 296 | scrollToTab(position, 0); 297 | } 298 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 299 | mTabStrip.getChildAt(i).setSelected(position == i); 300 | } 301 | if (mViewPagerPageChangeListener != null) { 302 | mViewPagerPageChangeListener.onPageSelected(position); 303 | } 304 | } 305 | 306 | } 307 | 308 | private class TabClickListener implements OnClickListener { 309 | @Override 310 | public void onClick(View v) { 311 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 312 | if (v == mTabStrip.getChildAt(i)) { 313 | mViewPager.setCurrentItem(i); 314 | return; 315 | } 316 | } 317 | } 318 | } 319 | 320 | } -------------------------------------------------------------------------------- /sample/src/main/java/sundeepk/github/com/sample/SlidingTabStrip.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | /* 4 | * Copyright 2014 Google Inc. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.Paint; 23 | import android.util.AttributeSet; 24 | import android.util.TypedValue; 25 | import android.view.View; 26 | import android.widget.LinearLayout; 27 | 28 | class SlidingTabStrip extends LinearLayout { 29 | 30 | private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0; 31 | private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; 32 | private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3; 33 | private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; 34 | 35 | private final int mBottomBorderThickness; 36 | private final Paint mBottomBorderPaint; 37 | 38 | private final int mSelectedIndicatorThickness; 39 | private final Paint mSelectedIndicatorPaint; 40 | 41 | private int mSelectedPosition; 42 | private float mSelectionOffset; 43 | 44 | private SlidingTabLayout.TabColorizer mCustomTabColorizer; 45 | private final SimpleTabColorizer mDefaultTabColorizer; 46 | 47 | SlidingTabStrip(Context context) { 48 | this(context, null); 49 | } 50 | 51 | SlidingTabStrip(Context context, AttributeSet attrs) { 52 | super(context, attrs); 53 | setWillNotDraw(false); 54 | 55 | final float density = getResources().getDisplayMetrics().density; 56 | 57 | TypedValue outValue = new TypedValue(); 58 | context.getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true); 59 | final int themeForegroundColor = outValue.data; 60 | 61 | int defaultBottomBorderColor = setColorAlpha(themeForegroundColor, 62 | DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); 63 | 64 | mDefaultTabColorizer = new SimpleTabColorizer(); 65 | mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); 66 | 67 | mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); 68 | mBottomBorderPaint = new Paint(); 69 | mBottomBorderPaint.setColor(defaultBottomBorderColor); 70 | 71 | mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); 72 | mSelectedIndicatorPaint = new Paint(); 73 | } 74 | 75 | void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { 76 | mCustomTabColorizer = customTabColorizer; 77 | invalidate(); 78 | } 79 | 80 | void setSelectedIndicatorColors(int... colors) { 81 | // Make sure that the custom colorizer is removed 82 | mCustomTabColorizer = null; 83 | mDefaultTabColorizer.setIndicatorColors(colors); 84 | invalidate(); 85 | } 86 | 87 | void onViewPagerPageChanged(int position, float positionOffset) { 88 | mSelectedPosition = position; 89 | mSelectionOffset = positionOffset; 90 | invalidate(); 91 | } 92 | 93 | @Override 94 | protected void onDraw(Canvas canvas) { 95 | final int height = getHeight(); 96 | final int childCount = getChildCount(); 97 | final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null 98 | ? mCustomTabColorizer 99 | : mDefaultTabColorizer; 100 | 101 | // Thick colored underline below the current selection 102 | if (childCount > 0) { 103 | View selectedTitle = getChildAt(mSelectedPosition); 104 | int left = selectedTitle.getLeft(); 105 | int right = selectedTitle.getRight(); 106 | int color = tabColorizer.getIndicatorColor(mSelectedPosition); 107 | 108 | if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { 109 | int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); 110 | if (color != nextColor) { 111 | color = blendColors(nextColor, color, mSelectionOffset); 112 | } 113 | 114 | // Draw the selection partway between the tabs 115 | View nextTitle = getChildAt(mSelectedPosition + 1); 116 | left = (int) (mSelectionOffset * nextTitle.getLeft() + 117 | (1.0f - mSelectionOffset) * left); 118 | right = (int) (mSelectionOffset * nextTitle.getRight() + 119 | (1.0f - mSelectionOffset) * right); 120 | } 121 | 122 | mSelectedIndicatorPaint.setColor(color); 123 | 124 | canvas.drawRect(left, height - mSelectedIndicatorThickness, right, 125 | height, mSelectedIndicatorPaint); 126 | } 127 | 128 | // Thin underline along the entire bottom edge 129 | canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); 130 | } 131 | 132 | /** 133 | * Set the alpha value of the {@code color} to be the given {@code alpha} value. 134 | */ 135 | private static int setColorAlpha(int color, byte alpha) { 136 | return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); 137 | } 138 | 139 | /** 140 | * Blend {@code color1} and {@code color2} using the given ratio. 141 | * 142 | * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, 143 | * 0.0 will return {@code color2}. 144 | */ 145 | private static int blendColors(int color1, int color2, float ratio) { 146 | final float inverseRation = 1f - ratio; 147 | float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); 148 | float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); 149 | float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); 150 | return Color.rgb((int) r, (int) g, (int) b); 151 | } 152 | 153 | private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { 154 | private int[] mIndicatorColors; 155 | 156 | @Override 157 | public final int getIndicatorColor(int position) { 158 | return mIndicatorColors[position % mIndicatorColors.length]; 159 | } 160 | 161 | void setIndicatorColors(int... colors) { 162 | mIndicatorColors = colors; 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /sample/src/main/java/sundeepk/github/com/sample/Tab2.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | public class Tab2 extends Fragment { 11 | @Override 12 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 13 | View v = inflater.inflate(R.layout.tab_2,container,false); 14 | return v; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/src/main/java/sundeepk/github/com/sample/ViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package sundeepk.github.com.sample; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | 7 | public class ViewPagerAdapter extends FragmentStatePagerAdapter { 8 | 9 | CharSequence titles[]; 10 | int numbOfTabs; 11 | 12 | public ViewPagerAdapter(FragmentManager fm, CharSequence titles[], int mNumbOfTabs) { 13 | super(fm); 14 | this.titles = titles; 15 | this.numbOfTabs = mNumbOfTabs; 16 | } 17 | 18 | @Override 19 | public Fragment getItem(int position) { 20 | if (position == 0) { 21 | CompactCalendarTab compactCalendarTab = new CompactCalendarTab(); 22 | return compactCalendarTab; 23 | } else { 24 | Tab2 tab2 = new Tab2(); 25 | return tab2; 26 | } 27 | } 28 | 29 | @Override 30 | public CharSequence getPageTitle(int position) { 31 | return titles[position]; 32 | } 33 | 34 | @Override 35 | public int getCount() { 36 | return numbOfTabs; 37 | } 38 | } -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 23 | 24 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/main_tab.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 26 | 27 |