Simple-Calendar/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt

952 lines
42 KiB
Kotlin
Raw Normal View History

2018-11-09 17:12:02 +01:00
package com.simplemobiletools.calendar.pro.fragments
2017-01-15 10:41:47 +01:00
import android.annotation.SuppressLint
2021-08-22 22:26:03 +02:00
import android.content.ClipData
import android.content.ClipDescription
import android.content.Intent
2017-01-18 21:28:21 +01:00
import android.content.res.Resources
2017-01-16 23:41:06 +01:00
import android.graphics.drawable.ColorDrawable
2017-01-15 10:41:47 +01:00
import android.os.Bundle
import android.os.Handler
import android.util.Range
import android.view.*
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.collection.LongSparseArray
import androidx.fragment.app.Fragment
2018-11-09 17:12:02 +01:00
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.activities.MainActivity
import com.simplemobiletools.calendar.pro.activities.SimpleActivity
2023-09-04 11:47:16 +02:00
import com.simplemobiletools.calendar.pro.databinding.*
import com.simplemobiletools.calendar.pro.dialogs.EditRepeatingEventDialog
import com.simplemobiletools.calendar.pro.extensions.*
2018-11-09 17:12:02 +01:00
import com.simplemobiletools.calendar.pro.helpers.*
import com.simplemobiletools.calendar.pro.helpers.Formatter
import com.simplemobiletools.calendar.pro.interfaces.WeekFragmentListener
import com.simplemobiletools.calendar.pro.interfaces.WeeklyCalendar
import com.simplemobiletools.calendar.pro.models.Event
import com.simplemobiletools.calendar.pro.models.EventWeeklyView
2018-11-09 17:12:02 +01:00
import com.simplemobiletools.calendar.pro.views.MyScrollView
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
2021-08-22 22:26:03 +02:00
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem
import org.joda.time.DateTime
2017-02-03 22:51:14 +01:00
import org.joda.time.Days
import java.util.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
2017-01-15 10:41:47 +01:00
2017-01-15 22:14:01 +01:00
class WeekFragment : Fragment(), WeeklyCalendar {
2023-09-04 11:59:26 +02:00
private val WEEKLY_EVENT_ID_LABEL = "event_id_label"
private val PLUS_FADEOUT_DELAY = 5000L
2020-03-24 22:00:52 +01:00
private val MIN_SCALE_FACTOR = 0.3f
private val MAX_SCALE_FACTOR = 5f
private val MIN_SCALE_DIFFERENCE = 0.02f
2020-03-24 22:00:52 +01:00
private val SCALE_RANGE = MAX_SCALE_FACTOR - MIN_SCALE_FACTOR
2017-02-04 23:49:50 +01:00
2020-03-20 11:42:36 +01:00
var listener: WeekFragmentListener? = null
private var weekTimestamp = 0L
private var rowHeight = 0f
private var todayColumnIndex = -1
2017-02-03 22:51:14 +01:00
private var primaryColor = 0
2017-10-21 18:14:01 +02:00
private var lastHash = 0
2020-03-24 22:00:52 +01:00
private var prevScaleSpanY = 0f
private var scaleCenterPercent = 0f
private var defaultRowHeight = 0f
2020-03-24 22:00:52 +01:00
private var screenHeight = 0
private var rowHeightsAtScale = 0f
private var prevScaleFactor = 0f
private var mWasDestroyed = false
private var isFragmentVisible = false
private var wasFragmentInit = false
private var wasExtraHeightAdded = false
private var dimPastEvents = true
private var dimCompletedTasks = true
private var highlightWeekends = false
2020-03-24 17:21:39 +01:00
private var wasScaled = false
2020-10-25 20:37:02 +01:00
private var isPrintVersion = false
2017-02-05 10:18:09 +01:00
private var selectedGrid: View? = null
private var currentTimeView: ImageView? = null
private var fadeOutHandler = Handler()
private var allDayHolders = ArrayList<RelativeLayout>()
private var allDayRows = ArrayList<HashSet<Int>>()
private var allDayEventToRow = LinkedHashMap<Event, Int>()
private var currEvents = ArrayList<Event>()
private var dayColumns = ArrayList<RelativeLayout>()
private var eventTypeColors = LongSparseArray<Int>()
private var eventTimeRanges = LinkedHashMap<String, LinkedHashMap<Long, EventWeeklyView>>()
private var currentlyDraggedView: View? = null
private lateinit var binding: FragmentWeekBinding
2020-03-20 11:42:36 +01:00
private lateinit var scrollView: MyScrollView
private lateinit var res: Resources
private lateinit var config: Config
2017-01-15 18:37:51 +01:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
2021-11-21 15:10:20 +01:00
res = requireContext().resources
config = requireContext().config
rowHeight = requireContext().getWeeklyViewItemHeight()
defaultRowHeight = res.getDimension(R.dimen.weekly_view_row_height)
2021-11-21 15:10:20 +01:00
weekTimestamp = requireArguments().getLong(WEEK_START_TIMESTAMP)
2020-03-20 11:42:36 +01:00
dimPastEvents = config.dimPastEvents
dimCompletedTasks = config.dimCompletedTasks
highlightWeekends = config.highlightWeekends
2022-04-06 23:21:11 +02:00
primaryColor = requireContext().getProperPrimaryColor()
allDayRows.add(HashSet())
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
2021-11-21 15:10:20 +01:00
val fullHeight = requireContext().getWeeklyViewItemHeight().toInt() * 24
binding = FragmentWeekBinding.inflate(inflater, container, false).apply {
scrollView = weekEventsScrollview
weekHorizontalGridHolder.layoutParams.height = fullHeight
weekEventsColumnsHolder.layoutParams.height = fullHeight
val scaleDetector = getViewScaleDetector()
scrollView.setOnTouchListener { _, motionEvent ->
scaleDetector.onTouchEvent(motionEvent)
2020-03-24 17:21:39 +01:00
if (motionEvent.action == MotionEvent.ACTION_UP && wasScaled) {
scrollView.isScrollable = true
2020-03-24 17:21:39 +01:00
wasScaled = false
true
} else {
false
}
}
}
addDayColumns()
2020-03-20 11:42:36 +01:00
scrollView.setOnScrollviewListener(object : MyScrollView.ScrollViewListener {
override fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) {
checkScrollLimits(y)
}
})
2017-01-15 10:41:47 +01:00
2020-03-20 11:42:36 +01:00
scrollView.onGlobalLayout {
if (fullHeight < scrollView.height) {
2023-09-04 12:13:10 +02:00
scrollView.layoutParams.height = fullHeight - res.getDimension(com.simplemobiletools.commons.R.dimen.one_dp).toInt()
}
val initialScrollY = (rowHeight * config.startWeeklyAt).toInt()
updateScrollY(max(listener?.getCurrScrollY() ?: 0, initialScrollY))
}
wasFragmentInit = true
return binding.root
2017-01-15 10:41:47 +01:00
}
2017-01-21 21:30:51 +01:00
override fun onResume() {
super.onResume()
2021-11-21 15:10:20 +01:00
requireContext().eventsHelper.getEventTypes(requireActivity(), false) {
2022-05-25 07:23:41 +02:00
it.map { eventType ->
eventTypeColors.put(eventType.id!!, eventType.color)
}
2019-02-14 16:28:26 +01:00
}
setupDayLabels()
updateCalendar()
if (rowHeight != 0f && binding.root.width != 0) {
addCurrentTimeIndicator()
}
2017-01-21 21:30:51 +01:00
}
2018-10-24 22:28:15 +02:00
override fun onPause() {
super.onPause()
wasExtraHeightAdded = true
}
override fun onDestroyView() {
super.onDestroyView()
mWasDestroyed = true
}
override fun setMenuVisibility(menuVisible: Boolean) {
super.setMenuVisibility(menuVisible)
isFragmentVisible = menuVisible
if (isFragmentVisible && wasFragmentInit) {
listener?.updateHoursTopMargin(binding.weekTopHolder.height)
2020-03-20 11:42:36 +01:00
checkScrollLimits(scrollView.scrollY)
// fix some glitches like at swiping from a fully scaled out fragment with all-day events to an empty one
val fullFragmentHeight = (listener?.getFullFragmentHeight() ?: 0) - binding.weekTopHolder.height
if (scrollView.height < fullFragmentHeight) {
config.weeklyViewItemHeightMultiplier = fullFragmentHeight / 24 / defaultRowHeight
updateViewScale()
listener?.updateRowHeight(rowHeight.toInt())
}
}
}
fun updateCalendar() {
2020-05-25 20:56:14 +02:00
if (context != null) {
currentlyDraggedView = null
2021-11-21 15:10:20 +01:00
WeeklyCalendarImpl(this, requireContext()).updateWeeklyCalendar(weekTimestamp)
2020-05-25 20:56:14 +02:00
}
}
private fun addDayColumns() {
binding.weekEventsColumnsHolder.removeAllViews()
(0 until config.weeklyViewDays).forEach {
2023-09-04 11:47:16 +02:00
val column = WeeklyViewDayColumnBinding.inflate(layoutInflater, binding.weekEventsColumnsHolder, false).root
column.tag = Formatter.getDayCodeFromTS(weekTimestamp + it * DAY_SECONDS)
binding.weekEventsColumnsHolder.addView(column)
dayColumns.add(column)
}
}
private fun setupDayLabels() {
var curDay = Formatter.getDateTimeFromTS(weekTimestamp)
val todayCode = Formatter.getDayCodeFromDateTime(DateTime())
2020-06-10 10:33:32 +02:00
val screenWidth = context?.usableScreenSize?.x ?: return
val dayWidth = screenWidth / config.weeklyViewDays
val useLongerDayLabels = dayWidth > res.getDimension(R.dimen.weekly_view_min_day_label)
binding.weekLettersHolder.removeAllViews()
for (i in 0 until config.weeklyViewDays) {
val dayCode = Formatter.getDayCodeFromDateTime(curDay)
val labelIDs = if (useLongerDayLabels) {
2023-09-04 12:13:10 +02:00
com.simplemobiletools.commons.R.array.week_days_short
} else {
2023-09-04 12:13:10 +02:00
com.simplemobiletools.commons.R.array.week_day_letters
}
val dayLetters = res.getStringArray(labelIDs).toMutableList() as ArrayList<String>
val dayLetter = dayLetters[curDay.dayOfWeek - 1]
val textColor = if (isPrintVersion) {
2023-09-04 12:13:10 +02:00
resources.getColor(com.simplemobiletools.commons.R.color.theme_light_text_color)
} else if (todayCode == dayCode) {
primaryColor
} else if (highlightWeekends && isWeekend(curDay.dayOfWeek)) {
config.highlightWeekendsColor
} else {
2022-04-06 23:49:04 +02:00
requireContext().getProperTextColor()
}
2023-09-04 11:47:16 +02:00
val label = WeeklyViewDayLetterBinding.inflate(layoutInflater, binding.weekLettersHolder, false).root
label.text = "$dayLetter\n${curDay.dayOfMonth}"
label.setTextColor(textColor)
if (todayCode == dayCode) {
todayColumnIndex = i
}
binding.weekLettersHolder.addView(label)
2017-01-18 21:28:21 +01:00
curDay = curDay.plusDays(1)
}
}
2017-01-21 21:30:51 +01:00
private fun checkScrollLimits(y: Int) {
if (isFragmentVisible) {
2020-03-20 11:42:36 +01:00
listener?.scrollTo(y)
2017-01-21 21:30:51 +01:00
}
2017-01-15 22:14:01 +01:00
}
2017-02-04 23:49:50 +01:00
private fun initGrid() {
(0 until config.weeklyViewDays).mapNotNull { dayColumns.getOrNull(it) }
2020-05-25 20:56:14 +02:00
.forEachIndexed { index, layout ->
layout.removeAllViews()
val gestureDetector = getViewGestureDetector(layout, index)
layout.setOnTouchListener { _, motionEvent ->
2020-05-25 20:56:14 +02:00
gestureDetector.onTouchEvent(motionEvent)
true
2017-02-04 23:49:50 +01:00
}
2021-08-22 22:26:03 +02:00
layout.setOnDragListener { _, dragEvent ->
2021-08-22 22:26:03 +02:00
when (dragEvent.action) {
2021-09-21 15:28:00 +02:00
DragEvent.ACTION_DRAG_STARTED -> dragEvent.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
DragEvent.ACTION_DRAG_ENTERED,
DragEvent.ACTION_DRAG_EXITED,
DragEvent.ACTION_DRAG_LOCATION,
DragEvent.ACTION_DRAG_ENDED -> true
2021-08-22 22:26:03 +02:00
DragEvent.ACTION_DROP -> {
try {
val (eventId, originalStartTS, originalEndTS) = dragEvent.clipData.getItemAt(0).text.toString().split(";").map { it.toLong() }
val startHour = (dragEvent.y / rowHeight).toInt()
ensureBackgroundThread {
val event = context?.eventsDB?.getEventOrTaskWithId(eventId)
event?.let {
val currentStartTime = Formatter.getDateTimeFromTS(event.startTS)
val startTime = Formatter.getDateTimeFromTS(weekTimestamp + index * DAY_SECONDS)
.withTime(
startHour,
currentStartTime.minuteOfHour,
currentStartTime.secondOfMinute,
currentStartTime.millisOfSecond
).seconds()
val currentEventDuration = event.endTS - event.startTS
val endTime = startTime + currentEventDuration
val newEvent = event.copy(
startTS = startTime,
endTS = endTime,
flags = event.flags.removeBit(FLAG_ALL_DAY)
)
if (event.repeatInterval > 0) {
val activity = this.activity as SimpleActivity
activity.runOnUiThread {
EditRepeatingEventDialog(activity) {
activity.hideKeyboard()
when (it) {
null -> {
revertDraggedEvent()
}
EDIT_SELECTED_OCCURRENCE -> {
context?.eventsHelper?.editSelectedOccurrence(newEvent, false) {
updateCalendar()
}
}
EDIT_FUTURE_OCCURRENCES -> {
context?.eventsHelper?.editFutureOccurrences(newEvent, originalStartTS, false) {
// we need to refresh all fragments because they can contain future occurrences
(activity as MainActivity).refreshItems()
}
}
EDIT_ALL_OCCURRENCES -> {
context?.eventsHelper?.editAllOccurrences(newEvent, originalStartTS, originalEndTS, false) {
(activity as MainActivity).refreshItems()
}
}
}
}
}
} else {
if (event.startTS == newEvent.startTS && event.endTS == newEvent.endTS) {
revertDraggedEvent()
} else {
context?.eventsHelper?.updateEvent(newEvent, updateAtCalDAV = true, showToasts = false) {
updateCalendar()
}
}
}
2021-08-22 22:26:03 +02:00
}
}
true
} catch (ignored: Exception) {
false
2021-08-22 22:26:03 +02:00
}
}
2021-09-21 15:28:00 +02:00
else -> false
2021-08-22 22:26:03 +02:00
}
}
2020-05-25 20:56:14 +02:00
}
2017-02-04 23:49:50 +01:00
}
private fun revertDraggedEvent() {
activity?.runOnUiThread {
currentlyDraggedView?.beVisible()
currentlyDraggedView = null
}
}
private fun getViewGestureDetector(view: ViewGroup, index: Int): GestureDetector {
return GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(event: MotionEvent): Boolean {
selectedGrid?.animation?.cancel()
selectedGrid?.beGone()
val hour = (event.y / rowHeight).toInt()
2023-09-04 11:47:16 +02:00
selectedGrid = WeekGridItemBinding.inflate(layoutInflater).root.apply {
view.addView(this)
background = ColorDrawable(primaryColor)
layoutParams.width = view.width
layoutParams.height = rowHeight.toInt()
y = hour * rowHeight - hour / 2
applyColorFilter(primaryColor.getContrastColor())
setOnClickListener {
val timestamp = Formatter.getDateTimeFromTS(weekTimestamp + index * DAY_SECONDS).withTime(hour, 0, 0, 0).seconds()
if (config.allowCreatingTasks) {
val items = arrayListOf(
RadioItem(TYPE_EVENT, getString(R.string.event)),
RadioItem(TYPE_TASK, getString(R.string.task))
)
RadioGroupDialog(activity!!, items) {
launchNewEventIntent(timestamp, it as Int == TYPE_TASK)
}
} else {
launchNewEventIntent(timestamp, false)
2017-02-05 10:18:09 +01:00
}
}
// do not use setStartDelay, it will trigger instantly if the device has disabled animations
fadeOutHandler.removeCallbacksAndMessages(null)
fadeOutHandler.postDelayed({
animate().alpha(0f).withEndAction {
beGone()
}
}, PLUS_FADEOUT_DELAY)
2017-02-04 23:49:50 +01:00
}
return super.onSingleTapUp(event)
2017-02-04 23:49:50 +01:00
}
})
}
private fun launchNewEventIntent(timestamp: Long, isTask: Boolean) {
Intent(context, getActivityToOpen(isTask)).apply {
putExtra(NEW_EVENT_START_TS, timestamp)
putExtra(NEW_EVENT_SET_HOUR_DURATION, true)
startActivity(this)
}
}
private fun getViewScaleDetector(): ScaleGestureDetector {
return ScaleGestureDetector(requireContext(), object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
2020-03-24 22:00:52 +01:00
val percent = (prevScaleSpanY - detector.currentSpanY) / screenHeight
prevScaleSpanY = detector.currentSpanY
val wantedFactor = config.weeklyViewItemHeightMultiplier - (SCALE_RANGE * percent)
var newFactor = max(min(wantedFactor, MAX_SCALE_FACTOR), MIN_SCALE_FACTOR)
if (scrollView.height > defaultRowHeight * newFactor * 24) {
newFactor = scrollView.height / 24f / defaultRowHeight
}
if (Math.abs(newFactor - prevScaleFactor) > MIN_SCALE_DIFFERENCE) {
prevScaleFactor = newFactor
config.weeklyViewItemHeightMultiplier = newFactor
updateViewScale()
listener?.updateRowHeight(rowHeight.toInt())
val targetY = rowHeightsAtScale * rowHeight - scaleCenterPercent * getVisibleHeight()
scrollView.scrollTo(0, targetY.toInt())
}
return super.onScale(detector)
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
scaleCenterPercent = detector.focusY / scrollView.height
rowHeightsAtScale = (scrollView.scrollY + scaleCenterPercent * getVisibleHeight()) / rowHeight
scrollView.isScrollable = false
2020-03-24 22:00:52 +01:00
prevScaleSpanY = detector.currentSpanY
prevScaleFactor = config.weeklyViewItemHeightMultiplier
2020-03-24 17:21:39 +01:00
wasScaled = true
2020-03-24 22:00:52 +01:00
screenHeight = context!!.realScreenSize.y
return super.onScaleBegin(detector)
}
})
2017-02-04 23:49:50 +01:00
}
private fun getVisibleHeight(): Float {
val fullContentHeight = rowHeight * 24
val visibleRatio = scrollView.height / fullContentHeight
return fullContentHeight * visibleRatio
}
override fun updateWeeklyCalendar(events: ArrayList<Event>) {
val newHash = events.hashCode()
2018-10-24 22:28:15 +02:00
if (newHash == lastHash || mWasDestroyed || context == null) {
2017-10-21 18:14:01 +02:00
return
}
2017-10-21 18:14:01 +02:00
lastHash = newHash
2021-11-21 15:10:20 +01:00
requireActivity().runOnUiThread {
if (context != null && activity != null && isAdded) {
val replaceDescription = config.replaceDescription
val sorted = events.sortedWith(
2020-05-25 20:56:14 +02:00
compareBy<Event> { it.startTS }.thenBy { it.endTS }.thenBy { it.title }.thenBy { if (replaceDescription) it.location else it.description }
).toMutableList() as ArrayList<Event>
currEvents = sorted
addEvents(sorted)
}
}
}
private fun updateViewScale() {
rowHeight = context?.getWeeklyViewItemHeight() ?: return
2023-09-04 12:13:10 +02:00
val oneDp = res.getDimension(com.simplemobiletools.commons.R.dimen.one_dp).toInt()
val fullHeight = max(rowHeight.toInt() * 24, scrollView.height + oneDp)
scrollView.layoutParams.height = fullHeight - oneDp
binding.weekHorizontalGridHolder.layoutParams.height = fullHeight
binding.weekEventsColumnsHolder.layoutParams.height = fullHeight
addEvents(currEvents)
}
2020-03-22 16:30:46 +01:00
private fun addEvents(events: ArrayList<Event>) {
2017-02-04 23:49:50 +01:00
initGrid()
allDayHolders.clear()
allDayRows.clear()
eventTimeRanges.clear()
2017-11-07 16:38:58 +01:00
allDayRows.add(HashSet())
binding.weekAllDayHolder.removeAllViews()
addNewLine()
allDayEventToRow.clear()
val minuteHeight = rowHeight / 60
2020-03-20 11:42:36 +01:00
val minimalHeight = res.getDimension(R.dimen.weekly_view_minimal_event_height).toInt()
val density = res.displayMetrics.density.roundToInt()
2017-02-03 22:51:14 +01:00
for (event in events) {
val startDateTime = Formatter.getDateTimeFromTS(event.startTS)
val startDayCode = Formatter.getDayCodeFromDateTime(startDateTime)
val endDateTime = Formatter.getDateTimeFromTS(event.endTS)
val endDayCode = Formatter.getDayCodeFromDateTime(endDateTime)
val isAllDay = event.getIsAllDay()
if (shouldAddEventOnTopBar(isAllDay, startDayCode, endDayCode)) {
continue
}
var currentDateTime = startDateTime
var currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime)
do {
// all-day events always start at the 0 minutes and end at the end of the day (1440 minutes)
val startMinutes = when {
currentDayCode == startDayCode && !isAllDay -> (startDateTime.minuteOfDay)
else -> 0
}
val duration = when {
currentDayCode == endDayCode && !isAllDay -> (endDateTime.minuteOfDay - startMinutes)
else -> 1440
}
var endMinutes = startMinutes + duration
2022-03-03 09:38:02 +01:00
if ((endMinutes - startMinutes) * minuteHeight < minimalHeight) {
endMinutes = startMinutes + (minimalHeight / minuteHeight).toInt()
}
val range = Range(startMinutes, endMinutes)
val eventWeekly = EventWeeklyView(range)
if (!eventTimeRanges.containsKey(currentDayCode)) {
2022-02-26 20:48:35 +01:00
eventTimeRanges[currentDayCode] = LinkedHashMap()
}
eventTimeRanges[currentDayCode]?.put(event.id!!, eventWeekly)
currentDateTime = currentDateTime.plusDays(1)
currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime)
2021-07-06 14:49:42 +02:00
} while (currentDayCode.toInt() <= endDayCode.toInt())
}
2022-03-16 15:19:02 +01:00
for ((_, eventDayList) in eventTimeRanges) {
val eventsCollisionChecked = ArrayList<Long>()
2022-03-16 15:19:02 +01:00
for ((eventId, eventWeeklyView) in eventDayList) {
if (eventWeeklyView.slot == 0) {
eventWeeklyView.slot = 1
eventWeeklyView.slotMax = 1
}
eventsCollisionChecked.add(eventId)
2022-05-25 07:23:41 +02:00
val eventWeeklyViewsToCheck = eventDayList.filterNot { eventsCollisionChecked.contains(it.key) }
2022-03-16 15:19:02 +01:00
for ((toCheckId, eventWeeklyViewToCheck) in eventWeeklyViewsToCheck) {
val areTouching = eventWeeklyView.range.intersects(eventWeeklyViewToCheck.range)
val doHaveCommonMinutes = if (areTouching) {
eventWeeklyView.range.upper > eventWeeklyViewToCheck.range.lower || (eventWeeklyView.range.lower == eventWeeklyView.range.upper &&
eventWeeklyView.range.upper == eventWeeklyViewToCheck.range.lower)
} else {
false
}
if (areTouching && doHaveCommonMinutes) {
if (eventWeeklyViewToCheck.slot == 0) {
val nextSlot = eventWeeklyView.slotMax + 1
val slotRange = Array(eventWeeklyView.slotMax) { it + 1 }
val collisionEventWeeklyViews = eventDayList.filter { eventWeeklyView.collisions.contains(it.key) }
2022-03-16 15:19:02 +01:00
for ((_, collisionEventWeeklyView) in collisionEventWeeklyViews) {
if (collisionEventWeeklyView.range.intersects(eventWeeklyViewToCheck.range)) {
slotRange[collisionEventWeeklyView.slot - 1] = nextSlot
}
}
slotRange[eventWeeklyView.slot - 1] = nextSlot
val slot = slotRange.minOrNull()
eventWeeklyViewToCheck.slot = slot!!
if (slot == nextSlot) {
eventWeeklyViewToCheck.slotMax = nextSlot
eventWeeklyView.slotMax = nextSlot
2022-03-16 15:19:02 +01:00
for ((_, collisionEventWeeklyView) in collisionEventWeeklyViews) {
collisionEventWeeklyView.slotMax++
}
} else {
eventWeeklyViewToCheck.slotMax = eventWeeklyView.slotMax
}
}
eventWeeklyView.collisions.add(toCheckId)
eventWeeklyViewToCheck.collisions.add(eventId)
}
}
}
}
dayevents@ for (event in events) {
val startDateTime = Formatter.getDateTimeFromTS(event.startTS)
val startDayCode = Formatter.getDayCodeFromDateTime(startDateTime)
val endDateTime = Formatter.getDateTimeFromTS(event.endTS)
val endDayCode = Formatter.getDayCodeFromDateTime(endDateTime)
if (shouldAddEventOnTopBar(event.getIsAllDay(), startDayCode, endDayCode)) {
2017-02-03 22:51:14 +01:00
addAllDayEvent(event)
2021-07-06 14:49:42 +02:00
} else {
var currentDateTime = startDateTime
var currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime)
do {
val dayOfWeek = dayColumns.indexOfFirst { it.tag == currentDayCode }
if (dayOfWeek == -1 || dayOfWeek >= config.weeklyViewDays) {
if (startDayCode != endDayCode) {
currentDateTime = currentDateTime.plusDays(1)
currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime)
continue
} else {
continue@dayevents
}
}
val dayColumn = dayColumns[dayOfWeek]
WeekEventMarkerBinding.inflate(layoutInflater).apply {
2023-04-08 13:33:36 +02:00
var backgroundColor = if (event.color == 0) {
eventTypeColors.get(event.eventType, primaryColor)
} else {
event.color
}
var textColor = backgroundColor.getContrastColor()
2023-04-08 13:33:36 +02:00
val currentEventWeeklyView = eventTimeRanges[currentDayCode]!![event.id]
val adjustAlpha = if (event.isTask()) {
dimCompletedTasks && event.isTaskCompleted()
} else {
dimPastEvents && event.isPastEvent && !isPrintVersion
}
2022-05-18 17:08:05 +02:00
if (adjustAlpha) {
2021-11-21 15:10:20 +01:00
backgroundColor = backgroundColor.adjustAlpha(MEDIUM_ALPHA)
textColor = textColor.adjustAlpha(HIGHER_ALPHA)
}
2019-02-14 15:48:17 +01:00
root.background = ColorDrawable(backgroundColor)
dayColumn.addView(root)
root.y = currentEventWeeklyView!!.range.lower * minuteHeight
2022-02-26 23:04:32 +01:00
// compensate grid offset
root.y -= (currentEventWeeklyView.range.lower / 60) / 2
weekEventTaskImage.beVisibleIf(event.isTask())
2022-02-26 23:04:32 +01:00
if (event.isTask()) {
weekEventTaskImage.applyColorFilter(textColor)
2022-02-26 23:04:32 +01:00
}
weekEventLabel.apply {
2022-02-26 23:04:32 +01:00
setTextColor(textColor)
maxLines = if (event.isTask() || event.startTS == event.endTS) {
1
} else {
3
}
2022-02-26 23:04:32 +01:00
text = event.title
checkViewStrikeThrough(event.isTaskCompleted())
contentDescription = text
minHeight = if (event.startTS == event.endTS) {
minimalHeight
} else {
((currentEventWeeklyView.range.upper - currentEventWeeklyView.range.lower) * minuteHeight).toInt() - 1
}
}
(root.layoutParams as RelativeLayout.LayoutParams).apply {
width = (dayColumn.width - 1) / currentEventWeeklyView.slotMax
root.x = (width * (currentEventWeeklyView.slot - 1)).toFloat()
if (currentEventWeeklyView.slot > 1) {
root.x += density
width -= density
}
}
2021-07-06 14:49:42 +02:00
root.setOnClickListener {
Intent(context, getActivityToOpen(event.isTask())).apply {
putExtra(EVENT_ID, event.id!!)
putExtra(EVENT_OCCURRENCE_TS, event.startTS)
putExtra(IS_TASK_COMPLETED, event.isTaskCompleted())
startActivity(this)
}
2017-02-03 22:51:14 +01:00
}
2021-08-22 22:26:03 +02:00
root.setOnLongClickListener { view ->
currentlyDraggedView = view
2021-08-22 22:26:03 +02:00
val shadowBuilder = View.DragShadowBuilder(view)
val clipData = ClipData.newPlainText(WEEKLY_EVENT_ID_LABEL, "${event.id};${event.startTS};${event.endTS}")
if (isNougatPlus()) {
2021-08-22 22:26:03 +02:00
view.startDragAndDrop(clipData, shadowBuilder, null, 0)
} else {
view.startDrag(clipData, shadowBuilder, null, 0)
}
true
}
root.setOnDragListener(DragListener())
}
currentDateTime = currentDateTime.plusDays(1)
currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime)
2021-07-06 14:49:42 +02:00
} while (currentDayCode.toInt() <= endDayCode.toInt())
2017-01-16 23:41:06 +01:00
}
}
checkTopHolderHeight()
addCurrentTimeIndicator()
}
private fun addNewLine() {
val allDaysLine = AllDayEventsHolderLineBinding.inflate(layoutInflater).root
binding.weekAllDayHolder.addView(allDaysLine)
allDayHolders.add(allDaysLine)
}
private fun addCurrentTimeIndicator() {
if (todayColumnIndex != -1) {
val calendar = Calendar.getInstance()
val minutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)
if (todayColumnIndex >= dayColumns.size) {
currentTimeView?.alpha = 0f
return
}
if (currentTimeView != null) {
binding.weekEventsHolder.removeView(currentTimeView)
}
2020-10-25 20:37:02 +01:00
if (isPrintVersion) {
return
}
val weeklyViewDays = config.weeklyViewDays
2023-09-04 11:47:16 +02:00
currentTimeView = WeekNowMarkerBinding.inflate(layoutInflater).root.apply {
applyColorFilter(primaryColor)
binding.weekEventsHolder.addView(this, 0)
2023-09-04 12:13:10 +02:00
val extraWidth = res.getDimension(com.simplemobiletools.commons.R.dimen.activity_margin).toInt()
2020-03-20 11:42:36 +01:00
val markerHeight = res.getDimension(R.dimen.weekly_view_now_height).toInt()
val minuteHeight = rowHeight / 60
(layoutParams as RelativeLayout.LayoutParams).apply {
width = (binding.root.width / weeklyViewDays) + extraWidth
height = markerHeight
}
x = if (weeklyViewDays == 1) {
0f
} else {
(binding.root.width / weeklyViewDays * todayColumnIndex).toFloat() - extraWidth / 2f
}
y = minutes * minuteHeight - markerHeight / 2
}
}
2017-01-15 10:41:47 +01:00
}
2017-01-15 18:37:51 +01:00
2017-02-04 23:49:50 +01:00
private fun checkTopHolderHeight() {
binding.weekTopHolder.onGlobalLayout {
2018-10-24 22:28:15 +02:00
if (isFragmentVisible && activity != null && !mWasDestroyed) {
listener?.updateHoursTopMargin(binding.weekTopHolder.height)
2017-02-04 23:49:50 +01:00
}
2018-10-24 22:28:15 +02:00
}
2017-02-04 23:49:50 +01:00
}
private fun shouldAddEventOnTopBar(isAllDay: Boolean, startDayCode: String, endDayCode: String): Boolean {
val spansMultipleDays = startDayCode != endDayCode
val isSingleDayAllDayEvent = isAllDay && !spansMultipleDays
2022-11-24 22:44:04 +01:00
return isSingleDayAllDayEvent || (spansMultipleDays && config.showMidnightSpanningEventsAtTop)
}
@SuppressLint("NewApi")
2017-02-03 22:51:14 +01:00
private fun addAllDayEvent(event: Event) {
WeekAllDayEventMarkerBinding.inflate(layoutInflater).apply {
2023-04-30 11:43:17 +02:00
var backgroundColor = if (event.color == 0) {
eventTypeColors.get(event.eventType, primaryColor)
} else {
event.color
}
var textColor = backgroundColor.getContrastColor()
val adjustAlpha = if (event.isTask()) {
dimCompletedTasks && event.isTaskCompleted()
} else {
dimPastEvents && event.isPastEvent && !isPrintVersion
}
2022-05-18 17:08:05 +02:00
if (adjustAlpha) {
backgroundColor = backgroundColor.adjustAlpha(LOWER_ALPHA)
textColor = textColor.adjustAlpha(HIGHER_ALPHA)
}
2022-05-18 17:08:05 +02:00
root.background = ColorDrawable(backgroundColor)
weekEventLabel.apply {
setTextColor(textColor)
maxLines = if (event.isTask()) 1 else 2
text = event.title
checkViewStrikeThrough(event.isTaskCompleted())
contentDescription = text
}
weekEventTaskImage.beVisibleIf(event.isTask())
if (event.isTask()) {
weekEventTaskImage.applyColorFilter(textColor)
}
2017-02-03 22:51:14 +01:00
val startDateTime = Formatter.getDateTimeFromTS(event.startTS)
val endDateTime = Formatter.getDateTimeFromTS(event.endTS)
val eventStartDayStartTime = startDateTime.withTimeAtStartOfDay().seconds()
val eventEndDayStartTime = endDateTime.withTimeAtStartOfDay().seconds()
2017-02-03 22:51:14 +01:00
val minTS = max(startDateTime.seconds(), weekTimestamp)
val maxTS = min(endDateTime.seconds(), weekTimestamp + 2 * WEEK_SECONDS)
// fix a visual glitch with all-day events or events lasting multiple days starting at midnight on monday, being shown the previous week too
2020-03-20 11:42:36 +01:00
if (minTS == maxTS && (minTS - weekTimestamp == WEEK_SECONDS.toLong())) {
return
}
2017-02-19 23:39:36 +01:00
val isStartTimeDay = Formatter.getDateTimeFromTS(maxTS) == Formatter.getDateTimeFromTS(maxTS).withTimeAtStartOfDay()
val numDays = Days.daysBetween(Formatter.getDateTimeFromTS(minTS).toLocalDate(), Formatter.getDateTimeFromTS(maxTS).toLocalDate()).days
val daysCnt = if (numDays == 1 && isStartTimeDay) 0 else numDays
val startDateTimeInWeek = Formatter.getDateTimeFromTS(minTS)
2022-05-25 07:23:41 +02:00
val firstDayIndex = startDateTimeInWeek.dayOfMonth // indices must be unique for the visible range (2 weeks)
val lastDayIndex = firstDayIndex + daysCnt
val dayIndices = firstDayIndex..lastDayIndex
val isAllDayEvent = firstDayIndex == lastDayIndex
val isRepeatingOverlappingEvent = eventEndDayStartTime - eventStartDayStartTime >= event.repeatInterval
var doesEventFit: Boolean
var wasEventHandled = false
var drawAtLine = 0
2022-05-25 07:23:41 +02:00
for (index in allDayRows.indices) {
drawAtLine = index
2022-05-25 07:23:41 +02:00
val row = allDayRows[index]
2022-05-25 07:23:41 +02:00
doesEventFit = dayIndices.all { !row.contains(it) }
val firstEvent = allDayEventToRow.keys.firstOrNull { it.id == event.id }
val lastEvent = allDayEventToRow.keys.lastOrNull { it.id == event.id }
val firstEventRowIdx = allDayEventToRow[firstEvent]
val lastEventRowIdx = allDayEventToRow[lastEvent]
val adjacentEvents = currEvents.filter { event.id == it.id }
val repeatingEventIndex = adjacentEvents.indexOf(event)
val isRowValidForEvent = lastEvent == null || firstEventRowIdx!! + repeatingEventIndex == index && lastEventRowIdx!! < index
if (doesEventFit && (!isRepeatingOverlappingEvent || isAllDayEvent || isRowValidForEvent)) {
dayIndices.forEach {
row.add(it)
}
allDayEventToRow[event] = index
wasEventHandled = true
} else {
// create new row
if (index == allDayRows.lastIndex) {
allDayRows.add(HashSet())
addNewLine()
drawAtLine++
val lastRow = allDayRows.last()
dayIndices.forEach {
lastRow.add(it)
}
allDayEventToRow[event] = allDayRows.lastIndex
wasEventHandled = true
}
}
if (wasEventHandled) {
break
}
}
val dayCodeStart = Formatter.getDayCodeFromDateTime(startDateTime).toInt()
val dayCodeEnd = Formatter.getDayCodeFromDateTime(endDateTime).toInt()
2022-05-25 07:23:41 +02:00
val dayOfWeek = dayColumns.indexOfFirst { it.tag.toInt() == dayCodeStart || it.tag.toInt() in (dayCodeStart + 1)..dayCodeEnd }
if (dayOfWeek == -1) {
return
}
allDayHolders[drawAtLine].addView(root)
val dayWidth = binding.root.width / config.weeklyViewDays
(root.layoutParams as RelativeLayout.LayoutParams).apply {
leftMargin = dayOfWeek * dayWidth
bottomMargin = 1
width = (dayWidth) * (daysCnt + 1)
}
calculateExtraHeight()
root.setOnClickListener {
Intent(context, getActivityToOpen(event.isTask())).apply {
2017-02-03 22:51:14 +01:00
putExtra(EVENT_ID, event.id)
putExtra(EVENT_OCCURRENCE_TS, event.startTS)
putExtra(IS_TASK_COMPLETED, event.isTaskCompleted())
2017-02-03 22:51:14 +01:00
startActivity(this)
}
}
}
}
private fun calculateExtraHeight() {
binding.weekTopHolder.onGlobalLayout {
2018-10-24 22:28:15 +02:00
if (activity != null && !mWasDestroyed) {
if (isFragmentVisible) {
listener?.updateHoursTopMargin(binding.weekTopHolder.height)
}
if (!wasExtraHeightAdded) {
wasExtraHeightAdded = true
}
}
2018-10-24 22:28:15 +02:00
}
}
2017-01-15 18:37:51 +01:00
fun updateScrollY(y: Int) {
if (wasFragmentInit) {
2020-03-20 11:42:36 +01:00
scrollView.scrollY = y
}
2017-01-15 18:37:51 +01:00
}
2020-03-24 22:00:52 +01:00
fun updateNotVisibleViewScaleLevel() {
if (!isFragmentVisible) {
updateViewScale()
}
}
2020-10-25 20:37:02 +01:00
fun togglePrintMode() {
isPrintVersion = !isPrintVersion
updateCalendar()
setupDayLabels()
addEvents(currEvents)
}
2021-08-22 22:26:03 +02:00
inner class DragListener : View.OnDragListener {
2021-08-22 22:26:03 +02:00
override fun onDrag(view: View, dragEvent: DragEvent): Boolean {
return when (dragEvent.action) {
DragEvent.ACTION_DRAG_STARTED -> currentlyDraggedView == view
DragEvent.ACTION_DRAG_ENTERED -> {
view.beGone()
false
}
// handle ACTION_DRAG_LOCATION due to https://stackoverflow.com/a/19460338
2021-09-21 15:28:00 +02:00
DragEvent.ACTION_DRAG_LOCATION -> true
DragEvent.ACTION_DROP -> {
view.beVisible()
true
}
DragEvent.ACTION_DRAG_ENDED -> {
if (!dragEvent.result) {
view.beVisible()
}
true
}
else -> false
2021-08-22 22:26:03 +02:00
}
}
}
2017-01-15 10:41:47 +01:00
}