diff --git a/README.md b/README.md index abdd676f4..33d3b718c 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,13 @@ -[Join our slack](https://join.slack.com/t/androidweekview/shared_invite/enQtMzEyMDE3NzU3NTM3LWQyZGRhNjRlMTUzNzNlNjNlM2M0OTMyMDhjMzE1NDMzOGQzYzhjNzI2YjZhZWM3MzJiY2I1YmY2NGEwOTlkNTY) +A fork of this repository: +https://github.com/Quachero/Android-Week-View +Which is a fork of this: +https://github.com/Quivr/Android-Week-View +Which is a fork of this: +https://github.com/alamkanak/Android-Week-View +All because all of those aren't functional anymore... -Android Week View -================= +I've added so many things compared to them. You can read about them in a pull request I've made: +https://github.com/Quivr/Android-Week-View/pull/97 +https://github.com/Quachero/Android-Week-View/pull/1 -**Android Week View** is an android library to display calendars (week view or day view) within the app. It supports custom styling. - -[Why this fork? (features + community & contributing)](https://github.com/Quivr/Android-Week-View/issues/45) - -![](images/screen-shot.png) - -Features ------------- - -* Week view calendar -* Day view calendar -* Custom styling -* Vertical scrolling and zooming -* Infinite horizontal scrolling -* Possibility to set min and max date -* Possibility to set range of visible hours -* All day events at the top -* Live preview of custom styling in xml preview window - -Who uses it ---------------- - -* [Quivr](https://quivr.be/en/) -* [Series Addict](https://play.google.com/store/apps/details?id=com.alamkanak.seriesaddict) -* [Unicaen Timetable](https://play.google.com/store/apps/details?id=fr.skyost.timetable) -* Using the library? Just make an issue - -Getting Started ---------------- - -See the [wiki](https://github.com/Quivr/Android-Week-View/wiki) - -Sample ----------- - -There is also a [sample app](https://github.com/quivr/Android-Week-View/tree/master/sample) to get you started. - - -License ----------- - - Copyright 2014 Raquib-ul-Alam - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/build.gradle b/build.gradle index bd1e1fd48..5b646abf7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.21' repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.3.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -18,6 +21,7 @@ allprojects { group = GROUP repositories { + google() jcenter() } } diff --git a/gradle.properties b/gradle.properties index 998877f4f..9adbf9d4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,6 @@ POM_LICENCE_NAME=The Apache Software License, Version 2.0 POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt POM_LICENCE_DIST=repo POM_DEVELOPER_ID=quivr -POM_DEVELOPER_NAME=Quivr \ No newline at end of file +POM_DEVELOPER_NAME=Quivr +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 66b2b75d1..2cb0ce4da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Aug 06 18:02:35 CEST 2017 +#Fri Feb 08 08:07:15 IST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/library/build.gradle b/library/build.gradle index a9291dd32..191f15056 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,16 +1,16 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' repositories { mavenCentral() } android { - compileSdkVersion 25 - buildToolsVersion '25.0.2' + compileSdkVersion 28 defaultConfig { - minSdkVersion 9 - targetSdkVersion 25 + minSdkVersion 14 + targetSdkVersion 28 } } @@ -18,8 +18,9 @@ configurations { javadocDeps } dependencies { - compile 'com.android.support:appcompat-v7:25.1.0' - javadocDeps 'com.android.support:appcompat-v7:25.1.0' + implementation 'androidx.appcompat:appcompat:1.0.2' + javadocDeps 'androidx.appcompat:appcompat:1.0.2' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } -apply from: 'gradle-mvn-push.gradle' \ No newline at end of file +apply from: 'gradle-mvn-push.gradle' diff --git a/library/gradle-mvn-push.gradle b/library/gradle-mvn-push.gradle index 167f0388d..f59176b26 100644 --- a/library/gradle-mvn-push.gradle +++ b/library/gradle-mvn-push.gradle @@ -18,7 +18,7 @@ apply plugin: 'maven' apply plugin: 'signing' def isReleaseBuild() { - return VERSION_NAME.contains("SNAPSHOT") == false + return !VERSION_NAME.contains("SNAPSHOT") } def getReleaseRepositoryUrl() { @@ -112,4 +112,4 @@ afterEvaluate { project -> archives androidSourcesJar // archives androidJavadocsJar } -} \ No newline at end of file +} diff --git a/library/project.properties b/library/project.properties index 7568f54a0..efe3b01bc 100644 --- a/library/project.properties +++ b/library/project.properties @@ -1 +1,2 @@ -android.library=true \ No newline at end of file +# suppress inspection "UnusedProperty" for whole file +android.library=true diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index e8432516e..4dab7b2f0 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1 +1 @@ - + diff --git a/library/src/main/java/com/alamkanak/weekview/DateTimeInterpreter.java b/library/src/main/java/com/alamkanak/weekview/DateTimeInterpreter.java deleted file mode 100644 index 942c8a653..000000000 --- a/library/src/main/java/com/alamkanak/weekview/DateTimeInterpreter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.alamkanak.weekview; - -import java.util.Calendar; - -/** - * Created by Raquib on 1/6/2015. - */ -public interface DateTimeInterpreter { - public String interpretDate(Calendar date); - - public String interpretTime(int hour, int minutes); -} diff --git a/library/src/main/java/com/alamkanak/weekview/DateTimeInterpreter.kt b/library/src/main/java/com/alamkanak/weekview/DateTimeInterpreter.kt new file mode 100644 index 000000000..c805d8ac3 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/DateTimeInterpreter.kt @@ -0,0 +1,11 @@ +package com.alamkanak.weekview + +import java.util.* + +interface DateTimeInterpreter { + fun getFormattedWeekDayTitle(date: Calendar): String + + fun getFormattedTimeOfDay(hour: Int, minutes: Int): String + + +} diff --git a/library/src/main/java/com/alamkanak/weekview/DrawPerformanceTester.kt b/library/src/main/java/com/alamkanak/weekview/DrawPerformanceTester.kt new file mode 100644 index 000000000..1a8cc4b08 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/DrawPerformanceTester.kt @@ -0,0 +1,27 @@ +package com.alamkanak.weekview + +import android.util.Log + +class DrawPerformanceTester(val measureDrawTime: Boolean = true) { + var drawSamplesCount = 0L + var drawTotalTime = 0L + + private var startTime: Long = 0L + + fun startMeasure() { + if (!measureDrawTime) + return + startTime = System.currentTimeMillis() + } + + fun endMeasure() { + if (!measureDrawTime) + return + val endTime = System.currentTimeMillis() + val totalTime = endTime - startTime + ++drawSamplesCount + drawTotalTime += totalTime + val drawAverageTime = drawTotalTime.toFloat() / drawSamplesCount.toFloat() + Log.d("AppLog", "currentTime:$totalTime average:$drawAverageTime") + } +} diff --git a/library/src/main/java/com/alamkanak/weekview/MonthLoader.java b/library/src/main/java/com/alamkanak/weekview/MonthLoader.java deleted file mode 100644 index cec26bd13..000000000 --- a/library/src/main/java/com/alamkanak/weekview/MonthLoader.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.alamkanak.weekview; - -import java.util.Calendar; -import java.util.List; - -public class MonthLoader implements WeekViewLoader { - - private MonthChangeListener mOnMonthChangeListener; - - public MonthLoader(MonthChangeListener listener) { - this.mOnMonthChangeListener = listener; - } - - @Override - public double toWeekViewPeriodIndex(Calendar instance) { - return instance.get(Calendar.YEAR) * 12 + instance.get(Calendar.MONTH) + (instance.get(Calendar.DAY_OF_MONTH) - 1) / 30.0; - } - - @Override - public List onLoad(int periodIndex) { - return mOnMonthChangeListener.onMonthChange(periodIndex / 12, periodIndex % 12 + 1); - } - - public MonthChangeListener getOnMonthChangeListener() { - return mOnMonthChangeListener; - } - - public void setOnMonthChangeListener(MonthChangeListener onMonthChangeListener) { - this.mOnMonthChangeListener = onMonthChangeListener; - } - - public interface MonthChangeListener { - /** - *

Very important interface, it's the base to load events in the calendar. - * This method is called three times: once to load the previous month, once to load the next month and once to load the current month.

- * That's why you can have three times the same event at the same place if you mess up with the configuration - * - * @param newYear : year of the events required by the view. - * @param newMonth :

month of the events required by the view

1 based (not like JAVA API) : January = 1 and December = 12. - * @return a list of the events happening during the specified month. - */ - List onMonthChange(int newYear, int newMonth); - } -} diff --git a/library/src/main/java/com/alamkanak/weekview/MonthLoader.kt b/library/src/main/java/com/alamkanak/weekview/MonthLoader.kt new file mode 100644 index 000000000..adf9b6139 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/MonthLoader.kt @@ -0,0 +1,30 @@ +package com.alamkanak.weekview + +import java.util.* + +class MonthLoader(var onMonthChangeListener: MonthChangeListener?) : WeekViewLoader { + + override fun toWeekViewPeriodIndex(instance: Calendar): Double { + return (instance.get(Calendar.YEAR) * 12).toDouble() + instance.get(Calendar.MONTH).toDouble() + (instance.get(Calendar.DAY_OF_MONTH) - 1) / 30.0 + } + + override fun onLoad(periodIndex: Int): MutableList? { + return onMonthChangeListener!!.onMonthChange(periodIndex / 12, periodIndex % 12 + 1) + } + + interface MonthChangeListener { + /** + * + * Very important interface, it's the base to load events in the calendar. + * This method is called three times: once to load the previous month, once to load the next month and once to load the current month. + * **That's why you can have three times the same event at the same place if you mess up with the configuration** + * + * @param newYear : year of the events required by the view. + * @param newMonth : + * + *month of the events required by the view **1 based (not like JAVA API) : January = 1 and December = 12**. + * @return a list of the events happening **during the specified month**. + */ + fun onMonthChange(newYear: Int, newMonth: Int): MutableList? + } +} diff --git a/library/src/main/java/com/alamkanak/weekview/PrefetchingWeekViewLoader.kt b/library/src/main/java/com/alamkanak/weekview/PrefetchingWeekViewLoader.kt new file mode 100644 index 000000000..3bafb444d --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/PrefetchingWeekViewLoader.kt @@ -0,0 +1,45 @@ +package com.alamkanak.weekview + +import androidx.annotation.IntRange +import java.util.* + +/** + *

PrefetchingWeekViewLoader

+ * This class provides prefetching data loading behaviour. + * By setting a specific period of N, data is retrieved for the current period, + * the next N periods and the previous N periods. + */ +/** + * @param weekViewLoader An instance of the WeekViewLoader class + * @param prefetchingPeriod The amount of periods to be fetched before and after the + * current period. Must be 1 or greater. + */ +class PrefetchingWeekViewLoader(val weekViewLoader: WeekViewLoader, @IntRange(from = 1L) val prefetchingPeriod: Int = 1) : WeekViewLoader { + + init { + if (prefetchingPeriod < 1) + throw IllegalArgumentException("Must specify prefetching period of at least 1!") + } + + override fun onLoad(periodIndex: Int): MutableList? { + // fetch the current period + var loadedEvents = weekViewLoader.onLoad(periodIndex) + val events = ArrayList() + if (loadedEvents != null) + events.addAll(loadedEvents) + // fetch periods before/after + for (i in 1..this.prefetchingPeriod) { + loadedEvents = weekViewLoader.onLoad(periodIndex - i) + if (loadedEvents != null) + events.addAll(loadedEvents) + loadedEvents = weekViewLoader.onLoad(periodIndex + i) + if (loadedEvents != null) + events.addAll(loadedEvents) + } + // return list of all events together + return events + } + + override fun toWeekViewPeriodIndex(instance: Calendar) = weekViewLoader.toWeekViewPeriodIndex(instance) + +} diff --git a/library/src/main/java/com/alamkanak/weekview/SimpleDate.kt b/library/src/main/java/com/alamkanak/weekview/SimpleDate.kt new file mode 100644 index 000000000..7571e9c34 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/SimpleDate.kt @@ -0,0 +1,9 @@ +package com.alamkanak.weekview + +import java.util.* + +data class SimpleDate(val year: Int, val month: Int, val dayOfMonth: Int) { + override fun toString(): String = "$year-$month-$dayOfMonth" + + constructor(cal: Calendar) : this(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)) +} diff --git a/library/src/main/java/com/alamkanak/weekview/TextColorPicker.java b/library/src/main/java/com/alamkanak/weekview/TextColorPicker.java deleted file mode 100644 index 9aa48feec..000000000 --- a/library/src/main/java/com/alamkanak/weekview/TextColorPicker.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.alamkanak.weekview; - -import android.support.annotation.ColorInt; - -public interface TextColorPicker { - - @ColorInt - int getTextColor(WeekViewEvent event); - -} diff --git a/library/src/main/java/com/alamkanak/weekview/TextColorPicker.kt b/library/src/main/java/com/alamkanak/weekview/TextColorPicker.kt new file mode 100644 index 000000000..bc1d7b6f0 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/TextColorPicker.kt @@ -0,0 +1,10 @@ +package com.alamkanak.weekview + +import androidx.annotation.ColorInt + +interface TextColorPicker { + + @ColorInt + fun getTextColor(event: WeekViewEvent): Int + +} diff --git a/library/src/main/java/com/alamkanak/weekview/TimeChangedBroadcastReceiver.kt b/library/src/main/java/com/alamkanak/weekview/TimeChangedBroadcastReceiver.kt new file mode 100644 index 000000000..56b1754a9 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/TimeChangedBroadcastReceiver.kt @@ -0,0 +1,38 @@ +package com.alamkanak.weekview + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import java.util.* + +/**a broadcast receiver that tells you when the time has changed (minute, date, configuration of time/date...), based on: https://stackoverflow.com/a/48782963/878126 */ +abstract class TimeChangedBroadcastReceiver : BroadcastReceiver() { + private var curCal = Calendar.getInstance() + + abstract fun onTimeChanged() + + @Suppress("MemberVisibilityCanBePrivate") + fun register(context: Context, cal: Calendar) { + curCal = cal + val filter = IntentFilter() + filter.addAction(Intent.ACTION_TIME_CHANGED) + filter.addAction(Intent.ACTION_DATE_CHANGED) + filter.addAction(Intent.ACTION_TIME_TICK) + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED) + context.registerReceiver(this, filter) + val newDate = Calendar.getInstance() + if (!WeekViewUtil.isSameDayAndHourAndMinute(newDate, curCal)) { + curCal = newDate + onTimeChanged() + } + } + + override fun onReceive(context: Context, intent: Intent) { + val newTime = Calendar.getInstance() + if (!WeekViewUtil.isSameDayAndHourAndMinute(newTime, curCal)) { + curCal = newTime + onTimeChanged() + } + } +} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekDaySubtitleInterpreter.kt b/library/src/main/java/com/alamkanak/weekview/WeekDaySubtitleInterpreter.kt new file mode 100644 index 000000000..0add99ecd --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/WeekDaySubtitleInterpreter.kt @@ -0,0 +1,7 @@ +package com.alamkanak.weekview + +import java.util.* + +interface WeekDaySubtitleInterpreter { + fun getFormattedWeekDaySubtitle(date: Calendar): String +} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekView.java b/library/src/main/java/com/alamkanak/weekview/WeekView.java deleted file mode 100755 index f6685e020..000000000 --- a/library/src/main/java/com/alamkanak/weekview/WeekView.java +++ /dev/null @@ -1,2854 +0,0 @@ -package com.alamkanak.weekview; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.support.v4.view.GestureDetectorCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.animation.FastOutLinearInInterpolator; -import android.text.Layout; -import android.text.SpannableStringBuilder; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.text.style.StyleSpan; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.DragEvent; -import android.view.GestureDetector; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.SoundEffectConstants; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.OverScroller; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; - -import static com.alamkanak.weekview.WeekViewUtil.daysBetween; -import static com.alamkanak.weekview.WeekViewUtil.getPassedMinutesInDay; -import static com.alamkanak.weekview.WeekViewUtil.isSameDay; -import static com.alamkanak.weekview.WeekViewUtil.today; - -/** - * Created by Raquib-ul-Alam Kanak on 7/21/2014. - * Website: http://alamkanak.github.io/ - */ -public class WeekView extends View { - - private enum Direction { - NONE, LEFT, RIGHT, VERTICAL - } - - @Deprecated - public static final int LENGTH_SHORT = 1; - @Deprecated - public static final int LENGTH_LONG = 2; - private final Context mContext; - private Calendar mHomeDate; - private Calendar mMinDate; - private Calendar mMaxDate; - private Paint mTimeTextPaint; - private float mTimeTextWidth; - private float mTimeTextHeight; - private Paint mHeaderTextPaint; - private float mHeaderTextHeight; - private float mHeaderHeight; - private GestureDetectorCompat mGestureDetector; - private OverScroller mScroller; - private PointF mCurrentOrigin = new PointF(0f, 0f); - private Direction mCurrentScrollDirection = Direction.NONE; - private Paint mHeaderBackgroundPaint; - private float mWidthPerDay; - private Paint mDayBackgroundPaint; - private Paint mHourSeparatorPaint; - private float mHeaderMarginBottom; - private Paint mTodayBackgroundPaint; - private Paint mFutureBackgroundPaint; - private Paint mPastBackgroundPaint; - private Paint mFutureWeekendBackgroundPaint; - private Paint mPastWeekendBackgroundPaint; - private Paint mNowLinePaint; - private Paint mTodayHeaderTextPaint; - private Paint mEventBackgroundPaint; - private Paint mNewEventBackgroundPaint; - private float mHeaderColumnWidth; - private List mEventRects; - private List mEvents; - private TextPaint mEventTextPaint; - private TextPaint mNewEventTextPaint; - private Paint mHeaderColumnBackgroundPaint; - private int mFetchedPeriod = -1; // the middle period the calendar has fetched. - private boolean mRefreshEvents = false; - private Direction mCurrentFlingDirection = Direction.NONE; - private ScaleGestureDetector mScaleDetector; - private boolean mIsZooming; - private Calendar mFirstVisibleDay; - private Calendar mLastVisibleDay; - private int mMinimumFlingVelocity = 0; - private int mScaledTouchSlop = 0; - private EventRect mNewEventRect; - private TextColorPicker textColorPicker; - - // Attributes and their default values. - private int mHourHeight = 50; - private int mNewHourHeight = -1; - private int mMinHourHeight = 0; //no minimum specified (will be dynamic, based on screen) - private int mEffectiveMinHourHeight = mMinHourHeight; //compensates for the fact that you can't keep zooming out. - private int mMaxHourHeight = 250; - private int mColumnGap = 10; - private int mFirstDayOfWeek = Calendar.MONDAY; - private int mTextSize = 12; - private int mHeaderColumnPadding = 10; - private int mHeaderColumnTextColor = Color.BLACK; - private int mNumberOfVisibleDays = 3; - private int mHeaderRowPadding = 10; - private int mHeaderRowBackgroundColor = Color.WHITE; - private int mDayBackgroundColor = Color.rgb(245, 245, 245); - private int mPastBackgroundColor = Color.rgb(227, 227, 227); - private int mFutureBackgroundColor = Color.rgb(245, 245, 245); - private int mPastWeekendBackgroundColor = 0; - private int mFutureWeekendBackgroundColor = 0; - private int mNowLineColor = Color.rgb(102, 102, 102); - private int mNowLineThickness = 5; - private int mHourSeparatorColor = Color.rgb(230, 230, 230); - private int mTodayBackgroundColor = Color.rgb(239, 247, 254); - private int mHourSeparatorHeight = 2; - private int mTodayHeaderTextColor = Color.rgb(39, 137, 228); - private int mEventTextSize = 12; - private int mEventTextColor = Color.BLACK; - private int mEventPadding = 8; - private int mHeaderColumnBackgroundColor = Color.WHITE; - private int mDefaultEventColor; - private int mNewEventColor; - private String mNewEventIdentifier = "-100"; - private Drawable mNewEventIconDrawable; - private int mNewEventLengthInMinutes = 60; - private int mNewEventTimeResolutionInMinutes = 15; - private boolean mShowFirstDayOfWeekFirst = false; - - private boolean mIsFirstDraw = true; - private boolean mAreDimensionsInvalid = true; - @Deprecated - private int mDayNameLength = LENGTH_LONG; - private int mOverlappingEventGap = 0; - private int mEventMarginVertical = 0; - private float mXScrollingSpeed = 1f; - private Calendar mScrollToDay = null; - private double mScrollToHour = -1; - private int mEventCornerRadius = 0; - private boolean mShowDistinctWeekendColor = false; - private boolean mShowNowLine = false; - private boolean mShowDistinctPastFutureColor = false; - private boolean mHorizontalFlingEnabled = true; - private boolean mVerticalFlingEnabled = true; - private int mAllDayEventHeight = 100; - private float mZoomFocusPoint = 0; - private boolean mZoomFocusPointEnabled = true; - private int mScrollDuration = 250; - private int mTimeColumnResolution = 60; - private Typeface mTypeface = Typeface.DEFAULT_BOLD; - private int mMinTime = 0; - private int mMaxTime = 24; - private boolean mAutoLimitTime = false; - private boolean mEnableDropListener = false; - private int mMinOverlappingMinutes = 0; - - // Listeners. - private EventClickListener mEventClickListener; - private EventLongPressListener mEventLongPressListener; - private WeekViewLoader mWeekViewLoader; - private EmptyViewClickListener mEmptyViewClickListener; - private EmptyViewLongPressListener mEmptyViewLongPressListener; - private DateTimeInterpreter mDateTimeInterpreter; - private ScrollListener mScrollListener; - private AddEventClickListener mAddEventClickListener; - private DropListener mDropListener; - - private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { - - @Override - public boolean onDown(MotionEvent e) { - goToNearestOrigin(); - return true; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - // Check if view is zoomed. - if (mIsZooming) - return true; - - switch (mCurrentScrollDirection) { - case NONE: { - // Allow scrolling only in one direction. - if (Math.abs(distanceX) > Math.abs(distanceY)) { - if (distanceX > 0) { - mCurrentScrollDirection = Direction.LEFT; - } else { - mCurrentScrollDirection = Direction.RIGHT; - } - } else { - mCurrentScrollDirection = Direction.VERTICAL; - } - break; - } - case LEFT: { - // Change direction if there was enough change. - if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX < -mScaledTouchSlop)) { - mCurrentScrollDirection = Direction.RIGHT; - } - break; - } - case RIGHT: { - // Change direction if there was enough change. - if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX > mScaledTouchSlop)) { - mCurrentScrollDirection = Direction.LEFT; - } - break; - } - default: - break; - } - - // Calculate the new origin after scroll. - switch (mCurrentScrollDirection) { - case LEFT: - case RIGHT: - float minX = getXMinLimit(); - float maxX = getXMaxLimit(); - if ((mCurrentOrigin.x - (distanceX * mXScrollingSpeed)) > maxX) { - mCurrentOrigin.x = maxX; - } else if ((mCurrentOrigin.x - (distanceX * mXScrollingSpeed)) < minX) { - mCurrentOrigin.x = minX; - } else { - mCurrentOrigin.x -= distanceX * mXScrollingSpeed; - } - ViewCompat.postInvalidateOnAnimation(WeekView.this); - break; - case VERTICAL: - float minY = getYMinLimit(); - float maxY = getYMaxLimit(); - if ((mCurrentOrigin.y - (distanceY)) > maxY) { - mCurrentOrigin.y = maxY; - } else if ((mCurrentOrigin.y - (distanceY)) < minY) { - mCurrentOrigin.y = minY; - } else { - mCurrentOrigin.y -= distanceY; - } - ViewCompat.postInvalidateOnAnimation(WeekView.this); - break; - default: - break; - } - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (mIsZooming) - return true; - - if ((mCurrentFlingDirection == Direction.LEFT && !mHorizontalFlingEnabled) || - (mCurrentFlingDirection == Direction.RIGHT && !mHorizontalFlingEnabled) || - (mCurrentFlingDirection == Direction.VERTICAL && !mVerticalFlingEnabled)) { - return true; - } - - mScroller.forceFinished(true); - - mCurrentFlingDirection = mCurrentScrollDirection; - switch (mCurrentFlingDirection) { - case LEFT: - case RIGHT: - mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, (int) (velocityX * mXScrollingSpeed), 0, (int) getXMinLimit(), (int) getXMaxLimit(), (int) getYMinLimit(), (int) getYMaxLimit()); - break; - case VERTICAL: - mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, 0, (int) velocityY, (int) getXMinLimit(), (int) getXMaxLimit(), (int) getYMinLimit(), (int) getYMaxLimit()); - break; - default: - break; - } - - ViewCompat.postInvalidateOnAnimation(WeekView.this); - return true; - } - - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - - // If the tap was on an event then trigger the callback. - if (mEventRects != null && mEventClickListener != null) { - List reversedEventRects = mEventRects; - Collections.reverse(reversedEventRects); - for (EventRect eventRect : reversedEventRects) { - if (!mNewEventIdentifier.equals(eventRect.event.getIdentifier()) && eventRect.rectF != null && e.getX() > eventRect.rectF.left && e.getX() < eventRect.rectF.right && e.getY() > eventRect.rectF.top && e.getY() < eventRect.rectF.bottom) { - mEventClickListener.onEventClick(eventRect.originalEvent, eventRect.rectF); - playSoundEffect(SoundEffectConstants.CLICK); - return super.onSingleTapConfirmed(e); - } - } - } - - float xOffset = getXStartPixel(); - - float x = e.getX() - xOffset; - float y = e.getY() - mCurrentOrigin.y; - // If the tap was on add new Event space, then trigger the callback - if (mAddEventClickListener != null && mNewEventRect != null && mNewEventRect.rectF != null && - mNewEventRect.rectF.contains(x, y)) { - mAddEventClickListener.onAddEventClicked(mNewEventRect.event.getStartTime(), mNewEventRect.event.getEndTime()); - return super.onSingleTapConfirmed(e); - } - - // If the tap was on an empty space, then trigger the callback. - if ((mEmptyViewClickListener != null || mAddEventClickListener != null) && e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) { - Calendar selectedTime = getTimeFromPoint(e.getX(), e.getY()); - - if (selectedTime != null) { - List tempEvents = new ArrayList<>(mEvents); - if (mNewEventRect != null) { - tempEvents.remove(mNewEventRect.event); - mNewEventRect = null; - } - - playSoundEffect(SoundEffectConstants.CLICK); - - if (mEmptyViewClickListener != null) - mEmptyViewClickListener.onEmptyViewClicked((Calendar) selectedTime.clone()); - - if (mAddEventClickListener != null) { - //round selectedTime to resolution - selectedTime.add(Calendar.MINUTE, -(mNewEventLengthInMinutes / 2)); - //Fix selected time if before the minimum hour - if (selectedTime.get(Calendar.HOUR_OF_DAY) < mMinTime) { - selectedTime.set(Calendar.HOUR_OF_DAY, mMinTime); - selectedTime.set(Calendar.MINUTE, 0); - } - int unroundedMinutes = selectedTime.get(Calendar.MINUTE); - int mod = unroundedMinutes % mNewEventTimeResolutionInMinutes; - selectedTime.add(Calendar.MINUTE, mod < Math.ceil(mNewEventTimeResolutionInMinutes / 2) ? -mod : (mNewEventTimeResolutionInMinutes - mod)); - - Calendar endTime = (Calendar) selectedTime.clone(); - - //Minus one to ensure it is the same day and not midnight (next day) - int maxMinutes = (mMaxTime - selectedTime.get(Calendar.HOUR_OF_DAY)) * 60 - selectedTime.get(Calendar.MINUTE) - 1; - endTime.add(Calendar.MINUTE, Math.min(maxMinutes, mNewEventLengthInMinutes)); - //If clicked at end of the day, fix selected startTime - if (maxMinutes < mNewEventLengthInMinutes) { - selectedTime.add(Calendar.MINUTE, maxMinutes - mNewEventLengthInMinutes); - } - - WeekViewEvent newEvent = new WeekViewEvent(mNewEventIdentifier, "", null, selectedTime, endTime); - - float top = mHourHeight * getPassedMinutesInDay(selectedTime) / 60 + getEventsTop(); - float bottom = mHourHeight * getPassedMinutesInDay(endTime) / 60 + getEventsTop(); - - // Calculate left and right. - float left = mWidthPerDay * WeekViewUtil.daysBetween(getFirstVisibleDay(), selectedTime); - float right = left + mWidthPerDay; - - // Add the new event if its bounds are valid - if (left < right && - left < getWidth() && - top < getHeight() && - right > mHeaderColumnWidth && - bottom > 0 - ) { - RectF dayRectF = new RectF(left, top, right, bottom - mCurrentOrigin.y); - newEvent.setColor(mNewEventColor); - mNewEventRect = new EventRect(newEvent, newEvent, dayRectF); - tempEvents.add(newEvent); - WeekView.this.clearEvents(); - cacheAndSortEvents(tempEvents); - computePositionOfEvents(mEventRects); - invalidate(); - } - - } - } - - } - return super.onSingleTapConfirmed(e); - } - - @Override - public void onLongPress(MotionEvent e) { - super.onLongPress(e); - - if (mEventLongPressListener != null && mEventRects != null) { - List reversedEventRects = mEventRects; - Collections.reverse(reversedEventRects); - for (EventRect event : reversedEventRects) { - if (event.rectF != null && e.getX() > event.rectF.left && e.getX() < event.rectF.right && e.getY() > event.rectF.top && e.getY() < event.rectF.bottom) { - mEventLongPressListener.onEventLongPress(event.originalEvent, event.rectF); - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - return; - } - } - } - - // If the tap was on in an empty space, then trigger the callback. - if (mEmptyViewLongPressListener != null && e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) { - Calendar selectedTime = getTimeFromPoint(e.getX(), e.getY()); - if (selectedTime != null) { - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - mEmptyViewLongPressListener.onEmptyViewLongPress(selectedTime); - } - } - } - }; - - public WeekView(Context context) { - this(context, null); - } - - public WeekView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WeekView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - // Hold references. - mContext = context; - - // Get the attribute values (if any). - TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WeekView, 0, 0); - try { - mFirstDayOfWeek = a.getInteger(R.styleable.WeekView_firstDayOfWeek, mFirstDayOfWeek); - mHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourHeight, mHourHeight); - mMinHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_minHourHeight, mMinHourHeight); - mEffectiveMinHourHeight = mMinHourHeight; - mMaxHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_maxHourHeight, mMaxHourHeight); - mTextSize = a.getDimensionPixelSize(R.styleable.WeekView_textSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, context.getResources().getDisplayMetrics())); - mHeaderColumnPadding = a.getDimensionPixelSize(R.styleable.WeekView_headerColumnPadding, mHeaderColumnPadding); - mColumnGap = a.getDimensionPixelSize(R.styleable.WeekView_columnGap, mColumnGap); - mHeaderColumnTextColor = a.getColor(R.styleable.WeekView_headerColumnTextColor, mHeaderColumnTextColor); - mNumberOfVisibleDays = a.getInteger(R.styleable.WeekView_noOfVisibleDays, mNumberOfVisibleDays); - mShowFirstDayOfWeekFirst = a.getBoolean(R.styleable.WeekView_showFirstDayOfWeekFirst, mShowFirstDayOfWeekFirst); - mHeaderRowPadding = a.getDimensionPixelSize(R.styleable.WeekView_headerRowPadding, mHeaderRowPadding); - mHeaderRowBackgroundColor = a.getColor(R.styleable.WeekView_headerRowBackgroundColor, mHeaderRowBackgroundColor); - mDayBackgroundColor = a.getColor(R.styleable.WeekView_dayBackgroundColor, mDayBackgroundColor); - mFutureBackgroundColor = a.getColor(R.styleable.WeekView_futureBackgroundColor, mFutureBackgroundColor); - mPastBackgroundColor = a.getColor(R.styleable.WeekView_pastBackgroundColor, mPastBackgroundColor); - mFutureWeekendBackgroundColor = a.getColor(R.styleable.WeekView_futureWeekendBackgroundColor, mFutureBackgroundColor); // If not set, use the same color as in the week - mPastWeekendBackgroundColor = a.getColor(R.styleable.WeekView_pastWeekendBackgroundColor, mPastBackgroundColor); - mNowLineColor = a.getColor(R.styleable.WeekView_nowLineColor, mNowLineColor); - mNowLineThickness = a.getDimensionPixelSize(R.styleable.WeekView_nowLineThickness, mNowLineThickness); - mHourSeparatorColor = a.getColor(R.styleable.WeekView_hourSeparatorColor, mHourSeparatorColor); - mTodayBackgroundColor = a.getColor(R.styleable.WeekView_todayBackgroundColor, mTodayBackgroundColor); - mHourSeparatorHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourSeparatorHeight, mHourSeparatorHeight); - mTodayHeaderTextColor = a.getColor(R.styleable.WeekView_todayHeaderTextColor, mTodayHeaderTextColor); - mEventTextSize = a.getDimensionPixelSize(R.styleable.WeekView_eventTextSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mEventTextSize, context.getResources().getDisplayMetrics())); - mEventTextColor = a.getColor(R.styleable.WeekView_eventTextColor, mEventTextColor); - mNewEventColor = a.getColor(R.styleable.WeekView_newEventColor, mNewEventColor); - mNewEventIconDrawable = a.getDrawable(R.styleable.WeekView_newEventIconResource); - // For backward compatibility : Set "mNewEventIdentifier" if the attribute is "WeekView_newEventId" of type int - setNewEventId(a.getInt(R.styleable.WeekView_newEventId, Integer.parseInt(mNewEventIdentifier))); - mNewEventIdentifier = (a.getString(R.styleable.WeekView_newEventIdentifier) != null) ? a.getString(R.styleable.WeekView_newEventIdentifier) : mNewEventIdentifier; - mNewEventLengthInMinutes = a.getInt(R.styleable.WeekView_newEventLengthInMinutes, mNewEventLengthInMinutes); - mNewEventTimeResolutionInMinutes = a.getInt(R.styleable.WeekView_newEventTimeResolutionInMinutes, mNewEventTimeResolutionInMinutes); - mEventPadding = a.getDimensionPixelSize(R.styleable.WeekView_eventPadding, mEventPadding); - mHeaderColumnBackgroundColor = a.getColor(R.styleable.WeekView_headerColumnBackground, mHeaderColumnBackgroundColor); - mDayNameLength = a.getInteger(R.styleable.WeekView_dayNameLength, mDayNameLength); - mOverlappingEventGap = a.getDimensionPixelSize(R.styleable.WeekView_overlappingEventGap, mOverlappingEventGap); - mEventMarginVertical = a.getDimensionPixelSize(R.styleable.WeekView_eventMarginVertical, mEventMarginVertical); - mXScrollingSpeed = a.getFloat(R.styleable.WeekView_xScrollingSpeed, mXScrollingSpeed); - mEventCornerRadius = a.getDimensionPixelSize(R.styleable.WeekView_eventCornerRadius, mEventCornerRadius); - mShowDistinctPastFutureColor = a.getBoolean(R.styleable.WeekView_showDistinctPastFutureColor, mShowDistinctPastFutureColor); - mShowDistinctWeekendColor = a.getBoolean(R.styleable.WeekView_showDistinctWeekendColor, mShowDistinctWeekendColor); - mShowNowLine = a.getBoolean(R.styleable.WeekView_showNowLine, mShowNowLine); - mHorizontalFlingEnabled = a.getBoolean(R.styleable.WeekView_horizontalFlingEnabled, mHorizontalFlingEnabled); - mVerticalFlingEnabled = a.getBoolean(R.styleable.WeekView_verticalFlingEnabled, mVerticalFlingEnabled); - mAllDayEventHeight = a.getDimensionPixelSize(R.styleable.WeekView_allDayEventHeight, mAllDayEventHeight); - mZoomFocusPoint = a.getFraction(R.styleable.WeekView_zoomFocusPoint, 1, 1, mZoomFocusPoint); - mZoomFocusPointEnabled = a.getBoolean(R.styleable.WeekView_zoomFocusPointEnabled, mZoomFocusPointEnabled); - mScrollDuration = a.getInt(R.styleable.WeekView_scrollDuration, mScrollDuration); - mTimeColumnResolution = a.getInt(R.styleable.WeekView_timeColumnResolution, mTimeColumnResolution); - mAutoLimitTime = a.getBoolean(R.styleable.WeekView_autoLimitTime, mAutoLimitTime); - mMinTime = a.getInt(R.styleable.WeekView_minTime, mMinTime); - mMaxTime = a.getInt(R.styleable.WeekView_maxTime, mMaxTime); - if (a.getBoolean(R.styleable.WeekView_dropListenerEnabled, false)) - this.enableDropListener(); - mMinOverlappingMinutes = a.getInt(R.styleable.WeekView_minOverlappingMinutes, 0); - } finally { - a.recycle(); - } - - init(); - } - - private void init() { - resetHomeDate(); - - // Scrolling initialization. - mGestureDetector = new GestureDetectorCompat(mContext, mGestureListener); - mScroller = new OverScroller(mContext, new FastOutLinearInInterpolator()); - - mMinimumFlingVelocity = ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity(); - mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - - // Measure settings for time column. - mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTimeTextPaint.setTextAlign(Paint.Align.RIGHT); - mTimeTextPaint.setTextSize(mTextSize); - mTimeTextPaint.setColor(mHeaderColumnTextColor); - - Rect rect = new Rect(); - final String exampleTime = (mTimeColumnResolution % 60 != 0) ? "00:00 PM" : "00 PM"; - mTimeTextPaint.getTextBounds(exampleTime, 0, exampleTime.length(), rect); - mTimeTextWidth = mTimeTextPaint.measureText(exampleTime); - mTimeTextHeight = rect.height(); - mHeaderMarginBottom = mTimeTextHeight / 2; - initTextTimeWidth(); - - // Measure settings for header row. - mHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mHeaderTextPaint.setColor(mHeaderColumnTextColor); - mHeaderTextPaint.setTextAlign(Paint.Align.CENTER); - mHeaderTextPaint.setTextSize(mTextSize); - mHeaderTextPaint.getTextBounds(exampleTime, 0, exampleTime.length(), rect); - mHeaderTextHeight = rect.height(); - mHeaderTextPaint.setTypeface(mTypeface); - - - // Prepare header background paint. - mHeaderBackgroundPaint = new Paint(); - mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor); - - // Prepare day background color paint. - mDayBackgroundPaint = new Paint(); - mDayBackgroundPaint.setColor(mDayBackgroundColor); - mFutureBackgroundPaint = new Paint(); - mFutureBackgroundPaint.setColor(mFutureBackgroundColor); - mPastBackgroundPaint = new Paint(); - mPastBackgroundPaint.setColor(mPastBackgroundColor); - mFutureWeekendBackgroundPaint = new Paint(); - mFutureWeekendBackgroundPaint.setColor(mFutureWeekendBackgroundColor); - mPastWeekendBackgroundPaint = new Paint(); - mPastWeekendBackgroundPaint.setColor(mPastWeekendBackgroundColor); - - // Prepare hour separator color paint. - mHourSeparatorPaint = new Paint(); - mHourSeparatorPaint.setStyle(Paint.Style.STROKE); - mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight); - mHourSeparatorPaint.setColor(mHourSeparatorColor); - - // Prepare the "now" line color paint - mNowLinePaint = new Paint(); - mNowLinePaint.setStrokeWidth(mNowLineThickness); - mNowLinePaint.setColor(mNowLineColor); - - // Prepare today background color paint. - mTodayBackgroundPaint = new Paint(); - mTodayBackgroundPaint.setColor(mTodayBackgroundColor); - - // Prepare today header text color paint. - mTodayHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTodayHeaderTextPaint.setTextAlign(Paint.Align.CENTER); - mTodayHeaderTextPaint.setTextSize(mTextSize); - mTodayHeaderTextPaint.setTypeface(mTypeface); - - mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor); - - // Prepare event background color. - mEventBackgroundPaint = new Paint(); - mEventBackgroundPaint.setColor(Color.rgb(174, 208, 238)); - // Prepare empty event background color. - mNewEventBackgroundPaint = new Paint(); - mNewEventBackgroundPaint.setColor(Color.rgb(60, 147, 217)); - - // Prepare header column background color. - mHeaderColumnBackgroundPaint = new Paint(); - mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor); - - // Prepare event text size and color. - mEventTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG); - mEventTextPaint.setStyle(Paint.Style.FILL); - mEventTextPaint.setColor(mEventTextColor); - mEventTextPaint.setTextSize(mEventTextSize); - - // Set default event color. - mDefaultEventColor = Color.parseColor("#9fc6e7"); - // Set default empty event color. - mNewEventColor = Color.parseColor("#3c93d9"); - - mScaleDetector = new ScaleGestureDetector(mContext, new WeekViewGestureListener()); - } - - private void resetHomeDate() { - Calendar newHomeDate = today(); - - if (mMinDate != null && newHomeDate.before(mMinDate)) { - newHomeDate = (Calendar) mMinDate.clone(); - } - if (mMaxDate != null && newHomeDate.after(mMaxDate)) { - newHomeDate = (Calendar) mMaxDate.clone(); - } - - if (mMaxDate != null) { - Calendar date = (Calendar) mMaxDate.clone(); - date.add(Calendar.DATE, 1 - getRealNumberOfVisibleDays()); - while (date.before(mMinDate)) { - date.add(Calendar.DATE, 1); - } - - if (newHomeDate.after(date)) { - newHomeDate = date; - } - } - - mHomeDate = newHomeDate; - } - - private float getXOriginForDate(Calendar date) { - return -daysBetween(mHomeDate, date) * (mWidthPerDay + mColumnGap); - } - - private int getNumberOfPeriods() { - return (int) ((mMaxTime - mMinTime) * (60.0 / mTimeColumnResolution)); - } - - private float getYMinLimit() { - return -(mHourHeight * (mMaxTime - mMinTime) - + mHeaderHeight - + mHeaderRowPadding * 2 - + mHeaderMarginBottom - + mTimeTextHeight / 2 - - getHeight()); - } - - private float getYMaxLimit() { - return 0; - } - - private float getXMinLimit() { - if (mMaxDate == null) { - return Integer.MIN_VALUE; - } else { - Calendar date = (Calendar) mMaxDate.clone(); - date.add(Calendar.DATE, 1 - getRealNumberOfVisibleDays()); - while (date.before(mMinDate)) { - date.add(Calendar.DATE, 1); - } - - return getXOriginForDate(date); - } - } - - private float getXMaxLimit() { - if (mMinDate == null) { - return Integer.MAX_VALUE; - } else { - return getXOriginForDate(mMinDate); - } - } - - // fix rotation changes - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mAreDimensionsInvalid = true; - } - - /** - * Initialize time column width. Calculate value with all possible hours (supposed widest text). - */ - private void initTextTimeWidth() { - mTimeTextWidth = 0; - for (int i = 0; i < getNumberOfPeriods(); i++) { - // Measure time string and get max width. - String time = getDateTimeInterpreter().interpretTime(i, (i % 2) * 30); - if (time == null) - throw new IllegalStateException("A DateTimeInterpreter must not return null time"); - mTimeTextWidth = Math.max(mTimeTextWidth, mTimeTextPaint.measureText(time)); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Draw the header row. - drawHeaderRowAndEvents(canvas); - - // Draw the time column and all the axes/separators. - drawTimeColumnAndAxes(canvas); - } - - private void calculateHeaderHeight() { - //Make sure the header is the right size (depends on AllDay events) - boolean containsAllDayEvent = false; - if (mEventRects != null && mEventRects.size() > 0) { - for (int dayNumber = 0; - dayNumber < getRealNumberOfVisibleDays(); - dayNumber++) { - Calendar day = (Calendar) getFirstVisibleDay().clone(); - day.add(Calendar.DATE, dayNumber); - for (int i = 0; i < mEventRects.size(); i++) { - - if (isSameDay(mEventRects.get(i).event.getStartTime(), day) && mEventRects.get(i).event.isAllDay()) { - containsAllDayEvent = true; - break; - } - } - if (containsAllDayEvent) { - break; - } - } - } - if (containsAllDayEvent) { - mHeaderHeight = mHeaderTextHeight + (mAllDayEventHeight + mHeaderMarginBottom); - } else { - mHeaderHeight = mHeaderTextHeight; - } - } - - private void drawTimeColumnAndAxes(Canvas canvas) { - // Draw the background color for the header column. - canvas.drawRect(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight(), mHeaderColumnBackgroundPaint); - - // Clip to paint in left column only. - canvas.clipRect(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight(), Region.Op.REPLACE); - - for (int i = 0; i < getNumberOfPeriods(); i++) { - // If we are showing half hours (eg. 5:30am), space the times out by half the hour height - // and need to provide 30 minutes on each odd period, otherwise, minutes is always 0. - float timeSpacing; - int minutes; - int hour; - - float timesPerHour = (float) 60.0 / mTimeColumnResolution; - - timeSpacing = mHourHeight / timesPerHour; - hour = mMinTime + i / (int) (timesPerHour); - minutes = i % ((int) timesPerHour) * (60 / (int) timesPerHour); - - - // Calculate the top of the rectangle where the time text will go - float top = mHeaderHeight + mHeaderRowPadding * 2 + mCurrentOrigin.y + timeSpacing * i + mHeaderMarginBottom; - - // Get the time to be displayed, as a String. - String time = getDateTimeInterpreter().interpretTime(hour, minutes); - // Draw the text if its y position is not outside of the visible area. The pivot point of the text is the point at the bottom-right corner. - if (time == null) - throw new IllegalStateException("A DateTimeInterpreter must not return null time"); - if (top < getHeight()) - canvas.drawText(time, mTimeTextWidth + mHeaderColumnPadding, top + mTimeTextHeight, mTimeTextPaint); - } - } - - private void drawHeaderRowAndEvents(Canvas canvas) { - // Calculate the available width for each day. - mHeaderColumnWidth = mTimeTextWidth + mHeaderColumnPadding * 2; - mWidthPerDay = getWidth() - mHeaderColumnWidth - mColumnGap * (getRealNumberOfVisibleDays() - 1); - mWidthPerDay = mWidthPerDay / getRealNumberOfVisibleDays(); - - calculateHeaderHeight(); //Make sure the header is the right size (depends on AllDay events) - - Calendar today = today(); - - if (mAreDimensionsInvalid) { - mEffectiveMinHourHeight = Math.max(mMinHourHeight, (int) ((getHeight() - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom) / (mMaxTime - mMinTime))); - - mAreDimensionsInvalid = false; - if (mScrollToDay != null) - goToDate(mScrollToDay); - - mAreDimensionsInvalid = false; - if (mScrollToHour >= 0) - goToHour(mScrollToHour); - - mScrollToDay = null; - mScrollToHour = -1; - mAreDimensionsInvalid = false; - } - if (mIsFirstDraw) { - mIsFirstDraw = false; - - // If the week view is being drawn for the first time, then consider the first day of the week. - if (getRealNumberOfVisibleDays() >= 7 && mHomeDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek && mShowFirstDayOfWeekFirst) { - int difference = (mHomeDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek); - mCurrentOrigin.x += (mWidthPerDay + mColumnGap) * difference; - } - setLimitTime(mMinTime, mMaxTime); - } - - // Calculate the new height due to the zooming. - if (mNewHourHeight > 0) { - if (mNewHourHeight < mEffectiveMinHourHeight) - mNewHourHeight = mEffectiveMinHourHeight; - else if (mNewHourHeight > mMaxHourHeight) - mNewHourHeight = mMaxHourHeight; - - mHourHeight = mNewHourHeight; - mNewHourHeight = -1; - } - - // If the new mCurrentOrigin.y is invalid, make it valid. - if (mCurrentOrigin.y < getHeight() - mHourHeight * (mMaxTime - mMinTime) - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom - mTimeTextHeight / 2) - mCurrentOrigin.y = getHeight() - mHourHeight * (mMaxTime - mMinTime) - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom - mTimeTextHeight / 2; - - // Don't put an "else if" because it will trigger a glitch when completely zoomed out and - // scrolling vertically. - if (mCurrentOrigin.y > 0) { - mCurrentOrigin.y = 0; - } - - int leftDaysWithGaps = getLeftDaysWithGaps(); - // Consider scroll offset. - float startFromPixel = getXStartPixel(); - float startPixel = startFromPixel; - - // Prepare to iterate for each day. - Calendar day = (Calendar) today.clone(); - day.add(Calendar.HOUR_OF_DAY, 6); - - // Prepare to iterate for each hour to draw the hour lines. - int lineCount = (int) ((getHeight() - mHeaderHeight - mHeaderRowPadding * 2 - - mHeaderMarginBottom) / mHourHeight) + 1; - - lineCount = (lineCount) * (getRealNumberOfVisibleDays() + 1); - - float[] hourLines = new float[lineCount * 4]; - - // Clear the cache for event rectangles. - if (mEventRects != null) { - for (EventRect eventRect : mEventRects) { - eventRect.rectF = null; - } - } - - // Clip to paint events only. - canvas.clipRect(mHeaderColumnWidth, mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2, getWidth(), getHeight(), Region.Op.REPLACE); - - // Iterate through each day. - Calendar oldFirstVisibleDay = mFirstVisibleDay; - mFirstVisibleDay = (Calendar) mHomeDate.clone(); - mFirstVisibleDay.add(Calendar.DATE, -(Math.round(mCurrentOrigin.x / (mWidthPerDay + mColumnGap)))); - if (!mFirstVisibleDay.equals(oldFirstVisibleDay) && mScrollListener != null) { - mScrollListener.onFirstVisibleDayChanged(mFirstVisibleDay, oldFirstVisibleDay); - } - - if (mAutoLimitTime) { - List days = new ArrayList<>(); - for (int dayNumber = leftDaysWithGaps + 1; - dayNumber <= leftDaysWithGaps + getRealNumberOfVisibleDays(); - dayNumber++) { - day = (Calendar) mHomeDate.clone(); - day.add(Calendar.DATE, dayNumber - 1); - days.add(day); - } - limitEventTime(days); - } - - for (int dayNumber = leftDaysWithGaps + 1; - dayNumber <= leftDaysWithGaps + getRealNumberOfVisibleDays() + 1; - dayNumber++) { - - // Check if the day is today. - day = (Calendar) mHomeDate.clone(); - mLastVisibleDay = (Calendar) day.clone(); - day.add(Calendar.DATE, dayNumber - 1); - mLastVisibleDay.add(Calendar.DATE, dayNumber - 2); - boolean isToday = isSameDay(day, today); - - // Don't draw days which are outside requested range - if (!dateIsValid(day)) { - continue; - } - - // Get more events if necessary. We want to store the events 3 months beforehand. Get - // events only when it is the first iteration of the loop. - if (mEventRects == null || mRefreshEvents || - (dayNumber == leftDaysWithGaps + 1 && mFetchedPeriod != (int) mWeekViewLoader.toWeekViewPeriodIndex(day) && - Math.abs(mFetchedPeriod - mWeekViewLoader.toWeekViewPeriodIndex(day)) > 0.5)) { - getMoreEvents(day); - mRefreshEvents = false; - } - - // Draw background color for each day. - float start = (startPixel < mHeaderColumnWidth ? mHeaderColumnWidth : startPixel); - if (mWidthPerDay + startPixel - start > 0) { - if (mShowDistinctPastFutureColor) { - boolean isWeekend = day.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || day.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY; - Paint pastPaint = isWeekend && mShowDistinctWeekendColor ? mPastWeekendBackgroundPaint : mPastBackgroundPaint; - Paint futurePaint = isWeekend && mShowDistinctWeekendColor ? mFutureWeekendBackgroundPaint : mFutureBackgroundPaint; - float startY = mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom + mCurrentOrigin.y; - - if (isToday) { - Calendar now = Calendar.getInstance(); - float beforeNow = (now.get(Calendar.HOUR_OF_DAY) - mMinTime + now.get(Calendar.MINUTE) / 60.0f) * mHourHeight; - canvas.drawRect(start, startY, startPixel + mWidthPerDay, startY + beforeNow, pastPaint); - canvas.drawRect(start, startY + beforeNow, startPixel + mWidthPerDay, getHeight(), futurePaint); - } else if (day.before(today)) { - canvas.drawRect(start, startY, startPixel + mWidthPerDay, getHeight(), pastPaint); - } else { - canvas.drawRect(start, startY, startPixel + mWidthPerDay, getHeight(), futurePaint); - } - } else { - canvas.drawRect(start, mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom, startPixel + mWidthPerDay, getHeight(), isToday ? mTodayBackgroundPaint : mDayBackgroundPaint); - } - } - - // Prepare the separator lines for hours. - int i = 0; - for (int hourNumber = mMinTime; hourNumber < mMaxTime; hourNumber++) { - float top = mHeaderHeight + mHeaderRowPadding * 2 + mCurrentOrigin.y + mHourHeight * (hourNumber - mMinTime) + mTimeTextHeight / 2 + mHeaderMarginBottom; - if (top > mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom - mHourSeparatorHeight && top < getHeight() && startPixel + mWidthPerDay - start > 0) { - hourLines[i * 4] = start; - hourLines[i * 4 + 1] = top; - hourLines[i * 4 + 2] = startPixel + mWidthPerDay; - hourLines[i * 4 + 3] = top; - i++; - } - } - - // Draw the lines for hours. - canvas.drawLines(hourLines, mHourSeparatorPaint); - - // Draw the events. - drawEvents(day, startPixel, canvas); - - // Draw the line at the current time. - if (mShowNowLine && isToday) { - float startY = mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom + mCurrentOrigin.y; - Calendar now = Calendar.getInstance(); - float beforeNow = (now.get(Calendar.HOUR_OF_DAY) - mMinTime + now.get(Calendar.MINUTE) / 60.0f) * mHourHeight; - float top = startY + beforeNow; - canvas.drawLine(start, top, startPixel + mWidthPerDay, top, mNowLinePaint); - } - - // In the next iteration, start from the next day. - startPixel += mWidthPerDay + mColumnGap; - } - - // Hide everything in the first cell (top left corner). - canvas.clipRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2, Region.Op.REPLACE); - canvas.drawRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint); - - // Clip to paint header row only. - canvas.clipRect(mHeaderColumnWidth, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2, Region.Op.REPLACE); - - // Draw the header background. - canvas.drawRect(0, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint); - - // Draw the header row texts. - startPixel = startFromPixel; - for (int dayNumber = leftDaysWithGaps + 1; dayNumber <= leftDaysWithGaps + getRealNumberOfVisibleDays() + 1; dayNumber++) { - // Check if the day is today. - day = (Calendar) mHomeDate.clone(); - day.add(Calendar.DATE, dayNumber - 1); - boolean isToday = isSameDay(day, today); - - // Don't draw days which are outside requested range - if (!dateIsValid(day)) - continue; - - // Draw the day labels. - String dayLabel = getDateTimeInterpreter().interpretDate(day); - if (dayLabel == null) - throw new IllegalStateException("A DateTimeInterpreter must not return null date"); - canvas.drawText(dayLabel, startPixel + mWidthPerDay / 2, mHeaderTextHeight + mHeaderRowPadding, isToday ? mTodayHeaderTextPaint : mHeaderTextPaint); - drawAllDayEvents(day, startPixel, canvas); - startPixel += mWidthPerDay + mColumnGap; - } - - } - - /** - * Get the time and date where the user clicked on. - * - * @param x The x position of the touch event. - * @param y The y position of the touch event. - * @return The time and date at the clicked position. - */ - private Calendar getTimeFromPoint(float x, float y) { - int leftDaysWithGaps = getLeftDaysWithGaps(); - float startPixel = getXStartPixel(); - for (int dayNumber = leftDaysWithGaps + 1; - dayNumber <= leftDaysWithGaps + getRealNumberOfVisibleDays() + 1; - dayNumber++) { - float start = (startPixel < mHeaderColumnWidth ? mHeaderColumnWidth : startPixel); - if (mWidthPerDay + startPixel - start > 0 && x > start && x < startPixel + mWidthPerDay) { - Calendar day = (Calendar) mHomeDate.clone(); - day.add(Calendar.DATE, dayNumber - 1); - float pixelsFromZero = y - mCurrentOrigin.y - mHeaderHeight - - mHeaderRowPadding * 2 - mTimeTextHeight / 2 - mHeaderMarginBottom; - int hour = (int) (pixelsFromZero / mHourHeight); - int minute = (int) (60 * (pixelsFromZero - hour * mHourHeight) / mHourHeight); - day.add(Calendar.HOUR_OF_DAY, hour + mMinTime); - day.set(Calendar.MINUTE, minute); - return day; - } - startPixel += mWidthPerDay + mColumnGap; - } - return null; - } - - /** - * limit current time of event by update mMinTime & mMaxTime - * find smallest of start time & latest of end time - */ - private void limitEventTime(List dates) { - if (mEventRects != null && mEventRects.size() > 0) { - Calendar startTime = null; - Calendar endTime = null; - - for (EventRect eventRect : mEventRects) { - for (Calendar date : dates) { - if (isSameDay(eventRect.event.getStartTime(), date) && !eventRect.event.isAllDay()) { - - if (startTime == null || getPassedMinutesInDay(startTime) > getPassedMinutesInDay(eventRect.event.getStartTime())) { - startTime = eventRect.event.getStartTime(); - } - - if (endTime == null || getPassedMinutesInDay(endTime) < getPassedMinutesInDay(eventRect.event.getEndTime())) { - endTime = eventRect.event.getEndTime(); - } - } - } - } - - if (startTime != null && endTime != null && startTime.before(endTime)) { - setLimitTime(Math.max(0, startTime.get(Calendar.HOUR_OF_DAY)), - Math.min(24, endTime.get(Calendar.HOUR_OF_DAY) + 1)); - return; - } - } - } - - private int getMinHourOffset() { - return mHourHeight * mMinTime; - } - - private float getEventsTop() { - // Calculate top. - return mCurrentOrigin.y + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 + mEventMarginVertical - getMinHourOffset(); - - } - - private int getLeftDaysWithGaps() { - return (int) -(Math.ceil(mCurrentOrigin.x / (mWidthPerDay + mColumnGap))); - } - - private float getXStartPixel() { - return mCurrentOrigin.x + (mWidthPerDay + mColumnGap) * getLeftDaysWithGaps() + - mHeaderColumnWidth; - } - - /** - * Draw all the events of a particular day. - * - * @param date The day. - * @param startFromPixel The left position of the day area. The events will never go any left from this value. - * @param canvas The canvas to draw upon. - */ - private void drawEvents(Calendar date, float startFromPixel, Canvas canvas) { - if (mEventRects != null && mEventRects.size() > 0) { - for (int i = 0; i < mEventRects.size(); i++) { - if (isSameDay(mEventRects.get(i).event.getStartTime(), date) && !mEventRects.get(i).event.isAllDay()) { - float top = mHourHeight * mEventRects.get(i).top / 60 + getEventsTop(); - float bottom = mHourHeight * mEventRects.get(i).bottom / 60 + getEventsTop(); - - // Calculate left and right. - float left = startFromPixel + mEventRects.get(i).left * mWidthPerDay; - if (left < startFromPixel) - left += mOverlappingEventGap; - float right = left + mEventRects.get(i).width * mWidthPerDay; - if (right < startFromPixel + mWidthPerDay) - right -= mOverlappingEventGap; - - // Draw the event and the event name on top of it. - if (left < right && - left < getWidth() && - top < getHeight() && - right > mHeaderColumnWidth && - bottom > mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom - ) { - mEventRects.get(i).rectF = new RectF(left, top, right, bottom); - mEventBackgroundPaint.setColor(mEventRects.get(i).event.getColor() == 0 ? mDefaultEventColor : mEventRects.get(i).event.getColor()); - mEventBackgroundPaint.setShader(mEventRects.get(i).event.getShader()); - canvas.drawRoundRect(mEventRects.get(i).rectF, mEventCornerRadius, mEventCornerRadius, mEventBackgroundPaint); - float topToUse = top; - if (mEventRects.get(i).event.getStartTime().get(Calendar.HOUR_OF_DAY) < mMinTime) - topToUse = mHourHeight * getPassedMinutesInDay(mMinTime, 0) / 60 + getEventsTop(); - - if (!mNewEventIdentifier.equals(mEventRects.get(i).event.getIdentifier())) - drawEventTitle(mEventRects.get(i).event, mEventRects.get(i).rectF, canvas, topToUse, left); - else - drawEmptyImage(mEventRects.get(i).event, mEventRects.get(i).rectF, canvas, topToUse, left); - - } else - mEventRects.get(i).rectF = null; - } - } - } - } - - /** - * Draw all the Allday-events of a particular day. - * - * @param date The day. - * @param startFromPixel The left position of the day area. The events will never go any left from this value. - * @param canvas The canvas to draw upon. - */ - private void drawAllDayEvents(Calendar date, float startFromPixel, Canvas canvas) { - if (mEventRects != null && mEventRects.size() > 0) { - for (int i = 0; i < mEventRects.size(); i++) { - if (isSameDay(mEventRects.get(i).event.getStartTime(), date) && mEventRects.get(i).event.isAllDay()) { - - // Calculate top. - float top = mHeaderRowPadding * 2 + mHeaderMarginBottom + +mTimeTextHeight / 2 + mEventMarginVertical; - - // Calculate bottom. - float bottom = top + mEventRects.get(i).bottom; - - // Calculate left and right. - float left = startFromPixel + mEventRects.get(i).left * mWidthPerDay; - if (left < startFromPixel) - left += mOverlappingEventGap; - float right = left + mEventRects.get(i).width * mWidthPerDay; - if (right < startFromPixel + mWidthPerDay) - right -= mOverlappingEventGap; - - // Draw the event and the event name on top of it. - if (left < right && - left < getWidth() && - top < getHeight() && - right > mHeaderColumnWidth && - bottom > 0 - ) { - mEventRects.get(i).rectF = new RectF(left, top, right, bottom); - mEventBackgroundPaint.setColor(mEventRects.get(i).event.getColor() == 0 ? mDefaultEventColor : mEventRects.get(i).event.getColor()); - mEventBackgroundPaint.setShader(mEventRects.get(i).event.getShader()); - canvas.drawRoundRect(mEventRects.get(i).rectF, mEventCornerRadius, mEventCornerRadius, mEventBackgroundPaint); - drawEventTitle(mEventRects.get(i).event, mEventRects.get(i).rectF, canvas, top, left); - } else - mEventRects.get(i).rectF = null; - } - } - } - } - - /** - * Draw the name of the event on top of the event rectangle. - * - * @param event The event of which the title (and location) should be drawn. - * @param rect The rectangle on which the text is to be drawn. - * @param canvas The canvas to draw upon. - * @param originalTop The original top position of the rectangle. The rectangle may have some of its portion outside of the visible area. - * @param originalLeft The original left position of the rectangle. The rectangle may have some of its portion outside of the visible area. - */ - private void drawEventTitle(WeekViewEvent event, RectF rect, Canvas canvas, float originalTop, float originalLeft) { - if (rect.right - rect.left - mEventPadding * 2 < 0) return; - if (rect.bottom - rect.top - mEventPadding * 2 < 0) return; - - // Prepare the name of the event. - SpannableStringBuilder bob = new SpannableStringBuilder(); - if (!TextUtils.isEmpty(event.getName())) { - bob.append(event.getName()); - bob.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length(), 0); - } - // Prepare the location of the event. - if (!TextUtils.isEmpty(event.getLocation())) { - if (bob.length() > 0) - bob.append(' '); - bob.append(event.getLocation()); - } - - int availableHeight = (int) (rect.bottom - originalTop - mEventPadding * 2); - int availableWidth = (int) (rect.right - originalLeft - mEventPadding * 2); - - // Get text color if necessary - if (textColorPicker != null) { - mEventTextPaint.setColor(textColorPicker.getTextColor(event)); - } - // Get text dimensions. - StaticLayout textLayout = new StaticLayout(bob, mEventTextPaint, availableWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (textLayout.getLineCount() > 0) { - int lineHeight = textLayout.getHeight() / textLayout.getLineCount(); - - if (availableHeight >= lineHeight) { - // Calculate available number of line counts. - int availableLineCount = availableHeight / lineHeight; - do { - // Ellipsize text to fit into event rect. - if (!mNewEventIdentifier.equals(event.getIdentifier())) - textLayout = new StaticLayout(TextUtils.ellipsize(bob, mEventTextPaint, availableLineCount * availableWidth, TextUtils.TruncateAt.END), mEventTextPaint, (int) (rect.right - originalLeft - mEventPadding * 2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - - // Reduce line count. - availableLineCount--; - - // Repeat until text is short enough. - } while (textLayout.getHeight() > availableHeight); - - // Draw text. - canvas.save(); - canvas.translate(originalLeft + mEventPadding, originalTop + mEventPadding); - textLayout.draw(canvas); - canvas.restore(); - } - } - } - - /** - * Draw the text on top of the rectangle in the empty event. - */ - private void drawEmptyImage(WeekViewEvent event, RectF rect, Canvas canvas, float originalTop, float originalLeft) { - int size = Math.max(1, (int) Math.floor(Math.min(0.8 * rect.height(), 0.8 * rect.width()))); - if (mNewEventIconDrawable == null) - mNewEventIconDrawable = getResources().getDrawable(android.R.drawable.ic_input_add); - Bitmap icon = ((BitmapDrawable) mNewEventIconDrawable).getBitmap(); - icon = Bitmap.createScaledBitmap(icon, size, size, false); - canvas.drawBitmap(icon, originalLeft + (rect.width() - icon.getWidth()) / 2, originalTop + (rect.height() - icon.getHeight()) / 2, new Paint()); - - } - - /** - * A class to hold reference to the events and their visual representation. An EventRect is - * actually the rectangle that is drawn on the calendar for a given event. There may be more - * than one rectangle for a single event (an event that expands more than one day). In that - * case two instances of the EventRect will be used for a single event. The given event will be - * stored in "originalEvent". But the event that corresponds to rectangle the rectangle - * instance will be stored in "event". - */ - private class EventRect { - public WeekViewEvent event; - public WeekViewEvent originalEvent; - public RectF rectF; - public float left; - public float width; - public float top; - public float bottom; - - /** - * Create a new instance of event rect. An EventRect is actually the rectangle that is drawn - * on the calendar for a given event. There may be more than one rectangle for a single - * event (an event that expands more than one day). In that case two instances of the - * EventRect will be used for a single event. The given event will be stored in - * "originalEvent". But the event that corresponds to rectangle the rectangle instance will - * be stored in "event". - * - * @param event Represents the event which this instance of rectangle represents. - * @param originalEvent The original event that was passed by the user. - * @param rectF The rectangle. - */ - public EventRect(WeekViewEvent event, WeekViewEvent originalEvent, RectF rectF) { - this.event = event; - this.rectF = rectF; - this.originalEvent = originalEvent; - } - } - - - /** - * Gets more events of one/more month(s) if necessary. This method is called when the user is - * scrolling the week view. The week view stores the events of three months: the visible month, - * the previous month, the next month. - * - * @param day The day where the user is currently is. - */ - private void getMoreEvents(Calendar day) { - - // Get more events if the month is changed. - if (mEventRects == null) - mEventRects = new ArrayList<>(); - - if (mEvents == null) - mEvents = new ArrayList<>(); - - if (mWeekViewLoader == null && !isInEditMode()) - throw new IllegalStateException("You must provide a MonthChangeListener"); - - // If a refresh was requested then reset some variables. - if (mRefreshEvents) { - this.clearEvents(); - mFetchedPeriod = -1; - } - - if (mWeekViewLoader != null) { - int periodToFetch = (int) mWeekViewLoader.toWeekViewPeriodIndex(day); - if (!isInEditMode() && (mFetchedPeriod < 0 || mFetchedPeriod != periodToFetch || mRefreshEvents)) { - List newEvents = mWeekViewLoader.onLoad(periodToFetch); - - // Clear events. - this.clearEvents(); - cacheAndSortEvents(newEvents); - calculateHeaderHeight(); - - mFetchedPeriod = periodToFetch; - } - } - - // Prepare to calculate positions of each events. - List tempEvents = mEventRects; - mEventRects = new ArrayList<>(); - - // Iterate through each day with events to calculate the position of the events. - while (tempEvents.size() > 0) { - ArrayList eventRects = new ArrayList<>(tempEvents.size()); - - // Get first event for a day. - EventRect eventRect1 = tempEvents.remove(0); - eventRects.add(eventRect1); - - int i = 0; - while (i < tempEvents.size()) { - // Collect all other events for same day. - EventRect eventRect2 = tempEvents.get(i); - if (isSameDay(eventRect1.event.getStartTime(), eventRect2.event.getStartTime())) { - tempEvents.remove(i); - eventRects.add(eventRect2); - } else { - i++; - } - } - computePositionOfEvents(eventRects); - } - } - - private void clearEvents() { - mEventRects.clear(); - mEvents.clear(); - } - - /** - * Cache the event for smooth scrolling functionality. - * - * @param event The event to cache. - */ - private void cacheEvent(WeekViewEvent event) { - if (event.getStartTime().compareTo(event.getEndTime()) >= 0) - return; - List splitedEvents = event.splitWeekViewEvents(); - for (WeekViewEvent splitedEvent : splitedEvents) { - mEventRects.add(new EventRect(splitedEvent, event, null)); - } - - mEvents.add(event); - } - - /** - * Cache and sort events. - * - * @param events The events to be cached and sorted. - */ - private void cacheAndSortEvents(List events) { - for (WeekViewEvent event : events) { - cacheEvent(event); - } - sortEventRects(mEventRects); - } - - /** - * Sorts the events in ascending order. - * - * @param eventRects The events to be sorted. - */ - private void sortEventRects(List eventRects) { - Collections.sort(eventRects, new Comparator() { - @Override - public int compare(EventRect left, EventRect right) { - long start1 = left.event.getStartTime().getTimeInMillis(); - long start2 = right.event.getStartTime().getTimeInMillis(); - int comparator = start1 > start2 ? 1 : (start1 < start2 ? -1 : 0); - if (comparator == 0) { - long end1 = left.event.getEndTime().getTimeInMillis(); - long end2 = right.event.getEndTime().getTimeInMillis(); - comparator = end1 > end2 ? 1 : (end1 < end2 ? -1 : 0); - } - return comparator; - } - }); - } - - /** - * Calculates the left and right positions of each events. This comes handy specially if events - * are overlapping. - * - * @param eventRects The events along with their wrapper class. - */ - private void computePositionOfEvents(List eventRects) { - // Make "collision groups" for all events that collide with others. - List> collisionGroups = new ArrayList>(); - for (EventRect eventRect : eventRects) { - boolean isPlaced = false; - - outerLoop: - for (List collisionGroup : collisionGroups) { - for (EventRect groupEvent : collisionGroup) { - if (isEventsCollide(groupEvent.event, eventRect.event) && groupEvent.event.isAllDay() == eventRect.event.isAllDay()) { - collisionGroup.add(eventRect); - isPlaced = true; - break outerLoop; - } - } - } - - if (!isPlaced) { - List newGroup = new ArrayList(); - newGroup.add(eventRect); - collisionGroups.add(newGroup); - } - } - - for (List collisionGroup : collisionGroups) { - expandEventsToMaxWidth(collisionGroup); - } - } - - /** - * Expands all the events to maximum possible width. The events will try to occupy maximum - * space available horizontally. - * - * @param collisionGroup The group of events which overlap with each other. - */ - private void expandEventsToMaxWidth(List collisionGroup) { - // Expand the events to maximum possible width. - List> columns = new ArrayList>(); - columns.add(new ArrayList()); - for (EventRect eventRect : collisionGroup) { - boolean isPlaced = false; - for (List column : columns) { - if (column.size() == 0) { - column.add(eventRect); - isPlaced = true; - } else if (!isEventsCollide(eventRect.event, column.get(column.size() - 1).event)) { - column.add(eventRect); - isPlaced = true; - break; - } - } - if (!isPlaced) { - List newColumn = new ArrayList(); - newColumn.add(eventRect); - columns.add(newColumn); - } - } - - // Calculate left and right position for all the events. - // Get the maxRowCount by looking in all columns. - int maxRowCount = 0; - for (List column : columns) { - maxRowCount = Math.max(maxRowCount, column.size()); - } - for (int i = 0; i < maxRowCount; i++) { - // Set the left and right values of the event. - float j = 0; - for (List column : columns) { - if (column.size() >= i + 1) { - EventRect eventRect = column.get(i); - eventRect.width = 1f / columns.size(); - eventRect.left = j / columns.size(); - if (!eventRect.event.isAllDay()) { - eventRect.top = getPassedMinutesInDay(eventRect.event.getStartTime()); - eventRect.bottom = getPassedMinutesInDay(eventRect.event.getEndTime()); - } else { - eventRect.top = 0; - eventRect.bottom = mAllDayEventHeight; - } - mEventRects.add(eventRect); - } - j++; - } - } - } - - /** - * Checks if two events overlap. - * - * @param event1 The first event. - * @param event2 The second event. - * @return true if the events overlap. - */ - private boolean isEventsCollide(WeekViewEvent event1, WeekViewEvent event2) { - long start1 = event1.getStartTime().getTimeInMillis(); - long end1 = event1.getEndTime().getTimeInMillis(); - long start2 = event2.getStartTime().getTimeInMillis(); - long end2 = event2.getEndTime().getTimeInMillis(); - - long minOverlappingMillis = mMinOverlappingMinutes * 60 * 1000; - - return !((start1 + minOverlappingMillis >= end2) || (end1 <= start2 + minOverlappingMillis)); - } - - - /** - * Checks if time1 occurs after (or at the same time) time2. - * - * @param time1 The time to check. - * @param time2 The time to check against. - * @return true if time1 and time2 are equal or if time1 is after time2. Otherwise false. - */ - private boolean isTimeAfterOrEquals(Calendar time1, Calendar time2) { - return !(time1 == null || time2 == null) && time1.getTimeInMillis() >= time2.getTimeInMillis(); - } - - @Override - public void invalidate() { - super.invalidate(); - mAreDimensionsInvalid = true; - } - - ///////////////////////////////////////////////////////////////// - // - // Functions related to setting and getting the properties. - // - ///////////////////////////////////////////////////////////////// - - public void setOnEventClickListener(EventClickListener listener) { - this.mEventClickListener = listener; - } - - public void setDropListener(DropListener dropListener) { - this.mDropListener = dropListener; - } - - public EventClickListener getEventClickListener() { - return mEventClickListener; - } - - public - @Nullable - MonthLoader.MonthChangeListener getMonthChangeListener() { - if (mWeekViewLoader instanceof MonthLoader) - return ((MonthLoader) mWeekViewLoader).getOnMonthChangeListener(); - return null; - } - - public void setMonthChangeListener(MonthLoader.MonthChangeListener monthChangeListener) { - this.mWeekViewLoader = new MonthLoader(monthChangeListener); - } - - /** - * Get event loader in the week view. Event loaders define the interval after which the events - * are loaded in week view. For a MonthLoader events are loaded for every month. You can define - * your custom event loader by extending WeekViewLoader. - * - * @return The event loader. - */ - public WeekViewLoader getWeekViewLoader() { - return mWeekViewLoader; - } - - /** - * Set event loader in the week view. For example, a MonthLoader. Event loaders define the - * interval after which the events are loaded in week view. For a MonthLoader events are loaded - * for every month. You can define your custom event loader by extending WeekViewLoader. - * - * @param loader The event loader. - */ - public void setWeekViewLoader(WeekViewLoader loader) { - this.mWeekViewLoader = loader; - } - - public EventLongPressListener getEventLongPressListener() { - return mEventLongPressListener; - } - - public void setEventLongPressListener(EventLongPressListener eventLongPressListener) { - this.mEventLongPressListener = eventLongPressListener; - } - - public void setEmptyViewClickListener(EmptyViewClickListener emptyViewClickListener) { - this.mEmptyViewClickListener = emptyViewClickListener; - } - - public EmptyViewClickListener getEmptyViewClickListener() { - return mEmptyViewClickListener; - } - - public void setEmptyViewLongPressListener(EmptyViewLongPressListener emptyViewLongPressListener) { - this.mEmptyViewLongPressListener = emptyViewLongPressListener; - } - - public EmptyViewLongPressListener getEmptyViewLongPressListener() { - return mEmptyViewLongPressListener; - } - - public void setScrollListener(ScrollListener scrolledListener) { - this.mScrollListener = scrolledListener; - } - - public ScrollListener getScrollListener() { - return mScrollListener; - } - - public void setTimeColumnResolution(int resolution) { - mTimeColumnResolution = resolution; - } - - public int getTimeColumnResolution() { - return mTimeColumnResolution; - } - - public void setAddEventClickListener(AddEventClickListener addEventClickListener) { - this.mAddEventClickListener = addEventClickListener; - } - - public AddEventClickListener getAddEventClickListener() { - return mAddEventClickListener; - } - - /** - * Get the interpreter which provides the text to show in the header column and the header row. - * - * @return The date, time interpreter. - */ - public DateTimeInterpreter getDateTimeInterpreter() { - if (mDateTimeInterpreter == null) { - mDateTimeInterpreter = new DateTimeInterpreter() { - @Override - public String interpretDate(Calendar date) { - try { - SimpleDateFormat sdf = mDayNameLength == LENGTH_SHORT ? new SimpleDateFormat("EEEEE M/dd", Locale.getDefault()) : new SimpleDateFormat("EEE M/dd", Locale.getDefault()); - return sdf.format(date.getTime()).toUpperCase(); - } catch (Exception e) { - e.printStackTrace(); - return ""; - } - } - - @Override - public String interpretTime(int hour, int minutes) { - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.HOUR_OF_DAY, hour); - calendar.set(Calendar.MINUTE, minutes); - - try { - SimpleDateFormat sdf; - if (DateFormat.is24HourFormat(getContext())) { - sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); - } else { - if ((mTimeColumnResolution % 60 != 0)) { - sdf = new SimpleDateFormat("hh:mm a", Locale.getDefault()); - } else { - sdf = new SimpleDateFormat("hh a", Locale.getDefault()); - } - } - return sdf.format(calendar.getTime()); - } catch (Exception e) { - e.printStackTrace(); - return ""; - } - } - }; - } - return mDateTimeInterpreter; - } - - /** - * Set the interpreter which provides the text to show in the header column and the header row. - * - * @param dateTimeInterpreter The date, time interpreter. - */ - public void setDateTimeInterpreter(DateTimeInterpreter dateTimeInterpreter) { - this.mDateTimeInterpreter = dateTimeInterpreter; - - // Refresh time column width. - initTextTimeWidth(); - } - - - /** - * Get the real number of visible days - * If the amount of days between max date and min date is smaller, that value is returned - * - * @return The real number of visible days - */ - public int getRealNumberOfVisibleDays() { - if (mMinDate == null || mMaxDate == null) - return getNumberOfVisibleDays(); - - return Math.min(mNumberOfVisibleDays, daysBetween(mMinDate, mMaxDate) + 1); - } - - /** - * Get the number of visible days - * - * @return The set number of visible days. - */ - public int getNumberOfVisibleDays() { - return mNumberOfVisibleDays; - } - - /** - * Set the number of visible days in a week. - * - * @param numberOfVisibleDays The number of visible days in a week. - */ - public void setNumberOfVisibleDays(int numberOfVisibleDays) { - this.mNumberOfVisibleDays = numberOfVisibleDays; - resetHomeDate(); - mCurrentOrigin.x = 0; - mCurrentOrigin.y = 0; - invalidate(); - } - - public int getHourHeight() { - return mHourHeight; - } - - public void setHourHeight(int hourHeight) { - mNewHourHeight = hourHeight; - invalidate(); - } - - public int getColumnGap() { - return mColumnGap; - } - - public void setColumnGap(int columnGap) { - mColumnGap = columnGap; - invalidate(); - } - - public int getFirstDayOfWeek() { - return mFirstDayOfWeek; - } - - /** - * Set the first day of the week. First day of the week is used only when the week view is first - * drawn. It does not of any effect after user starts scrolling horizontally. - *

- * Note: This method will only work if the week view is set to display more than 6 days at - * once. - *

- * - * @param firstDayOfWeek The supported values are {@link java.util.Calendar#SUNDAY}, - * {@link java.util.Calendar#MONDAY}, {@link java.util.Calendar#TUESDAY}, - * {@link java.util.Calendar#WEDNESDAY}, {@link java.util.Calendar#THURSDAY}, - * {@link java.util.Calendar#FRIDAY}. - */ - public void setFirstDayOfWeek(int firstDayOfWeek) { - mFirstDayOfWeek = firstDayOfWeek; - invalidate(); - } - - public boolean isShowFirstDayOfWeekFirst() { - return mShowFirstDayOfWeekFirst; - } - - public void setShowFirstDayOfWeekFirst(boolean show) { - mShowFirstDayOfWeekFirst = show; - } - - public int getTextSize() { - return mTextSize; - } - - public void setTextSize(int textSize) { - mTextSize = textSize; - mTodayHeaderTextPaint.setTextSize(mTextSize); - mHeaderTextPaint.setTextSize(mTextSize); - mTimeTextPaint.setTextSize(mTextSize); - invalidate(); - } - - public int getHeaderColumnPadding() { - return mHeaderColumnPadding; - } - - public void setHeaderColumnPadding(int headerColumnPadding) { - mHeaderColumnPadding = headerColumnPadding; - invalidate(); - } - - public int getHeaderColumnTextColor() { - return mHeaderColumnTextColor; - } - - public void setHeaderColumnTextColor(int headerColumnTextColor) { - mHeaderColumnTextColor = headerColumnTextColor; - mHeaderTextPaint.setColor(mHeaderColumnTextColor); - mTimeTextPaint.setColor(mHeaderColumnTextColor); - invalidate(); - } - - public void setTypeface(Typeface typeface) { - if (typeface != null) { - mEventTextPaint.setTypeface(typeface); - mTodayHeaderTextPaint.setTypeface(typeface); - mTimeTextPaint.setTypeface(typeface); - mTypeface = typeface; - init(); - } - } - - public int getHeaderRowPadding() { - return mHeaderRowPadding; - } - - public void setHeaderRowPadding(int headerRowPadding) { - mHeaderRowPadding = headerRowPadding; - invalidate(); - } - - public int getHeaderRowBackgroundColor() { - return mHeaderRowBackgroundColor; - } - - public void setHeaderRowBackgroundColor(int headerRowBackgroundColor) { - mHeaderRowBackgroundColor = headerRowBackgroundColor; - mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor); - invalidate(); - } - - public int getDayBackgroundColor() { - return mDayBackgroundColor; - } - - public void setDayBackgroundColor(int dayBackgroundColor) { - mDayBackgroundColor = dayBackgroundColor; - mDayBackgroundPaint.setColor(mDayBackgroundColor); - invalidate(); - } - - public int getHourSeparatorColor() { - return mHourSeparatorColor; - } - - public void setHourSeparatorColor(int hourSeparatorColor) { - mHourSeparatorColor = hourSeparatorColor; - mHourSeparatorPaint.setColor(mHourSeparatorColor); - invalidate(); - } - - public int getTodayBackgroundColor() { - return mTodayBackgroundColor; - } - - public void setTodayBackgroundColor(int todayBackgroundColor) { - mTodayBackgroundColor = todayBackgroundColor; - mTodayBackgroundPaint.setColor(mTodayBackgroundColor); - invalidate(); - } - - public int getHourSeparatorHeight() { - return mHourSeparatorHeight; - } - - public void setHourSeparatorHeight(int hourSeparatorHeight) { - mHourSeparatorHeight = hourSeparatorHeight; - mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight); - invalidate(); - } - - public int getTodayHeaderTextColor() { - return mTodayHeaderTextColor; - } - - public void setTodayHeaderTextColor(int todayHeaderTextColor) { - mTodayHeaderTextColor = todayHeaderTextColor; - mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor); - invalidate(); - } - - public int getEventTextSize() { - return mEventTextSize; - } - - public void setEventTextSize(int eventTextSize) { - mEventTextSize = eventTextSize; - mEventTextPaint.setTextSize(mEventTextSize); - invalidate(); - } - - public int getEventTextColor() { - return mEventTextColor; - } - - public void setEventTextColor(int eventTextColor) { - mEventTextColor = eventTextColor; - mEventTextPaint.setColor(mEventTextColor); - invalidate(); - } - - public void setTextColorPicker(TextColorPicker textColorPicker) { - this.textColorPicker = textColorPicker; - } - - public TextColorPicker getTextColorPicker() { - return textColorPicker; - } - - public int getEventPadding() { - return mEventPadding; - } - - public void setEventPadding(int eventPadding) { - mEventPadding = eventPadding; - invalidate(); - } - - public int getHeaderColumnBackgroundColor() { - return mHeaderColumnBackgroundColor; - } - - public void setHeaderColumnBackgroundColor(int headerColumnBackgroundColor) { - mHeaderColumnBackgroundColor = headerColumnBackgroundColor; - mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor); - invalidate(); - } - - public int getDefaultEventColor() { - return mDefaultEventColor; - } - - public void setDefaultEventColor(int defaultEventColor) { - mDefaultEventColor = defaultEventColor; - invalidate(); - } - - public int getNewEventColor() { - return mNewEventColor; - } - - public void setNewEventColor(int defaultNewEventColor) { - mNewEventColor = defaultNewEventColor; - invalidate(); - } - - public String getNewEventIdentifier() { - return mNewEventIdentifier; - } - - @Deprecated - public int getNewEventId() { - return Integer.parseInt(mNewEventIdentifier); - } - - public void setNewEventIdentifier(String newEventId) { - this.mNewEventIdentifier = newEventId; - } - - @Deprecated - public void setNewEventId(int newEventId) { - this.mNewEventIdentifier = String.valueOf(newEventId); - } - - public int getNewEventLengthInMinutes() { - return mNewEventLengthInMinutes; - } - - public void setNewEventLengthInMinutes(int newEventLengthInMinutes) { - this.mNewEventLengthInMinutes = newEventLengthInMinutes; - } - - public int getNewEventTimeResolutionInMinutes() { - return mNewEventTimeResolutionInMinutes; - } - - public void setNewEventTimeResolutionInMinutes(int newEventTimeResolutionInMinutes) { - this.mNewEventTimeResolutionInMinutes = newEventTimeResolutionInMinutes; - } - - /** - * Note: Use {@link #setDateTimeInterpreter(DateTimeInterpreter)} and - * {@link #getDateTimeInterpreter()} instead. - * - * @return Either long or short day name is being used. - */ - @Deprecated - public int getDayNameLength() { - return mDayNameLength; - } - - /** - * Set the length of the day name displayed in the header row. Example of short day names is - * 'M' for 'Monday' and example of long day names is 'Mon' for 'Monday'. - *

- * Note: Use {@link #setDateTimeInterpreter(DateTimeInterpreter)} instead. - *

- * - * @param length Supported values are {@link com.alamkanak.weekview.WeekView#LENGTH_SHORT} and - * {@link com.alamkanak.weekview.WeekView#LENGTH_LONG}. - */ - @Deprecated - public void setDayNameLength(int length) { - if (length != LENGTH_LONG && length != LENGTH_SHORT) { - throw new IllegalArgumentException("length parameter must be either LENGTH_LONG or LENGTH_SHORT"); - } - this.mDayNameLength = length; - } - - public int getOverlappingEventGap() { - return mOverlappingEventGap; - } - - /** - * Set the gap between overlapping events. - * - * @param overlappingEventGap The gap between overlapping events. - */ - public void setOverlappingEventGap(int overlappingEventGap) { - this.mOverlappingEventGap = overlappingEventGap; - invalidate(); - } - - public int getEventCornerRadius() { - return mEventCornerRadius; - } - - /** - * Set corner radius for event rect. - * - * @param eventCornerRadius the radius in px. - */ - public void setEventCornerRadius(int eventCornerRadius) { - mEventCornerRadius = eventCornerRadius; - } - - public int getEventMarginVertical() { - return mEventMarginVertical; - } - - /** - * Set the top and bottom margin of the event. The event will release this margin from the top - * and bottom edge. This margin is useful for differentiation consecutive events. - * - * @param eventMarginVertical The top and bottom margin. - */ - public void setEventMarginVertical(int eventMarginVertical) { - this.mEventMarginVertical = eventMarginVertical; - invalidate(); - } - - /** - * Returns the first visible day in the week view. - * - * @return The first visible day in the week view. - */ - public Calendar getFirstVisibleDay() { - return mFirstVisibleDay; - } - - /** - * Returns the last visible day in the week view. - * - * @return The last visible day in the week view. - */ - public Calendar getLastVisibleDay() { - return mLastVisibleDay; - } - - /** - * Get the scrolling speed factor in horizontal direction. - * - * @return The speed factor in horizontal direction. - */ - public float getXScrollingSpeed() { - return mXScrollingSpeed; - } - - /** - * Sets the speed for horizontal scrolling. - * - * @param xScrollingSpeed The new horizontal scrolling speed. - */ - public void setXScrollingSpeed(float xScrollingSpeed) { - this.mXScrollingSpeed = xScrollingSpeed; - } - - /** - * Get the earliest day that can be displayed. Will return null if no minimum date is set. - * - * @return the earliest day that can be displayed, null if no minimum date set - */ - public Calendar getMinDate() { - return mMinDate; - } - - /** - * Set the earliest day that can be displayed. This will determine the left horizontal scroll - * limit. The default value is null (allow unlimited scrolling into the past). - * - * @param minDate The new minimum date (pass null for no minimum) - */ - public void setMinDate(Calendar minDate) { - if (minDate != null) { - minDate.set(Calendar.HOUR_OF_DAY, 0); - minDate.set(Calendar.MINUTE, 0); - minDate.set(Calendar.SECOND, 0); - minDate.set(Calendar.MILLISECOND, 0); - if (mMaxDate != null && minDate.after(mMaxDate)) { - throw new IllegalArgumentException("minDate cannot be later than maxDate"); - } - } - - mMinDate = minDate; - resetHomeDate(); - mCurrentOrigin.x = 0; - invalidate(); - } - - /** - * Get the latest day that can be displayed. Will return null if no maximum date is set. - * - * @return the latest day the can be displayed, null if no max date set - */ - public Calendar getMaxDate() { - return mMaxDate; - } - - /** - * Set the latest day that can be displayed. This will determine the right horizontal scroll - * limit. The default value is null (allow unlimited scrolling in to the future). - * - * @param maxDate The new maximum date (pass null for no maximum) - */ - public void setMaxDate(Calendar maxDate) { - if (maxDate != null) { - maxDate.set(Calendar.HOUR_OF_DAY, 0); - maxDate.set(Calendar.MINUTE, 0); - maxDate.set(Calendar.SECOND, 0); - maxDate.set(Calendar.MILLISECOND, 0); - if (mMinDate != null && maxDate.before(mMinDate)) { - throw new IllegalArgumentException("maxDate has to be after minDate"); - } - } - - mMaxDate = maxDate; - resetHomeDate(); - mCurrentOrigin.x = 0; - invalidate(); - } - - /** - * Whether weekends should have a background color different from the normal day background - * color. The weekend background colors are defined by the attributes - * `futureWeekendBackgroundColor` and `pastWeekendBackgroundColor`. - * - * @return True if weekends should have different background colors. - */ - public boolean isShowDistinctWeekendColor() { - return mShowDistinctWeekendColor; - } - - /** - * Set whether weekends should have a background color different from the normal day background - * color. The weekend background colors are defined by the attributes - * `futureWeekendBackgroundColor` and `pastWeekendBackgroundColor`. - * - * @param showDistinctWeekendColor True if weekends should have different background colors. - */ - public void setShowDistinctWeekendColor(boolean showDistinctWeekendColor) { - this.mShowDistinctWeekendColor = showDistinctWeekendColor; - invalidate(); - } - - /** - * auto calculate limit time on events in visible days. - */ - public void setAutoLimitTime(boolean isAuto) { - this.mAutoLimitTime = isAuto; - invalidate(); - } - - private void recalculateHourHeight() { - int height = (int) ((getHeight() - (mHeaderHeight + mHeaderRowPadding * 2 + mTimeTextHeight / 2 + mHeaderMarginBottom)) / (this.mMaxTime - this.mMinTime)); - if (height > mHourHeight) { - if (height > mMaxHourHeight) - mMaxHourHeight = height; - mNewHourHeight = height; - } - } - - /** - * Set visible time span. - * - * @param startHour limit time display on top (between 0~24) - * @param endHour limit time display at bottom (between 0~24 and larger than startHour) - */ - public void setLimitTime(int startHour, int endHour) { - if (endHour <= startHour) { - throw new IllegalArgumentException("endHour must larger startHour."); - } else if (startHour < 0) { - throw new IllegalArgumentException("startHour must be at least 0."); - } else if (endHour > 24) { - throw new IllegalArgumentException("endHour can't be higher than 24."); - } - this.mMinTime = startHour; - this.mMaxTime = endHour; - recalculateHourHeight(); - invalidate(); - } - - /** - * Set minimal shown time - * - * @param startHour limit time display on top (between 0~24) and smaller than endHour - */ - public void setMinTime(int startHour) { - if (mMaxTime <= startHour) { - throw new IllegalArgumentException("startHour must smaller than endHour"); - } else if (startHour < 0) { - throw new IllegalArgumentException("startHour must be at least 0."); - } - this.mMinTime = startHour; - recalculateHourHeight(); - } - - /** - * Set highest shown time - * - * @param endHour limit time display at bottom (between 0~24 and larger than startHour) - */ - public void setMaxTime(int endHour) { - if (endHour <= mMinTime) { - throw new IllegalArgumentException("endHour must larger startHour."); - } else if (endHour > 24) { - throw new IllegalArgumentException("endHour can't be higher than 24."); - } - this.mMaxTime = endHour; - recalculateHourHeight(); - invalidate(); - } - - /** - * Whether past and future days should have two different background colors. The past and - * future day colors are defined by the attributes `futureBackgroundColor` and - * `pastBackgroundColor`. - * - * @return True if past and future days should have two different background colors. - */ - public boolean isShowDistinctPastFutureColor() { - return mShowDistinctPastFutureColor; - } - - /** - * Set whether weekends should have a background color different from the normal day background - * color. The past and future day colors are defined by the attributes `futureBackgroundColor` - * and `pastBackgroundColor`. - * - * @param showDistinctPastFutureColor True if past and future should have two different - * background colors. - */ - public void setShowDistinctPastFutureColor(boolean showDistinctPastFutureColor) { - this.mShowDistinctPastFutureColor = showDistinctPastFutureColor; - invalidate(); - } - - /** - * Get whether "now" line should be displayed. "Now" line is defined by the attributes - * `nowLineColor` and `nowLineThickness`. - * - * @return True if "now" line should be displayed. - */ - public boolean isShowNowLine() { - return mShowNowLine; - } - - /** - * Set whether "now" line should be displayed. "Now" line is defined by the attributes - * `nowLineColor` and `nowLineThickness`. - * - * @param showNowLine True if "now" line should be displayed. - */ - public void setShowNowLine(boolean showNowLine) { - this.mShowNowLine = showNowLine; - invalidate(); - } - - /** - * Get the "now" line color. - * - * @return The color of the "now" line. - */ - public int getNowLineColor() { - return mNowLineColor; - } - - /** - * Set the "now" line color. - * - * @param nowLineColor The color of the "now" line. - */ - public void setNowLineColor(int nowLineColor) { - this.mNowLineColor = nowLineColor; - invalidate(); - } - - /** - * Get the "now" line thickness. - * - * @return The thickness of the "now" line. - */ - public int getNowLineThickness() { - return mNowLineThickness; - } - - /** - * Set the "now" line thickness. - * - * @param nowLineThickness The thickness of the "now" line. - */ - public void setNowLineThickness(int nowLineThickness) { - this.mNowLineThickness = nowLineThickness; - invalidate(); - } - - /** - * Get whether the week view should fling horizontally. - * - * @return True if the week view has horizontal fling enabled. - */ - public boolean isHorizontalFlingEnabled() { - return mHorizontalFlingEnabled; - } - - /** - * Set whether the week view should fling horizontally. - * - * @param enabled whether the week view should fling horizontally - */ - public void setHorizontalFlingEnabled(boolean enabled) { - mHorizontalFlingEnabled = enabled; - } - - /** - * Get whether the week view should fling vertically. - * - * @return True if the week view has vertical fling enabled. - */ - public boolean isVerticalFlingEnabled() { - return mVerticalFlingEnabled; - } - - /** - * Set whether the week view should fling vertically. - * - * @param enabled whether the week view should fling vertically - */ - public void setVerticalFlingEnabled(boolean enabled) { - mVerticalFlingEnabled = enabled; - } - - /** - * Get the height of AllDay-events. - * - * @return Height of AllDay-events. - */ - public int getAllDayEventHeight() { - return mAllDayEventHeight; - } - - /** - * Set the height of AllDay-events. - * - * @param height the new height of AllDay-events - */ - public void setAllDayEventHeight(int height) { - mAllDayEventHeight = height; - } - - /** - * Enable zoom focus point - * If you set this to false the `zoomFocusPoint` won't take effect any more while zooming. - * The zoom will always be focused at the center of your gesture. - * - * @param zoomFocusPointEnabled whether the zoomFocusPoint is enabled - */ - public void setZoomFocusPointEnabled(boolean zoomFocusPointEnabled) { - mZoomFocusPointEnabled = zoomFocusPointEnabled; - } - - /* - * Is focus point enabled - * @return fixed focus point enabled? - */ - public boolean isZoomFocusPointEnabled() { - return mZoomFocusPointEnabled; - } - - /* - * Get focus point - * 0 = top of view, 1 = bottom of view - * The focused point (multiplier of the view height) where the week view is zoomed around. - * This point will not move while zooming. - * @return focus point - */ - public float getZoomFocusPoint() { - return mZoomFocusPoint; - } - - /** - * Set focus point - * 0 = top of view, 1 = bottom of view - * The focused point (multiplier of the view height) where the week view is zoomed around. - * This point will not move while zooming. - * - * @param zoomFocusPoint the new zoomFocusPoint - */ - public void setZoomFocusPoint(float zoomFocusPoint) { - if (0 > zoomFocusPoint || zoomFocusPoint > 1) - throw new IllegalStateException("The zoom focus point percentage has to be between 0 and 1"); - mZoomFocusPoint = zoomFocusPoint; - } - - - /** - * Get scroll duration - * - * @return scroll duration - */ - public int getScrollDuration() { - return mScrollDuration; - } - - /** - * Set the scroll duration - * - * @param scrollDuration the new scrollDuraction - */ - public void setScrollDuration(int scrollDuration) { - mScrollDuration = scrollDuration; - } - - public int getMaxHourHeight() { - return mMaxHourHeight; - } - - public void setMaxHourHeight(int maxHourHeight) { - mMaxHourHeight = maxHourHeight; - } - - public int getMinHourHeight() { - return mMinHourHeight; - } - - public void setMinHourHeight(int minHourHeight) { - this.mMinHourHeight = minHourHeight; - } - - public int getPastBackgroundColor() { - return mPastBackgroundColor; - } - - public void setPastBackgroundColor(int pastBackgroundColor) { - this.mPastBackgroundColor = pastBackgroundColor; - mPastBackgroundPaint.setColor(mPastBackgroundColor); - } - - public int getFutureBackgroundColor() { - return mFutureBackgroundColor; - } - - public void setFutureBackgroundColor(int futureBackgroundColor) { - this.mFutureBackgroundColor = futureBackgroundColor; - mFutureBackgroundPaint.setColor(mFutureBackgroundColor); - } - - public int getPastWeekendBackgroundColor() { - return mPastWeekendBackgroundColor; - } - - public void setPastWeekendBackgroundColor(int pastWeekendBackgroundColor) { - this.mPastWeekendBackgroundColor = pastWeekendBackgroundColor; - this.mPastWeekendBackgroundPaint.setColor(mPastWeekendBackgroundColor); - } - - public int getFutureWeekendBackgroundColor() { - return mFutureWeekendBackgroundColor; - } - - public void setFutureWeekendBackgroundColor(int futureWeekendBackgroundColor) { - this.mFutureWeekendBackgroundColor = futureWeekendBackgroundColor; - this.mFutureWeekendBackgroundPaint.setColor(mFutureWeekendBackgroundColor); - } - - public Drawable getNewEventIconDrawable() { - return mNewEventIconDrawable; - } - - public void setNewEventIconDrawable(Drawable newEventIconDrawable) { - this.mNewEventIconDrawable = newEventIconDrawable; - } - - public void enableDropListener() { - this.mEnableDropListener = true; - //set drag and drop listener, required Honeycomb+ Api level - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - setOnDragListener(new DragListener()); - } - } - - public void disableDropListener() { - this.mEnableDropListener = false; - //set drag and drop listener, required Honeycomb+ Api level - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - setOnDragListener(null); - } - } - - public boolean isDropListenerEnabled() { - return this.mEnableDropListener; - } - - public void setMinOverlappingMinutes(int minutes) { - this.mMinOverlappingMinutes = minutes; - } - - public int getMinOverlappingMinutes() { - return this.mMinOverlappingMinutes; - } - - ///////////////////////////////////////////////////////////////// - // - // Functions related to scrolling. - // - ///////////////////////////////////////////////////////////////// - - @Override - public boolean onTouchEvent(MotionEvent event) { - mScaleDetector.onTouchEvent(event); - boolean val = mGestureDetector.onTouchEvent(event); - - // Check after call of mGestureDetector, so mCurrentFlingDirection and mCurrentScrollDirection are set. - if (event.getAction() == MotionEvent.ACTION_UP && !mIsZooming && mCurrentFlingDirection == Direction.NONE) { - if (mCurrentScrollDirection == Direction.RIGHT || mCurrentScrollDirection == Direction.LEFT) { - goToNearestOrigin(); - } - mCurrentScrollDirection = Direction.NONE; - } - - return val; - } - - private void goToNearestOrigin() { - double leftDays = mCurrentOrigin.x / (mWidthPerDay + mColumnGap); - - if (mCurrentFlingDirection != Direction.NONE) { - // snap to nearest day - leftDays = Math.round(leftDays); - } else if (mCurrentScrollDirection == Direction.LEFT) { - // snap to last day - leftDays = Math.floor(leftDays); - } else if (mCurrentScrollDirection == Direction.RIGHT) { - // snap to next day - leftDays = Math.ceil(leftDays); - } else { - // snap to nearest day - leftDays = Math.round(leftDays); - } - - int nearestOrigin = (int) (mCurrentOrigin.x - leftDays * (mWidthPerDay + mColumnGap)); - boolean mayScrollHorizontal = mCurrentOrigin.x - nearestOrigin < getXMaxLimit() - && mCurrentOrigin.x - nearestOrigin > getXMinLimit(); - - if (mayScrollHorizontal) { - mScroller.startScroll((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, -nearestOrigin, 0); - ViewCompat.postInvalidateOnAnimation(WeekView.this); - } - - if (nearestOrigin != 0 && mayScrollHorizontal) { - // Stop current animation. - mScroller.forceFinished(true); - // Snap to date. - mScroller.startScroll((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, -nearestOrigin, 0, (int) (Math.abs(nearestOrigin) / mWidthPerDay * mScrollDuration)); - ViewCompat.postInvalidateOnAnimation(WeekView.this); - } - // Reset scrolling and fling direction. - mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE; - } - - - @Override - public void computeScroll() { - super.computeScroll(); - - if (mScroller.isFinished()) { - if (mCurrentFlingDirection != Direction.NONE) { - // Snap to day after fling is finished. - goToNearestOrigin(); - } - } else { - if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) { - goToNearestOrigin(); - } else if (mScroller.computeScrollOffset()) { - mCurrentOrigin.y = mScroller.getCurrY(); - mCurrentOrigin.x = mScroller.getCurrX(); - ViewCompat.postInvalidateOnAnimation(this); - } - } - } - - /** - * Check if scrolling should be stopped. - * - * @return true if scrolling should be stopped before reaching the end of animation. - */ - private boolean forceFinishScroll() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - // current velocity only available since api 14 - return mScroller.getCurrVelocity() <= mMinimumFlingVelocity; - } else { - return false; - } - } - - - ///////////////////////////////////////////////////////////////// - // - // Public methods. - // - ///////////////////////////////////////////////////////////////// - - /** - * Show today on the week view. - */ - public void goToToday() { - Calendar today = Calendar.getInstance(); - goToDate(today); - } - - /** - * Show a specific day on the week view. - * - * @param date The date to show. - */ - public void goToDate(Calendar date) { - mScroller.forceFinished(true); - mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE; - - date.set(Calendar.HOUR_OF_DAY, 0); - date.set(Calendar.MINUTE, 0); - date.set(Calendar.SECOND, 0); - date.set(Calendar.MILLISECOND, 0); - - if (mAreDimensionsInvalid) { - mScrollToDay = date; - return; - } - - mRefreshEvents = true; - - mCurrentOrigin.x = -daysBetween(mHomeDate, date) * (mWidthPerDay + mColumnGap); - invalidate(); - } - - /** - * Refreshes the view and loads the events again. - */ - public void notifyDatasetChanged() { - mRefreshEvents = true; - invalidate(); - } - - /** - * Vertically scroll to a specific hour in the week view. - * - * @param hour The hour to scroll to in 24-hour format. Supported values are 0-24. - */ - public void goToHour(double hour) { - if (mAreDimensionsInvalid) { - mScrollToHour = hour; - return; - } - - int verticalOffset = 0; - if (hour > mMaxTime) - verticalOffset = mHourHeight * (mMaxTime - mMinTime); - else if (hour > mMinTime) - verticalOffset = (int) (mHourHeight * hour); - - if (verticalOffset > mHourHeight * (mMaxTime - mMinTime) - getHeight() + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom) - verticalOffset = (int) (mHourHeight * (mMaxTime - mMinTime) - getHeight() + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom); - - mCurrentOrigin.y = -verticalOffset; - invalidate(); - } - - /** - * Get the first hour that is visible on the screen. - * - * @return The first hour that is visible. - */ - public double getFirstVisibleHour() { - return -mCurrentOrigin.y / mHourHeight; - } - - /** - * Determine whether a given calendar day falls within the scroll limits set for this view. - * - * @param day the day to check - * @return True if there are no limit or the date is within the limits. - * @see #setMinDate(Calendar) - * @see #setMaxDate(Calendar) - */ - public boolean dateIsValid(Calendar day) { - if (mMinDate != null && day.before(mMinDate)) { - return false; - } - if (mMaxDate != null && day.after(mMaxDate)) { - return false; - } - return true; - } - - ///////////////////////////////////////////////////////////////// - // - // Interfaces. - // - ///////////////////////////////////////////////////////////////// - - public interface DropListener { - /** - * Triggered when view dropped - * - * @param view: dropped view. - * @param date: object set with the date and time of the dropped coordinates on the view. - */ - void onDrop(View view, Calendar date); - } - - public interface EventClickListener { - /** - * Triggered when clicked on one existing event - * - * @param event: event clicked. - * @param eventRect: view containing the clicked event. - */ - void onEventClick(WeekViewEvent event, RectF eventRect); - } - - public interface EventLongPressListener { - /** - * Similar to {@link com.alamkanak.weekview.WeekView.EventClickListener} but with a long press. - * - * @param event: event clicked. - * @param eventRect: view containing the clicked event. - */ - void onEventLongPress(WeekViewEvent event, RectF eventRect); - } - - public interface EmptyViewClickListener { - /** - * Triggered when the users clicks on a empty space of the calendar. - * - * @param date: {@link Calendar} object set with the date and time of the clicked position on the view. - */ - void onEmptyViewClicked(Calendar date); - - } - - public interface EmptyViewLongPressListener { - /** - * Similar to {@link com.alamkanak.weekview.WeekView.EmptyViewClickListener} but with long press. - * - * @param time: {@link Calendar} object set with the date and time of the long pressed position on the view. - */ - void onEmptyViewLongPress(Calendar time); - } - - public interface ScrollListener { - /** - * Called when the first visible day has changed. - *

- * (this will also be called during the first draw of the weekview) - * - * @param newFirstVisibleDay The new first visible day - * @param oldFirstVisibleDay The old first visible day (is null on the first call). - */ - void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay); - } - - public interface AddEventClickListener { - /** - * Triggered when the users clicks to create a new event. - * - * @param startTime The startTime of a new event - * @param endTime The endTime of a new event - */ - void onAddEventClicked(Calendar startTime, Calendar endTime); - } - - /** - * A simple GestureListener that holds the focused hour while scaling. - */ - private class WeekViewGestureListener implements ScaleGestureDetector.OnScaleGestureListener { - float mFocusedPointY; - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - mIsZooming = false; - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - mIsZooming = true; - goToNearestOrigin(); - - // Calculate focused point for scale action - if (mZoomFocusPointEnabled) { - // Use fractional focus, percentage of height - mFocusedPointY = (getHeight() - mHeaderHeight - mHeaderRowPadding * 2 - mHeaderMarginBottom) * mZoomFocusPoint; - } else { - // Grab focus - mFocusedPointY = detector.getFocusY(); - } - - return true; - } - - @Override - public boolean onScale(ScaleGestureDetector detector) { - final float scale = detector.getScaleFactor(); - - mNewHourHeight = Math.round(mHourHeight * scale); - - // Calculating difference - float diffY = mFocusedPointY - mCurrentOrigin.y; - // Scaling difference - diffY = diffY * scale - diffY; - // Updating week view origin - mCurrentOrigin.y -= diffY; - - invalidate(); - return true; - } - - } - - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) - private class DragListener implements View.OnDragListener { - @Override - public boolean onDrag(View v, DragEvent e) { - switch (e.getAction()) { - case DragEvent.ACTION_DROP: - if (e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) { - Calendar selectedTime = getTimeFromPoint(e.getX(), e.getY()); - if (selectedTime != null) { - mDropListener.onDrop(v, selectedTime); - } - } - break; - } - return true; - } - } -} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekView.kt b/library/src/main/java/com/alamkanak/weekview/WeekView.kt new file mode 100644 index 000000000..eb20f0aae --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/WeekView.kt @@ -0,0 +1,2529 @@ +package com.alamkanak.weekview + +import android.content.Context +import android.graphics.* +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import androidx.annotation.ColorInt +import androidx.core.view.GestureDetectorCompat +import androidx.core.view.ViewCompat +import androidx.interpolator.view.animation.FastOutLinearInInterpolator +import androidx.appcompat.content.res.AppCompatResources +import android.text.* +import android.text.format.DateFormat +import android.text.style.StyleSpan +import android.util.AttributeSet +import android.util.TypedValue +import android.view.* +import android.widget.OverScroller +import com.alamkanak.weekview.MonthLoader.MonthChangeListener +import com.alamkanak.weekview.WeekViewUtil.daysBetween +import com.alamkanak.weekview.WeekViewUtil.getPassedMinutesInDay +import com.alamkanak.weekview.WeekViewUtil.isSameDay +import com.alamkanak.weekview.WeekViewUtil.today +import java.text.SimpleDateFormat +import java.util.* + +class WeekView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { + //region fields and properties + val drawPerformanceTester = DrawPerformanceTester(false) + private var mHomeDate: Calendar? = null + /** + * the earliest day that can be displayed. null if no minimum date is set. + */ + var minDate: Calendar? = null + set(value) { + if (value === field) + return + if (value != null) { + value.set(Calendar.HOUR_OF_DAY, 0) + value.set(Calendar.MINUTE, 0) + value.set(Calendar.SECOND, 0) + value.set(Calendar.MILLISECOND, 0) + if (maxDate != null && value.after(maxDate)) { + throw IllegalArgumentException("minDate cannot be later than maxDate") + } + if (field != null && WeekViewUtil.isSameDay(field!!, value)) + return + } + field = value + resetHomeDate() + mCurrentOrigin.x = 0f + invalidate() + } + /** + * the latest day that can be displayed. null if no maximum date is set. + */ + var maxDate: Calendar? = null + set(value) { + if (field === value) + return + if (value != null) { + value.set(Calendar.HOUR_OF_DAY, 0) + value.set(Calendar.MINUTE, 0) + value.set(Calendar.SECOND, 0) + value.set(Calendar.MILLISECOND, 0) + if (minDate != null && value.before(minDate)) { + throw IllegalArgumentException("maxDate has to be after minDate") + } + if (field != null && WeekViewUtil.isSameDay(field!!, value)) + return + } + field = value + resetHomeDate() + mCurrentOrigin.x = 0f + invalidate() + } + private val timeTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val mHeaderWeekDayTitleTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val mHeaderWeekDaySubtitleTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val mHeaderWeekDayTitleTodayTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val mHeaderWeekDaySubtitleTodayTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val mEventTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.LINEAR_TEXT_FLAG) + private val sideTitleTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val sideSubtitleTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val allDaySideTitleTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private val mEmptyEventPaint = Paint() + private val mHeaderBackgroundPaint: Paint = Paint() + private val mDayBackgroundPaint: Paint = Paint() + private val mHourSeparatorPaint = Paint() + private val mTodayColumnBackgroundPaint: Paint = Paint() + private val mFutureBackgroundPaint: Paint = Paint() + private val mPastBackgroundPaint = Paint() + private val mFutureWeekendBackgroundPaint = Paint() + private val mPastWeekendBackgroundPaint = Paint() + private val mNowLinePaint = Paint() + private val mEventBackgroundPaint = Paint() + private val mNewEventBackgroundPaint = Paint() + private var containsAllDayEvent: Boolean = false + private var timeTextWidth: Float = 0f + private var timeTextHeight: Float = 0f + var headerWeekDayTitleTextHeight: Float = 0f + private set + private var headerHeight: Float = 0f + var headerWeekDaySubtitleTextHeight: Float = 0f + private set + private var mGestureDetector: GestureDetectorCompat? = null + private var mScroller: OverScroller? = null + private val mCurrentOrigin = PointF(0f, 0f) + private var mCurrentScrollDirection = Direction.NONE + private var widthPerDay: Float = 0f + private var mHeaderColumnWidth: Float = 0f + private var eventRects: MutableList? = null + private val mEvents = ArrayList() + private var mFetchedPeriod = -1 // the middle period the calendar has fetched. + private var mRefreshEvents = false + private var mCurrentFlingDirection = Direction.NONE + private val scaleDetector = ScaleGestureDetector(context, WeekViewGestureListener()) + private var mIsZooming: Boolean = false + /** + * the first visible day in the week view. + */ + var firstVisibleDay: Calendar? = null + private set + + private var mMinimumFlingVelocity = 0 + private var mScaledTouchSlop = 0 + private var mNewEventRect: EventRect? = null + var textColorPicker: TextColorPicker? = null + set(value) { + if (field == value) + return + field = value + invalidate() + } + private var mSizeOfWeekView: Float = 0f + private var mDistanceDone = 0f + private var mDistanceMin: Float = 0f + private var mOffsetValueToSecureScreen = 9 + private var mStartOriginForScroll = 0f + private var mNewHourHeight = -1 + var minHourHeight = 0 + //no minimum specified (will be dynamic, based on screen) + private var mEffectiveMinHourHeight = minHourHeight + //compensates for the fact that you can't keep zooming out. + var maxHourHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 125f, resources.displayMetrics).toInt() + var newEventIdentifier: String? = "-100" + var newEventIconDrawable: Drawable? = null + var newEventLengthInMinutes = 60 + var newEventTimeResolutionInMinutes = 15 + var isShowFirstDayOfWeekFirst = false + private var mIsFirstDraw = true + private var mAreDimensionsInvalid = true + /** + * the scrolling speed factor in horizontal direction. + */ + var xScrollingSpeed = 1f + private var mScrollToDay: Calendar? = null + private var mScrollToHour = -1.0 + /** + * corner radius for event rect (in px) + */ + var eventCornerRadius = 0.0f + /** + * whether the week view should fling horizontally. + */ + var isHorizontalFlingEnabled = true + /** + * whether the week view should fling vertically. + */ + var isVerticalFlingEnabled = true + /** + * the height of AllDay-events. + */ + var allDayEventHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40f, resources.displayMetrics).toInt() + /** + * If you set this to false the `zoomFocusPoint` won't take effect any more while zooming. + * The zoom will always be focused at the center of your gesture. + */ + var isZoomFocusPointEnabled = true + var scrollDuration = 250 + var timeColumnResolution = 60 + var typeface: Typeface? = Typeface.DEFAULT_BOLD + set(value) { + if (value == field) + return + if (value != null) { + field = value + init() + } + } + var allDaySideTitleText: String? = null + set(value) { + if (value == field) + return + field = value + invalidate() + } + + private var mMinTime = 0 + private var mMaxTime = 24 + + /** + * auto calculate limit time on events in visible days. + */ + var autoLimitTime = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var minOverlappingMinutes = 0 + + // Listeners. + var eventClickListener: EventClickListener? = null + var eventLongPressListener: EventLongPressListener? = null + /** + * event loader in the week view. Event loaders define the interval after which the events + * are loaded in week view. For a MonthLoader events are loaded for every month. You can define + * your custom event loader by extending WeekViewLoader. + */ + val weekViewLoader: WeekViewLoader = PrefetchingWeekViewLoader(MonthLoader(object : MonthChangeListener { + override fun onMonthChange(newYear: Int, newMonth: Int): MutableList? { + return monthChangeListener?.onMonthChange(newYear, newMonth) + } + })) + var emptyViewClickListener: EmptyViewClickListener? = null + var emptyViewLongPressListener: EmptyViewLongPressListener? = null + var scrollListener: ScrollListener? = null + var addEventClickListener: AddEventClickListener? = null + var dropListener: DropListener? = null + set(value) { + if (field == value) + return + field = value + setOnDragListener(if (value != null) DragListener() else null) + } + + var enableDrawHeaderBackgroundOnlyOnWeekDays = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + var sideTitleText: String? = null + set(value) { + if (field == value) + return + field = value + invalidate() + } + var sideSubtitleText: String? = null + set(value) { + if (field == value) + return + field = value + invalidate() + } + var untitledEventText: String? = null + set(value) { + if (field == value) + return + field = value + invalidate() + } + + private val mGestureListener = object : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent): Boolean { + goToNearestOrigin() + return true + } + + override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { + // Check if view is zoomed. + if (mIsZooming) + return true + + when (mCurrentScrollDirection) { + WeekView.Direction.NONE -> { + // Allow scrolling only in one direction. + mCurrentScrollDirection = if (Math.abs(distanceX) > Math.abs(distanceY)) { + if (distanceX > 0) { + Direction.LEFT + } else { + Direction.RIGHT + } + } else { + Direction.VERTICAL + } + } + WeekView.Direction.LEFT -> { + // Change direction if there was enough change. + if (Math.abs(distanceX) > Math.abs(distanceY) && distanceX < -mScaledTouchSlop) { + mCurrentScrollDirection = Direction.RIGHT + } + } + WeekView.Direction.RIGHT -> { + // Change direction if there was enough change. + if (Math.abs(distanceX) > Math.abs(distanceY) && distanceX > mScaledTouchSlop) { + mCurrentScrollDirection = Direction.LEFT + } + } + else -> { + } + } + + // Calculate the new origin after scroll. + when (mCurrentScrollDirection) { + WeekView.Direction.LEFT, WeekView.Direction.RIGHT -> { + val minX = xMinLimit + val maxX = xMaxLimit + + mDistanceDone = if (e2.x < 0) { + e2.x - e1.x + } else { + e1.x - e2.x + } + + when { + mCurrentOrigin.x - distanceX * xScrollingSpeed > maxX -> mCurrentOrigin.x = maxX + mCurrentOrigin.x - distanceX * xScrollingSpeed < minX -> mCurrentOrigin.x = minX + else -> mCurrentOrigin.x -= distanceX * xScrollingSpeed + } + ViewCompat.postInvalidateOnAnimation(this@WeekView) + } + WeekView.Direction.VERTICAL -> { + val minY = yMinLimit + val maxY = yMaxLimit + when { + mCurrentOrigin.y - distanceY > maxY -> mCurrentOrigin.y = maxY + mCurrentOrigin.y - distanceY < minY -> mCurrentOrigin.y = minY + else -> mCurrentOrigin.y -= distanceY + } + ViewCompat.postInvalidateOnAnimation(this@WeekView) + } + else -> { + } + } + return true + } + + override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + if (mIsZooming) + return true + + if (mCurrentFlingDirection == Direction.LEFT && !isHorizontalFlingEnabled || + mCurrentFlingDirection == Direction.RIGHT && !isHorizontalFlingEnabled || + mCurrentFlingDirection == Direction.VERTICAL && !isVerticalFlingEnabled) { + return true + } + + mScroller!!.forceFinished(true) + + mCurrentFlingDirection = mCurrentScrollDirection + when (mCurrentFlingDirection) { + WeekView.Direction.LEFT, WeekView.Direction.RIGHT -> if (!isScrollNumberOfVisibleDays) { + mScroller!!.fling(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), (velocityX * xScrollingSpeed).toInt(), 0, xMinLimit.toInt(), xMaxLimit.toInt(), yMinLimit.toInt(), yMaxLimit.toInt()) + } + WeekView.Direction.VERTICAL -> mScroller!!.fling(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), 0, velocityY.toInt(), xMinLimit.toInt(), xMaxLimit.toInt(), yMinLimit.toInt(), yMaxLimit.toInt()) + else -> { + } + } + + ViewCompat.postInvalidateOnAnimation(this@WeekView) + return true + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + // If the tap was on an event then trigger the callback. + if (eventRects != null && eventClickListener != null) + for (eventRect in eventRects!!) { + if (newEventIdentifier != eventRect.event.id && eventRect.rectF != null && e.x > eventRect.rectF!!.left && e.x < eventRect.rectF!!.right && e.y > eventRect.rectF!!.top && e.y < eventRect.rectF!!.bottom) { + eventClickListener!!.onEventClick(eventRect.originalEvent, eventRect.rectF!!) + playSoundEffect(SoundEffectConstants.CLICK) + return super.onSingleTapConfirmed(e) + } + } + val xOffset = xStartPixel + val x = e.x - xOffset + val y = e.y - mCurrentOrigin.y + // If the tap was on add new Event space, then trigger the callback + if (addEventClickListener != null && mNewEventRect != null && mNewEventRect!!.rectF != null && + mNewEventRect!!.rectF!!.contains(x, y)) { + addEventClickListener!!.onAddEventClicked(mNewEventRect!!.event.startTime, mNewEventRect!!.event.endTime) + return super.onSingleTapConfirmed(e) + } + // If the tap was on an empty space, then trigger the callback. + if ((emptyViewClickListener != null || addEventClickListener != null) && e.x > mHeaderColumnWidth && e.y > headerHeight + weekDaysHeaderRowTotalPadding + spaceBelowAllDayEvents) { + val selectedTime = getTimeFromPoint(e.x, e.y) + if (selectedTime != null) { + val tempEvents = ArrayList(mEvents) + if (mNewEventRect != null) { + tempEvents.remove(mNewEventRect!!.event) + mNewEventRect = null + } + playSoundEffect(SoundEffectConstants.CLICK) + if (emptyViewClickListener != null) + emptyViewClickListener!!.onEmptyViewClicked(selectedTime.clone() as Calendar) + if (addEventClickListener != null) { + //round selectedTime to resolution + selectedTime.add(Calendar.MINUTE, -(newEventLengthInMinutes / 2)) + //Fix selected time if before the minimum hour + if (selectedTime.get(Calendar.HOUR_OF_DAY) < mMinTime) { + selectedTime.set(Calendar.HOUR_OF_DAY, mMinTime) + selectedTime.set(Calendar.MINUTE, 0) + } + val unroundedMinutes = selectedTime.get(Calendar.MINUTE) + val mod = unroundedMinutes % newEventTimeResolutionInMinutes + selectedTime.add(Calendar.MINUTE, if (mod < Math.ceil((newEventTimeResolutionInMinutes / 2).toDouble())) -mod else newEventTimeResolutionInMinutes - mod) + val endTime = selectedTime.clone() as Calendar + //Minus one to ensure it is the same day and not midnight (next day) + val maxMinutes = (mMaxTime - selectedTime.get(Calendar.HOUR_OF_DAY)) * 60 - selectedTime.get(Calendar.MINUTE) - 1 + endTime.add(Calendar.MINUTE, Math.min(maxMinutes, newEventLengthInMinutes)) + //If clicked at end of the day, fix selected startTime + if (maxMinutes < newEventLengthInMinutes) { + selectedTime.add(Calendar.MINUTE, maxMinutes - newEventLengthInMinutes) + } + val newEvent = WeekViewEvent(newEventIdentifier!!, "", null, selectedTime, endTime) + val top = hourHeight * getPassedMinutesInDay(selectedTime) / 60 + eventsTop + val bottom = hourHeight * getPassedMinutesInDay(endTime) / 60 + eventsTop + // Calculate left and right. + val left = widthPerDay * WeekViewUtil.daysBetween(firstVisibleDay!!, selectedTime) + val right = left + widthPerDay + // Add the new event if its bounds are valid + if (left < right && left < width && top < height && right > mHeaderColumnWidth && bottom > 0) { + val dayRectF = RectF(left, top, right, bottom - mCurrentOrigin.y) + newEvent.color = newEventColor + mNewEventRect = EventRect(newEvent, newEvent, dayRectF) + tempEvents.add(newEvent) + clearEvents() + cacheAndSortEvents(tempEvents) + computePositionOfEvents(eventRects!!) + invalidate() + } + } + } + } + return super.onSingleTapConfirmed(e) + } + + override fun onLongPress(e: MotionEvent) { + super.onLongPress(e) + if (eventLongPressListener != null && eventRects != null) { + for (event in eventRects!!) { + if (event.rectF != null && e.x > event.rectF!!.left && e.x < event.rectF!!.right && e.y > event.rectF!!.top && e.y < event.rectF!!.bottom) { + eventLongPressListener!!.onEventLongPress(event.originalEvent, event.rectF!!) + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + return + } + } + } + // If the tap was on in an empty space, then trigger the callback. + if (emptyViewLongPressListener != null && e.x > mHeaderColumnWidth && e.y > headerHeight + weekDaysHeaderRowTotalPadding + spaceBelowAllDayEvents) { + val selectedTime = getTimeFromPoint(e.x, e.y) + if (selectedTime != null) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + emptyViewLongPressListener!!.onEmptyViewLongPress(selectedTime) + } + } + } + } + + private val numberOfPeriods: Int + get() = ((mMaxTime - mMinTime) * (60.0 / timeColumnResolution)).toInt() + + private val yMinLimit: Float + get() = -(((hourHeight * (mMaxTime - mMinTime)).toFloat() + + headerHeight + + weekDaysHeaderRowTotalPadding + + spaceBelowAllDayEvents + + timeTextHeight / 2) - height) + + private val yMaxLimit: Float + get() = 0f + + private val xMinLimit: Float + get() { + return if (maxDate == null) { + Integer.MIN_VALUE.toFloat() + } else { + val date = maxDate!!.clone() as Calendar + date.add(Calendar.DATE, 1 - realNumberOfVisibleDays) + while (date.before(minDate)) { + date.add(Calendar.DATE, 1) + } + + getXOriginForDate(date) + } + } + + private val xMaxLimit: Float + get() = if (minDate == null) { + Integer.MAX_VALUE.toFloat() + } else { + getXOriginForDate(minDate!!) + } + + private val minHourOffset: Int + get() = hourHeight * mMinTime + + private + val eventsTop: Float + get() = mCurrentOrigin.y + headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 - minHourOffset + + private val leftDaysWithGaps: Int + get() = (-Math.ceil((mCurrentOrigin.x / (widthPerDay + columnGap)).toDouble())).toInt() + + private val xStartPixel: Float + get() = mCurrentOrigin.x + (widthPerDay + columnGap) * leftDaysWithGaps + + mHeaderColumnWidth + + var monthChangeListener: MonthChangeListener? = null + + /** + * the interpreter which provides the text to show in the header column and the header row. + */ + var dateTimeInterpreter: DateTimeInterpreter = object : DateTimeInterpreter { + val calendar = Calendar.getInstance() + val timeFormat = DateFormat.getTimeFormat(context) + ?: SimpleDateFormat("HH:mm", Locale.getDefault()) + val shortDateFormat = WeekViewUtil.getWeekdayWithNumericDayAndMonthFormat(context, true) + val normalDateFormat = WeekViewUtil.getWeekdayWithNumericDayAndMonthFormat(context, false) + + init { + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + } + + override fun getFormattedTimeOfDay(hour: Int, minutes: Int): String { + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minutes) + return timeFormat.format(calendar.time) + } + + override fun getFormattedWeekDayTitle(date: Calendar): String { + val shortDate = dayNameLength == LENGTH_SHORT + return if (shortDate) shortDateFormat.format(date.time) else normalDateFormat.format(date.time) + } + } + set(value) { + if (field == value) + return + field = value + timeFormatterCache.clear() + initTextTimeWidth() + } + + var weekDaySubtitleInterpreter: WeekDaySubtitleInterpreter? = null + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * the real number of visible days + * If the amount of days between max date and min date is smaller, that value is returned + */ + val realNumberOfVisibleDays: Int + get() = if (minDate == null || maxDate == null) numberOfVisibleDays else Math.min(numberOfVisibleDays, daysBetween(minDate!!, maxDate!!) + 1) + + /** + * the number of visible days in a week. + */ + var numberOfVisibleDays: Int = 3 + set(value) { + if (field == value) + return + field = value + resetHomeDate() + mCurrentOrigin.x = 0f + mCurrentOrigin.y = 0f + invalidate() + } + + var hourHeight: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var columnGap: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * the first day of the week. First day of the week is used only when the week view is first + * drawn. It does not of any effect after user starts scrolling horizontally. + * + * **Note:** This method will only work if the week view is set to display more than 6 days at + * once. + * + * supported values are [java.util.Calendar.SUNDAY], + * [java.util.Calendar.MONDAY], [java.util.Calendar.TUESDAY], + * [java.util.Calendar.WEDNESDAY], [java.util.Calendar.THURSDAY], + * [java.util.Calendar.FRIDAY]. + */ + var firstDayOfWeek: Int = Calendar.getInstance().firstDayOfWeek + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var textSize: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12.0f, context.resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + timeTextPaint.textSize = value + allDaySideTitleTextPaint.textSize = value + invalidate() + } + + var headerWeekDayTitleTextSize: Float = textSize + set(value) { + if (field == value) + return + field = value + mHeaderWeekDayTitleTextPaint.textSize = value + mHeaderWeekDayTitleTodayTextPaint.textSize = value + sideTitleTextPaint.textSize = value + invalidate() + } + + var headerWeekDaySubtitleTextSize: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 22.0f, context.resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + mHeaderWeekDaySubtitleTextPaint.textSize = value + mHeaderWeekDaySubtitleTodayTextPaint.textSize = value + sideSubtitleTextPaint.textSize = value + invalidate() + } + + var headerColumnPadding: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + mHeaderColumnWidth = timeTextWidth + headerColumnPadding * 2.0f + invalidate() + } + + @ColorInt + var headerColumnTextColor: Int = Color.BLACK + set(value) { + if (field == value) + return + field = value + mHeaderWeekDayTitleTextPaint.color = value + mHeaderWeekDaySubtitleTextPaint.color = value + timeTextPaint.color = value + sideTitleTextPaint.color = value + sideSubtitleTextPaint.color = value + allDaySideTitleTextPaint.color = value + invalidate() + } + + @ColorInt + var allDaySideTitleTextColor = headerColumnTextColor + set(value) { + if (field == value) + return + field = value + allDaySideTitleTextPaint.color = value + invalidate() + } + + @ColorInt + var headerRowBackgroundColor: Int = Color.WHITE + set(value) { + if (field == value) + return + field = value + mHeaderBackgroundPaint.color = value + invalidate() + } + + @ColorInt + var dayBackgroundColor: Int = Color.rgb(245, 245, 245) + set(value) { + if (field == value) + return + field = value + mDayBackgroundPaint.color = value + invalidate() + } + + @ColorInt + var hourSeparatorColor: Int = Color.rgb(230, 230, 230) + set(value) { + if (field == value) + return + field = value + mHourSeparatorPaint.color = value + invalidate() + } + + @ColorInt + var todayColumnBackgroundColor: Int = Color.rgb(239, 247, 254) + set(value) { + if (field == value) + return + field = value + mTodayColumnBackgroundPaint.color = value + invalidate() + } + + var hourSeparatorHeight: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + mHourSeparatorPaint.strokeWidth = value.toFloat() + invalidate() + } + + @ColorInt + var todayHeaderTextColor: Int = Color.rgb(39, 137, 228) + set(value) { + if (field == value) + return + field = value + mHeaderWeekDayTitleTodayTextPaint.color = value + mHeaderWeekDaySubtitleTodayTextPaint.color = value + invalidate() + } + + var eventTextSize: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12.0f, context.resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + mEventTextPaint.textSize = value + invalidate() + } + + @ColorInt + var eventTextColor: Int = Color.BLACK + set(value) { + if (field == value) + return + field = value + mEventTextPaint.color = value + invalidate() + } + + var eventPadding: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + @ColorInt + var defaultEventColor: Int = 0 + set(value) { + if (field == value) + return + field = value + invalidate() + } + + @ColorInt + var newEventColor: Int = 0 + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * the length of the day name displayed in the header row. Example of short day names is + * 'M' for 'Monday' and example of long day names is 'Mon' for 'Monday'. + * **Note:** Use [.setDateTimeInterpreter] instead. + */ + var dayNameLength: Int = LENGTH_LONG + @Deprecated("") + set(value) { + if (value != LENGTH_LONG && value != LENGTH_SHORT) + throw IllegalArgumentException("length parameter must be either LENGTH_LONG or LENGTH_SHORT") + field = value + } + + /** + * the gap between overlapping events. + */ + var overlappingEventGap: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var weekDayHeaderRowPaddingTop = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var weekDayHeaderRowPaddingBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, resources.displayMetrics) + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var weekDaysHeaderRowTotalPadding: Float = weekDayHeaderRowPaddingTop + weekDayHeaderRowPaddingBottom + get() = weekDayHeaderRowPaddingTop + weekDayHeaderRowPaddingBottom + + var spaceBetweenWeekDaysAndAllDayEvents = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var spaceBelowAllDayEvents = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var spaceBetweenHeaderWeekDayTitleAndSubtitle = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, context.resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * Whether weekends should have a background color different from the normal day background + * color. The weekend background colors are defined by the attributes + * `futureWeekendBackgroundColor` and `pastWeekendBackgroundColor`. + */ + var isShowDistinctWeekendColor: Boolean = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * Whether past and future days should have two different background colors. The past and + * future day colors are defined by the attributes `futureBackgroundColor` and + * `pastBackgroundColor`. + */ + var isShowDistinctPastFutureColor: Boolean = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * whether "now" line should be displayed. "Now" line is defined by the attributes + * `nowLineColor` and `nowLineThickness`. + */ + var isShowNowLine: Boolean = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * the "now" line color. + */ + @ColorInt + var nowLineColor: Int = Color.rgb(102, 102, 102) + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var nowLineThickness: Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics).toInt() + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /* + * focus point + * 0 = top of view, 1 = bottom of view + * The focused point (multiplier of the view height) where the week view is zoomed around. + * This point will not move while zooming. + */ + var zoomFocusPoint: Float = 0f + set(value) { + if (0 > value || value > 1) + throw IllegalStateException("The zoom focus point percentage has to be between 0 and 1") + field = value + } + + @ColorInt + var pastBackgroundColor: Int = Color.rgb(227, 227, 227) + set(value) { + field = value + mPastBackgroundPaint.color = value + } + + @ColorInt + var futureBackgroundColor: Int = Color.rgb(245, 245, 245) + set(value) { + field = value + mFutureBackgroundPaint.color = value + } + + @ColorInt + var pastWeekendBackgroundColor: Int = 0 + set(value) { + field = value + this.mPastWeekendBackgroundPaint.color = value + } + + @ColorInt + var futureWeekendBackgroundColor: Int = 0 + set(value) { + field = value + this.mFutureWeekendBackgroundPaint.color = value + } + + var isScrollNumberOfVisibleDays: Boolean = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + + /** + * Get the first hour that is visible on the screen. + * + * @return The first hour that is visible. + */ + val firstVisibleHour: Double + get() = (-mCurrentOrigin.y / hourHeight).toDouble() + + var isUsingCheckersStyle: Boolean = false + set(value) { + if (field == value) + return + field = value + invalidate() + } + + var isSubtitleHeaderEnabled: Boolean + get() = weekDaySubtitleInterpreter != null + private set(value) {} + + private val timeChangedBroadcastReceiver: TimeChangedBroadcastReceiver + private var today: Calendar = WeekViewUtil.today() + private val containsAllDayEventCache = HashMap, Boolean>() + private val weekDayTitleFormatterCache = HashMap() + private val weekDaySubtitleFormatterCache = HashMap() + private val timeFormatterCache = HashMap, String>() + + //endregion fields and properties + + private enum class Direction { + NONE, LEFT, RIGHT, VERTICAL + } + + init { + timeChangedBroadcastReceiver = object : TimeChangedBroadcastReceiver() { + override fun onTimeChanged() { + this@WeekView.today = WeekViewUtil.today() + invalidate() + } + } + timeChangedBroadcastReceiver.register(context, Calendar.getInstance()) + textColorPicker = object : TextColorPicker { + override fun getTextColor(event: WeekViewEvent): Int { + val color = event.color + val a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255 + return if (a < 0.2) Color.BLACK else Color.WHITE + } + } + // Get the attribute values (if any). + val a = context.theme.obtainStyledAttributes(attrs, R.styleable.WeekView, 0, 0) + try { + firstDayOfWeek = a.getInteger(R.styleable.WeekView_firstDayOfWeek, firstDayOfWeek) + hourHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourHeight, hourHeight) + minHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_minHourHeight, minHourHeight) + mEffectiveMinHourHeight = minHourHeight + maxHourHeight = a.getDimensionPixelSize(R.styleable.WeekView_maxHourHeight, maxHourHeight) + textSize = a.getDimension(R.styleable.WeekView_textSize, textSize) + headerColumnPadding = a.getDimension(R.styleable.WeekView_headerColumnPadding, headerColumnPadding) + columnGap = a.getDimensionPixelSize(R.styleable.WeekView_columnGap, columnGap) + headerColumnTextColor = a.getColor(R.styleable.WeekView_headerColumnTextColor, headerColumnTextColor) + numberOfVisibleDays = a.getInteger(R.styleable.WeekView_noOfVisibleDays, numberOfVisibleDays) + isShowFirstDayOfWeekFirst = a.getBoolean(R.styleable.WeekView_showFirstDayOfWeekFirst, isShowFirstDayOfWeekFirst) + weekDayHeaderRowPaddingTop = a.getDimension(R.styleable.WeekView_weekDayHeaderRowPaddingTop, weekDayHeaderRowPaddingTop) + weekDayHeaderRowPaddingBottom = a.getDimension(R.styleable.WeekView_weekDayHeaderRowPaddingBottom, weekDayHeaderRowPaddingBottom) + headerRowBackgroundColor = a.getColor(R.styleable.WeekView_headerRowBackgroundColor, headerRowBackgroundColor) + dayBackgroundColor = a.getColor(R.styleable.WeekView_dayBackgroundColor, dayBackgroundColor) + futureBackgroundColor = a.getColor(R.styleable.WeekView_futureBackgroundColor, futureBackgroundColor) + pastBackgroundColor = a.getColor(R.styleable.WeekView_pastBackgroundColor, pastBackgroundColor) + // If not set, use the same color as in the week + futureWeekendBackgroundColor = a.getColor(R.styleable.WeekView_futureWeekendBackgroundColor, futureBackgroundColor) + pastWeekendBackgroundColor = a.getColor(R.styleable.WeekView_pastWeekendBackgroundColor, pastBackgroundColor) + nowLineColor = a.getColor(R.styleable.WeekView_nowLineColor, nowLineColor) + nowLineThickness = a.getDimensionPixelSize(R.styleable.WeekView_nowLineThickness, nowLineThickness) + hourSeparatorColor = a.getColor(R.styleable.WeekView_hourSeparatorColor, hourSeparatorColor) + todayColumnBackgroundColor = a.getColor(R.styleable.WeekView_todayColumnBackgroundColor, todayColumnBackgroundColor) + hourSeparatorHeight = a.getDimensionPixelSize(R.styleable.WeekView_hourSeparatorHeight, hourSeparatorHeight) + todayHeaderTextColor = a.getColor(R.styleable.WeekView_todayHeaderTextColor, todayHeaderTextColor) + eventTextSize = a.getDimension(R.styleable.WeekView_eventTextSize, eventTextSize) + eventTextColor = a.getColor(R.styleable.WeekView_eventTextColor, eventTextColor) + newEventColor = a.getColor(R.styleable.WeekView_newEventColor, newEventColor) + newEventIconDrawable = a.getDrawable(R.styleable.WeekView_newEventIconResource) + // For backward compatibility : Set "mNewEventIdentifier" if the attribute is "WeekView_newEventId" of type int + newEventIdentifier = a.getString(R.styleable.WeekView_newEventIdentifier) ?: newEventIdentifier + newEventLengthInMinutes = a.getInt(R.styleable.WeekView_newEventLengthInMinutes, newEventLengthInMinutes) + newEventTimeResolutionInMinutes = a.getInt(R.styleable.WeekView_newEventTimeResolutionInMinutes, newEventTimeResolutionInMinutes) + eventPadding = a.getDimensionPixelSize(R.styleable.WeekView_eventPadding, eventPadding) + dayNameLength = a.getInteger(R.styleable.WeekView_dayNameLength, dayNameLength) + overlappingEventGap = a.getDimension(R.styleable.WeekView_overlappingEventGap, overlappingEventGap) + spaceBetweenWeekDaysAndAllDayEvents = a.getDimensionPixelSize(R.styleable.WeekView_spaceBetweenWeekDaysAndAllDayEvents, spaceBetweenWeekDaysAndAllDayEvents) + xScrollingSpeed = a.getFloat(R.styleable.WeekView_xScrollingSpeed, xScrollingSpeed) + eventCornerRadius = a.getDimension(R.styleable.WeekView_eventCornerRadius, eventCornerRadius) + isShowDistinctPastFutureColor = a.getBoolean(R.styleable.WeekView_showDistinctPastFutureColor, isShowDistinctPastFutureColor) + isShowDistinctWeekendColor = a.getBoolean(R.styleable.WeekView_showDistinctWeekendColor, isShowDistinctWeekendColor) + isShowNowLine = a.getBoolean(R.styleable.WeekView_showNowLine, isShowNowLine) + isHorizontalFlingEnabled = a.getBoolean(R.styleable.WeekView_horizontalFlingEnabled, isHorizontalFlingEnabled) + isVerticalFlingEnabled = a.getBoolean(R.styleable.WeekView_verticalFlingEnabled, isVerticalFlingEnabled) + allDayEventHeight = a.getDimensionPixelSize(R.styleable.WeekView_allDayEventHeight, allDayEventHeight) + zoomFocusPoint = a.getFraction(R.styleable.WeekView_zoomFocusPoint, 1, 1, zoomFocusPoint) + isZoomFocusPointEnabled = a.getBoolean(R.styleable.WeekView_zoomFocusPointEnabled, isZoomFocusPointEnabled) + scrollDuration = a.getInt(R.styleable.WeekView_scrollDuration, scrollDuration) + timeColumnResolution = a.getInt(R.styleable.WeekView_timeColumnResolution, timeColumnResolution) + autoLimitTime = a.getBoolean(R.styleable.WeekView_autoLimitTime, autoLimitTime) + mMinTime = a.getInt(R.styleable.WeekView_minTime, mMinTime) + mMaxTime = a.getInt(R.styleable.WeekView_maxTime, mMaxTime) + minOverlappingMinutes = a.getInt(R.styleable.WeekView_minOverlappingMinutes, minOverlappingMinutes) + isScrollNumberOfVisibleDays = a.getBoolean(R.styleable.WeekView_isScrollNumberOfVisibleDays, isScrollNumberOfVisibleDays) + enableDrawHeaderBackgroundOnlyOnWeekDays = a.getBoolean(R.styleable.WeekView_enableDrawHeaderBackgroundOnlyOnWeekDays, enableDrawHeaderBackgroundOnlyOnWeekDays) + isUsingCheckersStyle = a.getBoolean(R.styleable.WeekView_isUsingCheckersStyle, isUsingCheckersStyle) + headerWeekDayTitleTextSize = a.getDimension(R.styleable.WeekView_headerWeekDayTitleTextSize, headerWeekDayTitleTextSize) + headerWeekDaySubtitleTextSize = a.getDimension(R.styleable.WeekView_headerWeekDaySubtitleTextSize, headerWeekDaySubtitleTextSize) + spaceBetweenHeaderWeekDayTitleAndSubtitle = a.getDimensionPixelSize(R.styleable.WeekView_spaceBetweenHeaderWeekDayTitleAndSubtitle, spaceBetweenHeaderWeekDayTitleAndSubtitle) + untitledEventText = a.getString(R.styleable.WeekView_untitledEventText) ?: untitledEventText + } finally { + a.recycle() + } + //some one time initializations + mHeaderWeekDayTitleTextPaint.textAlign = Paint.Align.CENTER + mHeaderWeekDaySubtitleTextPaint.textAlign = Paint.Align.CENTER + sideTitleTextPaint.textAlign = Paint.Align.CENTER + sideSubtitleTextPaint.textAlign = Paint.Align.CENTER + allDaySideTitleTextPaint.textAlign = Paint.Align.CENTER + timeTextPaint.textAlign = Paint.Align.RIGHT + mEventTextPaint.style = Paint.Style.FILL + mHourSeparatorPaint.style = Paint.Style.STROKE + mHeaderWeekDayTitleTodayTextPaint.textAlign = Paint.Align.CENTER + mHeaderWeekDaySubtitleTodayTextPaint.textAlign = Paint.Align.CENTER + init() + } + + private fun init() { + resetHomeDate() + + // Scrolling initialization. + mGestureDetector = GestureDetectorCompat(context, mGestureListener) + mScroller = OverScroller(context, FastOutLinearInInterpolator()) + + mMinimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity + mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop + + // Measure settings for time column. + timeTextPaint.textSize = textSize + timeTextPaint.color = headerColumnTextColor + timeTextPaint.typeface = typeface + + val rect = Rect() + val exampleTime = if (timeColumnResolution % 60 != 0) "00:00 PM" else "00 PM" + timeTextPaint.getTextBounds(exampleTime, 0, exampleTime.length, rect) + timeTextHeight = rect.height().toFloat() + initTextTimeWidth() + + //handle sideTitleTextPaint + sideTitleTextPaint.color = headerColumnTextColor + sideTitleTextPaint.typeface = typeface + sideTitleTextPaint.textSize = headerWeekDayTitleTextSize + // handle sideSubtitleTextPaint + sideSubtitleTextPaint.textSize = headerWeekDaySubtitleTextSize + sideSubtitleTextPaint.color = headerColumnTextColor + sideSubtitleTextPaint.typeface = typeface + + //handle allDaySideTitleTextPaint + allDaySideTitleTextPaint.textSize = textSize + allDaySideTitleTextPaint.color = allDaySideTitleTextColor + allDaySideTitleTextPaint.typeface = typeface + + // Measure settings for header row. + //TODO measure the text that will actually be used, based on the locale and dates. Important because various characters might look different. + val sampleText = "ABCDEFGHIKLMNOPQRSTUVWXYZabcdefghiklmnopqrstuvwxyz0123456789" + mHeaderWeekDayTitleTextPaint.color = headerColumnTextColor + mHeaderWeekDayTitleTextPaint.textSize = headerWeekDayTitleTextSize + mHeaderWeekDayTitleTextPaint.typeface = typeface + mHeaderWeekDayTitleTextPaint.getTextBounds(sampleText, 0, sampleText.length, rect) + headerWeekDayTitleTextHeight = rect.height().toFloat() + + //measure settings for header subtitle + mHeaderWeekDaySubtitleTextPaint.color = headerColumnTextColor + mHeaderWeekDaySubtitleTextPaint.textSize = headerWeekDaySubtitleTextSize + mHeaderWeekDaySubtitleTextPaint.typeface = typeface + mHeaderWeekDaySubtitleTextPaint.getTextBounds(sampleText, 0, sampleText.length, rect) + headerWeekDaySubtitleTextHeight = rect.height().toFloat() + + // Prepare header background paint. + mHeaderBackgroundPaint.color = headerRowBackgroundColor + + // Prepare day background color paint. + mDayBackgroundPaint.color = dayBackgroundColor + mFutureBackgroundPaint.color = futureBackgroundColor + mPastBackgroundPaint.color = pastBackgroundColor + mFutureWeekendBackgroundPaint.color = futureWeekendBackgroundColor + mPastWeekendBackgroundPaint.color = pastWeekendBackgroundColor + + // Prepare hour separator color paint. + mHourSeparatorPaint.strokeWidth = hourSeparatorHeight.toFloat() + mHourSeparatorPaint.color = hourSeparatorColor + + // Prepare the "now" line color paint + mNowLinePaint.strokeWidth = nowLineThickness.toFloat() + mNowLinePaint.color = nowLineColor + + // Prepare today background color paint. + mTodayColumnBackgroundPaint.color = todayColumnBackgroundColor + + // Prepare today header text color paint. + + mHeaderWeekDayTitleTodayTextPaint.textSize = headerWeekDayTitleTextSize + mHeaderWeekDayTitleTodayTextPaint.typeface = typeface + mHeaderWeekDayTitleTodayTextPaint.color = todayHeaderTextColor + + mHeaderWeekDaySubtitleTodayTextPaint.textSize = headerWeekDaySubtitleTextSize + mHeaderWeekDaySubtitleTodayTextPaint.typeface = typeface + mHeaderWeekDaySubtitleTodayTextPaint.color = todayHeaderTextColor + + // Prepare event background color. + mEventBackgroundPaint.color = Color.rgb(174, 208, 238) + // Prepare empty event background color. + mNewEventBackgroundPaint.color = Color.rgb(60, 147, 217) + + // Prepare event text size and color. + mEventTextPaint.color = eventTextColor + mEventTextPaint.textSize = eventTextSize + mEventTextPaint.typeface = typeface + + + // Set default event color. + defaultEventColor = 0xff9fc6e7.toInt() + // Set default empty event color. + newEventColor = 0xff3c93d9.toInt() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + context.unregisterReceiver(timeChangedBroadcastReceiver) + } + + private fun resetHomeDate() { + var newHomeDate = today() + + if (minDate != null && newHomeDate.before(minDate)) { + newHomeDate = minDate!!.clone() as Calendar + } + if (maxDate != null && newHomeDate.after(maxDate)) { + newHomeDate = maxDate!!.clone() as Calendar + } + + if (maxDate != null) { + val date = maxDate!!.clone() as Calendar + date.add(Calendar.DATE, 1 - realNumberOfVisibleDays) + while (date.before(minDate)) { + date.add(Calendar.DATE, 1) + } + + if (newHomeDate.after(date)) { + newHomeDate = date + } + } + + mHomeDate = newHomeDate + } + + private fun getXOriginForDate(date: Calendar): Float { + return -daysBetween(mHomeDate!!, date) * (widthPerDay + columnGap) + } + + // fix rotation changes + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mAreDimensionsInvalid = true + } + + /** + * Initialize time column width. Calculate value with all possible hours (supposed widest text). + */ + private fun initTextTimeWidth() { + timeTextWidth = 0f + for (i in 0 until numberOfPeriods) { + // Measure time string and get max width. + val time = getFormattedTime(i, i % 2 * 30) + timeTextWidth = Math.max(timeTextWidth, timeTextPaint.measureText(time)) + } + mHeaderColumnWidth = timeTextWidth + headerColumnPadding * 2.0f + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawPerformanceTester.startMeasure() + // Draw the header row. + drawHeaderRowAndEvents(canvas) + // Draw the time column and all the axes/separators. + drawTimeColumnAndAxes(canvas) + drawPerformanceTester.endMeasure() + } + + private fun calculateHeaderHeight() { + //Make sure the header is the right size (depends on AllDay events) + if (eventRects != null && eventRects!!.size > 0) { + val cacheKey = Pair(SimpleDate(firstVisibleDay!!), realNumberOfVisibleDays) + val cachedResult = containsAllDayEventCache[cacheKey] + if (cachedResult != null) + containsAllDayEvent = cachedResult + else { + containsAllDayEvent = false + val day = firstVisibleDay!!.clone() as Calendar + outerLoop@ + for (dayNumber in 0 until realNumberOfVisibleDays) { + for (i in eventRects!!.indices) { + val event = eventRects!![i].event + if (isSameDay(event.startTime, day) && event.isAllDay) { + containsAllDayEvent = true + break@outerLoop + } + } + day.add(Calendar.DATE, 1) + } + containsAllDayEventCache[cacheKey] = containsAllDayEvent + } + } + headerHeight = if (containsAllDayEvent) { + headerWeekDayTitleTextHeight + (allDayEventHeight + spaceBelowAllDayEvents + spaceBetweenWeekDaysAndAllDayEvents) + } else { + headerWeekDayTitleTextHeight + } + if (isSubtitleHeaderEnabled) + headerHeight += headerWeekDaySubtitleTextHeight + spaceBetweenHeaderWeekDayTitleAndSubtitle + } + + private fun drawTimeColumnAndAxes(canvas: Canvas) { + canvas.save() + // Clip to paint in left column only. + canvas.clipRect(0.0f, headerHeight + weekDaysHeaderRowTotalPadding, mHeaderColumnWidth, height.toFloat()) + + for (i in 0 until numberOfPeriods) { + // If we are showing half hours (eg. 5:30am), space the times out by half the hour height + // and need to provide 30 minutes on each odd period, otherwise, minutes is always 0. + val timeSpacing: Float + val minutes: Int + val hour: Int + val timesPerHour = 60.0f / timeColumnResolution + timeSpacing = hourHeight / timesPerHour + hour = mMinTime + i / timesPerHour.toInt() + minutes = i % timesPerHour.toInt() * (60 / timesPerHour.toInt()) + // Calculate the top of the rectangle where the time text will go + val top = headerHeight + weekDaysHeaderRowTotalPadding + mCurrentOrigin.y + timeSpacing * i + spaceBelowAllDayEvents + // Get the time to be displayed, as a String. + val time = getFormattedTime(hour, minutes) + // Draw the text if its y position is not outside of the visible area. The pivot point of the text is the point at the bottom-right corner. + if (top < height) + canvas.drawText(time, timeTextWidth + headerColumnPadding, top + timeTextHeight, timeTextPaint) + } + canvas.restore() + } + + private fun drawHeaderRowAndEvents(canvas: Canvas) { + // Calculate the available width for each day. + widthPerDay = (width.toFloat() - mHeaderColumnWidth - (columnGap.toFloat() * (realNumberOfVisibleDays.toFloat() - 1.0f))) / realNumberOfVisibleDays.toFloat() + calculateHeaderHeight() //Make sure the header is the right size (depends on AllDay events) + + if (mAreDimensionsInvalid) { + mEffectiveMinHourHeight = Math.max(minHourHeight, ((height.toFloat() - headerHeight - weekDaysHeaderRowTotalPadding - spaceBelowAllDayEvents) / (mMaxTime - mMinTime)).toInt()) + + mAreDimensionsInvalid = false + if (mScrollToDay != null) + goToDate(mScrollToDay!!) + + mAreDimensionsInvalid = false + if (mScrollToHour >= 0) + goToHour(mScrollToHour) + + mScrollToDay = null + mScrollToHour = -1.0 + mAreDimensionsInvalid = false + } + if (mIsFirstDraw) { + mIsFirstDraw = false + + // If the week view is being drawn for the first time, then consider the first day of the week. + if (realNumberOfVisibleDays >= 7 && mHomeDate!!.get(Calendar.DAY_OF_WEEK) != firstDayOfWeek && isShowFirstDayOfWeekFirst) { + val difference = mHomeDate!!.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek + mCurrentOrigin.x += (widthPerDay + columnGap) * difference + } + setLimitTime(mMinTime, mMaxTime) + } + + // Calculate the new height due to the zooming. + if (mNewHourHeight > 0) { + if (mNewHourHeight < mEffectiveMinHourHeight) + mNewHourHeight = mEffectiveMinHourHeight + else if (mNewHourHeight > maxHourHeight) + mNewHourHeight = maxHourHeight + + hourHeight = mNewHourHeight + mNewHourHeight = -1 + } + + // If the new mCurrentOrigin.y is invalid, make it valid. + mCurrentOrigin.y = Math.min(0f, Math.max(mCurrentOrigin.y, + height.toFloat() - (hourHeight * (mMaxTime - mMinTime)).toFloat() - headerHeight - weekDaysHeaderRowTotalPadding - spaceBelowAllDayEvents - timeTextHeight / 2)) + + val leftDaysWithGaps = leftDaysWithGaps + // Consider scroll offset. + val startFromPixel = xStartPixel + var startPixel = startFromPixel + + // Prepare to iterate for each hour to draw the hour lines. + var lineCount = ((height.toFloat() - headerHeight - weekDaysHeaderRowTotalPadding - spaceBelowAllDayEvents) / hourHeight).toInt() + 1 + lineCount *= (realNumberOfVisibleDays + 1) + + val hourLines = FloatArray(lineCount * 4) + + // Clear the cache for event rectangles. + if (eventRects != null) + for (eventRect in eventRects!!) + eventRect.rectF = null + + canvas.save() + + // Clip to paint events only. + canvas.clipRect(mHeaderColumnWidth, headerHeight + weekDaysHeaderRowTotalPadding + spaceBelowAllDayEvents + timeTextHeight / 2, width.toFloat(), height.toFloat()) + + // Iterate through each day. + + val oldFirstVisibleDay = firstVisibleDay + firstVisibleDay = mHomeDate!!.clone() as Calendar + firstVisibleDay!!.add(Calendar.DATE, -Math.round(mCurrentOrigin.x / (widthPerDay + columnGap))) + + if (oldFirstVisibleDay == null || !WeekViewUtil.isSameDay(firstVisibleDay!!, oldFirstVisibleDay)) { + scrollListener?.onFirstVisibleDayChanged(firstVisibleDay!!, oldFirstVisibleDay) + } + + if (autoLimitTime) { + val days = ArrayList() + for (dayNumber in leftDaysWithGaps + 1..leftDaysWithGaps + realNumberOfVisibleDays) { + val day = mHomeDate!!.clone() as Calendar + day.add(Calendar.DATE, dayNumber - 1) + days.add(day) + } + limitEventTime(days) + } + run { + val day = mHomeDate!!.clone() as Calendar + day.add(Calendar.DATE, leftDaysWithGaps) + + for (dayNumber in leftDaysWithGaps + 1..leftDaysWithGaps + realNumberOfVisibleDays + 1) { + // Check if the day is today. + val isToday = isSameDay(day, today) + + // Don't draw days which are outside requested range + if (!dateIsValid(day)) + continue + + // Get more events if necessary. + + // mFetchedPeriod: currently fetched period index + // mWeekViewLoader.toWeekViewPeriodIndex(day): index for the day we want to display + // fetchIndex = 1.0: end of period in the future reached + // fetchIndex = 0.0: end of period in the past reached + val fetchIndex = this.weekViewLoader.toWeekViewPeriodIndex(day) - mFetchedPeriod + + // if we are using the PrefetchingWeekViewLoader class, we need to adjust the bounds + // so that we wait to fetch new data until we really need it + var upperBound = 1.0 + var lowerBound = 0.0 + + if (this.weekViewLoader is PrefetchingWeekViewLoader) { + // the offset causes the onMonthChangeListener to be trigger when half of the + // last fetched period is passed + + // example: + // if the prefetching period = 1, we load the current period, the next and the previous + // when half of the next/previous period is passed, the listener is triggered to fetch new data + val boundOffset = this.weekViewLoader.prefetchingPeriod - 0.5 + + upperBound = 1.0 + boundOffset + lowerBound = 0.0 - boundOffset + } + + if ((eventRects == null || mRefreshEvents || + dayNumber == leftDaysWithGaps + 1 && mFetchedPeriod != this.weekViewLoader.toWeekViewPeriodIndex(day).toInt() && + (fetchIndex >= upperBound || fetchIndex <= lowerBound))) { + getMoreEvents(day) + mRefreshEvents = false + } + + // Draw background color for each day. + val start = if (startPixel < mHeaderColumnWidth) mHeaderColumnWidth else startPixel + if (widthPerDay + startPixel - start > 0) { + if (isShowDistinctPastFutureColor) { + val isWeekend = day.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || day.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY + val pastPaint = if (isWeekend && isShowDistinctWeekendColor) mPastWeekendBackgroundPaint else mPastBackgroundPaint + val futurePaint = if (isWeekend && isShowDistinctWeekendColor) mFutureWeekendBackgroundPaint else mFutureBackgroundPaint + val startY = headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 + spaceBelowAllDayEvents + mCurrentOrigin.y + when { + isToday -> { + val now = Calendar.getInstance() + val beforeNow = (now.get(Calendar.HOUR_OF_DAY) - mMinTime + now.get(Calendar.MINUTE) / 60f) * hourHeight + canvas.drawRect(start, startY, startPixel + widthPerDay, startY + beforeNow, pastPaint) + canvas.drawRect(start, startY + beforeNow, startPixel + widthPerDay, height.toFloat(), futurePaint) + } + day.before(today) -> canvas.drawRect(start, startY, startPixel + widthPerDay, height.toFloat(), pastPaint) + else -> canvas.drawRect(start, startY, startPixel + widthPerDay, height.toFloat(), futurePaint) + } + } else { + val cellBackgroundPaint = if (isToday) mTodayColumnBackgroundPaint else mDayBackgroundPaint + if (cellBackgroundPaint.color != 0) + canvas.drawRect(start, headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 + spaceBelowAllDayEvents, startPixel + widthPerDay, height.toFloat(), cellBackgroundPaint) + } + } + + // Prepare the separator lines for hours. + var i = 0 + for (hourNumber in mMinTime until mMaxTime) { + val top = headerHeight + weekDaysHeaderRowTotalPadding + mCurrentOrigin.y + (hourHeight * (hourNumber - mMinTime)).toFloat() + timeTextHeight / 2 + if (top > headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 + spaceBelowAllDayEvents - hourSeparatorHeight && top < height && startPixel + widthPerDay - start > 0) { + hourLines[i * 4] = start + hourLines[i * 4 + 1] = top + hourLines[i * 4 + 2] = startPixel + widthPerDay + if (isUsingCheckersStyle) columnGap else 0 + hourLines[i * 4 + 3] = top + i++ + } + } + // Draw the lines for hours. + canvas.drawLines(hourLines, mHourSeparatorPaint) + + // Draw line between days (before current one) + if (isUsingCheckersStyle) { + val x = if (dayNumber == leftDaysWithGaps + 1) start else start - columnGap / 2 + canvas.drawLine(x, headerHeight, x, height.toFloat(), mHourSeparatorPaint) + } + + // Draw the events. + drawEvents(day, startPixel, canvas) + + // Draw the line at the current time. + if (isShowNowLine && isToday) { + val startY = headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 + spaceBelowAllDayEvents + mCurrentOrigin.y + val now = Calendar.getInstance() + val beforeNow = (now.get(Calendar.HOUR_OF_DAY) - mMinTime + now.get(Calendar.MINUTE) / 60f) * hourHeight + val top = startY + beforeNow + canvas.drawLine(start, top, startPixel + widthPerDay, top, mNowLinePaint) + } + + // In the next iteration, start from the next day. + startPixel += widthPerDay + columnGap + day.add(Calendar.DATE, 1) + } + } + + canvas.restore() + + // Hide everything in the first cell (top left corner). + canvas.save() + canvas.clipRect(0f, 0f, mHeaderColumnWidth, headerHeight + weekDaysHeaderRowTotalPadding) + val headerTitleAndSubtitleTextHeight = headerWeekDayTitleTextHeight + (if (isSubtitleHeaderEnabled) headerWeekDaySubtitleTextHeight + spaceBetweenHeaderWeekDayTitleAndSubtitle else 0.0f) + if (enableDrawHeaderBackgroundOnlyOnWeekDays) + canvas.drawRect(0f, 0f, mHeaderColumnWidth, headerTitleAndSubtitleTextHeight + weekDaysHeaderRowTotalPadding, mHeaderBackgroundPaint) + else + canvas.drawRect(canvas.clipBounds, mHeaderBackgroundPaint) + + // draw text on the left of the week days + when { + //TODO set left column size based on possible text of sideTitle and sideSubtitle, or auto-resize text according to available space + !TextUtils.isEmpty(sideTitleText) && TextUtils.isEmpty(sideSubtitleText) -> + canvas.drawText(sideTitleText, mHeaderColumnWidth / 2, (headerTitleAndSubtitleTextHeight + headerWeekDayTitleTextHeight) / 2.0f + weekDayHeaderRowPaddingTop, sideTitleTextPaint) + !TextUtils.isEmpty(sideTitleText) && !TextUtils.isEmpty(sideSubtitleText) -> { + canvas.drawText(sideTitleText, mHeaderColumnWidth / 2, headerWeekDayTitleTextHeight + weekDayHeaderRowPaddingTop, sideTitleTextPaint) + canvas.drawText(sideSubtitleText, mHeaderColumnWidth / 2, headerTitleAndSubtitleTextHeight + weekDayHeaderRowPaddingTop, sideSubtitleTextPaint) + } + TextUtils.isEmpty(sideTitleText) && !TextUtils.isEmpty(sideSubtitleText) -> + canvas.drawText(sideSubtitleText, mHeaderColumnWidth / 2, (headerTitleAndSubtitleTextHeight + sideSubtitleTextPaint.textSize) / 2.0f + weekDayHeaderRowPaddingTop, sideSubtitleTextPaint) + } + + canvas.restore() + // Clip to paint header row only. + canvas.save() + canvas.clipRect(mHeaderColumnWidth, 0f, width.toFloat(), headerHeight + weekDaysHeaderRowTotalPadding) + + + // Draw the header background. + if (enableDrawHeaderBackgroundOnlyOnWeekDays) + canvas.drawRect(0f, 0f, width.toFloat(), headerTitleAndSubtitleTextHeight + weekDaysHeaderRowTotalPadding, mHeaderBackgroundPaint) + else + canvas.drawRect(0f, 0f, width.toFloat(), headerHeight + weekDaysHeaderRowTotalPadding, mHeaderBackgroundPaint) + + canvas.restore() + canvas.save() + + canvas.clipRect(mHeaderColumnWidth, 0f, width.toFloat(), headerHeight + weekDaysHeaderRowTotalPadding - spaceBelowAllDayEvents) + + // Draw the header row texts. + run { + val day = mHomeDate!!.clone() as Calendar + startPixel = startFromPixel + day.add(Calendar.DATE, leftDaysWithGaps) + for (dayNumber in leftDaysWithGaps + 1..leftDaysWithGaps + realNumberOfVisibleDays + 1) { + // Check if the day is today. + val isToday = isSameDay(day, today) + // Don't draw days which are outside requested range + if (!dateIsValid(day)) { + day.add(Calendar.DAY_OF_YEAR, 1) + continue + } + // Draw the day labels title + val dayLabel = getFormattedWeekDayTitle(day) + canvas.drawText(dayLabel, startPixel + widthPerDay / 2, headerWeekDayTitleTextHeight + weekDayHeaderRowPaddingTop, if (isToday) mHeaderWeekDayTitleTodayTextPaint else mHeaderWeekDayTitleTextPaint) + + //draw day subtitle + if (isSubtitleHeaderEnabled) { + val subtitleText = getFormattedWeekDaySubtitle(day) + canvas.drawText(subtitleText, startPixel + widthPerDay / 2, headerTitleAndSubtitleTextHeight + weekDayHeaderRowPaddingTop, + if (isToday) mHeaderWeekDaySubtitleTodayTextPaint else mHeaderWeekDaySubtitleTextPaint) + } + if (containsAllDayEvent) + drawAllDayEvents(day, startPixel, canvas) + startPixel += widthPerDay + columnGap + day.add(Calendar.DAY_OF_YEAR, 1) + } + } + canvas.restore() + //draw text on the left of the all-day events + if (containsAllDayEvent && !TextUtils.isEmpty(allDaySideTitleText)) { + canvas.save() + val weekDaysHeight = headerTitleAndSubtitleTextHeight + weekDaysHeaderRowTotalPadding + val top = weekDaysHeight + spaceBetweenWeekDaysAndAllDayEvents + timeTextHeight / 2 + val bottom = top + allDayEventHeight + canvas.clipRect(0f, 0f, top, bottom) + canvas.drawText(allDaySideTitleText, mHeaderColumnWidth / 2, (top + bottom) / 2, allDaySideTitleTextPaint) + canvas.restore() + } + } + + private fun getFormattedTime(hour: Int, minutes: Int): String { + val cacheKey = Pair(hour, minutes) + val cachedResult = timeFormatterCache[cacheKey] + if (cachedResult != null) + return cachedResult + val result = dateTimeInterpreter.getFormattedTimeOfDay(hour, minutes) + timeFormatterCache[cacheKey] = result + return result + } + + private fun getFormattedWeekDayTitle(cal: Calendar): String { + val cacheKey = SimpleDate(cal) + val cachedResult = weekDayTitleFormatterCache[cacheKey] + if (cachedResult != null) + return cachedResult + val result = dateTimeInterpreter.getFormattedWeekDayTitle(cal) + weekDayTitleFormatterCache[cacheKey] = result + return result + } + + private fun getFormattedWeekDaySubtitle(cal: Calendar): String { + val cacheKey = SimpleDate(cal) + val cachedResult = weekDaySubtitleFormatterCache[cacheKey] + if (cachedResult != null) + return cachedResult + val result = weekDaySubtitleInterpreter!!.getFormattedWeekDaySubtitle(cal) + weekDaySubtitleFormatterCache[cacheKey] = result + return result + } + + /** + * Get the time and date where the user clicked on. + * + * @param x The x position of the touch event. + * @param y The y position of the touch event. + * @return The time and date at the clicked position. + */ + private fun getTimeFromPoint(x: Float, y: Float): Calendar? { + val leftDaysWithGaps = leftDaysWithGaps + var startPixel = xStartPixel + for (dayNumber in leftDaysWithGaps + 1..leftDaysWithGaps + realNumberOfVisibleDays + 1) { + val start = if (startPixel < mHeaderColumnWidth) mHeaderColumnWidth else startPixel + if (widthPerDay + startPixel - start > 0 && x > start && x < startPixel + widthPerDay) { + val day = mHomeDate!!.clone() as Calendar + day.add(Calendar.DATE, dayNumber - 1) + val pixelsFromZero = (y - mCurrentOrigin.y - headerHeight + - weekDaysHeaderRowTotalPadding - timeTextHeight / 2 - spaceBelowAllDayEvents) + val hour = (pixelsFromZero / hourHeight).toInt() + val minute = (60 * (pixelsFromZero - hour * hourHeight) / hourHeight).toInt() + day.add(Calendar.HOUR_OF_DAY, hour + mMinTime) + day.set(Calendar.MINUTE, minute) + return day + } + startPixel += widthPerDay + columnGap + } + return null + } + + /** + * limit current time of event by update mMinTime & mMaxTime + * find smallest of start time & latest of end time + */ + private fun limitEventTime(dates: MutableList) { + if (eventRects != null && eventRects!!.size > 0) { + var startTime: Calendar? = null + var endTime: Calendar? = null + + for (eventRect in eventRects!!) { + for (date in dates) { + if (isSameDay(eventRect.event.startTime, date) && !eventRect.event.isAllDay) { + + if (startTime == null || getPassedMinutesInDay(startTime) > getPassedMinutesInDay(eventRect.event.startTime)) { + startTime = eventRect.event.startTime + } + + if (endTime == null || getPassedMinutesInDay(endTime) < getPassedMinutesInDay(eventRect.event.endTime)) { + endTime = eventRect.event.endTime + } + } + } + } + + if (startTime != null && endTime != null && startTime.before(endTime)) { + setLimitTime(Math.max(0, startTime.get(Calendar.HOUR_OF_DAY)), + Math.min(24, endTime.get(Calendar.HOUR_OF_DAY) + 1)) + return + } + } + } + + /** + * Draw all the events of a particular day. + * + * @param date The day. + * @param startFromPixel The left position of the day area. The events will never go any left from this value. + * @param canvas The canvas to draw upon. + */ + private fun drawEvents(date: Calendar, startFromPixel: Float, canvas: Canvas) { + if (eventRects == null || eventRects!!.isEmpty()) + return + for (eventRect in eventRects!!) { + if (isSameDay(eventRect.event.startTime, date) && !eventRect.event.isAllDay) { + val top = hourHeight * eventRect.top / 60 + eventsTop + val bottom = hourHeight * eventRect.bottom / 60 + eventsTop + + // Calculate left and right. + var left = startFromPixel + eventRect.left * widthPerDay + if (left < startFromPixel) + left += overlappingEventGap + var right = left + eventRect.width * widthPerDay + if (right < startFromPixel + widthPerDay) + right -= overlappingEventGap + + // Draw the event and the event name on top of it. + if (left < right && left < width && top < height && right > mHeaderColumnWidth && + bottom > headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 + spaceBelowAllDayEvents) { + eventRect.rectF = RectF(left, top, right, bottom) + mEventBackgroundPaint.color = if (eventRect.event.color == 0) defaultEventColor else eventRect.event.color + mEventBackgroundPaint.shader = eventRect.event.shader + canvas.drawRoundRect(eventRect.rectF!!, eventCornerRadius, eventCornerRadius, mEventBackgroundPaint) + var topToUse = top + if (eventRect.event.startTime.get(Calendar.HOUR_OF_DAY) < mMinTime) + topToUse = hourHeight * getPassedMinutesInDay(mMinTime, 0) / 60 + eventsTop + + if (newEventIdentifier != eventRect.event.id) + drawEventTitle(eventRect.event, eventRect.rectF!!, canvas, topToUse, left) + else + drawEmptyImage(eventRect.event, eventRect.rectF!!, canvas, topToUse, left) + + } else + eventRect.rectF = null + } + } + } + + /** + * Draw all the Allday-events of a particular day. + * + * @param date The day. + * @param startFromPixel The left position of the day area. The events will never go any left from this value. + * @param canvas The canvas to draw upon. + */ + private fun drawAllDayEvents(date: Calendar, startFromPixel: Float, canvas: Canvas) { + if (eventRects == null || eventRects!!.isEmpty()) + return + val headerTitleAndSubtitleTextHeight = headerWeekDayTitleTextHeight + (if (isSubtitleHeaderEnabled) headerWeekDaySubtitleTextHeight + spaceBetweenHeaderWeekDayTitleAndSubtitle else 0.0f) + for (eventRect in eventRects!!) { + if (isSameDay(eventRect.event.startTime, date) && eventRect.event.isAllDay) { + // Calculate top. + val weekDaysHeight = headerTitleAndSubtitleTextHeight + weekDaysHeaderRowTotalPadding + val top = weekDaysHeight + spaceBetweenWeekDaysAndAllDayEvents + timeTextHeight / 2 + // Calculate bottom. + val bottom = top + eventRect.bottom + // Calculate left and right. + var left = startFromPixel + eventRect.left * widthPerDay + if (left < startFromPixel) + left += overlappingEventGap + var right = left + eventRect.width * widthPerDay + if (right < startFromPixel + widthPerDay) + right -= overlappingEventGap + // Draw the event and the event name on top of it. + if (left < right && left < width && top < height && right > mHeaderColumnWidth && bottom > 0) { + eventRect.rectF = RectF(left, top, right, bottom) + mEventBackgroundPaint.color = if (eventRect.event.color == 0) defaultEventColor else eventRect.event.color + mEventBackgroundPaint.shader = eventRect.event.shader + canvas.drawRoundRect(eventRect.rectF!!, eventCornerRadius, eventCornerRadius, mEventBackgroundPaint) + drawEventTitle(eventRect.event, eventRect.rectF!!, canvas, top, left) + } else + eventRect.rectF = null + } + } + } + + /** + * Draw the name of the event on top of the event rectangle. + * + * @param event The event of which the title (and location) should be drawn. + * @param rect The rectangle on which the text is to be drawn. + * @param canvas The canvas to draw upon. + * @param originalTop The original top position of the rectangle. The rectangle may have some of its portion outside of the visible area. + * @param originalLeft The original left position of the rectangle. The rectangle may have some of its portion outside of the visible area. + */ + private fun drawEventTitle(event: WeekViewEvent, rect: RectF, canvas: Canvas, originalTop: Float, originalLeft: Float) { + if (rect.right - rect.left - (eventPadding * 2).toFloat() < 0) return + if (rect.bottom - rect.top - (eventPadding * 2).toFloat() < 0) return + + // Prepare the name of the event. + val bob = SpannableStringBuilder() + if (!TextUtils.isEmpty(event.name) || !TextUtils.isEmpty(untitledEventText)) { + if (!TextUtils.isEmpty(event.name)) + bob.append(event.name) + else bob.append(untitledEventText) + bob.setSpan(StyleSpan(android.graphics.Typeface.BOLD), 0, bob.length, 0) + } + // Prepare the location of the event. + if (!TextUtils.isEmpty(event.location)) { + if (bob.isNotEmpty()) + bob.append(' ') + bob.append(event.location) + } + + val availableHeight = (rect.bottom - originalTop - (eventPadding * 2).toFloat()).toInt() + val availableWidth = (rect.right - originalLeft - (eventPadding * 2).toFloat()).toInt() + + // Get text color if necessary + if (textColorPicker != null) { + mEventTextPaint.color = textColorPicker!!.getTextColor(event) + } + // Get text dimensions. + var textLayout = StaticLayout(bob, mEventTextPaint, availableWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, false) + if (textLayout.lineCount > 0) { + val lineHeight = textLayout.height / textLayout.lineCount + + if (availableHeight >= lineHeight) { + // Calculate available number of line counts. + var availableLineCount = availableHeight / lineHeight + do { + // Ellipsize text to fit into event rect. + if (newEventIdentifier != event.id) + textLayout = StaticLayout(TextUtils.ellipsize(bob, mEventTextPaint, (availableLineCount * availableWidth).toFloat(), TextUtils.TruncateAt.END), mEventTextPaint, (rect.right - originalLeft - (eventPadding * 2).toFloat()).toInt(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, false) + + // Reduce line count. + availableLineCount-- + + // Repeat until text is short enough. + } while (textLayout.height > availableHeight) + + // Draw text. + canvas.save() + canvas.translate(originalLeft + eventPadding, originalTop + eventPadding) + textLayout.draw(canvas) + canvas.restore() + } + } + } + + /** + * Draw the text on top of the rectangle in the empty event. + */ + private fun drawEmptyImage(event: WeekViewEvent, rect: RectF, canvas: Canvas, originalTop: Float, originalLeft: Float) { + val size = Math.max(1, Math.floor(Math.min(0.8 * rect.height(), 0.8 * rect.width())).toInt()) + if (newEventIconDrawable == null) + newEventIconDrawable = AppCompatResources.getDrawable(context, android.R.drawable.ic_input_add) + var icon = (newEventIconDrawable as BitmapDrawable).bitmap + icon = Bitmap.createScaledBitmap(icon, size, size, false) + canvas.drawBitmap(icon, originalLeft + (rect.width() - icon.width) / 2, originalTop + (rect.height() - icon.height) / 2, mEmptyEventPaint) + } + + /** + * A class to hold reference to the events and their visual representation. An EventRect is + * actually the rectangle that is drawn on the calendar for a given event. There may be more + * than one rectangle for a single event (an event that expands more than one day). In that + * case two instances of the EventRect will be used for a single event. The given event will be + * stored in "originalEvent". But the event that corresponds to rectangle the rectangle + * instance will be stored in "event". + */ + /** + * Create a new instance of event rect. An EventRect is actually the rectangle that is drawn + * on the calendar for a given event. There may be more than one rectangle for a single + * event (an event that expands more than one day). In that case two instances of the + * EventRect will be used for a single event. The given event will be stored in + * "originalEvent". But the event that corresponds to rectangle the rectangle instance will + * be stored in "event". + * + * @param event Represents the event which this instance of rectangle represents. + * @param originalEvent The original event that was passed by the user. + * @param rectF The rectangle. + */ + private inner class EventRect(var event: WeekViewEvent, var originalEvent: WeekViewEvent, var rectF: RectF?) { + var left: Float = 0f + var width: Float = 0f + var top: Float = 0f + var bottom: Float = 0f + override fun toString(): String { + return "EventRect(left=$left, width=$width, top=$top, bottom=$bottom, rectF=$rectF, event=$event, originalEvent=$originalEvent)" + } + } + + /** + * Gets more events of one/more month(s) if necessary. This method is called when the user is + * scrolling the week view. The week view stores the events of three months: the visible month, + * the previous month, the next month. + * + * @param day The day where the user is currently is. + */ + private fun getMoreEvents(day: Calendar) { + clearOptimizationsCaches() + // Get more events if the month is changed. + if (eventRects == null) + eventRects = ArrayList() + // If a refresh was requested then reset some variables. + if (mRefreshEvents) { + this.clearEvents() + mFetchedPeriod = -1 + } + val periodToFetch = this.weekViewLoader.toWeekViewPeriodIndex(day).toInt() + if (!isInEditMode && (mFetchedPeriod < 0 || mFetchedPeriod != periodToFetch || mRefreshEvents)) { + val newEvents = this.weekViewLoader.onLoad(periodToFetch) + // Clear events. + this.clearEvents() + cacheAndSortEvents(newEvents) + calculateHeaderHeight() + mFetchedPeriod = periodToFetch + } + // Prepare to calculate positions of each events. + val tempEvents = eventRects + eventRects = ArrayList() + // Iterate through each day with events to calculate the position of the events. + while (tempEvents!!.size > 0) { + val eventRects = ArrayList(tempEvents.size) + // Get first event for a day. + val eventRect1 = tempEvents.removeAt(0) + eventRects.add(eventRect1) + var i = 0 + while (i < tempEvents.size) { + // Collect all other events for same day. + val eventRect2 = tempEvents[i] + if (isSameDay(eventRect1.event.startTime, eventRect2.event.startTime)) { + tempEvents.removeAt(i) + eventRects.add(eventRect2) + } else { + i++ + } + } + computePositionOfEvents(eventRects) + } + } + + private fun clearEvents() { + clearOptimizationsCaches() + eventRects!!.clear() + mEvents.clear() + } + + private fun clearOptimizationsCaches() { + containsAllDayEventCache.clear() + timeFormatterCache.clear() + weekDayTitleFormatterCache.clear() + weekDaySubtitleFormatterCache.clear() + } + + /** + * Cache the event for smooth scrolling functionality. + * + * @param event The event to cache. + */ + private fun cacheEvent(event: WeekViewEvent) { + if (!event.isAllDay && event.startTime >= event.endTime) + return + val splitEvents = event.splitWeekViewEvents() + for (splitEvent in splitEvents) { + eventRects!!.add(EventRect(splitEvent, event, null)) + } + mEvents.add(event) + } + + /** + * Cache and sort events. + * + * @param events The events to be cached and sorted. + */ + private fun cacheAndSortEvents(events: MutableList?) { + if (events != null) + for (event in events) + cacheEvent(event) + sortEventRects(eventRects) + } + + /** + * Sorts the events in ascending order. + * + * @param eventRects The events to be sorted. + */ + private fun sortEventRects(eventRects: MutableList?) { + eventRects?.sortWith(Comparator { left, right -> + val start1 = left.event.startTime.timeInMillis + val start2 = right.event.startTime.timeInMillis + var comparator = if (start1 > start2) 1 else if (start1 < start2) -1 else 0 + if (comparator == 0) { + val end1 = left.event.endTime.timeInMillis + val end2 = right.event.endTime.timeInMillis + comparator = if (end1 > end2) 1 else if (end1 < end2) -1 else 0 + } + comparator + }) + } + + /** + * Calculates the left and right positions of each events. This comes handy specially if events + * are overlapping. + * + * @param eventRects The events along with their wrapper class. + */ + private fun computePositionOfEvents(eventRects: MutableList) { + // Make "collision groups" for all events that collide with others. + val collisionGroups = ArrayList>() + for (eventRect in eventRects) { + var isPlaced = false + outerLoop@ + for (collisionGroup in collisionGroups) + for (groupEvent in collisionGroup) { + if (isEventsCollide(groupEvent.event, eventRect.event)) { //&& groupEvent.event.isAllDay == eventRect.event.isAllDay) { + collisionGroup.add(eventRect) + isPlaced = true + break@outerLoop + } + } + if (!isPlaced) { + val newGroup = ArrayList() + newGroup.add(eventRect) + collisionGroups.add(newGroup) + } + } + for (collisionGroup in collisionGroups) + expandEventsToMaxWidth(collisionGroup) + } + + /** + * Expands all the events to maximum possible width. The events will try to occupy maximum + * space available horizontally. + * + * @param collisionGroup The group of events which overlap with each other. + */ + private fun expandEventsToMaxWidth(collisionGroup: MutableList) { + // Expand the events to maximum possible width. + val columns = ArrayList>() + columns.add(ArrayList()) + for (eventRect in collisionGroup) { + var isPlaced = false + for (column in columns) { + if (column.size == 0) { + column.add(eventRect) + isPlaced = true + } else if (!isEventsCollide(eventRect.event, column[column.size - 1].event)) { + column.add(eventRect) + isPlaced = true + break + } + } + if (!isPlaced) { + val newColumn = ArrayList() + newColumn.add(eventRect) + columns.add(newColumn) + } + } + + // Calculate left and right position for all the events. + // Get the maxRowCount by looking in all columns. + var maxRowCount = 0 + for (column in columns) { + maxRowCount = Math.max(maxRowCount, column.size) + } + for (i in 0 until maxRowCount) { + // Set the left and right values of the event. + var j = 0f + for (column in columns) { + if (column.size >= i + 1) { + val eventRect = column[i] + eventRect.width = 1f / columns.size + eventRect.left = j / columns.size + if (!eventRect.event.isAllDay) { + eventRect.top = getPassedMinutesInDay(eventRect.event.startTime).toFloat() + eventRect.bottom = getPassedMinutesInDay(eventRect.event.endTime).toFloat() + } else { + eventRect.top = 0f + eventRect.bottom = allDayEventHeight.toFloat() + } + eventRects!!.add(eventRect) + } + j++ + } + } + } + + /** + * Checks if two events overlap. + * + * @param event1 The first event. + * @param event2 The second event. + * @return true if the events overlap. + */ + private fun isEventsCollide(event1: WeekViewEvent, event2: WeekViewEvent): Boolean { + if (event1.isAllDay != event2.isAllDay) + return false + val start1 = event1.startTime.timeInMillis + val start2 = event2.startTime.timeInMillis + val end1 = event1.endTime.timeInMillis + val end2 = event2.endTime.timeInMillis + if (event1.isAllDay) + return !(start1 > end2 || end1 < start2) + val minOverlappingMillis = (minOverlappingMinutes * 60 * 1000).toLong() + return !(start1 + minOverlappingMillis >= end2 || end1 <= start2 + minOverlappingMillis) + } + + + /** + * Checks if time1 occurs after (or at the same time) time2. + * + * @param time1 The time to check. + * @param time2 The time to check against. + * @return true if time1 and time2 are equal or if time1 is after time2. Otherwise false. + */ + private fun isTimeAfterOrEquals(time1: Calendar?, time2: Calendar?): Boolean { + return !(time1 == null || time2 == null) && time1.timeInMillis >= time2.timeInMillis + } + + override fun invalidate() { + super.invalidate() + mAreDimensionsInvalid = true + } + +///////////////////////////////////////////////////////////////// +// +// Functions related to setting and getting the properties. +// +///////////////////////////////////////////////////////////////// + + private fun recalculateHourHeight() { + val height = ((height - (headerHeight + weekDaysHeaderRowTotalPadding + timeTextHeight / 2 + spaceBelowAllDayEvents)) / (this.mMaxTime - this.mMinTime)).toInt() + if (height > hourHeight) { + if (height > maxHourHeight) + maxHourHeight = height + mNewHourHeight = height + } + } + + /** + * Set visible time span. + * + * @param startHour limit time display on top (between 0~24) + * @param endHour limit time display at bottom (between 0~24 and larger than startHour) + */ + fun setLimitTime(startHour: Int, endHour: Int) { + when { + endHour <= startHour -> throw IllegalArgumentException("endHour must larger startHour.") + startHour < 0 -> throw IllegalArgumentException("startHour must be at least 0.") + endHour > 24 -> throw IllegalArgumentException("endHour can't be higher than 24.") + else -> { + this.mMinTime = startHour + this.mMaxTime = endHour + recalculateHourHeight() + invalidate() + } + } + } + + /** + * Set minimal shown time + * + * @param startHour limit time display on top (between 0~24) and smaller than endHour + */ + fun setMinTime(startHour: Int) { + if (mMaxTime <= startHour) { + throw IllegalArgumentException("startHour must smaller than endHour") + } else if (startHour < 0) { + throw IllegalArgumentException("startHour must be at least 0.") + } + this.mMinTime = startHour + recalculateHourHeight() + } + + /** + * Set highest shown time + * + * @param endHour limit time display at bottom (between 0~24 and larger than startHour) + */ + fun setMaxTime(endHour: Int) { + if (endHour <= mMinTime) { + throw IllegalArgumentException("endHour must be larger than startHour.") + } else if (endHour > 24) { + throw IllegalArgumentException("endHour can't be higher than 24.") + } + this.mMaxTime = endHour + recalculateHourHeight() + invalidate() + } + +///////////////////////////////////////////////////////////////// +// +// Functions related to scrolling. +// +///////////////////////////////////////////////////////////////// + + override fun onTouchEvent(event: MotionEvent): Boolean { + + mSizeOfWeekView = (widthPerDay + columnGap) * numberOfVisibleDays + mDistanceMin = mSizeOfWeekView / mOffsetValueToSecureScreen + + scaleDetector.onTouchEvent(event) + val value = mGestureDetector!!.onTouchEvent(event) + + // Check after call of mGestureDetector, so mCurrentFlingDirection and mCurrentScrollDirection are set. + if (event.action == MotionEvent.ACTION_UP && !mIsZooming && mCurrentFlingDirection == Direction.NONE) { + if (mCurrentScrollDirection == Direction.RIGHT || mCurrentScrollDirection == Direction.LEFT) { + goToNearestOrigin() + } + mCurrentScrollDirection = Direction.NONE + } + + return value + } + + private fun goToNearestOrigin() { + var leftDays = (mCurrentOrigin.x / (widthPerDay + columnGap)).toDouble() + + val beforeScroll = mStartOriginForScroll + var isPassed = false + + if (mDistanceDone > mDistanceMin || mDistanceDone < -mDistanceMin || !isScrollNumberOfVisibleDays) { + + when { + !isScrollNumberOfVisibleDays && mCurrentFlingDirection != Direction.NONE -> // snap to nearest day + leftDays = Math.round(leftDays).toDouble() + mCurrentScrollDirection == Direction.LEFT -> { + // snap to last day + leftDays = Math.floor(leftDays) + mStartOriginForScroll -= mSizeOfWeekView + isPassed = true + } + mCurrentScrollDirection == Direction.RIGHT -> { + // snap to next day + leftDays = Math.floor(leftDays) + mStartOriginForScroll += mSizeOfWeekView + isPassed = true + } + else -> // snap to nearest day + leftDays = Math.round(leftDays).toDouble() + } + + + if (isScrollNumberOfVisibleDays) { + val mayScrollHorizontal = beforeScroll - mStartOriginForScroll < xMaxLimit && mCurrentOrigin.x - mStartOriginForScroll > xMinLimit + if (isPassed && mayScrollHorizontal) { + // Stop current animation. + mScroller!!.forceFinished(true) + // Snap to date. + if (mCurrentScrollDirection == Direction.LEFT) { + mScroller!!.startScroll(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), (beforeScroll - mCurrentOrigin.x - mSizeOfWeekView).toInt(), 0, 200) + } else if (mCurrentScrollDirection == Direction.RIGHT) { + mScroller!!.startScroll(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), (mSizeOfWeekView - (mCurrentOrigin.x - beforeScroll)).toInt(), 0, 200) + } + ViewCompat.postInvalidateOnAnimation(this@WeekView) + } + } else { + val nearestOrigin = (mCurrentOrigin.x - leftDays * (widthPerDay + columnGap)).toInt() + val mayScrollHorizontal = mCurrentOrigin.x - nearestOrigin < xMaxLimit && mCurrentOrigin.x - nearestOrigin > xMinLimit + if (mayScrollHorizontal) { + mScroller!!.startScroll(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), -nearestOrigin, 0) + ViewCompat.postInvalidateOnAnimation(this@WeekView) + } + + if (nearestOrigin != 0 && mayScrollHorizontal) { + // Stop current animation. + mScroller!!.forceFinished(true) + // Snap to date. + mScroller!!.startScroll(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), -nearestOrigin, 0, (Math.abs(nearestOrigin) / widthPerDay * scrollDuration).toInt()) + ViewCompat.postInvalidateOnAnimation(this@WeekView) + } + } + + // Reset scrolling and fling direction. + mCurrentFlingDirection = Direction.NONE + mCurrentScrollDirection = mCurrentFlingDirection + + + } else { + mScroller!!.forceFinished(true) + if (mCurrentScrollDirection == Direction.LEFT) { + mScroller!!.startScroll(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), beforeScroll.toInt() - mCurrentOrigin.x.toInt(), 0, 200) + } else if (mCurrentScrollDirection == Direction.RIGHT) { + mScroller!!.startScroll(mCurrentOrigin.x.toInt(), mCurrentOrigin.y.toInt(), beforeScroll.toInt() - mCurrentOrigin.x.toInt(), 0, 200) + } + ViewCompat.postInvalidateOnAnimation(this@WeekView) + + // Reset scrolling and fling direction. + mCurrentFlingDirection = Direction.NONE + mCurrentScrollDirection = mCurrentFlingDirection + } + } + + override fun computeScroll() { + super.computeScroll() + + if (mScroller!!.isFinished) { + if (mCurrentFlingDirection != Direction.NONE) { + // Snap to day after fling is finished. + goToNearestOrigin() + } + } else { + if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) { + goToNearestOrigin() + } else if (mScroller!!.computeScrollOffset()) { + mCurrentOrigin.y = mScroller!!.currY.toFloat() + mCurrentOrigin.x = mScroller!!.currX.toFloat() + ViewCompat.postInvalidateOnAnimation(this) + } + } + } + + /** + * Check if scrolling should be stopped. + * + * @return true if scrolling should be stopped before reaching the end of animation. + */ + private fun forceFinishScroll(): Boolean { + return mScroller!!.currVelocity <= mMinimumFlingVelocity + } + + + ///////////////////////////////////////////////////////////////// +// +// Public methods. +// +///////////////////////////////////////////////////////////////// + fun getLastVisibleDay(): Calendar? { + if (firstVisibleDay == null) + return null + val result = firstVisibleDay!!.clone() as Calendar + result.add(Calendar.DATE, realNumberOfVisibleDays - 1) + return result + } + + /** + * Show today on the week view. + */ + fun goToToday() { + val today = Calendar.getInstance() + goToDate(today) + } + + /** + * Show a specific day on the week view. + * + * @param date The date to show. + */ + fun goToDate(date: Calendar) { + mScroller!!.forceFinished(true) + mCurrentFlingDirection = Direction.NONE + mCurrentScrollDirection = mCurrentFlingDirection + + date.set(Calendar.HOUR_OF_DAY, 0) + date.set(Calendar.MINUTE, 0) + date.set(Calendar.SECOND, 0) + date.set(Calendar.MILLISECOND, 0) + + if (mAreDimensionsInvalid) { + mScrollToDay = date + return + } + + mRefreshEvents = true + + mCurrentOrigin.x = -daysBetween(mHomeDate!!, date) * (widthPerDay + columnGap) + mStartOriginForScroll = mCurrentOrigin.x + invalidate() + } + + /** + * Refreshes the view and loads the events again. + */ + fun notifyDataSetChanged() { + mRefreshEvents = true + invalidate() + } + + /** + * Vertically scroll to a specific hour in the week view. + * + * @param hour The hour to scroll to in 24-hour format. Supported values are 0-24. + */ + fun goToHour(hour: Double) { + if (mAreDimensionsInvalid) { + mScrollToHour = hour + return + } + + var verticalOffset = 0 + if (hour > mMaxTime) + verticalOffset = hourHeight * (mMaxTime - mMinTime) + else if (hour > mMinTime) + verticalOffset = (hourHeight * hour).toInt() + + if (verticalOffset > (hourHeight * (mMaxTime - mMinTime) - height).toFloat() + headerHeight + weekDaysHeaderRowTotalPadding + spaceBelowAllDayEvents) + verticalOffset = ((hourHeight * (mMaxTime - mMinTime) - height).toFloat() + headerHeight + weekDaysHeaderRowTotalPadding + spaceBelowAllDayEvents).toInt() + + mCurrentOrigin.y = (-verticalOffset).toFloat() + invalidate() + } + + /** + * Determine whether a given calendar day falls within the scroll limits set for this view. + * + * @param day the day to check + * @return True if there are no limit or the date is within the limits. + * @see .setMinDate + * @see .setMaxDate + */ + fun dateIsValid(day: Calendar): Boolean { + if (minDate != null && day.before(minDate)) + return false + return !(maxDate != null && day.after(maxDate)) + } + +//region interfaces + + interface DropListener { + /** + * Triggered when view dropped + * + * @param view: dropped view. + * @param date: object set with the date and time of the dropped coordinates on the view. + */ + fun onDrop(view: View, date: Calendar) + } + + interface EventClickListener { + /** + * Triggered when clicked on one existing event + * + * @param event: event clicked. + * @param eventRect: view containing the clicked event. + */ + fun onEventClick(event: WeekViewEvent, eventRect: RectF) + } + + interface EventLongPressListener { + fun onEventLongPress(event: WeekViewEvent, eventRect: RectF) + } + + interface EmptyViewClickListener { + /** + * Triggered when the users clicks on a empty space of the calendar. + * + * @param date: [Calendar] object set with the date and time of the clicked position on the view. + */ + fun onEmptyViewClicked(date: Calendar) + + } + + interface EmptyViewLongPressListener { + /** + * Similar to [com.alamkanak.weekview.WeekView.EmptyViewClickListener] but with long press. + * + * @param time: [Calendar] object set with the date and time of the long pressed position on the view. + */ + fun onEmptyViewLongPress(time: Calendar) + } + + interface ScrollListener { + /** + * Called when the first visible day has changed. + * + * + * (this will also be called during the first draw of the weekview) + * + * @param newFirstVisibleDay The new first visible day + * @param oldFirstVisibleDay The old first visible day (is null on the first call). + */ + fun onFirstVisibleDayChanged(newFirstVisibleDay: Calendar, oldFirstVisibleDay: Calendar?) + } + + interface AddEventClickListener { + /** + * Triggered when the users clicks to create a new event. + * + * @param startTime The startTime of a new event + * @param endTime The endTime of a new event + */ + fun onAddEventClicked(startTime: Calendar, endTime: Calendar) + } + + /** + * A simple GestureListener that holds the focused hour while scaling. + */ + private inner class WeekViewGestureListener : ScaleGestureDetector.OnScaleGestureListener { + internal var mFocusedPointY: Float = 0f + + override fun onScaleEnd(detector: ScaleGestureDetector) { + mIsZooming = false + } + + override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { + mIsZooming = true + goToNearestOrigin() + + // Calculate focused point for scale action + mFocusedPointY = if (isZoomFocusPointEnabled) { + // Use fractional focus, percentage of height + (height.toFloat() - headerHeight - weekDaysHeaderRowTotalPadding - spaceBelowAllDayEvents) * zoomFocusPoint + } else { + // Grab focus + detector.focusY + } + + return true + } + + override fun onScale(detector: ScaleGestureDetector): Boolean { + val scale = detector.scaleFactor + + mNewHourHeight = Math.round(hourHeight * scale) + + // Calculating difference + var diffY = mFocusedPointY - mCurrentOrigin.y + // Scaling difference + diffY = diffY * scale - diffY + // Updating week view origin + mCurrentOrigin.y -= diffY + + invalidate() + return true + } + + } + + private inner class DragListener : View.OnDragListener { + override fun onDrag(v: View, e: DragEvent): Boolean { + when (e.action) { + DragEvent.ACTION_DROP -> { + val headerTitleAndSubtitleTextHeight = headerWeekDayTitleTextHeight + (if (isSubtitleHeaderEnabled) headerWeekDaySubtitleTextHeight + spaceBetweenHeaderWeekDayTitleAndSubtitle else 0.0f) + if (e.x > mHeaderColumnWidth && e.y > headerTitleAndSubtitleTextHeight + weekDaysHeaderRowTotalPadding + spaceBelowAllDayEvents) { + val selectedTime = getTimeFromPoint(e.x, e.y) + if (selectedTime != null) { + dropListener!!.onDrop(v, selectedTime) + } + } + } + } + return true + } + } + +//endregion interfaces + + companion object { + @Deprecated("") + val LENGTH_SHORT = 1 + @Deprecated("") + val LENGTH_LONG = 2 + } +} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java b/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java deleted file mode 100644 index 28faa59aa..000000000 --- a/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java +++ /dev/null @@ -1,343 +0,0 @@ -package com.alamkanak.weekview; - -import android.graphics.Shader; -import android.support.annotation.ColorInt; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import static com.alamkanak.weekview.WeekViewUtil.isSameDay; - -/** - * Created by Raquib-ul-Alam Kanak on 7/21/2014. - * Website: http://april-shower.com - */ -public class WeekViewEvent { - private String mId; - private Calendar mStartTime; - private Calendar mEndTime; - private String mName; - private String mLocation; - private - @ColorInt - int mColor; - private boolean mAllDay; - private Shader mShader; - - public WeekViewEvent() { - - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event as String. - * @param name Name of the event. - * @param startYear Year when the event starts. - * @param startMonth Month when the event starts. - * @param startDay Day when the event starts. - * @param startHour Hour (in 24-hour format) when the event starts. - * @param startMinute Minute when the event starts. - * @param endYear Year when the event ends. - * @param endMonth Month when the event ends. - * @param endDay Day when the event ends. - * @param endHour Hour (in 24-hour format) when the event ends. - * @param endMinute Minute when the event ends. - */ - public WeekViewEvent(String id, String name, int startYear, int startMonth, int startDay, int startHour, int startMinute, int endYear, int endMonth, int endDay, int endHour, int endMinute) { - this.mId = id; - - this.mStartTime = Calendar.getInstance(); - this.mStartTime.set(Calendar.YEAR, startYear); - this.mStartTime.set(Calendar.MONTH, startMonth - 1); - this.mStartTime.set(Calendar.DAY_OF_MONTH, startDay); - this.mStartTime.set(Calendar.HOUR_OF_DAY, startHour); - this.mStartTime.set(Calendar.MINUTE, startMinute); - - this.mEndTime = Calendar.getInstance(); - this.mEndTime.set(Calendar.YEAR, endYear); - this.mEndTime.set(Calendar.MONTH, endMonth - 1); - this.mEndTime.set(Calendar.DAY_OF_MONTH, endDay); - this.mEndTime.set(Calendar.HOUR_OF_DAY, endHour); - this.mEndTime.set(Calendar.MINUTE, endMinute); - - this.mName = name; - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event. - * @param name Name of the event. - * @param startYear Year when the event starts. - * @param startMonth Month when the event starts. - * @param startDay Day when the event starts. - * @param startHour Hour (in 24-hour format) when the event starts. - * @param startMinute Minute when the event starts. - * @param endYear Year when the event ends. - * @param endMonth Month when the event ends. - * @param endDay Day when the event ends. - * @param endHour Hour (in 24-hour format) when the event ends. - * @param endMinute Minute when the event ends. - */ - @Deprecated - public WeekViewEvent(long id, String name, int startYear, int startMonth, int startDay, int startHour, int startMinute, int endYear, int endMonth, int endDay, int endHour, int endMinute) { - this(String.valueOf(id), name, startYear, startMonth, startDay, startHour, startMinute, endYear, endMonth, endDay, endHour, endMinute); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event as String. - * @param name Name of the event. - * @param location The location of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - * @param allDay Is the event an all day event. - * @param shader the Shader of the event rectangle - */ - public WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay, Shader shader) { - this.mId = id; - this.mName = name; - this.mLocation = location; - this.mStartTime = startTime; - this.mEndTime = endTime; - this.mAllDay = allDay; - this.mShader = shader; - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event. - * @param name Name of the event. - * @param location The location of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - * @param allDay Is the event an all day event. - * @param shader the Shader of the event rectangle - */ - @Deprecated - public WeekViewEvent(long id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay, Shader shader) { - this(String.valueOf(id), name, location, startTime, endTime, allDay, shader); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event as String. - * @param name Name of the event. - * @param location The location of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - * @param allDay Is the event an all day event - */ - public WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay) { - this(id, name, location, startTime, endTime, allDay, null); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event. - * @param name Name of the event. - * @param location The location of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - * @param allDay Is the event an all day event - */ - @Deprecated - public WeekViewEvent(long id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay) { - this(id, name, location, startTime, endTime, allDay, null); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event as String. - * @param name Name of the event. - * @param location The location of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - */ - public WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime) { - this(id, name, location, startTime, endTime, false); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event. - * @param name Name of the event. - * @param location The location of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - */ - @Deprecated - public WeekViewEvent(long id, String name, String location, Calendar startTime, Calendar endTime) { - this(id, name, location, startTime, endTime, false); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event specified as String. - * @param name Name of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - */ - public WeekViewEvent(String id, String name, Calendar startTime, Calendar endTime) { - this(id, name, null, startTime, endTime); - } - - /** - * Initializes the event for week view. - * - * @param id The id of the event. - * @param name Name of the event. - * @param startTime The time when the event starts. - * @param endTime The time when the event ends. - */ - @Deprecated - public WeekViewEvent(long id, String name, Calendar startTime, Calendar endTime) { - this(id, name, null, startTime, endTime); - } - - public Calendar getStartTime() { - return mStartTime; - } - - public void setStartTime(Calendar startTime) { - this.mStartTime = startTime; - } - - public Calendar getEndTime() { - return mEndTime; - } - - public void setEndTime(Calendar endTime) { - this.mEndTime = endTime; - } - - public String getName() { - return mName; - } - - public void setName(String name) { - this.mName = name; - } - - public String getLocation() { - return mLocation; - } - - public void setLocation(String location) { - this.mLocation = location; - } - - public - @ColorInt - int getColor() { - return mColor; - } - - public void setColor(int color) { - this.mColor = color; - } - - public boolean isAllDay() { - return mAllDay; - } - - public void setAllDay(boolean allDay) { - this.mAllDay = allDay; - } - - public Shader getShader() { - return mShader; - } - - public void setShader(Shader shader) { - mShader = shader; - } - - public String getIdentifier() { - return mId; - } - - @Deprecated - public long getId() { - return Long.parseLong(mId); - } - - public void setIdentifier(String id) { - this.mId = id; - } - - @Deprecated - public void setId(long id) { - this.mId = String.valueOf(id); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - WeekViewEvent that = (WeekViewEvent) o; - - return mId.equals(that.mId); - } - - @Override - public int hashCode() { - return mId.hashCode(); - } - - public List splitWeekViewEvents() { - //This function splits the WeekViewEvent in WeekViewEvents by day - List events = new ArrayList(); - // The first millisecond of the next day is still the same day. (no need to split events for this). - Calendar endTime = (Calendar) this.getEndTime().clone(); - endTime.add(Calendar.MILLISECOND, -1); - if (!isSameDay(this.getStartTime(), endTime)) { - endTime = (Calendar) this.getStartTime().clone(); - endTime.set(Calendar.HOUR_OF_DAY, 23); - endTime.set(Calendar.MINUTE, 59); - WeekViewEvent event1 = new WeekViewEvent(this.getIdentifier(), this.getName(), this.getLocation(), this.getStartTime(), endTime, this.isAllDay()); - event1.setColor(this.getColor()); - events.add(event1); - - // Add other days. - Calendar otherDay = (Calendar) this.getStartTime().clone(); - otherDay.add(Calendar.DATE, 1); - while (!isSameDay(otherDay, this.getEndTime())) { - Calendar overDay = (Calendar) otherDay.clone(); - overDay.set(Calendar.HOUR_OF_DAY, 0); - overDay.set(Calendar.MINUTE, 0); - Calendar endOfOverDay = (Calendar) overDay.clone(); - endOfOverDay.set(Calendar.HOUR_OF_DAY, 23); - endOfOverDay.set(Calendar.MINUTE, 59); - WeekViewEvent eventMore = new WeekViewEvent(this.getIdentifier(), this.getName(), null, overDay, endOfOverDay, this.isAllDay()); - eventMore.setColor(this.getColor()); - events.add(eventMore); - - // Add next day. - otherDay.add(Calendar.DATE, 1); - } - - // Add last day. - Calendar startTime = (Calendar) this.getEndTime().clone(); - startTime.set(Calendar.HOUR_OF_DAY, 0); - startTime.set(Calendar.MINUTE, 0); - WeekViewEvent event2 = new WeekViewEvent(this.getIdentifier(), this.getName(), this.getLocation(), startTime, this.getEndTime(), this.isAllDay()); - event2.setColor(this.getColor()); - events.add(event2); - } else { - events.add(this); - } - - return events; - } -} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.kt b/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.kt new file mode 100644 index 000000000..e10632643 --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.kt @@ -0,0 +1,131 @@ +package com.alamkanak.weekview + +import android.graphics.Shader +import androidx.annotation.ColorInt +import com.alamkanak.weekview.WeekViewUtil.isSameDay +import java.util.* + +open class WeekViewEvent { + val id: String? + val startTime: Calendar + val endTime: Calendar + var name: String? = null + var location: String? = null + @ColorInt + @get:ColorInt + var color: Int = 0 + val isAllDay: Boolean + var shader: Shader? = null + + /**CTOR for a single, all day event*/ + constructor(id: String?, name: String?, location: String? = null, allDayTime: Calendar, shader: Shader? = null) : this(id, name, location, allDayTime, allDayTime, true, shader) + + /** + * Initializes the event for week view. + * + * @param id The id of the event as String. + * @param name Name of the event. + * @param location The location of the event. + * @param startTime The time when the event starts. + * @param endTime The time when the event ends. + * @param allDay Is the event an all day event. + * @param shader the Shader of the event rectangle + */ + @JvmOverloads constructor(id: String?, name: String?, location: String?, startTime: Calendar, endTime: Calendar, allDay: Boolean = false, shader: Shader? = null) { + this.id = id + this.name = name + this.location = location + this.isAllDay = allDay + if (!allDay) { + this.startTime = startTime + this.endTime = endTime + } else { + WeekViewUtil.resetTime(startTime) + this.startTime = startTime + if (!WeekViewUtil.isSameDay(startTime, endTime)) { + WeekViewUtil.resetTime(endTime) + this.endTime = endTime + } else + this.endTime = startTime + } + this.shader = shader + } + + /** + * Initializes the event for week view. + * + * @param id The id of the event specified as String. + * @param name Name of the event. + * @param startTime The time when the event starts. + * @param endTime The time when the event ends. + */ + constructor(id: String?, name: String?, startTime: Calendar, endTime: Calendar) : this(id, name, null, startTime, endTime) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WeekViewEvent) return false + return id == other.id + } + + override fun hashCode(): Int { + return id?.hashCode() ?: 0 + } + + fun splitWeekViewEvents(): MutableList { + //This function splits the WeekViewEvent in WeekViewEvents by day + if (isSameDay(this.startTime, this.endTime)) { + val events = ArrayList(1) + events.add(this) + return events + } + val events = ArrayList() + // The first millisecond of the next day is still the same day. (no need to split events for this). + var endTime = this.endTime.clone() as Calendar + endTime.add(Calendar.MILLISECOND, -1) + endTime = this.startTime.clone() as Calendar + endTime.set(Calendar.HOUR_OF_DAY, 23) + endTime.set(Calendar.MINUTE, 59) + val event1 = WeekViewEvent(this.id, this.name, this.location, this.startTime, endTime, this.isAllDay) + event1.color = this.color + events.add(event1) + // Add other days. + if (!isSameDay(this.startTime, this.endTime)) { + val otherDay = this.startTime.clone() as Calendar + otherDay.add(Calendar.DATE, 1) + while (!isSameDay(otherDay, this.endTime)) { + val overDay = otherDay.clone() as Calendar + overDay.set(Calendar.HOUR_OF_DAY, 0) + overDay.set(Calendar.MINUTE, 0) + val endOfOverDay = overDay.clone() as Calendar + endOfOverDay.set(Calendar.HOUR_OF_DAY, 23) + endOfOverDay.set(Calendar.MINUTE, 59) + val eventMore = WeekViewEvent(this.id, this.name, null, overDay, endOfOverDay, this.isAllDay) + eventMore.color = this.color + events.add(eventMore) + + // Add next day. + otherDay.add(Calendar.DATE, 1) + } + // Add last day. + val startTime = this.endTime.clone() as Calendar + startTime.set(Calendar.HOUR_OF_DAY, 0) + startTime.set(Calendar.MINUTE, 0) + val event2 = WeekViewEvent(this.id, this.name, this.location, startTime, this.endTime, this.isAllDay) + event2.color = this.color + events.add(event2) + } + return events + } + + override fun toString(): String { + val colorStr = "#${Integer.toHexString(color)}" + val startTimeStr = WeekViewUtil.calendarToString(startTime, !isAllDay) + if (isAllDay) { + if (WeekViewUtil.isSameDay(startTime, endTime)) + return "allDayEvent(id=$id, time=$startTimeStr..${WeekViewUtil.calendarToString(startTime, false)}, name=$name, location=$location, color=$colorStr ,shader=$shader)" + return "allDayEvent(id=$id, time=$startTimeStr, name=$name, location=$location, color=$colorStr ,shader=$shader)" + } + val endTimeStr = WeekViewUtil.calendarToString(endTime, true) + return "normalEvent(id=$id, startTime=$colorStr, endTime=$endTimeStr, name=$name, location=$location, color=$colorStr , shader=$shader)" + } +} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewLoader.java b/library/src/main/java/com/alamkanak/weekview/WeekViewLoader.kt similarity index 69% rename from library/src/main/java/com/alamkanak/weekview/WeekViewLoader.java rename to library/src/main/java/com/alamkanak/weekview/WeekViewLoader.kt index 3b8d6a5b8..4e6c5b34d 100644 --- a/library/src/main/java/com/alamkanak/weekview/WeekViewLoader.java +++ b/library/src/main/java/com/alamkanak/weekview/WeekViewLoader.kt @@ -1,19 +1,20 @@ -package com.alamkanak.weekview; +package com.alamkanak.weekview -import java.util.Calendar; -import java.util.List; +import java.util.* + +interface WeekViewLoader { -public interface WeekViewLoader { /** * Convert a date into a double that will be used to reference when you're loading data. - *

+ * + * * All periods that have the same integer part, define one period. Dates that are later in time * should have a greater return value. * * @param instance the date * @return The period index in which the date falls (floating point number). */ - double toWeekViewPeriodIndex(Calendar instance); + fun toWeekViewPeriodIndex(instance: Calendar): Double /** * Load the events within the period @@ -21,5 +22,5 @@ public interface WeekViewLoader { * @param periodIndex the period to load * @return A list with the events of this period */ - List onLoad(int periodIndex); + fun onLoad(periodIndex: Int): MutableList? } diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java b/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java deleted file mode 100644 index 9a33c7221..000000000 --- a/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.alamkanak.weekview; - -import java.util.Calendar; - -/** - * Created by jesse on 6/02/2016. - */ -public class WeekViewUtil { - - - ///////////////////////////////////////////////////////////////// - // - // Helper methods. - // - ///////////////////////////////////////////////////////////////// - - /** - * Checks if two dates are on the same day. - * - * @param dateOne The first date. - * @param dateTwo The second date. * - * @return Whether the dates are on the same day. - */ - public static boolean isSameDay(Calendar dateOne, Calendar dateTwo) { - return dateOne.get(Calendar.YEAR) == dateTwo.get(Calendar.YEAR) && dateOne.get(Calendar.DAY_OF_YEAR) == dateTwo.get(Calendar.DAY_OF_YEAR); - } - - /** - * Returns a calendar instance at the start of today - * - * @return the calendar instance - */ - public static Calendar today() { - Calendar today = Calendar.getInstance(); - today.set(Calendar.HOUR_OF_DAY, 0); - today.set(Calendar.MINUTE, 0); - today.set(Calendar.SECOND, 0); - today.set(Calendar.MILLISECOND, 0); - return today; - } - - /** - * Checks if two dates are on the same day and hour. - * - * @param dateOne The first day. - * @param dateTwo The second day. - * @return Whether the dates are on the same day and hour. - */ - public static boolean isSameDayAndHour(Calendar dateOne, Calendar dateTwo) { - - if (dateTwo != null) { - return isSameDay(dateOne, dateTwo) && dateOne.get(Calendar.HOUR_OF_DAY) == dateTwo.get(Calendar.HOUR_OF_DAY); - } - return false; - } - - /** - * Returns the amount of days between the second date and the first date - * - * @param dateOne the first date - * @param dateTwo the second date - * @return the amount of days between dateTwo and dateOne - */ - public static int daysBetween(Calendar dateOne, Calendar dateTwo) { - return (int) (((dateTwo.getTimeInMillis() + dateTwo.getTimeZone().getOffset(dateTwo.getTimeInMillis())) / (1000 * 60 * 60 * 24)) - - ((dateOne.getTimeInMillis() + dateOne.getTimeZone().getOffset(dateOne.getTimeInMillis())) / (1000 * 60 * 60 * 24))); - } - - /* - * Returns the amount of minutes passed in the day before the time in the given date - * @param date - * @return amount of minutes in day before time - */ - public static int getPassedMinutesInDay(Calendar date) { - return getPassedMinutesInDay(date.get(Calendar.HOUR_OF_DAY), date.get(Calendar.MINUTE)); - } - - /** - * Returns the amount of minutes in the given hours and minutes - * - * @param hour - * @param minute - * @return amount of minutes in the given hours and minutes - */ - public static int getPassedMinutesInDay(int hour, int minute) { - return hour * 60 + minute; - } -} diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.kt b/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.kt new file mode 100644 index 000000000..2d83a424a --- /dev/null +++ b/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.kt @@ -0,0 +1,148 @@ +package com.alamkanak.weekview + +import android.content.Context +import android.os.Build +import android.text.format.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +object WeekViewUtil { + ///////////////////////////////////////////////////////////////// + // + // Helper methods. + // + ///////////////////////////////////////////////////////////////// + + /** + * Checks if two dates are on the same day. + * + * @param dateOne The first date. + * @param dateTwo The second date. * + * @return Whether the dates are on the same day. + */ + @JvmStatic + fun isSameDay(dateOne: Calendar, dateTwo: Calendar): Boolean { + if (dateOne === dateTwo) + return true + return dateOne.get(Calendar.YEAR) == dateTwo.get(Calendar.YEAR) && dateOne.get(Calendar.DAY_OF_YEAR) == dateTwo.get(Calendar.DAY_OF_YEAR) + } + + /** + * Returns a calendar instance at the start of today + * + * @return the calendar instance + */ + @JvmStatic + fun today(): Calendar { + val today = Calendar.getInstance() + today.set(Calendar.HOUR_OF_DAY, 0) + today.set(Calendar.MINUTE, 0) + today.set(Calendar.SECOND, 0) + today.set(Calendar.MILLISECOND, 0) + return today + } + + @JvmStatic + fun isSameDayAndHourAndMinute(dateOne: Calendar, dateTwo: Calendar): Boolean { + return isSameDay(dateOne, dateTwo) && dateOne.get(Calendar.HOUR_OF_DAY) == dateTwo.get(Calendar.HOUR_OF_DAY) + && dateOne.get(Calendar.MINUTE) == dateTwo.get(Calendar.MINUTE) + } + + /** + * Returns the amount of days between the second date and the first date + * + * @param dateOne the first date + * @param dateTwo the second date + * @return the amount of days between dateTwo and dateOne + */ + @JvmStatic + fun daysBetween(dateOne: Calendar, dateTwo: Calendar): Int { + return ((dateTwo.timeInMillis + dateTwo.timeZone.getOffset(dateTwo.timeInMillis)) / (1000 * 60 * 60 * 24) - (dateOne.timeInMillis + dateOne.timeZone.getOffset(dateOne.timeInMillis)) / (1000 * 60 * 60 * 24)).toInt() + } + + /* + * Returns the amount of minutes passed in the day before the time in the given date + * @param date + * @return amount of minutes in day before time + */ + @JvmStatic + fun getPassedMinutesInDay(date: Calendar): Int { + return getPassedMinutesInDay(date.get(Calendar.HOUR_OF_DAY), date.get(Calendar.MINUTE)) + } + + /** + * Returns the amount of minutes in the given hours and minutes + * + * @param hour + * @param minute + * @return amount of minutes in the given hours and minutes + */ + @JvmStatic + fun getPassedMinutesInDay(hour: Int, minute: Int): Int { + return hour * 60 + minute + } + + /**returns a date format of dayOfWeek+day&month, based on the current locale. + * This is important, as the format is different in many countries. Especially the numeric part that can be different : "d/M", "M/d", "d-M", "M-d" ,...*/ + @JvmStatic + fun getWeekdayWithNumericDayAndMonthFormat(context: Context, shortDate: Boolean): java.text.DateFormat { + val weekDayFormat = if (shortDate) "EEEEE" else "EEE" + val defaultDateFormatPattern = "$weekDayFormat d/M" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + val locale = Locale.getDefault() + var bestDateTimePattern = DateFormat.getBestDateTimePattern(locale, defaultDateFormatPattern) + //workaround fix for this issue: https://issuetracker.google.com/issues/79311044 + //TODO if there is a better API that doesn't require this workaround, use it. Be sure to check vs all locales, as done here: https://issuetracker.google.com/issues/37044127 + bestDateTimePattern = bestDateTimePattern.replace("d+".toRegex(), "d").replace("M+".toRegex(), "M") + bestDateTimePattern = bestDateTimePattern.replace("E+".toRegex(), weekDayFormat) + return SimpleDateFormat(bestDateTimePattern, locale) + } + try { + val dateFormatOrder = DateFormat.getDateFormatOrder(context) + if (dateFormatOrder.isEmpty()) + return SimpleDateFormat(defaultDateFormatPattern, Locale.getDefault()) + val sb = StringBuilder() + for (i in dateFormatOrder.indices) { + val c = dateFormatOrder[i] + if (Character.toLowerCase(c) == 'y') + continue + if (sb.isNotEmpty()) + sb.append('/') + when (Character.toLowerCase(c)) { + 'm' -> sb.append("M") + 'd' -> sb.append("d") + } + } + val dateFormatString = sb.toString() + return SimpleDateFormat("$weekDayFormat $dateFormatString", Locale.getDefault()) + } catch (e: Exception) { + return SimpleDateFormat(defaultDateFormatPattern, Locale.getDefault()) + } + } + + @JvmStatic + fun calendarToString(cal: Calendar?, includeTime: Boolean = true): String { + if (cal == null) + return "" + val sb = StringBuilder() + with(cal) { + sb.append(get(Calendar.YEAR).toString()).append('-').append((get(Calendar.MONTH) + 1).toString()) + .append('-').append(get(Calendar.DAY_OF_MONTH).toString()) + if (includeTime) + sb.append(" ").append(get(Calendar.HOUR_OF_DAY).toString()).append(':').append(get(Calendar.MINUTE).toString()).append(':') + .append(get(Calendar.SECOND).toString()).append('.').append(get(Calendar.MILLISECOND).toString()) + } + return sb.toString() + } + + @JvmStatic + fun resetTime(cal: Calendar) { + with(cal) + { + set(java.util.Calendar.HOUR_OF_DAY, 0) + set(java.util.Calendar.SECOND, 0) + set(java.util.Calendar.MINUTE, 0) + set(java.util.Calendar.MILLISECOND, 0) + } + } +} diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 516f90227..2656c95f0 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -1,69 +1,74 @@ - - - - + + + + - - + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/build.gradle b/sample/build.gradle index 23e0f47cc..38b5fdb01 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,13 +1,14 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 25 - buildToolsVersion '25.0.2' + compileSdkVersion 28 defaultConfig { applicationId "com.alamkanak.weekview" minSdkVersion 14 - targetSdkVersion 25 + targetSdkVersion 28 versionCode 1 versionName "1.0" } @@ -17,11 +18,25 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + repositories { + maven { url 'https://jitpack.io' } + } } + dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':library') - compile 'com.android.support:appcompat-v7:25.1.0' - compile 'com.squareup.retrofit:retrofit:1.9.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.squareup.retrofit:retrofit:1.9.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation project(':library') + implementation 'androidx.core:core-ktx:1.0.1' + +// implementation 'com.github.AndroidDeveloperLB:Android-Week-View:ef94b8d256' +// implementation 'com.github.AndroidDeveloperLB:Android-Week-View:develop-SNAPSHOT' +// implementation 'com.github.AndroidDeveloperLB:Android-Week-View:develop-v2.3.0-gef94b8d-26' + +} +repositories { + mavenCentral() } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 775a8b5f8..c7659a868 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,28 +1,29 @@ - + - + + android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" + android:theme="@style/AppTheme" tools:ignore="AllowBackup,GoogleAppIndexingWarning"> + android:name=".MainActivity" android:label="@string/app_name"> - - + + + + + + android:name=".AsynchronousActivity" android:label="@string/title_activity_asynchronous"/> + + android:name=".WholeViewSnappingActivity" android:label="@string/title_activity_whole_view_snap" + android:screenOrientation="reverseLandscape" android:theme="@style/AppTheme.FullScreen"/> diff --git a/sample/src/main/assets/fonts/Raleway/Raleway-Black.ttf b/sample/src/main/assets/fonts/Raleway/Raleway-Black.ttf deleted file mode 100644 index 6805f4f58..000000000 Binary files a/sample/src/main/assets/fonts/Raleway/Raleway-Black.ttf and /dev/null differ diff --git a/sample/src/main/assets/fonts/Raleway/Raleway-Medium.ttf b/sample/src/main/assets/fonts/Raleway/Raleway-Medium.ttf deleted file mode 100644 index 7a71a6ff0..000000000 Binary files a/sample/src/main/assets/fonts/Raleway/Raleway-Medium.ttf and /dev/null differ diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/AsynchronousActivity.java b/sample/src/main/java/com/alamkanak/weekview/sample/AsynchronousActivity.java deleted file mode 100644 index ea0bf4dc6..000000000 --- a/sample/src/main/java/com/alamkanak/weekview/sample/AsynchronousActivity.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.alamkanak.weekview.sample; - -import android.widget.Toast; - -import com.alamkanak.weekview.WeekViewEvent; -import com.alamkanak.weekview.sample.apiclient.Event; -import com.alamkanak.weekview.sample.apiclient.MyJsonService; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import retrofit.Callback; -import retrofit.RestAdapter; -import retrofit.RetrofitError; -import retrofit.client.Response; - -/** - * An example of how events can be fetched from network and be displayed on the week view. - * Created by Raquib-ul-Alam Kanak on 1/3/2014. - * Website: http://alamkanak.github.io - */ -public class AsynchronousActivity extends BaseActivity implements Callback> { - - private List events = new ArrayList(); - boolean calledNetwork = false; - - @Override - public List onMonthChange(int newYear, int newMonth) { - - // Download events from network if it hasn't been done already. To understand how events are - // downloaded using retrofit, visit http://square.github.io/retrofit - if (!calledNetwork) { - RestAdapter retrofit = new RestAdapter.Builder() - .setEndpoint("https://api.myjson.com/bins") - .build(); - MyJsonService service = retrofit.create(MyJsonService.class); - service.listEvents(this); - calledNetwork = true; - } - - // Return only the events that matches newYear and newMonth. - List matchedEvents = new ArrayList(); - for (WeekViewEvent event : events) { - if (eventMatches(event, newYear, newMonth)) { - matchedEvents.add(event); - } - } - return matchedEvents; - } - - /** - * Checks if an event falls into a specific year and month. - * - * @param event The event to check for. - * @param year The year. - * @param month The month. - * @return True if the event matches the year and month. - */ - private boolean eventMatches(WeekViewEvent event, int year, int month) { - return (event.getStartTime().get(Calendar.YEAR) == year && event.getStartTime().get(Calendar.MONTH) == month - 1) || (event.getEndTime().get(Calendar.YEAR) == year && event.getEndTime().get(Calendar.MONTH) == month - 1); - } - - @Override - public void success(List events, Response response) { - this.events.clear(); - for (Event event : events) { - this.events.add(event.toWeekViewEvent()); - } - getWeekView().notifyDatasetChanged(); - } - - @Override - public void failure(RetrofitError error) { - error.printStackTrace(); - Toast.makeText(this, R.string.async_error, Toast.LENGTH_SHORT).show(); - } -} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/AsynchronousActivity.kt b/sample/src/main/java/com/alamkanak/weekview/sample/AsynchronousActivity.kt new file mode 100644 index 000000000..a387be577 --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/AsynchronousActivity.kt @@ -0,0 +1,70 @@ +package com.alamkanak.weekview.sample + +import android.os.Bundle +import android.widget.Toast +import com.alamkanak.weekview.WeekViewEvent +import com.alamkanak.weekview.sample.apiclient.Event +import com.alamkanak.weekview.sample.apiclient.MyJsonService +import kotlinx.android.synthetic.main.activity_base.* +import retrofit.Callback +import retrofit.RestAdapter +import retrofit.RetrofitError +import retrofit.client.Response +import java.util.* + +/** + * An example of how events can be fetched from network and be displayed on the week view. + */ +class AsynchronousActivity : BaseActivity(), Callback> { + + private val events = ArrayList() + private var calledNetwork = false + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + weekView.setLimitTime(0, 24) + } + + override fun onMonthChange(newYear: Int, newMonth: Int): MutableList? { + + // Download events from network if it hasn't been done already. To understand how events are + // downloaded using retrofit, visit http://square.github.io/retrofit + if (!calledNetwork) { + val retrofit = RestAdapter.Builder() + .setEndpoint("https://api.myjson.com/bins") + .build() + val service = retrofit.create(MyJsonService::class.java) + service.listEvents(this) + calledNetwork = true + } + + // Return only the events that matches newYear and newMonth. + val matchedEvents = ArrayList() + for (event in events) + if (eventMatches(event, newYear, newMonth)) + matchedEvents.add(event) + return matchedEvents + } + + /** + * Checks if an event falls into a specific year and month. + * + * @param event The event to check for. + * @param year The year. + * @param month The month. + * @return True if the event matches the year and month. + */ + private fun eventMatches(event: WeekViewEvent, year: Int, month: Int): Boolean { + return event.startTime.get(Calendar.YEAR) == year && event.startTime.get(Calendar.MONTH) == month - 1 || event.endTime.get(Calendar.YEAR) == year && event.endTime.get(Calendar.MONTH) == month - 1 + } + + override fun success(events: MutableList, response: Response) { + for (event in events) + this.events.add(event.toWeekViewEvent()) + weekView.notifyDataSetChanged() + } + + override fun failure(error: RetrofitError) { + error.printStackTrace() + Toast.makeText(this, R.string.async_error, Toast.LENGTH_SHORT).show() + } +} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java b/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java deleted file mode 100644 index 58970a47e..000000000 --- a/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.alamkanak.weekview.sample; - -import android.content.ClipData; -import android.graphics.RectF; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.RequiresApi; -import android.support.v7.app.AppCompatActivity; -import android.util.TypedValue; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import com.alamkanak.weekview.DateTimeInterpreter; -import com.alamkanak.weekview.MonthLoader; -import com.alamkanak.weekview.WeekView; -import com.alamkanak.weekview.WeekViewEvent; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.List; -import java.util.Locale; - -/** - * This is a base activity which contains week view and all the codes necessary to initialize the - * week view. - * Created by Raquib-ul-Alam Kanak on 1/3/2014. - * Website: http://alamkanak.github.io - */ -public abstract class BaseActivity extends AppCompatActivity implements WeekView.EventClickListener, MonthLoader.MonthChangeListener, WeekView.EventLongPressListener, WeekView.EmptyViewLongPressListener, WeekView.EmptyViewClickListener, WeekView.AddEventClickListener, WeekView.DropListener { - private static final int TYPE_DAY_VIEW = 1; - private static final int TYPE_THREE_DAY_VIEW = 2; - private static final int TYPE_WEEK_VIEW = 3; - private int mWeekViewType = TYPE_THREE_DAY_VIEW; - protected WeekView mWeekView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_base); - - TextView draggableView = (TextView) findViewById(R.id.draggable_view); - draggableView.setOnLongClickListener(new DragTapListener()); - - - // Get a reference for the week view in the layout. - mWeekView = (WeekView) findViewById(R.id.weekView); - - // Show a toast message about the touched event. - mWeekView.setOnEventClickListener(this); - - // The week view has infinite scrolling horizontally. We have to provide the events of a - // month every time the month changes on the week view. - mWeekView.setMonthChangeListener(this); - - // Set long press listener for events. - mWeekView.setEventLongPressListener(this); - - // Set long press listener for empty view - mWeekView.setEmptyViewLongPressListener(this); - - // Set EmptyView Click Listener - mWeekView.setEmptyViewClickListener(this); - - // Set AddEvent Click Listener - mWeekView.setAddEventClickListener(this); - - // Set Drag and Drop Listener - mWeekView.setDropListener(this); - - // Set minDate - /*Calendar minDate = Calendar.getInstance(); - minDate.set(Calendar.DAY_OF_MONTH, 1); - minDate.add(Calendar.MONTH, 1); - mWeekView.setMinDate(minDate); - - // Set maxDate - Calendar maxDate = Calendar.getInstance(); - maxDate.add(Calendar.MONTH, 1); - maxDate.set(Calendar.DAY_OF_MONTH, 10); - mWeekView.setMaxDate(maxDate); - - Calendar calendar = (Calendar) maxDate.clone(); - calendar.add(Calendar.DATE, -2); - mWeekView.goToDate(calendar);*/ - - //mWeekView.setAutoLimitTime(true); - //mWeekView.setLimitTime(4, 16); - - //mWeekView.setMinTime(10); - //mWeekView.setMaxTime(20); - - // Set up a date time interpreter to interpret how the date and time will be formatted in - // the week view. This is optional. - setupDateTimeInterpreter(false); - } - - @Override - protected void onResume() { - super.onResume(); - /*mWeekView.setShowDistinctPastFutureColor(true); - mWeekView.setShowDistinctWeekendColor(true); - mWeekView.setFutureBackgroundColor(Color.rgb(24,85,96)); - mWeekView.setFutureWeekendBackgroundColor(Color.rgb(255,0,0)); - mWeekView.setPastBackgroundColor(Color.rgb(85,189,200)); - mWeekView.setPastWeekendBackgroundColor(Color.argb(50, 0,255,0)); - */ - } - - private final class DragTapListener implements View.OnLongClickListener { - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) - @Override - public boolean onLongClick(View v) { - ClipData data = ClipData.newPlainText("", ""); - View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v); - v.startDrag(data, shadowBuilder, v, 0); - return true; - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - setupDateTimeInterpreter(id == R.id.action_week_view); - switch (id) { - case R.id.action_today: - mWeekView.goToToday(); - return true; - case R.id.action_day_view: - if (mWeekViewType != TYPE_DAY_VIEW) { - item.setChecked(!item.isChecked()); - mWeekViewType = TYPE_DAY_VIEW; - mWeekView.setNumberOfVisibleDays(1); - - // Lets change some dimensions to best fit the view. - mWeekView.setColumnGap((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics())); - mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics())); - mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics())); - } - return true; - case R.id.action_three_day_view: - if (mWeekViewType != TYPE_THREE_DAY_VIEW) { - item.setChecked(!item.isChecked()); - mWeekViewType = TYPE_THREE_DAY_VIEW; - mWeekView.setNumberOfVisibleDays(3); - - // Lets change some dimensions to best fit the view. - mWeekView.setColumnGap((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics())); - mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics())); - mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics())); - } - return true; - case R.id.action_week_view: - if (mWeekViewType != TYPE_WEEK_VIEW) { - item.setChecked(!item.isChecked()); - mWeekViewType = TYPE_WEEK_VIEW; - mWeekView.setNumberOfVisibleDays(7); - - // Lets change some dimensions to best fit the view. - mWeekView.setColumnGap((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics())); - mWeekView.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics())); - mWeekView.setEventTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics())); - } - return true; - } - - return super.onOptionsItemSelected(item); - } - - /** - * Set up a date time interpreter which will show short date values when in week view and long - * date values otherwise. - * - * @param shortDate True if the date values should be short. - */ - private void setupDateTimeInterpreter(final boolean shortDate) { - mWeekView.setDateTimeInterpreter(new DateTimeInterpreter() { - @Override - public String interpretDate(Calendar date) { - SimpleDateFormat weekdayNameFormat = new SimpleDateFormat("EEE", Locale.getDefault()); - String weekday = weekdayNameFormat.format(date.getTime()); - SimpleDateFormat format = new SimpleDateFormat(" M/d", Locale.getDefault()); - - // All android api level do not have a standard way of getting the first letter of - // the week day name. Hence we get the first char programmatically. - // Details: http://stackoverflow.com/questions/16959502/get-one-letter-abbreviation-of-week-day-of-a-date-in-java#answer-16959657 - if (shortDate) - weekday = String.valueOf(weekday.charAt(0)); - return weekday.toUpperCase() + format.format(date.getTime()); - } - - @Override - public String interpretTime(int hour, int minutes) { - String strMinutes = String.format("%02d", minutes); - if (hour > 11) { - return (hour - 12) + ":" + strMinutes + " PM"; - } else { - if (hour == 0) { - return "12:" + strMinutes + " AM"; - } else { - return hour + ":" + strMinutes + " AM"; - } - } - } - }); - } - - protected String getEventTitle(Calendar time) { - return String.format("Event of %02d:%02d %s/%d", time.get(Calendar.HOUR_OF_DAY), time.get(Calendar.MINUTE), time.get(Calendar.MONTH) + 1, time.get(Calendar.DAY_OF_MONTH)); - } - - @Override - public void onEventClick(WeekViewEvent event, RectF eventRect) { - Toast.makeText(this, "Clicked " + event.getName(), Toast.LENGTH_SHORT).show(); - } - - @Override - public void onEventLongPress(WeekViewEvent event, RectF eventRect) { - Toast.makeText(this, "Long pressed event: " + event.getName(), Toast.LENGTH_SHORT).show(); - } - - @Override - public void onEmptyViewLongPress(Calendar time) { - Toast.makeText(this, "Empty view long pressed: " + getEventTitle(time), Toast.LENGTH_SHORT).show(); - } - - public WeekView getWeekView() { - return mWeekView; - } - - @Override - public void onEmptyViewClicked(Calendar date) { - Toast.makeText(this, "Empty view" + " clicked: " + getEventTitle(date), Toast.LENGTH_SHORT).show(); - } - - @Override - public List onMonthChange(int newYear, int newMonth) { - return null; - } - - @Override - public void onAddEventClicked(Calendar startTime, Calendar endTime) { - Toast.makeText(this, "Add event clicked.", Toast.LENGTH_SHORT).show(); - } - - @Override - public void onDrop(View view, Calendar date) { - Toast.makeText(this, "View dropped to " + date.toString(), Toast.LENGTH_SHORT).show(); - } -} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.kt b/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.kt new file mode 100644 index 000000000..693ddeaf3 --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.kt @@ -0,0 +1,248 @@ +package com.alamkanak.weekview.sample + +import android.content.ClipData +import android.graphics.RectF +import android.os.Bundle +import android.util.TypedValue +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.alamkanak.weekview.* +import kotlinx.android.synthetic.main.activity_base.* +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +//import android.text.format.DateFormat +/** + * This is a base activity which contains week view and all the codes necessary to initialize the + * week view. + */ +abstract class BaseActivity : AppCompatActivity(), WeekView.EventClickListener, MonthLoader.MonthChangeListener, WeekView.EventLongPressListener, WeekView.EmptyViewLongPressListener, WeekView.EmptyViewClickListener, WeekView.AddEventClickListener, WeekView.DropListener { + private var mWeekViewType = TYPE_THREE_DAY_VIEW + private lateinit var shortDateFormat: DateFormat + private lateinit var timeFormat: DateFormat + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + shortDateFormat = WeekViewUtil.getWeekdayWithNumericDayAndMonthFormat(this, true) + timeFormat = android.text.format.DateFormat.getTimeFormat(this) ?: SimpleDateFormat("HH:mm", Locale.getDefault()) + setContentView(R.layout.activity_base) + + draggable_view.setOnLongClickListener(DragTapListener()) + + // Get a reference for the week view in the layout. + + // Show a toast message about the touched event. + weekView.eventClickListener = this + + // The week view has infinite scrolling horizontally. We have to provide the events of a + // month every time the month changes on the week view. + weekView.monthChangeListener = this + + // Set long press listener for events. + weekView.eventLongPressListener = this + + // Set long press listener for empty view + weekView.emptyViewLongPressListener = this + + // Set EmptyView Click Listener + weekView.emptyViewClickListener = this + + // Set AddEvent Click Listener + weekView.addEventClickListener = this + + // Set Drag and Drop Listener + weekView.dropListener = this + + // Set minDate + /*Calendar minDate = Calendar.getInstance(); + minDate.set(Calendar.DAY_OF_MONTH, 1); + minDate.add(Calendar.MONTH, 1); + mWeekView.setMinDate(minDate); + + // Set maxDate + Calendar maxDate = Calendar.getInstance(); + maxDate.add(Calendar.MONTH, 1); + maxDate.set(Calendar.DAY_OF_MONTH, 10); + mWeekView.setMaxDate(maxDate); + + Calendar calendar = (Calendar) maxDate.clone(); + calendar.add(Calendar.DATE, -2); + mWeekView.goToDate(calendar);*/ + + //mWeekView.setAutoLimitTime(true); + //mWeekView.setLimitTime(4, 16); + + //mWeekView.setMinTime(10); + //mWeekView.setMaxTime(20); + + // Set up a date time interpreter to interpret how the date and time will be formatted in + // the week view. This is optional. + setupDateTimeInterpreter(false) + } + +// override fun onResume() { +// super.onResume() +// mWeekView.setShowDistinctPastFutureColor(true); +// mWeekView.setShowDistinctWeekendColor(true); +// mWeekView.setFutureBackgroundColor(Color.rgb(24,85,96)); +// mWeekView.setFutureWeekendBackgroundColor(Color.rgb(255,0,0)); +// mWeekView.setPastBackgroundColor(Color.rgb(85,189,200)); +// mWeekView.setPastWeekendBackgroundColor(Color.argb(50, 0,255,0)); +// } + + private inner class DragTapListener : View.OnLongClickListener { + override fun onLongClick(v: View): Boolean { + val data = ClipData.newPlainText("", "") + val shadowBuilder = View.DragShadowBuilder(v) + v.startDrag(data, shadowBuilder, v, 0) + return true + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + when (id) { + R.id.action_today -> { + weekView.goToToday() + return true + } + R.id.action_day_view -> { + if (!item.isChecked) { + item.isChecked = true + setDayViewType(TYPE_DAY_VIEW) + } + return true + } + R.id.action_three_day_view -> { + if (!item.isChecked) { + item.isChecked = true + setDayViewType(TYPE_THREE_DAY_VIEW) + } + return true + } + R.id.action_week_view -> { + if (!item.isChecked) { + item.isChecked = true + setDayViewType(TYPE_WEEK_VIEW) + } + return true + } + } + return super.onOptionsItemSelected(item) + } + + fun setDayViewType(dayViewType: Int) { + setupDateTimeInterpreter(dayViewType == TYPE_WEEK_VIEW) + + when (dayViewType) { + TYPE_DAY_VIEW -> { + mWeekViewType = TYPE_DAY_VIEW + weekView.numberOfVisibleDays = 1 + // Lets change some dimensions to best fit the view. + weekView.columnGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt() + weekView.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics) + weekView.eventTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics) + } + TYPE_THREE_DAY_VIEW -> { + mWeekViewType = TYPE_THREE_DAY_VIEW + weekView.numberOfVisibleDays = 3 + // Lets change some dimensions to best fit the view. + weekView.columnGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt() + weekView.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics) + weekView.eventTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics) + } + TYPE_WEEK_VIEW -> { + mWeekViewType = TYPE_WEEK_VIEW + weekView.numberOfVisibleDays = 7 + // Lets change some dimensions to best fit the view. + weekView.columnGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics).toInt() + weekView.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, resources.displayMetrics) + weekView.eventTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, resources.displayMetrics) + } + } + } + + /** + * Set up a date time interpreter which will show short date values when in week view and long + * date values otherwise. + * + * @param shortDate True if the date values should be short. + */ + protected open fun setupDateTimeInterpreter(shortDate: Boolean) { + val calendar = Calendar.getInstance().apply { + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + val normalDateFormat = WeekViewUtil.getWeekdayWithNumericDayAndMonthFormat(this@BaseActivity, false) + weekView.dateTimeInterpreter = object : DateTimeInterpreter { + override fun getFormattedTimeOfDay(hour: Int, minutes: Int): String { + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minutes) + return timeFormat.format(calendar.time) + } + + override fun getFormattedWeekDayTitle(date: Calendar): String { + return if (shortDate) shortDateFormat.format(date.time) else normalDateFormat.format(date.time) + } + } + } + + protected fun getEventTitle(startCal: Calendar, endCal: Calendar? = null, allDay: Boolean = false): String { + val startDate = startCal.time + val endDate = endCal?.time + return when { + allDay -> { + if (endCal == null || WeekViewUtil.isSameDay(startCal, endCal)) + shortDateFormat.format(startDate) + else "${shortDateFormat.format(startDate)}..${shortDateFormat.format(endDate)}" + } + endCal == null -> "${shortDateFormat.format(startDate)} ${timeFormat.format(startDate)}" + WeekViewUtil.isSameDay(startCal, endCal) -> "${shortDateFormat.format(startDate)} ${timeFormat.format(startDate)}..${timeFormat.format(endDate)}" + else -> "${shortDateFormat.format(startDate)} ${timeFormat.format(startDate)}..${shortDateFormat.format(endDate)} ${timeFormat.format(endDate)}" + } + } + + override fun onEventClick(event: WeekViewEvent, eventRect: RectF) { + Toast.makeText(this, "Clicked " + event.name, Toast.LENGTH_SHORT).show() + } + + override fun onEventLongPress(event: WeekViewEvent, eventRect: RectF) { + Toast.makeText(this, "Long pressed event: " + event.name, Toast.LENGTH_SHORT).show() + } + + override fun onEmptyViewLongPress(time: Calendar) { + Toast.makeText(this, "Empty view long pressed: " + getEventTitle(time), Toast.LENGTH_SHORT).show() + } + + override fun onEmptyViewClicked(date: Calendar) { + Toast.makeText(this, "Empty view" + " clicked: " + getEventTitle(date), Toast.LENGTH_SHORT).show() + } + + override fun onMonthChange(newYear: Int, newMonth: Int): MutableList? { + return null + } + + override fun onAddEventClicked(startTime: Calendar, endTime: Calendar) { + Toast.makeText(this, "Add event clicked.", Toast.LENGTH_SHORT).show() + } + + override fun onDrop(view: View, date: Calendar) { + Toast.makeText(this, "View dropped to " + date.toString(), Toast.LENGTH_SHORT).show() + } + + companion object { + const val TYPE_DAY_VIEW = 1 + const val TYPE_THREE_DAY_VIEW = 2 + const val TYPE_WEEK_VIEW = 3 + } +} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/BasicActivity.java b/sample/src/main/java/com/alamkanak/weekview/sample/BasicActivity.java deleted file mode 100644 index d0473cc27..000000000 --- a/sample/src/main/java/com/alamkanak/weekview/sample/BasicActivity.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.alamkanak.weekview.sample; - -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.Bundle; - -import com.alamkanak.weekview.TextColorPicker; -import com.alamkanak.weekview.WeekViewEvent; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -/** - * A basic example of how to use week view library. - * Created by Raquib-ul-Alam Kanak on 1/3/2014. - * Website: http://alamkanak.github.io - */ -public class BasicActivity extends BaseActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Typeface customTypeface = Typeface.createFromAsset(this.getAssets(), "fonts/Raleway/Raleway-Medium.ttf"); - mWeekView.setTypeface(customTypeface); - mWeekView.setTextColorPicker(new TextColorPicker() { - @Override - public int getTextColor(WeekViewEvent event) { - int color = event.getColor(); - double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255; - return a < 0.2 ? Color.BLACK : Color.WHITE; - } - }); - } - - @Override - public List onMonthChange(int newYear, int newMonth) { - // Populate the week view with some events. - List events = new ArrayList(); - - Calendar startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 3); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - Calendar endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR, 1); - endTime.set(Calendar.MONTH, newMonth - 1); - WeekViewEvent event = new WeekViewEvent("First", getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_01)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 3); - startTime.set(Calendar.MINUTE, 30); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.set(Calendar.HOUR_OF_DAY, 4); - endTime.set(Calendar.MINUTE, 30); - endTime.set(Calendar.MONTH, newMonth - 1); - event = new WeekViewEvent("Second", getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_05)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 4); - startTime.set(Calendar.MINUTE, 20); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.set(Calendar.HOUR_OF_DAY, 5); - endTime.set(Calendar.MINUTE, 0); - event = new WeekViewEvent(10, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_03)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 5); - startTime.set(Calendar.MINUTE, 30); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR_OF_DAY, 2); - endTime.set(Calendar.MONTH, newMonth - 1); - event = new WeekViewEvent(2, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_02)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 5); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - startTime.add(Calendar.DATE, 1); - endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR_OF_DAY, 3); - endTime.set(Calendar.MONTH, newMonth - 1); - event = new WeekViewEvent(3, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_03)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.DAY_OF_MONTH, 15); - startTime.set(Calendar.HOUR_OF_DAY, 3); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR_OF_DAY, 3); - event = new WeekViewEvent(4, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_04)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.DAY_OF_MONTH, 1); - startTime.set(Calendar.HOUR_OF_DAY, 3); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR_OF_DAY, 3); - event = new WeekViewEvent(5, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_01)); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.DAY_OF_MONTH, startTime.getActualMaximum(Calendar.DAY_OF_MONTH)); - startTime.set(Calendar.HOUR_OF_DAY, 15); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR_OF_DAY, 3); - event = new WeekViewEvent(5, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_02)); - events.add(event); - - //AllDay event - startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 0); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.add(Calendar.HOUR_OF_DAY, 23); - event = new WeekViewEvent(7, getEventTitle(startTime), null, startTime, endTime, true); - event.setColor(getResources().getColor(R.color.event_color_04)); - events.add(event); - events.add(event); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.DAY_OF_MONTH, 8); - startTime.set(Calendar.HOUR_OF_DAY, 2); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.set(Calendar.DAY_OF_MONTH, 10); - endTime.set(Calendar.HOUR_OF_DAY, 23); - event = new WeekViewEvent(8, getEventTitle(startTime), null, startTime, endTime, true); - event.setColor(getResources().getColor(R.color.event_color_03)); - events.add(event); - - // All day event until 00:00 next day - startTime = Calendar.getInstance(); - startTime.set(Calendar.DAY_OF_MONTH, 10); - startTime.set(Calendar.HOUR_OF_DAY, 0); - startTime.set(Calendar.MINUTE, 0); - startTime.set(Calendar.SECOND, 0); - startTime.set(Calendar.MILLISECOND, 0); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.set(Calendar.DAY_OF_MONTH, 11); - event = new WeekViewEvent(8, getEventTitle(startTime), null, startTime, endTime, true); - event.setColor(getResources().getColor(R.color.event_color_01)); - - startTime = Calendar.getInstance(); - startTime.set(Calendar.HOUR_OF_DAY, 18); - startTime.set(Calendar.MINUTE, 30); - startTime.set(Calendar.MONTH, newMonth - 1); - startTime.set(Calendar.YEAR, newYear); - endTime = (Calendar) startTime.clone(); - endTime.set(Calendar.HOUR_OF_DAY, 19); - endTime.set(Calendar.MINUTE, 30); - endTime.set(Calendar.MONTH, newMonth - 1); - event = new WeekViewEvent(22, getEventTitle(startTime), startTime, endTime); - event.setColor(getResources().getColor(R.color.event_color_02)); - events.add(event); - - return events; - } -} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/BasicActivity.kt b/sample/src/main/java/com/alamkanak/weekview/sample/BasicActivity.kt new file mode 100644 index 000000000..7d66593d7 --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/BasicActivity.kt @@ -0,0 +1,189 @@ +package com.alamkanak.weekview.sample + +import android.os.Bundle +import androidx.core.content.res.ResourcesCompat +import com.alamkanak.weekview.WeekViewEvent +import kotlinx.android.synthetic.main.activity_base.* +import java.util.* + +/** + * A basic example of how to use week view library. + */ +open class BasicActivity : BaseActivity() { + var uniqueId: Long = 0 + fun getUniqueId(): String = uniqueId++.toString() + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + weekView.typeface = ResourcesCompat.getFont(this, R.font.lato) + } + + override fun onMonthChange(newYear: Int, newMonth: Int): MutableList? { + // Populate the week view with some events. + val events = ArrayList() + + var startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 3) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + var endTime = startTime.clone() as Calendar + endTime.add(Calendar.HOUR, 1) + endTime.set(Calendar.MONTH, newMonth - 1) + var event = WeekViewEvent("First", getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_01, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.set(Calendar.HOUR_OF_DAY, startTime.get(Calendar.HOUR_OF_DAY) + 1) + endTime.set(Calendar.MINUTE, 0) + endTime.set(Calendar.MONTH, newMonth - 1) + event = WeekViewEvent("cur", "cur", startTime, endTime) + event.color = 0xffff0000.toInt() + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 3) + startTime.set(Calendar.MINUTE, 30) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.set(Calendar.HOUR_OF_DAY, 4) + endTime.set(Calendar.MINUTE, 30) + endTime.set(Calendar.MONTH, newMonth - 1) + event = WeekViewEvent("Second", getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_05, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 4) + startTime.set(Calendar.MINUTE, 20) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.set(Calendar.HOUR_OF_DAY, 5) + endTime.set(Calendar.MINUTE, 0) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_03, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 5) + startTime.set(Calendar.MINUTE, 30) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.add(Calendar.HOUR_OF_DAY, 2) + endTime.set(Calendar.MONTH, newMonth - 1) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_02, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 5) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + startTime.add(Calendar.DATE, 1) + endTime = startTime.clone() as Calendar + endTime.add(Calendar.HOUR_OF_DAY, 3) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_03, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.DAY_OF_MONTH, 15) + startTime.set(Calendar.HOUR_OF_DAY, 3) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.add(Calendar.HOUR_OF_DAY, 3) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_04, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.DAY_OF_MONTH, 1) + startTime.set(Calendar.HOUR_OF_DAY, 3) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.add(Calendar.HOUR_OF_DAY, 3) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_01, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.DAY_OF_MONTH, startTime.getActualMaximum(Calendar.DAY_OF_MONTH)) + startTime.set(Calendar.HOUR_OF_DAY, 15) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.add(Calendar.HOUR_OF_DAY, 3) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime), startTime, endTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_02, null) + events.add(event) + + //AllDay event + //single day + startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 0) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, allDay = true), null, startTime) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_04, null) + events.add(event) + + startTime = Calendar.getInstance() + startTime.set(Calendar.DAY_OF_MONTH, 8) + startTime.set(Calendar.HOUR_OF_DAY, 2) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.set(Calendar.DAY_OF_MONTH, 10) + endTime.set(Calendar.HOUR_OF_DAY, 23) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime, true), null, startTime, endTime, true) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_03, null) + events.add(event) + + // All day event until 00:00 next day + startTime = Calendar.getInstance() + startTime.set(Calendar.DAY_OF_MONTH, 10) + startTime.set(Calendar.HOUR_OF_DAY, 0) + startTime.set(Calendar.MINUTE, 0) + startTime.set(Calendar.SECOND, 0) + startTime.set(Calendar.MILLISECOND, 0) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.set(Calendar.DAY_OF_MONTH, 11) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime, true), null, startTime, endTime, true) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_01, null) + + startTime = Calendar.getInstance() + startTime.set(Calendar.HOUR_OF_DAY, 18) + startTime.set(Calendar.MINUTE, 30) + startTime.set(Calendar.MONTH, newMonth - 1) + startTime.set(Calendar.YEAR, newYear) + endTime = startTime.clone() as Calendar + endTime.set(Calendar.HOUR_OF_DAY, 19) + endTime.set(Calendar.MINUTE, 30) + endTime.set(Calendar.MONTH, newMonth - 1) + event = WeekViewEvent(getUniqueId(), getEventTitle(startTime, endTime, true), null, startTime, endTime, true) + event.color = ResourcesCompat.getColor(resources, R.color.event_color_02, null) + events.add(event) + + return events + } + +} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.java b/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.java deleted file mode 100644 index dc20a1477..000000000 --- a/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.alamkanak.weekview.sample; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; - -/** - * The launcher activity of the sample app. It contains the links to visit all the example screens. - * Created by Raquib-ul-Alam Kanak on 7/21/2014. - * Website: http://alamkanak.github.io - */ -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - findViewById(R.id.buttonBasic).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, BasicActivity.class); - startActivity(intent); - } - }); - - findViewById(R.id.buttonAsynchronous).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, AsynchronousActivity.class); - startActivity(intent); - } - }); - } -} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.kt b/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.kt new file mode 100644 index 000000000..060c8c073 --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.kt @@ -0,0 +1,52 @@ +package com.alamkanak.weekview.sample + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import kotlinx.android.synthetic.main.activity_main.* + +/** + * The launcher activity of the sample app. It contains the links to visit all the example screens. + */ +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + buttonBasic.setOnClickListener { + startActivity(Intent(this@MainActivity, BasicActivity::class.java)) + } + buttonAsynchronous.setOnClickListener { + startActivity(Intent(this@MainActivity, AsynchronousActivity::class.java)) + } + buttonWholeViewSnap.setOnClickListener { + startActivity(Intent(this@MainActivity, WholeViewSnappingActivity::class.java)) + } +// startActivity(Intent(this@MainActivity, WholeViewSnappingActivity::class.java)) +// finish() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.main_activity, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + var url: String? = null + when (item.itemId) { + R.id.menuItem_all_my_apps -> url = "https://play.google.com/store/apps/developer?id=AndroidDeveloperLB" + R.id.menuItem_all_my_repositories -> url = "https://github.com/AndroidDeveloperLB" + R.id.menuItem_current_repository_website -> url = "https://github.com/AndroidDeveloperLB/Android-Week-View" + } + if (url == null) + return true + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + startActivity(intent) + return true + } +} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/WholeViewSnappingActivity.kt b/sample/src/main/java/com/alamkanak/weekview/sample/WholeViewSnappingActivity.kt new file mode 100644 index 000000000..435372193 --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/WholeViewSnappingActivity.kt @@ -0,0 +1,109 @@ +package com.alamkanak.weekview.sample + +import android.graphics.RectF +import android.os.Bundle +import android.text.format.DateFormat +import android.util.TypedValue +import android.view.Menu +import android.view.MenuItem +import android.view.View +import com.alamkanak.weekview.DateTimeInterpreter +import com.alamkanak.weekview.WeekDaySubtitleInterpreter +import com.alamkanak.weekview.WeekView +import com.alamkanak.weekview.WeekViewEvent +import kotlinx.android.synthetic.main.activity_base.* +import java.text.SimpleDateFormat +import java.util.* + + +/** + * Activity to demonstrate snapping of the whole view, for example week-by-week. + */ +class WholeViewSnappingActivity : BasicActivity() { + val locale = Locale.getDefault() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + weekView.isShowNowLine = true +// weekView.setAutoLimitTime(true) + weekView.setLimitTime(0, 24) + weekView.isUsingCheckersStyle = true + weekView.columnGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics).toInt() + weekView.hourSeparatorHeight = weekView.columnGap + weekView.isScrollNumberOfVisibleDays = true + weekView.dropListener = null + weekView.allDaySideTitleText = getString(R.string.all_day) + setDayViewType(TYPE_WEEK_VIEW) + val cal = Calendar.getInstance() + val currentHour = cal.get(Calendar.HOUR_OF_DAY) + cal.get(Calendar.MINUTE) / 60.0 + weekView.goToHour(Math.max(currentHour - 1, 0.0)) + cal.set(Calendar.DAY_OF_WEEK, cal.firstDayOfWeek) + weekView.goToDate(cal) + weekView.scrollListener = object : WeekView.ScrollListener { + val monthFormatter = SimpleDateFormat("MMM", locale) + val yearFormatter = SimpleDateFormat("yyyy", locale) + + override fun onFirstVisibleDayChanged(newFirstVisibleDay: Calendar, oldFirstVisibleDay: Calendar?) { + //we show just the month here, so no need to update it every time + if (oldFirstVisibleDay == null || oldFirstVisibleDay.get(Calendar.MONTH) != newFirstVisibleDay.get(Calendar.MONTH)) { + val date = newFirstVisibleDay.time + if (cal.get(Calendar.YEAR) == newFirstVisibleDay.get(Calendar.YEAR)) { + weekView.sideSubtitleText = "" + weekView.sideTitleText = monthFormatter.format(date) + } else { + weekView.sideTitleText = monthFormatter.format(date) + weekView.sideSubtitleText = yearFormatter.format(date) + } + } + } + } + draggable_view.visibility = View.GONE + weekView.weekDaySubtitleInterpreter = object : WeekDaySubtitleInterpreter { + val dateFormatTitle = SimpleDateFormat("d", locale) + + override fun getFormattedWeekDaySubtitle(date: Calendar): String = dateFormatTitle.format(date.time) + } + weekView.eventClickListener = object : WeekView.EventClickListener { + override fun onEventClick(event: WeekViewEvent, eventRect: RectF) { + } + } + } + + override fun setupDateTimeInterpreter(shortDate: Boolean) { + val calendar = Calendar.getInstance().apply { + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + val timeFormat = DateFormat.getTimeFormat(this) + ?: SimpleDateFormat("HH:mm", locale) + val dateFormatTitle = SimpleDateFormat("EEE", locale) + weekView.dateTimeInterpreter = object : DateTimeInterpreter { + override fun getFormattedTimeOfDay(hour: Int, minutes: Int): String { + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minutes) + return timeFormat.format(calendar.time) + } + + override fun getFormattedWeekDayTitle(date: Calendar): String = dateFormatTitle.format(date.time) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.action_today) { + val cal = Calendar.getInstance().apply { set(Calendar.DAY_OF_WEEK, firstDayOfWeek) } + weekView.goToDate(cal) + return true + } + return super.onOptionsItemSelected(item) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val result = super.onCreateOptionsMenu(menu) + menu.findItem(R.id.action_day_view).isChecked = false + menu.findItem(R.id.action_three_day_view).isChecked = false + menu.findItem(R.id.action_week_view).isChecked = true + return result + } +} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/Event.java b/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/Event.java deleted file mode 100644 index 406409e17..000000000 --- a/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/Event.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.alamkanak.weekview.sample.apiclient; - -import android.annotation.SuppressLint; -import android.graphics.Color; - -import com.alamkanak.weekview.WeekViewEvent; -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -/** - * An event model that was built for automatic serialization from json to object. - * Created by Raquib-ul-Alam Kanak on 1/3/16. - * Website: http://alamkanak.github.io - */ -public class Event { - - @Expose - @SerializedName("name") - private String mName; - @Expose - @SerializedName("dayOfMonth") - private int mDayOfMonth; - @Expose - @SerializedName("startTime") - private String mStartTime; - @Expose - @SerializedName("endTime") - private String mEndTime; - @Expose - @SerializedName("color") - private String mColor; - - public String getName() { - return mName; - } - - public void setName(String name) { - this.mName = name; - } - - public int getDayOfMonth() { - return mDayOfMonth; - } - - public void setDayOfMonth(int dayOfMonth) { - this.mDayOfMonth = dayOfMonth; - } - - public String getStartTime() { - return mStartTime; - } - - public void setStartTime(String startTime) { - this.mStartTime = startTime; - } - - public String getEndTime() { - return mEndTime; - } - - public void setEndTime(String endTime) { - this.mEndTime = endTime; - } - - public String getColor() { - return mColor; - } - - public void setColor(String color) { - this.mColor = color; - } - - @SuppressLint("SimpleDateFormat") - public WeekViewEvent toWeekViewEvent() { - - // Parse time. - SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); - Date start = new Date(); - Date end = new Date(); - try { - start = sdf.parse(getStartTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - try { - end = sdf.parse(getEndTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - - // Initialize start and end time. - Calendar now = Calendar.getInstance(); - Calendar startTime = (Calendar) now.clone(); - startTime.setTimeInMillis(start.getTime()); - startTime.set(Calendar.YEAR, now.get(Calendar.YEAR)); - startTime.set(Calendar.MONTH, now.get(Calendar.MONTH)); - startTime.set(Calendar.DAY_OF_MONTH, getDayOfMonth()); - Calendar endTime = (Calendar) startTime.clone(); - endTime.setTimeInMillis(end.getTime()); - endTime.set(Calendar.YEAR, startTime.get(Calendar.YEAR)); - endTime.set(Calendar.MONTH, startTime.get(Calendar.MONTH)); - endTime.set(Calendar.DAY_OF_MONTH, startTime.get(Calendar.DAY_OF_MONTH)); - - // Create an week view event. - WeekViewEvent weekViewEvent = new WeekViewEvent(); - weekViewEvent.setIdentifier(getName()); - weekViewEvent.setName(getName()); - weekViewEvent.setStartTime(startTime); - weekViewEvent.setEndTime(endTime); - weekViewEvent.setColor(Color.parseColor(getColor())); - - return weekViewEvent; - } -} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/Event.kt b/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/Event.kt new file mode 100644 index 000000000..38bdbb62c --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/Event.kt @@ -0,0 +1,75 @@ +package com.alamkanak.weekview.sample.apiclient + +import android.annotation.SuppressLint +import android.graphics.Color +import com.alamkanak.weekview.WeekViewEvent +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +/** + * An event model that was built for automatic serialization from json to object. + */ +class Event { + + @Expose + @SerializedName("name") + var name: String? = null + @Expose + @SerializedName("dayOfMonth") + var dayOfMonth: Int = 0 + @Expose + @SerializedName("startTime") + var startTime: String? = null + @Expose + @SerializedName("endTime") + var endTime: String? = null + @Expose + @SerializedName("color") + var color: String? = null + + @SuppressLint("SimpleDateFormat") + fun toWeekViewEvent(): WeekViewEvent { + + // Parse time. + val sdf = SimpleDateFormat("HH:mm") + var start = Date() + var end = Date() + try { + start = sdf.parse(startTime) + } catch (e: ParseException) { + e.printStackTrace() + } + + try { + end = sdf.parse(endTime) + } catch (e: ParseException) { + e.printStackTrace() + } + + // Initialize start and end time. + val now = Calendar.getInstance() + val startTime = Calendar.getInstance() + startTime.timeInMillis = start.time + with(startTime) { + set(Calendar.YEAR, now.get(Calendar.YEAR)) + set(Calendar.MONTH, now.get(Calendar.MONTH)) + set(Calendar.DAY_OF_MONTH, dayOfMonth) + } + val endTime = startTime.clone() as Calendar + endTime.timeInMillis = end.time + with(endTime) { + set(Calendar.YEAR, startTime.get(Calendar.YEAR)) + set(Calendar.MONTH, startTime.get(Calendar.MONTH)) + set(Calendar.DAY_OF_MONTH, startTime.get(Calendar.DAY_OF_MONTH)) + } + + // Create an week view event. + val weekViewEvent = WeekViewEvent(name, name, null, startTime, endTime) + weekViewEvent.color = Color.parseColor(color) + + return weekViewEvent + } +} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/MyJsonService.java b/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/MyJsonService.java deleted file mode 100644 index cebe4c8c3..000000000 --- a/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/MyJsonService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.alamkanak.weekview.sample.apiclient; - -import java.util.List; - -import retrofit.Callback; -import retrofit.http.GET; - -/** - * Created by Raquib-ul-Alam Kanak on 1/3/16. - * Website: http://alamkanak.github.io - */ -public interface MyJsonService { - - @GET("/1kpjf") - void listEvents(Callback> eventsCallback); - -} diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/MyJsonService.kt b/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/MyJsonService.kt new file mode 100644 index 000000000..9c51ff677 --- /dev/null +++ b/sample/src/main/java/com/alamkanak/weekview/sample/apiclient/MyJsonService.kt @@ -0,0 +1,11 @@ +package com.alamkanak.weekview.sample.apiclient + +import retrofit.Callback +import retrofit.http.GET + +interface MyJsonService { + + @GET("/1kpjf") + fun listEvents(eventsCallback: Callback>) + +} diff --git a/sample/src/main/res/font/lato.ttf b/sample/src/main/res/font/lato.ttf new file mode 100644 index 000000000..04ea8efb1 Binary files /dev/null and b/sample/src/main/res/font/lato.ttf differ diff --git a/sample/src/main/res/layout/activity_base.xml b/sample/src/main/res/layout/activity_base.xml index 910d93e3b..1b8288b4f 100644 --- a/sample/src/main/res/layout/activity_base.xml +++ b/sample/src/main/res/layout/activity_base.xml @@ -1,43 +1,19 @@ - + + android:id="@+id/weekView" android:layout_width="match_parent" android:layout_height="match_parent" + app:autoLimitTime="false" app:dayBackgroundColor="#05000000" app:headerColumnTextColor="@color/toolbar_text" + app:headerRowBackgroundColor="@color/toolbar" app:maxTime="16" app:minOverlappingMinutes="5" app:minTime="4" + app:newEventTimeResolutionInMinutes="15" app:noOfVisibleDays="3" app:timeColumnResolution="60" + app:todayColumnBackgroundColor="#1848adff" app:todayHeaderTextColor="@color/accent" + app:untitledEventText="@string/untitled_event"/> + android:id="@+id/draggable_view" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:background="#ffff0000" android:padding="3dp" android:text="@string/drag_me" + android:textColor="@android:color/white"/> diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index f8e348f0b..a39762d25 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -1,23 +1,17 @@ - +