1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.calendar.month 17 18 import com.android.calendar.Event 19 import com.android.calendar.R 20 import com.android.calendar.Utils 21 import android.animation.Animator 22 import android.animation.AnimatorListenerAdapter 23 import android.animation.ObjectAnimator 24 import android.app.Service 25 import android.content.Context 26 import android.content.res.Configuration 27 import android.content.res.Resources 28 import android.graphics.Canvas 29 import android.graphics.Color 30 import android.graphics.Paint 31 import android.graphics.Paint.Align 32 import android.graphics.Paint.Style 33 import android.graphics.Typeface 34 import android.graphics.drawable.Drawable 35 import android.provider.CalendarContract.Attendees 36 import android.text.TextPaint 37 import android.text.TextUtils 38 import android.text.format.DateFormat 39 import android.text.format.DateUtils 40 import android.text.format.Time 41 import android.util.Log 42 import android.view.MotionEvent 43 import android.view.accessibility.AccessibilityEvent 44 import android.view.accessibility.AccessibilityManager 45 import java.util.ArrayList 46 import java.util.Arrays 47 import java.util.Formatter 48 import java.util.HashMap 49 import java.util.Iterator 50 import java.util.List 51 import java.util.Locale 52 53 class MonthWeekEventsView 54 /** 55 * Shows up as an error if we don't include this. 56 */ 57 (context: Context) : SimpleWeekView(context) { 58 // Renamed to avoid override modifier and type mismatch error 59 protected val mTodayTime: Time = Time() 60 override protected var mHasToday = false 61 protected var mTodayIndex = -1 62 protected var mOrientation: Int = Configuration.ORIENTATION_LANDSCAPE 63 protected var mEvents: List<ArrayList<Event?>>? = null 64 protected var mUnsortedEvents: ArrayList<Event?>? = null 65 var mDna: HashMap<Int, Utils.DNAStrand>? = null 66 67 // This is for drawing the outlines around event chips and supports up to 10 68 // events being drawn on each day. The code will expand this if necessary. 69 protected var mEventOutlines: FloatRef = FloatRef(10 * 4 * 4 * 7) 70 protected var mMonthNamePaint: Paint? = null 71 protected var mEventPaint: TextPaint = TextPaint() 72 protected var mSolidBackgroundEventPaint: TextPaint? = null 73 protected var mFramedEventPaint: TextPaint? = null 74 protected var mDeclinedEventPaint: TextPaint? = null 75 protected var mEventExtrasPaint: TextPaint = TextPaint() 76 protected var mEventDeclinedExtrasPaint: TextPaint = TextPaint() 77 protected var mWeekNumPaint: Paint = Paint() 78 protected var mDNAAllDayPaint: Paint = Paint() 79 protected var mDNATimePaint: Paint = Paint() 80 protected var mEventSquarePaint: Paint = Paint() 81 protected var mTodayDrawable: Drawable? = null 82 protected var mMonthNumHeight = 0 83 protected var mMonthNumAscentHeight = 0 84 protected var mEventHeight = 0 85 protected var mEventAscentHeight = 0 86 protected var mExtrasHeight = 0 87 protected var mExtrasAscentHeight = 0 88 protected var mExtrasDescent = 0 89 protected var mWeekNumAscentHeight = 0 90 protected var mMonthBGColor = 0 91 protected var mMonthBGOtherColor = 0 92 protected var mMonthBGTodayColor = 0 93 protected var mMonthNumColor = 0 94 protected var mMonthNumOtherColor = 0 95 protected var mMonthNumTodayColor = 0 96 protected var mMonthNameColor = 0 97 protected var mMonthNameOtherColor = 0 98 protected var mMonthEventColor = 0 99 protected var mMonthDeclinedEventColor = 0 100 protected var mMonthDeclinedExtrasColor = 0 101 protected var mMonthEventExtraColor = 0 102 protected var mMonthEventOtherColor = 0 103 protected var mMonthEventExtraOtherColor = 0 104 protected var mMonthWeekNumColor = 0 105 protected var mMonthBusyBitsBgColor = 0 106 protected var mMonthBusyBitsBusyTimeColor = 0 107 protected var mMonthBusyBitsConflictTimeColor = 0 108 private var mClickedDayIndex = -1 109 private var mClickedDayColor = 0 110 protected var mEventChipOutlineColor = -0x1 111 protected var mDaySeparatorInnerColor = 0 112 protected var mTodayAnimateColor = 0 113 private var mAnimateToday = false 114 private var mAnimateTodayAlpha = 0 115 private var mTodayAnimator: ObjectAnimator? = null 116 private val mAnimatorListener: TodayAnimatorListener = TodayAnimatorListener() 117 118 internal inner class TodayAnimatorListener : AnimatorListenerAdapter() { 119 @Volatile 120 private var mAnimator: Animator? = null 121 122 @Volatile 123 private var mFadingIn = false 124 @Override onAnimationEndnull125 override fun onAnimationEnd(animation: Animator) { 126 synchronized(this) { 127 if (mAnimator !== animation) { 128 animation.removeAllListeners() 129 animation.cancel() 130 return 131 } 132 if (mFadingIn) { 133 if (mTodayAnimator != null) { 134 mTodayAnimator?.removeAllListeners() 135 mTodayAnimator?.cancel() 136 } 137 mTodayAnimator = ObjectAnimator.ofInt(this@MonthWeekEventsView, 138 "animateTodayAlpha", 255, 0) 139 mAnimator = mTodayAnimator 140 mFadingIn = false 141 mTodayAnimator?.addListener(this) 142 mTodayAnimator?.setDuration(600) 143 mTodayAnimator?.start() 144 } else { 145 mAnimateToday = false 146 mAnimateTodayAlpha = 0 147 mAnimator?.removeAllListeners() 148 mAnimator = null 149 mTodayAnimator = null 150 invalidate() 151 } 152 } 153 } 154 setAnimatornull155 fun setAnimator(animation: Animator?) { 156 mAnimator = animation 157 } 158 setFadingInnull159 fun setFadingIn(fadingIn: Boolean) { 160 mFadingIn = fadingIn 161 } 162 } 163 164 private var mDayXs: IntArray? = null 165 166 /** 167 * This provides a reference to a float array which allows for easy size 168 * checking and reallocation. Used for drawing lines. 169 */ 170 inner class FloatRef(size: Int) { 171 var array: FloatArray ensureSizenull172 fun ensureSize(newSize: Int) { 173 if (newSize >= array.size) { 174 // Add enough space for 7 more boxes to be drawn 175 array = Arrays.copyOf(array, newSize + 16 * 7) 176 } 177 } 178 179 init { 180 array = FloatArray(size) 181 } 182 } 183 184 // Sets the list of events for this week. Takes a sorted list of arrays 185 // divided up by day for generating the large month version and the full 186 // arraylist sorted by start time to generate the dna version. setEventsnull187 fun setEvents(sortedEvents: List<ArrayList<Event?>>?, unsortedEvents: ArrayList<Event?>?) { 188 setEvents(sortedEvents) 189 // The MIN_WEEK_WIDTH is a hack to prevent the view from trying to 190 // generate dna bits before its width has been fixed. 191 createDna(unsortedEvents) 192 } 193 194 /** 195 * Sets up the dna bits for the view. This will return early if the view 196 * isn't in a state that will create a valid set of dna yet (such as the 197 * views width not being set correctly yet). 198 */ createDnanull199 fun createDna(unsortedEvents: ArrayList<Event?>?) { 200 if (unsortedEvents == null || mWidth <= MIN_WEEK_WIDTH || getContext() == null) { 201 // Stash the list of events for use when this view is ready, or 202 // just clear it if a null set has been passed to this view 203 mUnsortedEvents = unsortedEvents 204 mDna = null 205 return 206 } else { 207 // clear the cached set of events since we're ready to build it now 208 mUnsortedEvents = null 209 } 210 // Create the drawing coordinates for dna 211 if (!mShowDetailsInMonth) { 212 val numDays: Int = mEvents!!.size 213 var effectiveWidth: Int = mWidth - mPadding * 2 214 if (mShowWeekNum) { 215 effectiveWidth -= SPACING_WEEK_NUMBER 216 } 217 DNA_ALL_DAY_WIDTH = effectiveWidth / numDays - 2 * DNA_SIDE_PADDING 218 mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH.toFloat()) 219 mDayXs = IntArray(numDays) 220 for (day in 0 until numDays) { 221 mDayXs!![day] = computeDayLeftPosition(day) + DNA_WIDTH / 2 + DNA_SIDE_PADDING 222 } 223 val top = DAY_SEPARATOR_INNER_WIDTH + DNA_MARGIN + DNA_ALL_DAY_HEIGHT + 1 224 val bottom: Int = mHeight - DNA_MARGIN 225 mDna = Utils.createDNAStrands(mFirstJulianDay, unsortedEvents, top, bottom, 226 DNA_MIN_SEGMENT_HEIGHT, mDayXs, getContext()) 227 } 228 } 229 setEventsnull230 fun setEvents(sortedEvents: List<ArrayList<Event?>>?) { 231 mEvents = sortedEvents 232 if (sortedEvents == null) { 233 return 234 } 235 if (sortedEvents.size !== mNumDays) { 236 if (Log.isLoggable(TAG, Log.ERROR)) { 237 Log.wtf(TAG, ("Events size must be same as days displayed: size=" 238 + sortedEvents.size) + " days=" + mNumDays) 239 } 240 mEvents = null 241 return 242 } 243 } 244 loadColorsnull245 protected fun loadColors(context: Context) { 246 val res: Resources = context.getResources() 247 mMonthWeekNumColor = res.getColor(R.color.month_week_num_color) 248 mMonthNumColor = res.getColor(R.color.month_day_number) 249 mMonthNumOtherColor = res.getColor(R.color.month_day_number_other) 250 mMonthNumTodayColor = res.getColor(R.color.month_today_number) 251 mMonthNameColor = mMonthNumColor 252 mMonthNameOtherColor = mMonthNumOtherColor 253 mMonthEventColor = res.getColor(R.color.month_event_color) 254 mMonthDeclinedEventColor = res.getColor(R.color.agenda_item_declined_color) 255 mMonthDeclinedExtrasColor = res.getColor(R.color.agenda_item_where_declined_text_color) 256 mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color) 257 mMonthEventOtherColor = res.getColor(R.color.month_event_other_color) 258 mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color) 259 mMonthBGTodayColor = res.getColor(R.color.month_today_bgcolor) 260 mMonthBGOtherColor = res.getColor(R.color.month_other_bgcolor) 261 mMonthBGColor = res.getColor(R.color.month_bgcolor) 262 mDaySeparatorInnerColor = res.getColor(R.color.month_grid_lines) 263 mTodayAnimateColor = res.getColor(R.color.today_highlight_color) 264 mClickedDayColor = res.getColor(R.color.day_clicked_background_color) 265 mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light) 266 } 267 268 /** 269 * Sets up the text and style properties for painting. Override this if you 270 * want to use a different paint. 271 */ 272 @Override initViewnull273 protected override fun initView() { 274 super.initView() 275 if (!mInitialized) { 276 val resources: Resources = getContext().getResources() 277 mShowDetailsInMonth = Utils.getConfigBool(getContext(), R.bool.show_details_in_month) 278 TEXT_SIZE_EVENT_TITLE = resources.getInteger(R.integer.text_size_event_title) 279 TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number) 280 SIDE_PADDING_MONTH_NUMBER = resources.getInteger(R.integer.month_day_number_margin) 281 CONFLICT_COLOR = resources.getColor(R.color.month_dna_conflict_time_color) 282 EVENT_TEXT_COLOR = resources.getColor(R.color.calendar_event_text_color) 283 if (mScale != 1f) { 284 TOP_PADDING_MONTH_NUMBER *= mScale.toInt() 285 TOP_PADDING_WEEK_NUMBER *= mScale.toInt() 286 SIDE_PADDING_MONTH_NUMBER *= mScale.toInt() 287 SIDE_PADDING_WEEK_NUMBER *= mScale.toInt() 288 SPACING_WEEK_NUMBER *= mScale.toInt() 289 TEXT_SIZE_MONTH_NUMBER *= mScale.toInt() 290 TEXT_SIZE_EVENT *= mScale.toInt() 291 TEXT_SIZE_EVENT_TITLE *= mScale.toInt() 292 TEXT_SIZE_MORE_EVENTS *= mScale.toInt() 293 TEXT_SIZE_MONTH_NAME *= mScale.toInt() 294 TEXT_SIZE_WEEK_NUM *= mScale.toInt() 295 DAY_SEPARATOR_OUTER_WIDTH *= mScale.toInt() 296 DAY_SEPARATOR_INNER_WIDTH *= mScale.toInt() 297 DAY_SEPARATOR_VERTICAL_LENGTH *= mScale.toInt() 298 DAY_SEPARATOR_VERTICAL_LENGTH_PORTRAIT *= mScale.toInt() 299 EVENT_X_OFFSET_LANDSCAPE *= mScale.toInt() 300 EVENT_Y_OFFSET_LANDSCAPE *= mScale.toInt() 301 EVENT_Y_OFFSET_PORTRAIT *= mScale.toInt() 302 EVENT_SQUARE_WIDTH *= mScale.toInt() 303 EVENT_SQUARE_BORDER *= mScale.toInt() 304 EVENT_LINE_PADDING *= mScale.toInt() 305 EVENT_BOTTOM_PADDING *= mScale.toInt() 306 EVENT_RIGHT_PADDING *= mScale.toInt() 307 DNA_MARGIN *= mScale.toInt() 308 DNA_WIDTH *= mScale.toInt() 309 DNA_ALL_DAY_HEIGHT *= mScale.toInt() 310 DNA_MIN_SEGMENT_HEIGHT *= mScale.toInt() 311 DNA_SIDE_PADDING *= mScale.toInt() 312 DEFAULT_EDGE_SPACING *= mScale.toInt() 313 DNA_ALL_DAY_WIDTH *= mScale.toInt() 314 TODAY_HIGHLIGHT_WIDTH *= mScale.toInt() 315 } 316 if (!mShowDetailsInMonth) { 317 TOP_PADDING_MONTH_NUMBER += DNA_ALL_DAY_HEIGHT + DNA_MARGIN 318 } 319 mInitialized = true 320 } 321 mPadding = DEFAULT_EDGE_SPACING 322 loadColors(getContext()) 323 // TODO modify paint properties depending on isMini 324 mMonthNumPaint = Paint() 325 mMonthNumPaint.setFakeBoldText(false) 326 mMonthNumPaint.setAntiAlias(true) 327 mMonthNumPaint.setTextSize(TEXT_SIZE_MONTH_NUMBER.toFloat()) 328 mMonthNumPaint.setColor(mMonthNumColor) 329 mMonthNumPaint.setStyle(Style.FILL) 330 mMonthNumPaint.setTextAlign(Align.RIGHT) 331 mMonthNumPaint.setTypeface(Typeface.DEFAULT) 332 mMonthNumAscentHeight = (-mMonthNumPaint.ascent() + 0.5f).toInt() 333 mMonthNumHeight = (mMonthNumPaint.descent() - mMonthNumPaint.ascent() + 0.5f).toInt() 334 mEventPaint = TextPaint() 335 mEventPaint.setFakeBoldText(true) 336 mEventPaint.setAntiAlias(true) 337 mEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE.toFloat()) 338 mEventPaint.setColor(mMonthEventColor) 339 mSolidBackgroundEventPaint = TextPaint(mEventPaint) 340 mSolidBackgroundEventPaint?.setColor(EVENT_TEXT_COLOR) 341 mFramedEventPaint = TextPaint(mSolidBackgroundEventPaint) 342 mDeclinedEventPaint = TextPaint() 343 mDeclinedEventPaint?.setFakeBoldText(true) 344 mDeclinedEventPaint?.setAntiAlias(true) 345 mDeclinedEventPaint?.setTextSize(TEXT_SIZE_EVENT_TITLE.toFloat()) 346 mDeclinedEventPaint?.setColor(mMonthDeclinedEventColor) 347 mEventAscentHeight = (-mEventPaint.ascent() + 0.5f).toInt() 348 mEventHeight = (mEventPaint.descent() - mEventPaint.ascent() + 0.5f).toInt() 349 mEventExtrasPaint = TextPaint() 350 mEventExtrasPaint.setFakeBoldText(false) 351 mEventExtrasPaint.setAntiAlias(true) 352 mEventExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat()) 353 mEventExtrasPaint.setTextSize(TEXT_SIZE_EVENT.toFloat()) 354 mEventExtrasPaint.setColor(mMonthEventExtraColor) 355 mEventExtrasPaint.setStyle(Style.FILL) 356 mEventExtrasPaint.setTextAlign(Align.LEFT) 357 mExtrasHeight = (mEventExtrasPaint.descent() - mEventExtrasPaint.ascent() + 0.5f).toInt() 358 mExtrasAscentHeight = (-mEventExtrasPaint.ascent() + 0.5f).toInt() 359 mExtrasDescent = (mEventExtrasPaint.descent() + 0.5f).toInt() 360 mEventDeclinedExtrasPaint = TextPaint() 361 mEventDeclinedExtrasPaint.setFakeBoldText(false) 362 mEventDeclinedExtrasPaint.setAntiAlias(true) 363 mEventDeclinedExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat()) 364 mEventDeclinedExtrasPaint.setTextSize(TEXT_SIZE_EVENT.toFloat()) 365 mEventDeclinedExtrasPaint.setColor(mMonthDeclinedExtrasColor) 366 mEventDeclinedExtrasPaint.setStyle(Style.FILL) 367 mEventDeclinedExtrasPaint.setTextAlign(Align.LEFT) 368 mWeekNumPaint = Paint() 369 mWeekNumPaint.setFakeBoldText(false) 370 mWeekNumPaint.setAntiAlias(true) 371 mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM.toFloat()) 372 mWeekNumPaint.setColor(mWeekNumColor) 373 mWeekNumPaint.setStyle(Style.FILL) 374 mWeekNumPaint.setTextAlign(Align.RIGHT) 375 mWeekNumAscentHeight = (-mWeekNumPaint.ascent() + 0.5f).toInt() 376 mDNAAllDayPaint = Paint() 377 mDNATimePaint = Paint() 378 mDNATimePaint.setColor(mMonthBusyBitsBusyTimeColor) 379 mDNATimePaint.setStyle(Style.FILL_AND_STROKE) 380 mDNATimePaint.setStrokeWidth(DNA_WIDTH.toFloat()) 381 mDNATimePaint.setAntiAlias(false) 382 mDNAAllDayPaint.setColor(mMonthBusyBitsConflictTimeColor) 383 mDNAAllDayPaint.setStyle(Style.FILL_AND_STROKE) 384 mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH.toFloat()) 385 mDNAAllDayPaint.setAntiAlias(false) 386 mEventSquarePaint = Paint() 387 mEventSquarePaint.setStrokeWidth(EVENT_SQUARE_BORDER.toFloat()) 388 mEventSquarePaint.setAntiAlias(false) 389 if (DEBUG_LAYOUT) { 390 Log.d("EXTRA", "mScale=$mScale") 391 Log.d("EXTRA", "mMonthNumPaint ascent=" + mMonthNumPaint.ascent() 392 .toString() + " descent=" + mMonthNumPaint.descent().toString() + 393 " int height=" + mMonthNumHeight) 394 Log.d("EXTRA", "mEventPaint ascent=" + mEventPaint.ascent() 395 .toString() + " descent=" + mEventPaint.descent().toString() + 396 " int height=" + mEventHeight 397 .toString() + " int ascent=" + mEventAscentHeight) 398 Log.d("EXTRA", "mEventExtrasPaint ascent=" + mEventExtrasPaint.ascent() 399 .toString() + " descent=" + mEventExtrasPaint.descent().toString() + 400 " int height=" + mExtrasHeight) 401 Log.d("EXTRA", "mWeekNumPaint ascent=" + mWeekNumPaint.ascent() 402 .toString() + " descent=" + mWeekNumPaint.descent()) 403 } 404 } 405 406 @Override setWeekParamsnull407 override fun setWeekParams(params: HashMap<String?, Int?>, tz: String) { 408 super.setWeekParams(params, tz) 409 if (params.containsKey(VIEW_PARAMS_ORIENTATION)) { 410 mOrientation = params.get(VIEW_PARAMS_ORIENTATION) ?: 411 Configuration.ORIENTATION_LANDSCAPE 412 } 413 updateToday(tz) 414 mNumCells = mNumDays + 1 415 if (params.containsKey(VIEW_PARAMS_ANIMATE_TODAY) && mHasToday) { 416 synchronized(mAnimatorListener) { 417 if (mTodayAnimator != null) { 418 mTodayAnimator?.removeAllListeners() 419 mTodayAnimator?.cancel() 420 } 421 mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha", 422 Math.max(mAnimateTodayAlpha, 80), 255) 423 mTodayAnimator?.setDuration(150) 424 mAnimatorListener.setAnimator(mTodayAnimator) 425 mAnimatorListener.setFadingIn(true) 426 mTodayAnimator?.addListener(mAnimatorListener) 427 mAnimateToday = true 428 mTodayAnimator?.start() 429 } 430 } 431 } 432 433 /** 434 * @param tz 435 */ updateTodaynull436 fun updateToday(tz: String): Boolean { 437 mTodayTime.timezone = tz 438 mTodayTime.setToNow() 439 mTodayTime.normalize(true) 440 val julianToday: Int = Time.getJulianDay(mTodayTime.toMillis(false), mTodayTime.gmtoff) 441 if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) { 442 mHasToday = true 443 mTodayIndex = julianToday - mFirstJulianDay 444 } else { 445 mHasToday = false 446 mTodayIndex = -1 447 } 448 return mHasToday 449 } 450 setAnimateTodayAlphanull451 fun setAnimateTodayAlpha(alpha: Int) { 452 mAnimateTodayAlpha = alpha 453 invalidate() 454 } 455 456 @Override onDrawnull457 protected override fun onDraw(canvas: Canvas) { 458 drawBackground(canvas) 459 drawWeekNums(canvas) 460 drawDaySeparators(canvas) 461 if (mHasToday && mAnimateToday) { 462 drawToday(canvas) 463 } 464 if (mShowDetailsInMonth) { 465 drawEvents(canvas) 466 } else { 467 if (mDna == null && mUnsortedEvents != null) { 468 createDna(mUnsortedEvents) 469 } 470 drawDNA(canvas) 471 } 472 drawClick(canvas) 473 } 474 drawTodaynull475 protected fun drawToday(canvas: Canvas) { 476 r.top = DAY_SEPARATOR_INNER_WIDTH + TODAY_HIGHLIGHT_WIDTH / 2 477 r.bottom = mHeight - Math.ceil(TODAY_HIGHLIGHT_WIDTH.toDouble() / 2.0f).toInt() 478 p.setStyle(Style.STROKE) 479 p.setStrokeWidth(TODAY_HIGHLIGHT_WIDTH.toFloat()) 480 r.left = computeDayLeftPosition(mTodayIndex) + TODAY_HIGHLIGHT_WIDTH / 2 481 r.right = (computeDayLeftPosition(mTodayIndex + 1) 482 - Math.ceil(TODAY_HIGHLIGHT_WIDTH.toDouble() / 2.0f).toInt()) 483 p.setColor(mTodayAnimateColor or (mAnimateTodayAlpha shl 24)) 484 canvas.drawRect(r, p) 485 p.setStyle(Style.FILL) 486 } 487 488 // TODO move into SimpleWeekView 489 // Computes the x position for the left side of the given day computeDayLeftPositionnull490 private fun computeDayLeftPosition(day: Int): Int { 491 var effectiveWidth: Int = mWidth 492 var x = 0 493 var xOffset = 0 494 if (mShowWeekNum) { 495 xOffset = SPACING_WEEK_NUMBER + mPadding 496 effectiveWidth -= xOffset 497 } 498 x = day * effectiveWidth / mNumDays + xOffset 499 return x 500 } 501 502 @Override drawDaySeparatorsnull503 protected override fun drawDaySeparators(canvas: Canvas) { 504 val lines = FloatArray(8 * 4) 505 var count = 6 * 4 506 var wkNumOffset = 0 507 var i = 0 508 if (mShowWeekNum) { 509 // This adds the first line separating the week number 510 val xOffset: Int = SPACING_WEEK_NUMBER + mPadding 511 count += 4 512 lines[i++] = xOffset.toFloat() 513 lines[i++] = 0f 514 lines[i++] = xOffset.toFloat() 515 lines[i++] = mHeight.toFloat() 516 wkNumOffset++ 517 } 518 count += 4 519 lines[i++] = 0f 520 lines[i++] = 0f 521 lines[i++] = mWidth.toFloat() 522 lines[i++] = 0f 523 val y0 = 0 524 val y1: Int = mHeight 525 while (i < count) { 526 val x = computeDayLeftPosition(i / 4 - wkNumOffset) 527 lines[i++] = x.toFloat() 528 lines[i++] = y0.toFloat() 529 lines[i++] = x.toFloat() 530 lines[i++] = y1.toFloat() 531 } 532 p.setColor(mDaySeparatorInnerColor) 533 p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH.toFloat()) 534 canvas.drawLines(lines, 0, count, p) 535 } 536 537 @Override drawBackgroundnull538 protected override fun drawBackground(canvas: Canvas) { 539 var i = 0 540 var offset = 0 541 r.top = DAY_SEPARATOR_INNER_WIDTH 542 r.bottom = mHeight 543 if (mShowWeekNum) { 544 i++ 545 offset++ 546 } 547 if (!mOddMonth.get(i)) { 548 while (++i < mOddMonth.size && !mOddMonth.get(i)); 549 r.right = computeDayLeftPosition(i - offset) 550 r.left = 0 551 p.setColor(mMonthBGOtherColor) 552 canvas.drawRect(r, p) 553 // compute left edge for i, set up r, draw 554 } else if (!mOddMonth.get(mOddMonth.size - 1.also { i = it })) { 555 while (--i >= offset && !mOddMonth.get(i)); 556 i++ 557 // compute left edge for i, set up r, draw 558 r.right = mWidth 559 r.left = computeDayLeftPosition(i - offset) 560 p.setColor(mMonthBGOtherColor) 561 canvas.drawRect(r, p) 562 } 563 if (mHasToday) { 564 p.setColor(mMonthBGTodayColor) 565 r.left = computeDayLeftPosition(mTodayIndex) 566 r.right = computeDayLeftPosition(mTodayIndex + 1) 567 canvas.drawRect(r, p) 568 } 569 } 570 571 // Draw the "clicked" color on the tapped day drawClicknull572 private fun drawClick(canvas: Canvas) { 573 if (mClickedDayIndex != -1) { 574 val alpha: Int = p.getAlpha() 575 p.setColor(mClickedDayColor) 576 p.setAlpha(mClickedAlpha) 577 r.left = computeDayLeftPosition(mClickedDayIndex) 578 r.right = computeDayLeftPosition(mClickedDayIndex + 1) 579 r.top = DAY_SEPARATOR_INNER_WIDTH 580 r.bottom = mHeight 581 canvas.drawRect(r, p) 582 p.setAlpha(alpha) 583 } 584 } 585 586 @Override drawWeekNumsnull587 protected override fun drawWeekNums(canvas: Canvas) { 588 var y: Int 589 var i = 0 590 var offset = -1 591 var todayIndex = mTodayIndex 592 var x = 0 593 var numCount: Int = mNumDays 594 if (mShowWeekNum) { 595 x = SIDE_PADDING_WEEK_NUMBER + mPadding 596 y = mWeekNumAscentHeight + TOP_PADDING_WEEK_NUMBER 597 canvas.drawText(mDayNumbers!!.get(0) as String, x.toFloat(), y.toFloat(), mWeekNumPaint) 598 numCount++ 599 i++ 600 todayIndex++ 601 offset++ 602 } 603 y = mMonthNumAscentHeight + TOP_PADDING_MONTH_NUMBER 604 var isFocusMonth: Boolean = mFocusDay.get(i) 605 var isBold = false 606 mMonthNumPaint.setColor(if (isFocusMonth) mMonthNumColor else mMonthNumOtherColor) 607 while (i < numCount) { 608 if (mHasToday && todayIndex == i) { 609 mMonthNumPaint.setColor(mMonthNumTodayColor) 610 mMonthNumPaint.setFakeBoldText(true.also { isBold = it }) 611 if (i + 1 < numCount) { 612 // Make sure the color will be set back on the next 613 // iteration 614 isFocusMonth = !mFocusDay.get(i + 1) 615 } 616 } else if (mFocusDay.get(i) !== isFocusMonth) { 617 isFocusMonth = mFocusDay.get(i) 618 mMonthNumPaint.setColor(if (isFocusMonth) mMonthNumColor else mMonthNumOtherColor) 619 } 620 x = computeDayLeftPosition(i - offset) - SIDE_PADDING_MONTH_NUMBER 621 canvas.drawText(mDayNumbers!!.get(i) as String, x.toFloat(), y.toFloat(), 622 mMonthNumPaint as Paint) 623 if (isBold) { 624 mMonthNumPaint.setFakeBoldText(false.also { isBold = it }) 625 } 626 i++ 627 } 628 } 629 drawEventsnull630 protected fun drawEvents(canvas: Canvas) { 631 if (mEvents == null) { 632 return 633 } 634 var day = -1 635 for (eventDay in mEvents!!) { 636 day++ 637 if (eventDay == null || eventDay.size === 0) { 638 continue 639 } 640 var ySquare: Int 641 val xSquare = computeDayLeftPosition(day) + SIDE_PADDING_MONTH_NUMBER + 1 642 var rightEdge = computeDayLeftPosition(day + 1) 643 if (mOrientation == Configuration.ORIENTATION_PORTRAIT) { 644 ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + TOP_PADDING_MONTH_NUMBER 645 rightEdge -= SIDE_PADDING_MONTH_NUMBER + 1 646 } else { 647 ySquare = EVENT_Y_OFFSET_LANDSCAPE 648 rightEdge -= EVENT_X_OFFSET_LANDSCAPE 649 } 650 651 // Determine if everything will fit when time ranges are shown. 652 var showTimes = true 653 var iter: Iterator<Event> = eventDay.iterator() as Iterator<Event> 654 var yTest = ySquare 655 while (iter.hasNext()) { 656 val event: Event = iter.next() 657 val newY = drawEvent(canvas, event, xSquare, yTest, rightEdge, iter.hasNext(), 658 showTimes, /*doDraw*/false) 659 if (newY == yTest) { 660 showTimes = false 661 break 662 } 663 yTest = newY 664 } 665 var eventCount = 0 666 iter = eventDay.iterator() as Iterator<Event> 667 while (iter.hasNext()) { 668 val event: Event = iter.next() 669 val newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext(), 670 showTimes, /*doDraw*/true) 671 if (newY == ySquare) { 672 break 673 } 674 eventCount++ 675 ySquare = newY 676 } 677 val remaining: Int = eventDay.size- eventCount 678 if (remaining > 0) { 679 drawMoreEvents(canvas, remaining, xSquare) 680 } 681 } 682 } 683 addChipOutlinenull684 protected fun addChipOutline(lines: FloatRef, count: Int, x: Int, y: Int): Int { 685 var count = count 686 lines.ensureSize(count + 16) 687 // top of box 688 lines.array[count++] = x.toFloat() 689 lines.array[count++] = y.toFloat() 690 lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat() 691 lines.array[count++] = y.toFloat() 692 // right side of box 693 lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat() 694 lines.array[count++] = y.toFloat() 695 lines.array[count++] = (x + EVENT_SQUARE_WIDTH).toFloat() 696 lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat() 697 // left side of box 698 lines.array[count++] = x.toFloat() 699 lines.array[count++] = y.toFloat() 700 lines.array[count++] = x.toFloat() 701 lines.array[count++] = (y + EVENT_SQUARE_WIDTH + 1).toFloat() 702 // bottom of box 703 lines.array[count++] = x.toFloat() 704 lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat() 705 lines.array[count++] = (x + EVENT_SQUARE_WIDTH + 1).toFloat() 706 lines.array[count++] = (y + EVENT_SQUARE_WIDTH).toFloat() 707 return count 708 } 709 710 /** 711 * Attempts to draw the given event. Returns the y for the next event or the 712 * original y if the event will not fit. An event is considered to not fit 713 * if the event and its extras won't fit or if there are more events and the 714 * more events line would not fit after drawing this event. 715 * 716 * @param canvas the canvas to draw on 717 * @param event the event to draw 718 * @param x the top left corner for this event's color chip 719 * @param y the top left corner for this event's color chip 720 * @param rightEdge the rightmost point we're allowed to draw on (exclusive) 721 * @param moreEvents indicates whether additional events will follow this one 722 * @param showTimes if set, a second line with a time range will be displayed for non-all-day 723 * events 724 * @param doDraw if set, do the actual drawing; otherwise this just computes the height 725 * and returns 726 * @return the y for the next event or the original y if it won't fit 727 */ drawEventnull728 protected fun drawEvent(canvas: Canvas, event: Event, x: Int, y: Int, rightEdge: Int, 729 moreEvents: Boolean, showTimes: Boolean, doDraw: Boolean): Int { 730 /* 731 * Vertical layout: 732 * (top of box) 733 * a. EVENT_Y_OFFSET_LANDSCAPE or portrait equivalent 734 * b. Event title: mEventHeight for a normal event, + 2xBORDER_SPACE for all-day event 735 * c. [optional] Time range (mExtrasHeight) 736 * d. EVENT_LINE_PADDING 737 * 738 * Repeat (b,c,d) as needed and space allows. If we have more events than fit, we need 739 * to leave room for something like "+2" at the bottom: 740 * 741 * e. "+ more" line (mExtrasHeight) 742 * 743 * f. EVENT_BOTTOM_PADDING (overlaps EVENT_LINE_PADDING) 744 * (bottom of box) 745 */ 746 var y = y 747 val BORDER_SPACE = EVENT_SQUARE_BORDER + 1 // want a 1-pixel gap inside border 748 val STROKE_WIDTH_ADJ = EVENT_SQUARE_BORDER / 2 // adjust bounds for stroke width 749 val allDay: Boolean = event.allDay 750 var eventRequiredSpace = mEventHeight 751 if (allDay) { 752 // Add a few pixels for the box we draw around all-day events. 753 eventRequiredSpace += BORDER_SPACE * 2 754 } else if (showTimes) { 755 // Need room for the "1pm - 2pm" line. 756 eventRequiredSpace += mExtrasHeight 757 } 758 var reservedSpace = EVENT_BOTTOM_PADDING // leave a bit of room at the bottom 759 if (moreEvents) { 760 // More events follow. Leave a bit of space between events. 761 eventRequiredSpace += EVENT_LINE_PADDING 762 763 // Make sure we have room for the "+ more" line. (The "+ more" line is expected 764 // to be <= the height of an event line, so we won't show "+1" when we could be 765 // showing the event.) 766 reservedSpace += mExtrasHeight 767 } 768 if (y + eventRequiredSpace + reservedSpace > mHeight) { 769 // Not enough space, return original y 770 return y 771 } else if (!doDraw) { 772 return y + eventRequiredSpace 773 } 774 val isDeclined = event.selfAttendeeStatus === Attendees.ATTENDEE_STATUS_DECLINED 775 var color: Int = event.color 776 if (isDeclined) { 777 color = Utils.getDeclinedColorFromColor(color) 778 } 779 val textX: Int 780 var textY: Int 781 val textRightEdge: Int 782 if (allDay) { 783 // We shift the render offset "inward", because drawRect with a stroke width greater 784 // than 1 draws outside the specified bounds. (We don't adjust the left edge, since 785 // we want to match the existing appearance of the "event square".) 786 r.left = x 787 r.right = rightEdge - STROKE_WIDTH_ADJ 788 r.top = y + STROKE_WIDTH_ADJ 789 r.bottom = y + mEventHeight + BORDER_SPACE * 2 - STROKE_WIDTH_ADJ 790 textX = x + BORDER_SPACE 791 textY = y + mEventAscentHeight + BORDER_SPACE 792 textRightEdge = rightEdge - BORDER_SPACE 793 } else { 794 r.left = x 795 r.right = x + EVENT_SQUARE_WIDTH 796 r.bottom = y + mEventAscentHeight 797 r.top = r.bottom - EVENT_SQUARE_WIDTH 798 textX = x + EVENT_SQUARE_WIDTH + EVENT_RIGHT_PADDING 799 textY = y + mEventAscentHeight 800 textRightEdge = rightEdge 801 } 802 var boxStyle: Style = Style.STROKE 803 var solidBackground = false 804 if (event.selfAttendeeStatus !== Attendees.ATTENDEE_STATUS_INVITED) { 805 boxStyle = Style.FILL_AND_STROKE 806 if (allDay) { 807 solidBackground = true 808 } 809 } 810 mEventSquarePaint.setStyle(boxStyle) 811 mEventSquarePaint.setColor(color) 812 canvas.drawRect(r, mEventSquarePaint) 813 val avail = (textRightEdge - textX).toFloat() 814 var text: CharSequence = TextUtils.ellipsize( 815 event.title, mEventPaint, avail, TextUtils.TruncateAt.END) 816 val textPaint: TextPaint? 817 textPaint = if (solidBackground) { 818 // Text color needs to contrast with solid background. 819 mSolidBackgroundEventPaint 820 } else if (isDeclined) { 821 // Use "declined event" color. 822 mDeclinedEventPaint 823 } else if (allDay) { 824 // Text inside frame is same color as frame. 825 mFramedEventPaint?.setColor(color) 826 mFramedEventPaint 827 } else { 828 // Use generic event text color. 829 mEventPaint 830 } 831 canvas.drawText(text.toString(), textX.toFloat(), textY.toFloat(), textPaint as Paint) 832 y += mEventHeight 833 if (allDay) { 834 y += BORDER_SPACE * 2 835 } 836 if (showTimes && !allDay) { 837 // show start/end time, e.g. "1pm - 2pm" 838 textY = y + mExtrasAscentHeight 839 mStringBuilder.setLength(0) 840 text = DateUtils.formatDateRange(getContext(), mFormatter, event.startMillis, 841 event.endMillis, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_ALL, 842 Utils.getTimeZone(getContext(), null)).toString() 843 text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END) 844 canvas.drawText(text.toString(), textX.toFloat(), textY.toFloat(), 845 if (isDeclined) mEventDeclinedExtrasPaint else mEventExtrasPaint) 846 y += mExtrasHeight 847 } 848 y += EVENT_LINE_PADDING 849 return y 850 } 851 drawMoreEventsnull852 protected fun drawMoreEvents(canvas: Canvas, remainingEvents: Int, x: Int) { 853 val y: Int = mHeight - (mExtrasDescent + EVENT_BOTTOM_PADDING) 854 val text: String = getContext().getResources().getQuantityString( 855 R.plurals.month_more_events, remainingEvents) 856 mEventExtrasPaint.setAntiAlias(true) 857 mEventExtrasPaint.setFakeBoldText(true) 858 canvas.drawText(String.format(text, remainingEvents), x.toFloat(), y.toFloat(), 859 mEventExtrasPaint as Paint) 860 mEventExtrasPaint.setFakeBoldText(false) 861 } 862 863 /** 864 * Draws a line showing busy times in each day of week The method draws 865 * non-conflicting times in the event color and times with conflicting 866 * events in the dna conflict color defined in colors. 867 * 868 * @param canvas 869 */ drawDNAnull870 protected fun drawDNA(canvas: Canvas) { 871 // Draw event and conflict times 872 if (mDna != null) { 873 for (strand in mDna!!.values) { 874 if (strand.color === CONFLICT_COLOR || strand.points == null || 875 (strand.points as FloatArray).size === 0) { 876 continue 877 } 878 mDNATimePaint.setColor(strand.color) 879 canvas.drawLines(strand.points as FloatArray, mDNATimePaint as Paint) 880 } 881 // Draw black last to make sure it's on top 882 val strand: Utils.DNAStrand? = mDna?.get(CONFLICT_COLOR) 883 if (strand != null && strand.points != null && strand.points?.size !== 0) { 884 mDNATimePaint.setColor(strand.color) 885 canvas.drawLines(strand.points as FloatArray, mDNATimePaint as Paint) 886 } 887 if (mDayXs == null) { 888 return 889 } 890 val numDays = mDayXs!!.size 891 val xOffset = (DNA_ALL_DAY_WIDTH - DNA_WIDTH) / 2 892 if (strand != null && strand.allDays != null && strand.allDays?.size === numDays) { 893 for (i in 0 until numDays) { 894 // this adds at most 7 draws. We could sort it by color and 895 // build an array instead but this is easier. 896 if (strand.allDays?.get(i) !== 0) { 897 mDNAAllDayPaint.setColor(strand.allDays!!.get(i)) 898 canvas.drawLine(mDayXs!![i].toFloat() + xOffset.toFloat(), 899 DNA_MARGIN.toFloat(), mDayXs!![i].toFloat() + xOffset.toFloat(), 900 DNA_MARGIN.toFloat() + DNA_ALL_DAY_HEIGHT.toFloat(), 901 mDNAAllDayPaint as Paint) 902 } 903 } 904 } 905 } 906 } 907 908 @Override updateSelectionPositionsnull909 protected override fun updateSelectionPositions() { 910 if (mHasSelectedDay) { 911 var selectedPosition: Int = mSelectedDay - mWeekStart 912 if (selectedPosition < 0) { 913 selectedPosition += 7 914 } 915 var effectiveWidth: Int = mWidth - mPadding * 2 916 effectiveWidth -= SPACING_WEEK_NUMBER 917 mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding 918 mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding 919 mSelectedLeft += SPACING_WEEK_NUMBER 920 mSelectedRight += SPACING_WEEK_NUMBER 921 } 922 } 923 getDayIndexFromLocationnull924 fun getDayIndexFromLocation(x: Float): Int { 925 val dayStart: Int = if (mShowWeekNum) SPACING_WEEK_NUMBER + mPadding else mPadding 926 return if (x < dayStart || x > mWidth - mPadding) { 927 -1 928 } else (((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)).toInt()) 929 // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels 930 } 931 932 @Override getDayFromLocationnull933 override fun getDayFromLocation(x: Float): Time? { 934 val dayPosition = getDayIndexFromLocation(x) 935 if (dayPosition == -1) { 936 return null 937 } 938 var day: Int = mFirstJulianDay + dayPosition 939 val time = Time(mTimeZone) 940 if (mWeek === 0) { 941 // This week is weird... 942 if (day < Time.EPOCH_JULIAN_DAY) { 943 day++ 944 } else if (day == Time.EPOCH_JULIAN_DAY) { 945 time.set(1, 0, 1970) 946 time.normalize(true) 947 return time 948 } 949 } 950 time.setJulianDay(day) 951 return time 952 } 953 954 @Override onHoverEventnull955 override fun onHoverEvent(event: MotionEvent): Boolean { 956 val context: Context = getContext() 957 // only send accessibility events if accessibility and exploration are 958 // on. 959 val am: AccessibilityManager = context 960 .getSystemService(Service.ACCESSIBILITY_SERVICE) as AccessibilityManager 961 if (!am.isEnabled() || !am.isTouchExplorationEnabled()) { 962 return super.onHoverEvent(event) 963 } 964 if (event.getAction() !== MotionEvent.ACTION_HOVER_EXIT) { 965 val hover: Time? = getDayFromLocation(event.getX()) 966 if (hover != null 967 && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) !== 0)) { 968 val millis: Long = hover.toMillis(true) 969 val date: String = Utils.formatDateRange(context, millis, millis, 970 DateUtils.FORMAT_SHOW_DATE) as String 971 val accessEvent: AccessibilityEvent = AccessibilityEvent 972 .obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) 973 accessEvent.getText().add(date) 974 if (mShowDetailsInMonth && mEvents != null) { 975 val dayStart: Int = SPACING_WEEK_NUMBER + mPadding 976 val dayPosition = ((event.getX() - dayStart) * mNumDays / (mWidth 977 - dayStart - mPadding)).toInt() 978 val events: ArrayList<Event?> = mEvents!![dayPosition] 979 val text: List<CharSequence> = accessEvent.getText() as List<CharSequence> 980 for (e in events) { 981 text.add(e!!.titleAndLocation.toString() + ". ") 982 var flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR 983 if (!e.allDay) { 984 flags = flags or DateUtils.FORMAT_SHOW_TIME 985 if (DateFormat.is24HourFormat(context)) { 986 flags = flags or DateUtils.FORMAT_24HOUR 987 } 988 } else { 989 flags = flags or DateUtils.FORMAT_UTC 990 } 991 text.add(Utils.formatDateRange(context, e.startMillis, e.endMillis, 992 flags).toString() + ". ") 993 } 994 } 995 sendAccessibilityEventUnchecked(accessEvent) 996 mLastHoverTime = hover 997 } 998 } 999 return true 1000 } 1001 setClickedDaynull1002 fun setClickedDay(xLocation: Float) { 1003 mClickedDayIndex = getDayIndexFromLocation(xLocation) 1004 invalidate() 1005 } 1006 clearClickedDaynull1007 fun clearClickedDay() { 1008 mClickedDayIndex = -1 1009 invalidate() 1010 } 1011 1012 companion object { 1013 private const val TAG = "MonthView" 1014 private const val DEBUG_LAYOUT = false 1015 const val VIEW_PARAMS_ORIENTATION = "orientation" 1016 const val VIEW_PARAMS_ANIMATE_TODAY = "animate_today" 1017 1018 /* NOTE: these are not constants, and may be multiplied by a scale factor */ 1019 private var TEXT_SIZE_MONTH_NUMBER = 32 1020 private var TEXT_SIZE_EVENT = 12 1021 private var TEXT_SIZE_EVENT_TITLE = 14 1022 private var TEXT_SIZE_MORE_EVENTS = 12 1023 private var TEXT_SIZE_MONTH_NAME = 14 1024 private var TEXT_SIZE_WEEK_NUM = 12 1025 private var DNA_MARGIN = 4 1026 private var DNA_ALL_DAY_HEIGHT = 4 1027 private var DNA_MIN_SEGMENT_HEIGHT = 4 1028 private var DNA_WIDTH = 8 1029 private var DNA_ALL_DAY_WIDTH = 32 1030 private var DNA_SIDE_PADDING = 6 1031 private var CONFLICT_COLOR: Int = Color.BLACK 1032 private var EVENT_TEXT_COLOR: Int = Color.WHITE 1033 private var DEFAULT_EDGE_SPACING = 0 1034 private var SIDE_PADDING_MONTH_NUMBER = 4 1035 private var TOP_PADDING_MONTH_NUMBER = 4 1036 private var TOP_PADDING_WEEK_NUMBER = 4 1037 private var SIDE_PADDING_WEEK_NUMBER = 20 1038 private var DAY_SEPARATOR_OUTER_WIDTH = 0 1039 private var DAY_SEPARATOR_INNER_WIDTH = 1 1040 private var DAY_SEPARATOR_VERTICAL_LENGTH = 53 1041 private var DAY_SEPARATOR_VERTICAL_LENGTH_PORTRAIT = 64 1042 private const val MIN_WEEK_WIDTH = 50 1043 private var EVENT_X_OFFSET_LANDSCAPE = 38 1044 private var EVENT_Y_OFFSET_LANDSCAPE = 8 1045 private var EVENT_Y_OFFSET_PORTRAIT = 7 1046 private var EVENT_SQUARE_WIDTH = 10 1047 private var EVENT_SQUARE_BORDER = 2 1048 private var EVENT_LINE_PADDING = 2 1049 private var EVENT_RIGHT_PADDING = 4 1050 private var EVENT_BOTTOM_PADDING = 3 1051 private var TODAY_HIGHLIGHT_WIDTH = 2 1052 private var SPACING_WEEK_NUMBER = 24 1053 private var mInitialized = false 1054 private var mShowDetailsInMonth = false 1055 protected var mStringBuilder: StringBuilder = StringBuilder(50) 1056 1057 // TODO recreate formatter when locale changes 1058 protected var mFormatter: Formatter = Formatter(mStringBuilder, Locale.getDefault()) 1059 private const val mClickedAlpha = 128 1060 } 1061 }