From 9255297db806cf66ac563a685c9945939030fb56 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 3 Feb 2026 14:05:34 +0100 Subject: [PATCH 1/9] refactor: moved logic from WeekFragment.kt to WeeklyCalendarImpl.kt so it can be re-used for the widget --- .../calendar/fragments/WeekFragment.kt | 426 ++++-------------- .../calendar/helpers/WeeklyCalendarImpl.kt | 145 +++++- .../calendar/interfaces/WeeklyCalendar.kt | 5 +- .../org/fossify/calendar/models/DayWeekly.kt | 9 + .../calendar/models/EventWeeklyView.kt | 9 +- 5 files changed, 252 insertions(+), 342 deletions(-) create mode 100644 app/src/main/kotlin/org/fossify/calendar/models/DayWeekly.kt diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/WeekFragment.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/WeekFragment.kt index 67fe929d0..284b60f12 100644 --- a/app/src/main/kotlin/org/fossify/calendar/fragments/WeekFragment.kt +++ b/app/src/main/kotlin/org/fossify/calendar/fragments/WeekFragment.kt @@ -3,12 +3,12 @@ package org.fossify.calendar.fragments import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipDescription +import android.content.Context import android.content.Intent import android.content.res.Resources import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.os.Handler -import android.util.Range import android.view.DragEvent import android.view.GestureDetector import android.view.LayoutInflater @@ -37,7 +37,6 @@ import org.fossify.calendar.extensions.config import org.fossify.calendar.extensions.eventsDB import org.fossify.calendar.extensions.eventsHelper import org.fossify.calendar.extensions.getWeeklyViewItemHeight -import org.fossify.calendar.extensions.intersects import org.fossify.calendar.extensions.seconds import org.fossify.calendar.extensions.shouldStrikeThrough import org.fossify.calendar.helpers.Config @@ -59,8 +58,8 @@ import org.fossify.calendar.helpers.getActivityToOpen import org.fossify.calendar.helpers.isWeekend import org.fossify.calendar.interfaces.WeekFragmentListener import org.fossify.calendar.interfaces.WeeklyCalendar +import org.fossify.calendar.models.DayWeekly import org.fossify.calendar.models.Event -import org.fossify.calendar.models.EventWeeklyView import org.fossify.calendar.views.MyScrollView import org.fossify.commons.dialogs.RadioGroupDialog import org.fossify.commons.extensions.adjustAlpha @@ -75,17 +74,14 @@ import org.fossify.commons.extensions.hideKeyboard import org.fossify.commons.extensions.onGlobalLayout import org.fossify.commons.extensions.realScreenSize import org.fossify.commons.extensions.removeBit -import org.fossify.commons.extensions.toInt import org.fossify.commons.extensions.usableScreenSize import org.fossify.commons.helpers.HIGHER_ALPHA import org.fossify.commons.helpers.LOWER_ALPHA import org.fossify.commons.helpers.MEDIUM_ALPHA -import org.fossify.commons.helpers.WEEK_SECONDS import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.isNougatPlus import org.fossify.commons.models.RadioItem import org.joda.time.DateTime -import org.joda.time.Days import java.util.Calendar import kotlin.math.max import kotlin.math.min @@ -125,12 +121,10 @@ class WeekFragment : Fragment(), WeeklyCalendar { private var currentTimeView: ImageView? = null private var fadeOutHandler = Handler() private var allDayHolders = ArrayList() - private var allDayRows = ArrayList>() - private var allDayEventToRow = LinkedHashMap() - private var currEvents = ArrayList() + private var allDayRows = ArrayList() + private var currDays = ArrayList() private var dayColumns = ArrayList() private var calendarColors = LongSparseArray() - private var eventTimeRanges = LinkedHashMap>() private var currentlyDraggedView: View? = null private lateinit var binding: FragmentWeekBinding @@ -150,7 +144,6 @@ class WeekFragment : Fragment(), WeeklyCalendar { dimCompletedTasks = config.dimCompletedTasks highlightWeekends = config.highlightWeekends primaryColor = requireContext().getProperPrimaryColor() - allDayRows.add(HashSet()) } @SuppressLint("ClickableViewAccessibility") @@ -252,7 +245,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { fun updateCalendar() { if (context != null) { currentlyDraggedView = null - WeeklyCalendarImpl(this, requireContext()).updateWeeklyCalendar(weekTimestamp) + WeeklyCalendarImpl(this, requireContext()).updateWeeklyCalendar(weekDateTime) } } @@ -264,7 +257,6 @@ class WeekFragment : Fragment(), WeeklyCalendar { binding.weekEventsColumnsHolder, false ).root - column.tag = Formatter.getDayCodeFromDateTime(weekDateTime.plusDays(it)) binding.weekEventsColumnsHolder.addView(column) dayColumns.add(column) } @@ -547,24 +539,18 @@ class WeekFragment : Fragment(), WeeklyCalendar { return fullContentHeight * visibleRatio } - override fun updateWeeklyCalendar(events: ArrayList) { - val newHash = events.hashCode() - if (newHash == lastHash || mWasDestroyed || context == null) { + override fun updateWeeklyCalendar(context: Context, days: ArrayList) { + val newHash = days.hashCode() + if (newHash == lastHash || mWasDestroyed) { return } lastHash = newHash requireActivity().runOnUiThread { - if (context != null && activity != null && isAdded) { - val replaceDescription = config.replaceDescription - val sorted = events.sortedWith( - compareBy { it.startTS }.thenBy { it.endTS }.thenBy { it.title } - .thenBy { if (replaceDescription) it.location else it.description } - ).toMutableList() as ArrayList - - currEvents = sorted - addEvents(sorted) + if (activity != null && isAdded) { + currDays = days + addDays(days) } } } @@ -577,245 +563,107 @@ class WeekFragment : Fragment(), WeeklyCalendar { scrollView.layoutParams.height = fullHeight - oneDp binding.weekHorizontalGridHolder.layoutParams.height = fullHeight binding.weekEventsColumnsHolder.layoutParams.height = fullHeight - addEvents(currEvents) + addDays(currDays) } - private fun addEvents(events: ArrayList) { + private fun addDays(days: ArrayList) { initGrid() allDayHolders.clear() allDayRows.clear() - eventTimeRanges.clear() - allDayRows.add(HashSet()) binding.weekAllDayHolder.removeAllViews() - addNewLine() - allDayEventToRow.clear() val minuteHeight = rowHeight / 60 val minimalHeight = res.getDimension(R.dimen.weekly_view_minimal_event_height).toInt() val density = res.displayMetrics.density.roundToInt() - 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 + for ((dayOfWeek, day) in days.withIndex()) { + for (event in day.topBarEvents) { + addAllDayEvent(dayOfWeek, event) } - - 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 - if ((endMinutes - startMinutes) * minuteHeight < minimalHeight) { - endMinutes = startMinutes + (minimalHeight / minuteHeight).toInt() - } - - val range = Range(startMinutes, endMinutes) - val eventWeekly = EventWeeklyView(range) - - if (!eventTimeRanges.containsKey(currentDayCode)) { - eventTimeRanges[currentDayCode] = LinkedHashMap() - } - eventTimeRanges[currentDayCode]?.put(event.id!!, eventWeekly) - - currentDateTime = currentDateTime.plusDays(1) - currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime) - } while (currentDayCode.toInt() <= endDayCode.toInt()) - } - - for ((_, eventDayList) in eventTimeRanges) { - val eventsCollisionChecked = ArrayList() - for ((eventId, eventWeeklyView) in eventDayList) { - if (eventWeeklyView.slot == 0) { - eventWeeklyView.slot = 1 - eventWeeklyView.slotMax = 1 - } - - eventsCollisionChecked.add(eventId) - val eventWeeklyViewsToCheck = - eventDayList.filterNot { eventsCollisionChecked.contains(it.key) } - 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) + for (ews in day.dayEvents) { + val dayColumn = dayColumns[dayOfWeek] + val event = ews.event + WeekEventMarkerBinding.inflate(layoutInflater).apply { + var backgroundColor = if (event.color == 0) { + calendarColors.get(event.calendarId, primaryColor) } else { - false + event.color } + var textColor = backgroundColor.getContrastColor() - if (areTouching && doHaveCommonMinutes) { - if (eventWeeklyViewToCheck.slot == 0) { - val collisionEventWeeklyViews = eventDayList - .filter { eventWeeklyView.collisions.contains(it.key) } - var currentSlotMax = max( - eventWeeklyView.slotMax.coerceAtLeast(1), - eventWeeklyView.slot.coerceAtLeast(1) - ) - val maxCollisionSlot = collisionEventWeeklyViews - .maxOfOrNull { it.value.slot.coerceAtLeast(1) } ?: 1 - currentSlotMax = max(currentSlotMax, maxCollisionSlot) - val nextSlot = currentSlotMax + 1 - val slotRange = IntArray(currentSlotMax) { it + 1 } - for ((_, collisionEventWeeklyView) in collisionEventWeeklyViews) { - if ( - collisionEventWeeklyView.range.intersects(eventWeeklyViewToCheck.range) - && collisionEventWeeklyView.slot in 1..currentSlotMax - ) { - slotRange[collisionEventWeeklyView.slot - 1] = nextSlot - } - } - if (eventWeeklyView.slot in 1..currentSlotMax) { - slotRange[eventWeeklyView.slot - 1] = nextSlot - } - - val slot = slotRange.minOrNull() ?: nextSlot - eventWeeklyViewToCheck.slot = slot - - if (slot == nextSlot) { - eventWeeklyViewToCheck.slotMax = nextSlot - eventWeeklyView.slotMax = nextSlot - for ((_, collisionEventWeeklyView) in collisionEventWeeklyViews) { - collisionEventWeeklyView.slotMax = - max(collisionEventWeeklyView.slotMax, nextSlot) - } - } else { - eventWeeklyViewToCheck.slotMax = currentSlotMax - } - } - eventWeeklyView.collisions.add(toCheckId) - eventWeeklyViewToCheck.collisions.add(eventId) + val adjustAlpha = if (event.isTask()) { + dimCompletedTasks && event.isTaskCompleted() + } else { + dimPastEvents && event.isPastEvent && !isPrintVersion } - } - } - } - - 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)) { - addAllDayEvent(event) - } 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 - } + if (adjustAlpha) { + backgroundColor = backgroundColor.adjustAlpha(MEDIUM_ALPHA) + textColor = textColor.adjustAlpha(HIGHER_ALPHA) } - val dayColumn = dayColumns[dayOfWeek] - WeekEventMarkerBinding.inflate(layoutInflater).apply { - var backgroundColor = if (event.color == 0) { - calendarColors.get(event.calendarId, primaryColor) - } else { - event.color - } - var textColor = backgroundColor.getContrastColor() - val currentEventWeeklyView = eventTimeRanges[currentDayCode]!![event.id] - - val adjustAlpha = if (event.isTask()) { - dimCompletedTasks && event.isTaskCompleted() - } else { - dimPastEvents && event.isPastEvent && !isPrintVersion - } - - if (adjustAlpha) { - backgroundColor = backgroundColor.adjustAlpha(MEDIUM_ALPHA) - textColor = textColor.adjustAlpha(HIGHER_ALPHA) - } + root.background = ColorDrawable(backgroundColor) + dayColumn.addView(root) + root.y = ews.startMinute * minuteHeight - root.background = ColorDrawable(backgroundColor) - dayColumn.addView(root) - root.y = currentEventWeeklyView!!.range.lower * minuteHeight + // compensate grid offset + root.y -= (ews.startMinute / 60) / 2 - // compensate grid offset - root.y -= (currentEventWeeklyView.range.lower / 60) / 2 + weekEventTaskImage.beVisibleIf(event.isTask()) + if (event.isTask()) { + weekEventTaskImage.applyColorFilter(textColor) + } - weekEventTaskImage.beVisibleIf(event.isTask()) - if (event.isTask()) { - weekEventTaskImage.applyColorFilter(textColor) + weekEventLabel.apply { + setTextColor(textColor) + maxLines = if (event.isTask() || event.startTS == event.endTS) { + 1 + } else { + 3 } - weekEventLabel.apply { - setTextColor(textColor) - maxLines = if (event.isTask() || event.startTS == event.endTS) { - 1 - } else { - 3 - } + text = event.title + checkViewStrikeThrough(event.shouldStrikeThrough()) + contentDescription = text - text = event.title - checkViewStrikeThrough(event.shouldStrikeThrough()) - contentDescription = text - - minHeight = if (event.startTS == event.endTS) { - minimalHeight - } else { - ((currentEventWeeklyView.range.upper - currentEventWeeklyView.range.lower) * minuteHeight).toInt() - 1 - } - } + minHeight = minimalHeight.coerceAtLeast(((ews.endMinute - ews.startMinute) * 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 - } + (root.layoutParams as RelativeLayout.LayoutParams).apply { + width = (dayColumn.width - 1) / ews.slotMax + root.x = (width * ews.slot).toFloat() + if (ews.slot > 0) { + root.x += density + width -= density } + } - 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) - } + 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) } + } - root.setOnLongClickListener { view -> - currentlyDraggedView = view - val shadowBuilder = View.DragShadowBuilder(view) - val clipData = ClipData.newPlainText( - WEEKLY_EVENT_ID_LABEL, - "${event.id};${event.startTS};${event.endTS}" - ) - if (isNougatPlus()) { - view.startDragAndDrop(clipData, shadowBuilder, null, 0) - } else { - view.startDrag(clipData, shadowBuilder, null, 0) - } - true + root.setOnLongClickListener { view -> + currentlyDraggedView = view + val shadowBuilder = View.DragShadowBuilder(view) + val clipData = ClipData.newPlainText( + WEEKLY_EVENT_ID_LABEL, + "${event.id};${event.startTS};${event.endTS}" + ) + if (isNougatPlus()) { + view.startDragAndDrop(clipData, shadowBuilder, null, 0) + } else { + view.startDrag(clipData, shadowBuilder, null, 0) } - - root.setOnDragListener(DragListener()) + true } - currentDateTime = currentDateTime.plusDays(1) - currentDayCode = Formatter.getDayCodeFromDateTime(currentDateTime) - } while (currentDayCode.toInt() <= endDayCode.toInt()) + root.setOnDragListener(DragListener()) + } } } @@ -823,7 +671,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { addCurrentTimeIndicator() } - private fun addNewLine() { + private fun addTopEventLine() { val allDaysLine = AllDayEventsHolderLineBinding.inflate(layoutInflater).root binding.weekAllDayHolder.addView(allDaysLine) allDayHolders.add(allDaysLine) @@ -878,17 +726,8 @@ class WeekFragment : Fragment(), WeeklyCalendar { } } - private fun shouldAddEventOnTopBar( - isAllDay: Boolean, - startDayCode: String, - endDayCode: String - ): Boolean { - val spansMultipleDays = startDayCode != endDayCode - return isAllDay || (spansMultipleDays && config.showMidnightSpanningEventsAtTop) - } - @SuppressLint("NewApi") - private fun addAllDayEvent(event: Event) { + private fun addAllDayEvent(dayOfWeek: Int, event: Event) { WeekAllDayEventMarkerBinding.inflate(layoutInflater).apply { var backgroundColor = if (event.color == 0) { calendarColors.get(event.calendarId, primaryColor) @@ -923,95 +762,30 @@ class WeekFragment : Fragment(), WeeklyCalendar { weekEventTaskImage.applyColorFilter(textColor) } - val startDateTime = Formatter.getDateTimeFromTS(event.startTS) + // horizontal positioning + val dayWidth = binding.root.width / config.weeklyViewDays val endDateTime = Formatter.getDateTimeFromTS(event.endTS) - val eventStartDayStartTime = startDateTime.withTimeAtStartOfDay().seconds() - val eventEndDayStartTime = endDateTime.withTimeAtStartOfDay().seconds() - - 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 - if (minTS == maxTS && (minTS - weekTimestamp == WEEK_SECONDS.toLong())) { - return - } - - 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) - 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 - - for (index in allDayRows.indices) { - drawAtLine = index - - val row = allDayRows[index] - 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() - val dayOfWeek = - dayColumns.indexOfFirst { it.tag.toInt() == dayCodeStart || it.tag.toInt() in (dayCodeStart + 1)..dayCodeEnd } - if (dayOfWeek == -1) { - return + var lastDay = currDays.indexOfLast { it.start < endDateTime } + if (lastDay == -1) { + lastDay = config.weeklyViewDays + } + lastDay = lastDay.coerceAtLeast(dayOfWeek) + + // vertical positioning (i.e. find a row where this event fits) + var drawAtLine = allDayRows.indexOfFirst { it < dayOfWeek } + if (drawAtLine < 0) { + drawAtLine = allDayRows.size + addTopEventLine() + allDayRows.add(lastDay) + } else { + allDayRows[drawAtLine] = lastDay } - 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) + width = (dayWidth) * (lastDay - dayOfWeek + 1) } calculateExtraHeight() @@ -1057,7 +831,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { isPrintVersion = !isPrintVersion updateCalendar() setupDayLabels() - addEvents(currEvents) + addDays(currDays) } inner class DragListener : View.OnDragListener { diff --git a/app/src/main/kotlin/org/fossify/calendar/helpers/WeeklyCalendarImpl.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/WeeklyCalendarImpl.kt index 284e9ce70..4919dbdf7 100644 --- a/app/src/main/kotlin/org/fossify/calendar/helpers/WeeklyCalendarImpl.kt +++ b/app/src/main/kotlin/org/fossify/calendar/helpers/WeeklyCalendarImpl.kt @@ -1,20 +1,147 @@ package org.fossify.calendar.helpers import android.content.Context +import org.fossify.calendar.extensions.config import org.fossify.calendar.extensions.eventsHelper +import org.fossify.calendar.extensions.getFirstDayOfWeekDt +import org.fossify.calendar.extensions.seconds import org.fossify.calendar.interfaces.WeeklyCalendar +import org.fossify.calendar.models.DayWeekly import org.fossify.calendar.models.Event -import org.fossify.commons.helpers.DAY_SECONDS -import org.fossify.commons.helpers.WEEK_SECONDS +import org.fossify.calendar.models.EventWeeklyView +import org.joda.time.DateTime -class WeeklyCalendarImpl(val callback: WeeklyCalendar, val context: Context) { - var mEvents = ArrayList() +class WeeklyCalendarImpl(val callback: WeeklyCalendar, val context: Context, val timeStepMinutes: Int = 1) { - fun updateWeeklyCalendar(weekStartTS: Long) { - val endTS = weekStartTS + 2 * WEEK_SECONDS - context.eventsHelper.getEvents(weekStartTS - DAY_SECONDS, endTS) { - mEvents = it - callback.updateWeeklyCalendar(it) + fun updateWeeklyCalendar(dateWithinWeek: DateTime) { + val weekStart = context.getFirstDayOfWeekDt(dateWithinWeek); + val end = weekStart.plusDays(context.config.weeklyViewDays) + val toTS = end.seconds() + context.eventsHelper.getEvents(weekStart.seconds(), toTS) { events -> + val replaceDescription = context.config.replaceDescription + val sortedEvents = events.filter { it.startTS < toTS }.sortedWith( + compareBy { it.startTS }.thenBy { it.endTS }.thenBy { it.title } + .thenBy { if (replaceDescription) it.location else it.description } + ).toMutableList() as ArrayList + + getDays(weekStart, sortedEvents) } } + + fun getWeek(targetDate: DateTime) { + updateWeeklyCalendar(targetDate) + } + + private fun getDays(weekStart: DateTime, sortedEvents: ArrayList) { + val days = ArrayList(context.config.weeklyViewDays) + for (i in 0 until context.config.weeklyViewDays) { + val day = DayWeekly(weekStart.plusDays(i), ArrayList(), ArrayList()) + days.add(day) + } + + // add events to days + for (event in sortedEvents) { + val eventStart = Formatter.getDateTimeFromTS(event.startTS) + val eventEnd = Formatter.getDateTimeFromTS(event.endTS) + + if (shouldAddEventOnTopBar(event, eventStart, eventEnd)) { + // an event spanning multiple days still only gets added to one day's top bar + val day = days.lastOrNull { it.start <= eventStart } ?: days[0] + day.topBarEvents.add(event) + } else { + // the event gets added to all days it spans + for (day in days) { + val dayEnd = day.start.plusDays(1) + if ((eventStart < dayEnd && eventEnd > day.start) + || (eventStart == day.start && eventEnd == day.start)) { + val startMinute = divRound(eventStart.coerceAtLeast(day.start).minuteOfDay, timeStepMinutes) * timeStepMinutes + val endMinute = divRound(eventEnd.coerceAtMost(dayEnd).minuteOfDay, timeStepMinutes) * timeStepMinutes + day.dayEvents.add( + EventWeeklyView( + event, + startMinute, + endMinute.coerceAtLeast(startMinute + timeStepMinutes), + ) + ) + } + } + } + } + + // make sure that events don't overlap visually even if their timing overlaps + val currentEvents = ArrayList() + for (day in days) { + // prepare sweep-and-prune algorithm + val sapPoints = ArrayList() + for ((i, ews) in day.dayEvents.withIndex()) { + sapPoints.add(SweepAndPrunePoint(ews.startMinute, i, true)) + sapPoints.add(SweepAndPrunePoint(ews.endMinute, i, false)) + } + sapPoints.sortWith( + compareBy { it.minutes }.thenBy { it.isStart } + ) + var startOfCurrentBlock = 0 + var neededSlots = 0 + for ((i, sap) in sapPoints.withIndex()) { + if (sap.isStart) { + if (neededSlots == 0) { + startOfCurrentBlock = i + } + currentEvents.add(sap.eventIndex) + } else { + currentEvents.remove(sap.eventIndex) + if (currentEvents.isEmpty()) { + // no events remain in the current block + // slots can now be distributed + var slot = 0 + val slotUsage = (0 until neededSlots).map { false }.toMutableList(); + for (i in startOfCurrentBlock until i) { + // reuse sweep-and-prune points to assign slots + val sap = sapPoints[i] + if (sap.isStart) { + // find next free slot + while (slotUsage[slot]) { + slot = (slot + 1) % neededSlots + } + // block slot + slotUsage[slot] = true + day.dayEvents[sap.eventIndex].slot = slot + day.dayEvents[sap.eventIndex].slotMax = neededSlots + slot = (slot + 1) % neededSlots + } else { + // free slot + slotUsage[day.dayEvents[sap.eventIndex].slot] = false + } + } + // reset needed slots for the next block + neededSlots = 0 + } + } + // at least as many slots as concurrent events are needed + neededSlots = neededSlots.coerceAtLeast(currentEvents.size) + } + } + + callback.updateWeeklyCalendar(context, days) + } + + private class SweepAndPrunePoint ( + val minutes: Int, + val eventIndex: Int, + val isStart: Boolean, + ) + + /** + * Events are shown in the top bar if + * - they're marked as all-day + * - they don't end on the day they started and the 'showMidnightSpanningEventsAtTop' config flag is set to true + */ + private fun shouldAddEventOnTopBar(event: Event, startDateTime: DateTime, endDateTime: DateTime): Boolean { + val spansMultipleDays = Formatter.getDayCodeFromDateTime(startDateTime) != Formatter.getDayCodeFromDateTime(endDateTime) + return event.getIsAllDay() || (spansMultipleDays && context.config.showMidnightSpanningEventsAtTop) + } +} + +private fun divRound(a: Int, b: Int): Int { + return a / b + if ((a % b) * 2 >= b) 1 else 0 } diff --git a/app/src/main/kotlin/org/fossify/calendar/interfaces/WeeklyCalendar.kt b/app/src/main/kotlin/org/fossify/calendar/interfaces/WeeklyCalendar.kt index 397eea659..c971069a5 100644 --- a/app/src/main/kotlin/org/fossify/calendar/interfaces/WeeklyCalendar.kt +++ b/app/src/main/kotlin/org/fossify/calendar/interfaces/WeeklyCalendar.kt @@ -1,7 +1,8 @@ package org.fossify.calendar.interfaces -import org.fossify.calendar.models.Event +import android.content.Context +import org.fossify.calendar.models.DayWeekly interface WeeklyCalendar { - fun updateWeeklyCalendar(events: ArrayList) + fun updateWeeklyCalendar(context: Context, days: ArrayList) } diff --git a/app/src/main/kotlin/org/fossify/calendar/models/DayWeekly.kt b/app/src/main/kotlin/org/fossify/calendar/models/DayWeekly.kt new file mode 100644 index 000000000..eb6338007 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/calendar/models/DayWeekly.kt @@ -0,0 +1,9 @@ +package org.fossify.calendar.models + +import org.joda.time.DateTime + +data class DayWeekly( + val start: DateTime, + var topBarEvents: ArrayList, + var dayEvents: ArrayList, +) diff --git a/app/src/main/kotlin/org/fossify/calendar/models/EventWeeklyView.kt b/app/src/main/kotlin/org/fossify/calendar/models/EventWeeklyView.kt index 68163e258..2e9120f35 100644 --- a/app/src/main/kotlin/org/fossify/calendar/models/EventWeeklyView.kt +++ b/app/src/main/kotlin/org/fossify/calendar/models/EventWeeklyView.kt @@ -1,10 +1,9 @@ package org.fossify.calendar.models -import android.util.Range - data class EventWeeklyView( - val range: Range, + val event: Event, + val startMinute: Int, + val endMinute: Int, var slot: Int = 0, - var slotMax: Int = 0, - var collisions: ArrayList = ArrayList() + var slotMax: Int = 1, ) From d8eb50d0ad3016a08be0c400b66f8832368c3d46 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 4 Feb 2026 16:56:28 +0100 Subject: [PATCH 2/9] feat: add week widget --- app/src/main/AndroidManifest.xml | 25 + .../WidgetWeeklyConfigureActivity.kt | 177 +++++++ .../fossify/calendar/extensions/Context.kt | 16 + .../helpers/MyWidgetWeeklyProvider.kt | 443 ++++++++++++++++++ .../img_widget_weekly_preview.png | Bin 0 -> 36429 bytes .../main/res/layout/fragment_week_widget.xml | 80 ++++ app/src/main/res/layout/horizontal_1.xml | 7 + app/src/main/res/layout/horizontal_10.xml | 7 + app/src/main/res/layout/horizontal_11.xml | 7 + app/src/main/res/layout/horizontal_12.xml | 7 + app/src/main/res/layout/horizontal_13.xml | 7 + app/src/main/res/layout/horizontal_14.xml | 7 + app/src/main/res/layout/horizontal_2.xml | 7 + app/src/main/res/layout/horizontal_3.xml | 7 + app/src/main/res/layout/horizontal_4.xml | 7 + app/src/main/res/layout/horizontal_5.xml | 7 + app/src/main/res/layout/horizontal_6.xml | 7 + app/src/main/res/layout/horizontal_7.xml | 7 + app/src/main/res/layout/horizontal_8.xml | 7 + app/src/main/res/layout/horizontal_9.xml | 7 + app/src/main/res/layout/horizontal_line.xml | 5 + app/src/main/res/layout/vertical_1.xml | 7 + app/src/main/res/layout/vertical_10.xml | 7 + app/src/main/res/layout/vertical_11.xml | 7 + app/src/main/res/layout/vertical_12.xml | 7 + app/src/main/res/layout/vertical_13.xml | 7 + app/src/main/res/layout/vertical_14.xml | 7 + app/src/main/res/layout/vertical_15.xml | 7 + app/src/main/res/layout/vertical_16.xml | 7 + app/src/main/res/layout/vertical_17.xml | 7 + app/src/main/res/layout/vertical_18.xml | 7 + app/src/main/res/layout/vertical_19.xml | 7 + app/src/main/res/layout/vertical_2.xml | 7 + app/src/main/res/layout/vertical_20.xml | 7 + app/src/main/res/layout/vertical_21.xml | 7 + app/src/main/res/layout/vertical_22.xml | 7 + app/src/main/res/layout/vertical_23.xml | 7 + app/src/main/res/layout/vertical_24.xml | 7 + app/src/main/res/layout/vertical_25.xml | 7 + app/src/main/res/layout/vertical_26.xml | 7 + app/src/main/res/layout/vertical_27.xml | 7 + app/src/main/res/layout/vertical_28.xml | 7 + app/src/main/res/layout/vertical_29.xml | 7 + app/src/main/res/layout/vertical_3.xml | 7 + app/src/main/res/layout/vertical_30.xml | 7 + app/src/main/res/layout/vertical_31.xml | 7 + app/src/main/res/layout/vertical_32.xml | 7 + app/src/main/res/layout/vertical_33.xml | 7 + app/src/main/res/layout/vertical_34.xml | 7 + app/src/main/res/layout/vertical_35.xml | 7 + app/src/main/res/layout/vertical_36.xml | 7 + app/src/main/res/layout/vertical_37.xml | 7 + app/src/main/res/layout/vertical_38.xml | 7 + app/src/main/res/layout/vertical_39.xml | 7 + app/src/main/res/layout/vertical_4.xml | 7 + app/src/main/res/layout/vertical_40.xml | 7 + app/src/main/res/layout/vertical_41.xml | 7 + app/src/main/res/layout/vertical_42.xml | 7 + app/src/main/res/layout/vertical_43.xml | 7 + app/src/main/res/layout/vertical_44.xml | 7 + app/src/main/res/layout/vertical_45.xml | 7 + app/src/main/res/layout/vertical_46.xml | 7 + app/src/main/res/layout/vertical_47.xml | 7 + app/src/main/res/layout/vertical_48.xml | 7 + app/src/main/res/layout/vertical_49.xml | 7 + app/src/main/res/layout/vertical_5.xml | 7 + app/src/main/res/layout/vertical_50.xml | 7 + app/src/main/res/layout/vertical_51.xml | 7 + app/src/main/res/layout/vertical_52.xml | 7 + app/src/main/res/layout/vertical_53.xml | 7 + app/src/main/res/layout/vertical_54.xml | 7 + app/src/main/res/layout/vertical_55.xml | 7 + app/src/main/res/layout/vertical_56.xml | 7 + app/src/main/res/layout/vertical_57.xml | 7 + app/src/main/res/layout/vertical_58.xml | 7 + app/src/main/res/layout/vertical_59.xml | 7 + app/src/main/res/layout/vertical_6.xml | 7 + app/src/main/res/layout/vertical_60.xml | 7 + app/src/main/res/layout/vertical_61.xml | 7 + app/src/main/res/layout/vertical_62.xml | 7 + app/src/main/res/layout/vertical_63.xml | 7 + app/src/main/res/layout/vertical_64.xml | 7 + app/src/main/res/layout/vertical_65.xml | 7 + app/src/main/res/layout/vertical_66.xml | 7 + app/src/main/res/layout/vertical_67.xml | 7 + app/src/main/res/layout/vertical_68.xml | 7 + app/src/main/res/layout/vertical_69.xml | 7 + app/src/main/res/layout/vertical_7.xml | 7 + app/src/main/res/layout/vertical_70.xml | 7 + app/src/main/res/layout/vertical_71.xml | 7 + app/src/main/res/layout/vertical_72.xml | 7 + app/src/main/res/layout/vertical_73.xml | 7 + app/src/main/res/layout/vertical_74.xml | 7 + app/src/main/res/layout/vertical_75.xml | 7 + app/src/main/res/layout/vertical_76.xml | 7 + app/src/main/res/layout/vertical_77.xml | 7 + app/src/main/res/layout/vertical_78.xml | 7 + app/src/main/res/layout/vertical_79.xml | 7 + app/src/main/res/layout/vertical_8.xml | 7 + app/src/main/res/layout/vertical_80.xml | 7 + app/src/main/res/layout/vertical_81.xml | 7 + app/src/main/res/layout/vertical_82.xml | 7 + app/src/main/res/layout/vertical_83.xml | 7 + app/src/main/res/layout/vertical_84.xml | 7 + app/src/main/res/layout/vertical_85.xml | 7 + app/src/main/res/layout/vertical_86.xml | 7 + app/src/main/res/layout/vertical_87.xml | 7 + app/src/main/res/layout/vertical_88.xml | 7 + app/src/main/res/layout/vertical_89.xml | 7 + app/src/main/res/layout/vertical_9.xml | 7 + app/src/main/res/layout/vertical_90.xml | 7 + app/src/main/res/layout/vertical_91.xml | 7 + app/src/main/res/layout/vertical_92.xml | 7 + app/src/main/res/layout/vertical_93.xml | 7 + app/src/main/res/layout/vertical_94.xml | 7 + app/src/main/res/layout/vertical_95.xml | 7 + app/src/main/res/layout/vertical_96.xml | 7 + app/src/main/res/layout/vertical_line.xml | 5 + .../main/res/layout/widget_config_weekly.xml | 66 +++ .../res/layout/widget_week_all_day_row.xml | 7 + .../main/res/layout/widget_week_column.xml | 8 + .../res/layout/widget_week_day_letter.xml | 10 + .../res/layout/widget_week_event_marker.xml | 44 ++ app/src/main/res/layout/widget_week_hour.xml | 10 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/widget_weekly_info.xml | 13 + generate_weighted_layouts.py | 29 ++ 127 files changed, 1709 insertions(+) create mode 100644 app/src/main/kotlin/org/fossify/calendar/activities/WidgetWeeklyConfigureActivity.kt create mode 100644 app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetWeeklyProvider.kt create mode 100644 app/src/main/res/drawable-nodpi/img_widget_weekly_preview.png create mode 100644 app/src/main/res/layout/fragment_week_widget.xml create mode 100644 app/src/main/res/layout/horizontal_1.xml create mode 100644 app/src/main/res/layout/horizontal_10.xml create mode 100644 app/src/main/res/layout/horizontal_11.xml create mode 100644 app/src/main/res/layout/horizontal_12.xml create mode 100644 app/src/main/res/layout/horizontal_13.xml create mode 100644 app/src/main/res/layout/horizontal_14.xml create mode 100644 app/src/main/res/layout/horizontal_2.xml create mode 100644 app/src/main/res/layout/horizontal_3.xml create mode 100644 app/src/main/res/layout/horizontal_4.xml create mode 100644 app/src/main/res/layout/horizontal_5.xml create mode 100644 app/src/main/res/layout/horizontal_6.xml create mode 100644 app/src/main/res/layout/horizontal_7.xml create mode 100644 app/src/main/res/layout/horizontal_8.xml create mode 100644 app/src/main/res/layout/horizontal_9.xml create mode 100644 app/src/main/res/layout/horizontal_line.xml create mode 100644 app/src/main/res/layout/vertical_1.xml create mode 100644 app/src/main/res/layout/vertical_10.xml create mode 100644 app/src/main/res/layout/vertical_11.xml create mode 100644 app/src/main/res/layout/vertical_12.xml create mode 100644 app/src/main/res/layout/vertical_13.xml create mode 100644 app/src/main/res/layout/vertical_14.xml create mode 100644 app/src/main/res/layout/vertical_15.xml create mode 100644 app/src/main/res/layout/vertical_16.xml create mode 100644 app/src/main/res/layout/vertical_17.xml create mode 100644 app/src/main/res/layout/vertical_18.xml create mode 100644 app/src/main/res/layout/vertical_19.xml create mode 100644 app/src/main/res/layout/vertical_2.xml create mode 100644 app/src/main/res/layout/vertical_20.xml create mode 100644 app/src/main/res/layout/vertical_21.xml create mode 100644 app/src/main/res/layout/vertical_22.xml create mode 100644 app/src/main/res/layout/vertical_23.xml create mode 100644 app/src/main/res/layout/vertical_24.xml create mode 100644 app/src/main/res/layout/vertical_25.xml create mode 100644 app/src/main/res/layout/vertical_26.xml create mode 100644 app/src/main/res/layout/vertical_27.xml create mode 100644 app/src/main/res/layout/vertical_28.xml create mode 100644 app/src/main/res/layout/vertical_29.xml create mode 100644 app/src/main/res/layout/vertical_3.xml create mode 100644 app/src/main/res/layout/vertical_30.xml create mode 100644 app/src/main/res/layout/vertical_31.xml create mode 100644 app/src/main/res/layout/vertical_32.xml create mode 100644 app/src/main/res/layout/vertical_33.xml create mode 100644 app/src/main/res/layout/vertical_34.xml create mode 100644 app/src/main/res/layout/vertical_35.xml create mode 100644 app/src/main/res/layout/vertical_36.xml create mode 100644 app/src/main/res/layout/vertical_37.xml create mode 100644 app/src/main/res/layout/vertical_38.xml create mode 100644 app/src/main/res/layout/vertical_39.xml create mode 100644 app/src/main/res/layout/vertical_4.xml create mode 100644 app/src/main/res/layout/vertical_40.xml create mode 100644 app/src/main/res/layout/vertical_41.xml create mode 100644 app/src/main/res/layout/vertical_42.xml create mode 100644 app/src/main/res/layout/vertical_43.xml create mode 100644 app/src/main/res/layout/vertical_44.xml create mode 100644 app/src/main/res/layout/vertical_45.xml create mode 100644 app/src/main/res/layout/vertical_46.xml create mode 100644 app/src/main/res/layout/vertical_47.xml create mode 100644 app/src/main/res/layout/vertical_48.xml create mode 100644 app/src/main/res/layout/vertical_49.xml create mode 100644 app/src/main/res/layout/vertical_5.xml create mode 100644 app/src/main/res/layout/vertical_50.xml create mode 100644 app/src/main/res/layout/vertical_51.xml create mode 100644 app/src/main/res/layout/vertical_52.xml create mode 100644 app/src/main/res/layout/vertical_53.xml create mode 100644 app/src/main/res/layout/vertical_54.xml create mode 100644 app/src/main/res/layout/vertical_55.xml create mode 100644 app/src/main/res/layout/vertical_56.xml create mode 100644 app/src/main/res/layout/vertical_57.xml create mode 100644 app/src/main/res/layout/vertical_58.xml create mode 100644 app/src/main/res/layout/vertical_59.xml create mode 100644 app/src/main/res/layout/vertical_6.xml create mode 100644 app/src/main/res/layout/vertical_60.xml create mode 100644 app/src/main/res/layout/vertical_61.xml create mode 100644 app/src/main/res/layout/vertical_62.xml create mode 100644 app/src/main/res/layout/vertical_63.xml create mode 100644 app/src/main/res/layout/vertical_64.xml create mode 100644 app/src/main/res/layout/vertical_65.xml create mode 100644 app/src/main/res/layout/vertical_66.xml create mode 100644 app/src/main/res/layout/vertical_67.xml create mode 100644 app/src/main/res/layout/vertical_68.xml create mode 100644 app/src/main/res/layout/vertical_69.xml create mode 100644 app/src/main/res/layout/vertical_7.xml create mode 100644 app/src/main/res/layout/vertical_70.xml create mode 100644 app/src/main/res/layout/vertical_71.xml create mode 100644 app/src/main/res/layout/vertical_72.xml create mode 100644 app/src/main/res/layout/vertical_73.xml create mode 100644 app/src/main/res/layout/vertical_74.xml create mode 100644 app/src/main/res/layout/vertical_75.xml create mode 100644 app/src/main/res/layout/vertical_76.xml create mode 100644 app/src/main/res/layout/vertical_77.xml create mode 100644 app/src/main/res/layout/vertical_78.xml create mode 100644 app/src/main/res/layout/vertical_79.xml create mode 100644 app/src/main/res/layout/vertical_8.xml create mode 100644 app/src/main/res/layout/vertical_80.xml create mode 100644 app/src/main/res/layout/vertical_81.xml create mode 100644 app/src/main/res/layout/vertical_82.xml create mode 100644 app/src/main/res/layout/vertical_83.xml create mode 100644 app/src/main/res/layout/vertical_84.xml create mode 100644 app/src/main/res/layout/vertical_85.xml create mode 100644 app/src/main/res/layout/vertical_86.xml create mode 100644 app/src/main/res/layout/vertical_87.xml create mode 100644 app/src/main/res/layout/vertical_88.xml create mode 100644 app/src/main/res/layout/vertical_89.xml create mode 100644 app/src/main/res/layout/vertical_9.xml create mode 100644 app/src/main/res/layout/vertical_90.xml create mode 100644 app/src/main/res/layout/vertical_91.xml create mode 100644 app/src/main/res/layout/vertical_92.xml create mode 100644 app/src/main/res/layout/vertical_93.xml create mode 100644 app/src/main/res/layout/vertical_94.xml create mode 100644 app/src/main/res/layout/vertical_95.xml create mode 100644 app/src/main/res/layout/vertical_96.xml create mode 100644 app/src/main/res/layout/vertical_line.xml create mode 100644 app/src/main/res/layout/widget_config_weekly.xml create mode 100644 app/src/main/res/layout/widget_week_all_day_row.xml create mode 100644 app/src/main/res/layout/widget_week_column.xml create mode 100644 app/src/main/res/layout/widget_week_day_letter.xml create mode 100644 app/src/main/res/layout/widget_week_event_marker.xml create mode 100644 app/src/main/res/layout/widget_week_hour.xml create mode 100644 app/src/main/res/xml/widget_weekly_info.xml create mode 100644 generate_weighted_layouts.py diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7c6cbf715..16b4a2c9a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -94,6 +94,17 @@ + + + + + + + + + + + + + + + ? = null + + private var mBgAlpha = 0f + private var mWidgetId = 0 + private var mBgColorWithoutTransparency = 0 + private var mBgColor = 0 + private var mTextColor = 0 + + private val binding by viewBinding(WidgetConfigWeeklyBinding::inflate) + + public override fun onCreate(savedInstanceState: Bundle?) { + useDynamicTheme = false + super.onCreate(savedInstanceState) + setResult(RESULT_CANCELED) + setContentView(binding.root) + setupEdgeToEdge(padTopSystem = listOf(binding.configHolder), padBottomSystem = listOf(binding.root)) + initVariables() + + val isCustomizingColors = intent.extras?.getBoolean(IS_CUSTOMIZING_COLORS) ?: false + mWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID + + if (mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID && !isCustomizingColors) { + finish() + } + + val primaryColor = getProperPrimaryColor() + binding.apply { + configSave.setOnClickListener { saveConfig() } + configBgColor.setOnClickListener { pickBackgroundColor() } + configTextColor.setOnClickListener { pickTextColor() } + configBgSeekbar.setColors(mTextColor, primaryColor, primaryColor) + } + } + + private fun initVariables() { + mBgColor = config.widgetBgColor + mBgAlpha = Color.alpha(mBgColor) / 255f + + mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor)) + binding.configBgSeekbar.apply { + progress = (mBgAlpha * 100).toInt() + + onSeekBarChangeListener { progress -> + mBgAlpha = progress / 100f + updateBackgroundColor() + } + } + updateBackgroundColor() + + mTextColor = config.widgetTextColor + if (mTextColor == resources.getColor(R.color.default_widget_text_color) && isDynamicTheme()) { + mTextColor = resources.getColor(R.color.you_primary_color, theme) + } + + updateTextColor() + + WeeklyCalendarImpl(this, this).updateWeeklyCalendar(DateTime()) + } + + private fun saveConfig() { + storeWidgetColors() + requestWidgetUpdate() + + Intent().apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId) + setResult(RESULT_OK, this) + } + finish() + } + + private fun storeWidgetColors() { + config.apply { + widgetBgColor = mBgColor + widgetTextColor = mTextColor + } + } + + private fun pickBackgroundColor() { + ColorPickerDialog(this, mBgColorWithoutTransparency) { wasPositivePressed, color -> + if (wasPositivePressed) { + mBgColorWithoutTransparency = color + updateBackgroundColor() + } + } + } + + private fun pickTextColor() { + ColorPickerDialog(this, mTextColor) { wasPositivePressed, color -> + if (wasPositivePressed) { + mTextColor = color + updateTextColor() + updateDays() + } + } + } + + private fun requestWidgetUpdate() { + Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidgetWeeklyProvider::class.java).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(mWidgetId)) + sendBroadcast(this) + } + } + + private fun updateTextColor() { + binding.configTextColor.setFillWithStroke(mTextColor, mTextColor) + binding.configSave.setTextColor(getProperPrimaryColor().getContrastColor()) + /*val weekendsTextColor = config.highlightWeekendsColor + for ((i, view) in binding.configCalendar.weekLettersHolder.children.withIndex()) { + if (view is TextView) { + val textColor = if (config.highlightWeekends && isWeekend(mDays!![i].start.dayOfWeek)) { + weekendsTextColor + } else { + mTextColor + } + view.setTextColor(textColor) + } + } + for (view in binding.configCalendar.timeColumn.children) { + if (view is TextView) { + view.setTextColor(mTextColor) + } + }*/ + } + + private fun updateBackgroundColor() { + mBgColor = mBgColorWithoutTransparency.adjustAlpha(mBgAlpha) + binding.configCalendar.widgetWeekBackground.applyColorFilter(mBgColor) + binding.configBgColor.setFillWithStroke(mBgColor, mBgColor) + binding.configSave.backgroundTintList = ColorStateList.valueOf(getProperPrimaryColor()) + } + + private fun updateDays() { + // TODO + } + + override fun updateWeeklyCalendar(context: Context, days: ArrayList) { + runOnUiThread { + mDays = days + updateDays() + } + } + +} diff --git a/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt b/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt index f9dd6af1e..2ac20ae41 100644 --- a/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt +++ b/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt @@ -57,6 +57,7 @@ import org.fossify.calendar.helpers.MONTH import org.fossify.calendar.helpers.MyWidgetDateProvider import org.fossify.calendar.helpers.MyWidgetListProvider import org.fossify.calendar.helpers.MyWidgetMonthlyProvider +import org.fossify.calendar.helpers.MyWidgetWeeklyProvider import org.fossify.calendar.helpers.NEW_EVENT_START_TS import org.fossify.calendar.helpers.REMINDER_NOTIFICATION import org.fossify.calendar.helpers.REMINDER_OFF @@ -153,10 +154,25 @@ fun Context.updateWidgets() { } } + updateWeeklyWidget() updateListWidget() updateDateWidget() } +private fun Context.updateWeeklyWidget() { + val widgetIDs = AppWidgetManager.getInstance(applicationContext) + ?.getAppWidgetIds(ComponentName(applicationContext, MyWidgetWeeklyProvider::class.java)) + ?: return + + if (widgetIDs.isNotEmpty()) { + Intent(applicationContext, MyWidgetWeeklyProvider::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs) + sendBroadcast(this) + } + } +} + fun Context.updateListWidget() { val widgetIDs = AppWidgetManager.getInstance(applicationContext) ?.getAppWidgetIds(ComponentName(applicationContext, MyWidgetListProvider::class.java)) diff --git a/app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetWeeklyProvider.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetWeeklyProvider.kt new file mode 100644 index 000000000..c4e53c1b7 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetWeeklyProvider.kt @@ -0,0 +1,443 @@ +package org.fossify.calendar.helpers + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.graphics.Paint +import android.widget.RemoteViews +import org.fossify.calendar.R +import org.fossify.calendar.activities.SplashActivity +import org.fossify.calendar.extensions.* +import org.fossify.calendar.extensions.config +import org.fossify.calendar.interfaces.WeeklyCalendar +import org.fossify.calendar.models.DayWeekly +import org.fossify.calendar.models.Event +import org.fossify.commons.extensions.* +import org.fossify.commons.helpers.HIGHER_ALPHA +import org.fossify.commons.helpers.MEDIUM_ALPHA +import org.joda.time.DateTime + +class MyWidgetWeeklyProvider : AppWidgetProvider() { + + private var dayColumns = ArrayList() + private var mDays = ArrayList() + set(value) { + field = value + // don't show hours in which no events occur, so existing events aren't as squished + earliestEventStartHour = 24 + latestEventEndHour = 0 + for (day in value) { + val startHour = day.dayEvents.minOfOrNull { it.startMinute / 60 } + earliestEventStartHour = earliestEventStartHour.coerceAtMost(startHour ?: earliestEventStartHour) + val endHour = day.dayEvents.maxOfOrNull { it.endMinute / 60 + if (it.endMinute % 60 > 0) 1 else 0 } + latestEventEndHour = latestEventEndHour.coerceAtLeast(endHour ?: latestEventEndHour) + } + if (earliestEventStartHour > latestEventEndHour) { + // looks like there are no events during this week, show default range + earliestEventStartHour = 6 + latestEventEndHour = 18 + } else if (latestEventEndHour - earliestEventStartHour < 6 ) { + // make sure that more than one hour is shown + val hoursToAdd = 6 - latestEventEndHour + earliestEventStartHour + earliestEventStartHour = (earliestEventStartHour - hoursToAdd / 2).coerceAtLeast(0) + latestEventEndHour = (latestEventEndHour + hoursToAdd - (hoursToAdd / 2)).coerceAtMost(24) + } + } + private var earliestEventStartHour = 0 + private var latestEventEndHour = 24 + + companion object { + private val vertical_spaces = arrayOf( + R.layout.vertical_1, + R.layout.vertical_2, + R.layout.vertical_3, + R.layout.vertical_4, + R.layout.vertical_5, + R.layout.vertical_6, + R.layout.vertical_7, + R.layout.vertical_8, + R.layout.vertical_9, + R.layout.vertical_10, + R.layout.vertical_11, + R.layout.vertical_12, + R.layout.vertical_13, + R.layout.vertical_14, + R.layout.vertical_15, + R.layout.vertical_16, + R.layout.vertical_17, + R.layout.vertical_18, + R.layout.vertical_19, + R.layout.vertical_20, + R.layout.vertical_21, + R.layout.vertical_22, + R.layout.vertical_23, + R.layout.vertical_24, + R.layout.vertical_25, + R.layout.vertical_26, + R.layout.vertical_27, + R.layout.vertical_28, + R.layout.vertical_29, + R.layout.vertical_30, + R.layout.vertical_31, + R.layout.vertical_32, + R.layout.vertical_33, + R.layout.vertical_34, + R.layout.vertical_35, + R.layout.vertical_36, + R.layout.vertical_37, + R.layout.vertical_38, + R.layout.vertical_39, + R.layout.vertical_40, + R.layout.vertical_41, + R.layout.vertical_42, + R.layout.vertical_43, + R.layout.vertical_44, + R.layout.vertical_45, + R.layout.vertical_46, + R.layout.vertical_47, + R.layout.vertical_48, + R.layout.vertical_49, + R.layout.vertical_50, + R.layout.vertical_51, + R.layout.vertical_52, + R.layout.vertical_53, + R.layout.vertical_54, + R.layout.vertical_55, + R.layout.vertical_56, + R.layout.vertical_57, + R.layout.vertical_58, + R.layout.vertical_59, + R.layout.vertical_60, + R.layout.vertical_61, + R.layout.vertical_62, + R.layout.vertical_63, + R.layout.vertical_64, + R.layout.vertical_65, + R.layout.vertical_66, + R.layout.vertical_67, + R.layout.vertical_68, + R.layout.vertical_69, + R.layout.vertical_70, + R.layout.vertical_71, + R.layout.vertical_72, + R.layout.vertical_73, + R.layout.vertical_74, + R.layout.vertical_75, + R.layout.vertical_76, + R.layout.vertical_77, + R.layout.vertical_78, + R.layout.vertical_79, + R.layout.vertical_80, + R.layout.vertical_81, + R.layout.vertical_82, + R.layout.vertical_83, + R.layout.vertical_84, + R.layout.vertical_85, + R.layout.vertical_86, + R.layout.vertical_87, + R.layout.vertical_88, + R.layout.vertical_89, + R.layout.vertical_90, + R.layout.vertical_91, + R.layout.vertical_92, + R.layout.vertical_93, + R.layout.vertical_94, + R.layout.vertical_95, + R.layout.vertical_96, + ) + private val horizontal_spaces = arrayOf( + R.layout.horizontal_1, + R.layout.horizontal_2, + R.layout.horizontal_3, + R.layout.horizontal_4, + R.layout.horizontal_5, + R.layout.horizontal_6, + R.layout.horizontal_7, + R.layout.horizontal_8, + R.layout.horizontal_9, + R.layout.horizontal_10, + R.layout.horizontal_11, + R.layout.horizontal_12, + R.layout.horizontal_13, + R.layout.horizontal_14, + ) + // 15 minute chunks + private val timeStepMinutes = 24 * 60 / vertical_spaces.size + } + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + performUpdate(context) + } + + private fun performUpdate(context: Context) { + WeeklyCalendarImpl(weeklyCalendar, context, timeStepMinutes).getWeek(DateTime.now()) + } + + private fun getComponentName(context: Context) = ComponentName(context, MyWidgetWeeklyProvider::class.java) + + private fun setupDayColumns(context: Context, views: RemoteViews) { + val textColor = context.config.widgetTextColor + dayColumns.clear() + views.removeAllViews(R.id.week_events_columns_holder) + views.removeAllViews(R.id.time_column) + views.removeAllViews(R.id.week_events_hour_lines) + views.removeAllViews(R.id.week_events_day_lines) + val packageName = context.packageName + // columns that will contain events + (0 until context.config.weeklyViewDays).forEach { + val column = RemoteViews(packageName, R.layout.widget_week_column) + dayColumns.add(column) + } + // column on the left showing the time + views.addView(R.id.time_column, RemoteViews(packageName, R.layout.vertical_1)) + views.addView(R.id.week_events_hour_lines, RemoteViews(packageName, R.layout.vertical_1)) + for (i in earliestEventStartHour + 1 until latestEventEndHour) { + val hour = RemoteViews(packageName, R.layout.widget_week_hour) + val time = DateTime().withHourOfDay(i) + hour.setText(R.id.hour_textview, time.toString(Formatter.getHourPattern(context))) + hour.setTextColor(R.id.hour_textview, textColor) + views.addView(R.id.time_column, hour) + views.addView(R.id.week_events_hour_lines, RemoteViews(packageName, R.layout.horizontal_line)) + views.addView(R.id.week_events_hour_lines, RemoteViews(packageName, R.layout.vertical_1)) + } + views.addView(R.id.time_column, RemoteViews(packageName, R.layout.vertical_1)) + } + + private fun updateDays(context: Context, views: RemoteViews, days: ArrayList) { + mDays = days + views.removeAllViews(R.id.week_all_day_holder) + + val config = context.config + val packageName = context.packageName + val allDayEventRows = ArrayList() + val allDayEventNextStart = ArrayList() + + setupDayColumns(context, views) + + // add events to the view + for ((dayOfWeek, day) in days.withIndex()) { + for (event in day.topBarEvents) { + addAllDayEvent(context, packageName, dayOfWeek, event, allDayEventRows, allDayEventNextStart) + } + val dayColumn = dayColumns[dayOfWeek] + var subColumnStartMinute = earliestEventStartHour * 60 + val subColumns = ArrayList() + val subColumnLastMinutes = ArrayList() + subColumns.add(dayColumn) + subColumnLastMinutes.add(subColumnStartMinute) + for (ews in day.dayEvents) { + if (ews.slotMax != subColumns.size) { + // the number of subColumns has to be changed + fillEmptyEventColumns(packageName, dayColumn, ews.startMinute, subColumns, subColumnStartMinute, subColumnLastMinutes) + subColumns.clear() + subColumnLastMinutes.clear() + subColumnStartMinute = ews.startMinute + if (ews.slotMax == 1) { + // it would be pointless to add a row containing only one subColumn + // so instead pretend as if dayColumn was a subColumn + subColumns.add(dayColumn) + } else { + // subColumns will be shown side-by-side for events with overlapping timing + (0 until ews.slotMax).forEach { _ -> + val column = RemoteViews(packageName, R.layout.widget_week_column) + subColumns.add(column) + } + } + (0 until ews.slotMax).forEach { _ -> + subColumnLastMinutes.add(ews.startMinute) + } + } + fillEmptySpace(packageName, subColumns[ews.slot], ews.startMinute - subColumnLastMinutes[ews.slot]) + subColumnLastMinutes[ews.slot] = ews.endMinute + val eventView = eventView(context, packageName, ews.event) + val height = (ews.endMinute - ews.startMinute) / timeStepMinutes + val container = RemoteViews(packageName, vertical_spaces[height - 1]) + container.addView(R.id.space_vertical, eventView) + subColumns[ews.slot].addView(R.id.week_column, container) + } + fillEmptyEventColumns(packageName, dayColumn, latestEventEndHour * 60, subColumns, subColumnStartMinute, subColumnLastMinutes) + // add vertical grid line + views.addView(R.id.week_events_day_lines, RemoteViews(packageName, R.layout.vertical_line)) + views.addView(R.id.week_events_day_lines, RemoteViews(packageName, R.layout.horizontal_1)) + views.addView(R.id.week_events_columns_holder, dayColumn) + } + // add rows containing all-day events to the view + for ((i, row) in allDayEventRows.withIndex()) { + if (allDayEventNextStart[i] < config.weeklyViewDays) { + val space = RemoteViews(packageName, horizontal_spaces[config.weeklyViewDays - allDayEventNextStart[i] - 1]) + allDayEventRows[i].addView(R.id.week_all_day_row, space) + } + views.addView(R.id.week_all_day_holder, row) + } + } + + /** + * create a RemoteViews displaying the given event + * for all-day events in the top-bar and normal events in the dayColumns + */ + private fun eventView(context: Context, packageName: String, event: Event): RemoteViews { + val config = context.config + val eventView = RemoteViews(packageName, R.layout.widget_week_event_marker) + var backgroundColor = event.color + var textColor = backgroundColor.getContrastColor() + + val adjustAlpha = if (event.isTask()) { + config.dimCompletedTasks && event.isTaskCompleted() + } else { + config.dimPastEvents && event.isPastEvent + } + + if (adjustAlpha) { + backgroundColor = backgroundColor.adjustAlpha(MEDIUM_ALPHA) + textColor = textColor.adjustAlpha(HIGHER_ALPHA) + } + + eventView.setInt(R.id.week_event_background, "setColorFilter", backgroundColor) + + eventView.setVisibleIf(R.id.week_event_task_image, event.isTask()) + if (event.isTask()) { + eventView.applyColorFilter(R.id.week_event_task_image, textColor) + } + + // week_event_label + eventView.setTextColor(R.id.week_event_label, textColor) + val maxLines = if (event.isTask() || event.startTS == event.endTS) { + 1 + } else { + 3 + } + eventView.setInt(R.id.week_event_label, "setMaxLines", maxLines) + eventView.setText(R.id.week_event_label, event.title) + if (event.shouldStrikeThrough()) { + eventView.setInt(R.id.week_event_label, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG) + } + eventView.setContentDescription(R.id.week_event_label, event.title) + + val intent = context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java) + intent.putExtra(EVENT_ID, event.id) + intent.putExtra(EVENT_OCCURRENCE_TS, event.startTS) + val pendingIntent = PendingIntent.getActivity( + context, + event.id!!.toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + eventView.setOnClickPendingIntent(R.id.week_event_holder, pendingIntent) + return eventView + } + + private fun addAllDayEvent( + context: Context, + packageName: String, + dayOfWeek: Int, + event: Event, + allDayEventRows: ArrayList, + allDayEventNextStart: ArrayList, + ) { + val endDateTime = Formatter.getDateTimeFromTS(event.endTS) + var eventEndsOnDay = mDays.indexOfLast { endDateTime > it.start } + if (eventEndsOnDay < 0) { + eventEndsOnDay = mDays.size - 1 + } + var rowIndex = allDayEventNextStart.indexOfFirst { it <= dayOfWeek } + if (rowIndex < 0) { + rowIndex = allDayEventRows.size + val row = RemoteViews(packageName, R.layout.widget_week_all_day_row) + allDayEventRows.add(row) + allDayEventNextStart.add(0) + } + if (allDayEventNextStart[rowIndex] < dayOfWeek) { + val space = RemoteViews(packageName, horizontal_spaces[dayOfWeek - allDayEventNextStart[rowIndex] - 1]) + allDayEventRows[rowIndex].addView(R.id.week_all_day_row, space) + } + allDayEventNextStart[rowIndex] = eventEndsOnDay + 1 + val container = RemoteViews(packageName, horizontal_spaces[eventEndsOnDay - dayOfWeek]) + val eventView = eventView(context, packageName, event) + container.addView(R.id.space_horizontal, eventView) + allDayEventRows[rowIndex].addView(R.id.week_all_day_row, container) + } + + private val weeklyCalendar = object : WeeklyCalendar { + override fun updateWeeklyCalendar(context: Context, days: ArrayList) { + val textColor = context.config.widgetTextColor + val resources = context.resources + + val appWidgetManager = AppWidgetManager.getInstance(context) ?: return + appWidgetManager.getAppWidgetIds(getComponentName(context)).forEach { + val views = RemoteViews(context.packageName, R.layout.fragment_week_widget) + + views.applyColorFilter(R.id.widget_week_background, context.config.widgetBgColor) + + updateDayLabels(context, views, resources, textColor) + updateDays(context, views, days) + + try { + appWidgetManager.updateAppWidget(it, views) + } catch (ignored: RuntimeException) { + } + } + } + } + + private fun updateDayLabels(context: Context, views: RemoteViews, resources: Resources, textColor: Int) { + val config = context.config + val smallerFontSize = context.getWidgetFontSize() + val packageName = context.packageName + var curDay = context.getFirstDayOfWeekDt(DateTime()) + val dayLetters = resources.getStringArray(org.fossify.commons.R.array.week_days_short) + .toMutableList() as ArrayList + + views.removeAllViews(R.id.week_letters_holder) + (0 until config.weeklyViewDays).forEach { _ -> + val dayLetter = dayLetters[curDay.dayOfWeek - 1] + + val newRemoteView = RemoteViews(packageName, R.layout.widget_week_day_letter).apply { + setText(R.id.week_day_label, dayLetter) + setTextColor(R.id.week_day_label, textColor) + setTextSize(R.id.week_day_label, smallerFontSize) + } + val dayCode = Formatter.getDayCodeFromDateTime(curDay) + (context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)).apply { + putExtra(DAY_CODE, dayCode) + putExtra(VIEW_TO_OPEN, DAILY_VIEW) + val pendingIntent = PendingIntent.getActivity(context, Integer.parseInt(dayCode), this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + newRemoteView.setOnClickPendingIntent(R.id.week_day_label, pendingIntent) + } + views.addView(R.id.week_letters_holder, newRemoteView) + curDay = curDay.plusDays(1) + } + } + + private fun fillEmptySpace(packageName: String, column: RemoteViews, minutes: Int) { + val height = minutes / timeStepMinutes + if (height <= 0) + return + val space = RemoteViews(packageName, vertical_spaces[height - 1]) + column.addView(R.id.week_column, space) + } + + private fun fillEmptyEventColumns( + packageName: String, + dayColumn: RemoteViews, + fillToMinute: Int, + subColumns: ArrayList, + subColumnStartMinute: Int, + subColumnLastMinutes: ArrayList, + ) { + val lastMinute = subColumnLastMinutes.max() + if (subColumns.size > 1) { + val height = (lastMinute - subColumnStartMinute) / timeStepMinutes + val subColumnRow = RemoteViews(packageName, vertical_spaces[height - 1]) + for ((i, column) in subColumns.withIndex()) { + fillEmptySpace(packageName, column, lastMinute - subColumnLastMinutes[i]) + subColumnRow.addView(R.id.space_vertical, column) + } + dayColumn.addView(R.id.week_column, subColumnRow) + } + fillEmptySpace(packageName, dayColumn, fillToMinute - lastMinute) + } +} diff --git a/app/src/main/res/drawable-nodpi/img_widget_weekly_preview.png b/app/src/main/res/drawable-nodpi/img_widget_weekly_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..f93837426bb040ad0b3bb6db0ea9483e05277a89 GIT binary patch literal 36429 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 AOaK4? literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_week_widget.xml b/app/src/main/res/layout/fragment_week_widget.xml new file mode 100644 index 000000000..ce7880adc --- /dev/null +++ b/app/src/main/res/layout/fragment_week_widget.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/horizontal_1.xml b/app/src/main/res/layout/horizontal_1.xml new file mode 100644 index 000000000..e46b4f0f0 --- /dev/null +++ b/app/src/main/res/layout/horizontal_1.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_10.xml b/app/src/main/res/layout/horizontal_10.xml new file mode 100644 index 000000000..bdca00171 --- /dev/null +++ b/app/src/main/res/layout/horizontal_10.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_11.xml b/app/src/main/res/layout/horizontal_11.xml new file mode 100644 index 000000000..dac66e45e --- /dev/null +++ b/app/src/main/res/layout/horizontal_11.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_12.xml b/app/src/main/res/layout/horizontal_12.xml new file mode 100644 index 000000000..587240ff1 --- /dev/null +++ b/app/src/main/res/layout/horizontal_12.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_13.xml b/app/src/main/res/layout/horizontal_13.xml new file mode 100644 index 000000000..4b85ae038 --- /dev/null +++ b/app/src/main/res/layout/horizontal_13.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_14.xml b/app/src/main/res/layout/horizontal_14.xml new file mode 100644 index 000000000..da320c52a --- /dev/null +++ b/app/src/main/res/layout/horizontal_14.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_2.xml b/app/src/main/res/layout/horizontal_2.xml new file mode 100644 index 000000000..75a35c8f7 --- /dev/null +++ b/app/src/main/res/layout/horizontal_2.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_3.xml b/app/src/main/res/layout/horizontal_3.xml new file mode 100644 index 000000000..5ce4b4d8f --- /dev/null +++ b/app/src/main/res/layout/horizontal_3.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_4.xml b/app/src/main/res/layout/horizontal_4.xml new file mode 100644 index 000000000..99b8c5faa --- /dev/null +++ b/app/src/main/res/layout/horizontal_4.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_5.xml b/app/src/main/res/layout/horizontal_5.xml new file mode 100644 index 000000000..31aa8b738 --- /dev/null +++ b/app/src/main/res/layout/horizontal_5.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_6.xml b/app/src/main/res/layout/horizontal_6.xml new file mode 100644 index 000000000..d37033e0c --- /dev/null +++ b/app/src/main/res/layout/horizontal_6.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_7.xml b/app/src/main/res/layout/horizontal_7.xml new file mode 100644 index 000000000..0e3886a58 --- /dev/null +++ b/app/src/main/res/layout/horizontal_7.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_8.xml b/app/src/main/res/layout/horizontal_8.xml new file mode 100644 index 000000000..75536db73 --- /dev/null +++ b/app/src/main/res/layout/horizontal_8.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_9.xml b/app/src/main/res/layout/horizontal_9.xml new file mode 100644 index 000000000..e07f0022d --- /dev/null +++ b/app/src/main/res/layout/horizontal_9.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/horizontal_line.xml b/app/src/main/res/layout/horizontal_line.xml new file mode 100644 index 000000000..9fa590f7b --- /dev/null +++ b/app/src/main/res/layout/horizontal_line.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/layout/vertical_1.xml b/app/src/main/res/layout/vertical_1.xml new file mode 100644 index 000000000..39fbadd8f --- /dev/null +++ b/app/src/main/res/layout/vertical_1.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_10.xml b/app/src/main/res/layout/vertical_10.xml new file mode 100644 index 000000000..140d05810 --- /dev/null +++ b/app/src/main/res/layout/vertical_10.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_11.xml b/app/src/main/res/layout/vertical_11.xml new file mode 100644 index 000000000..e2776c178 --- /dev/null +++ b/app/src/main/res/layout/vertical_11.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_12.xml b/app/src/main/res/layout/vertical_12.xml new file mode 100644 index 000000000..04ad459aa --- /dev/null +++ b/app/src/main/res/layout/vertical_12.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_13.xml b/app/src/main/res/layout/vertical_13.xml new file mode 100644 index 000000000..929a8e7f4 --- /dev/null +++ b/app/src/main/res/layout/vertical_13.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_14.xml b/app/src/main/res/layout/vertical_14.xml new file mode 100644 index 000000000..78c6d2396 --- /dev/null +++ b/app/src/main/res/layout/vertical_14.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_15.xml b/app/src/main/res/layout/vertical_15.xml new file mode 100644 index 000000000..57875111f --- /dev/null +++ b/app/src/main/res/layout/vertical_15.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_16.xml b/app/src/main/res/layout/vertical_16.xml new file mode 100644 index 000000000..1a217aad9 --- /dev/null +++ b/app/src/main/res/layout/vertical_16.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_17.xml b/app/src/main/res/layout/vertical_17.xml new file mode 100644 index 000000000..7ccbaf9e1 --- /dev/null +++ b/app/src/main/res/layout/vertical_17.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_18.xml b/app/src/main/res/layout/vertical_18.xml new file mode 100644 index 000000000..663284ce1 --- /dev/null +++ b/app/src/main/res/layout/vertical_18.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_19.xml b/app/src/main/res/layout/vertical_19.xml new file mode 100644 index 000000000..2f939c5c3 --- /dev/null +++ b/app/src/main/res/layout/vertical_19.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_2.xml b/app/src/main/res/layout/vertical_2.xml new file mode 100644 index 000000000..656bc9855 --- /dev/null +++ b/app/src/main/res/layout/vertical_2.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_20.xml b/app/src/main/res/layout/vertical_20.xml new file mode 100644 index 000000000..611f319e6 --- /dev/null +++ b/app/src/main/res/layout/vertical_20.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_21.xml b/app/src/main/res/layout/vertical_21.xml new file mode 100644 index 000000000..85d998a6c --- /dev/null +++ b/app/src/main/res/layout/vertical_21.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_22.xml b/app/src/main/res/layout/vertical_22.xml new file mode 100644 index 000000000..bf739805d --- /dev/null +++ b/app/src/main/res/layout/vertical_22.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_23.xml b/app/src/main/res/layout/vertical_23.xml new file mode 100644 index 000000000..645376073 --- /dev/null +++ b/app/src/main/res/layout/vertical_23.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_24.xml b/app/src/main/res/layout/vertical_24.xml new file mode 100644 index 000000000..311287d2b --- /dev/null +++ b/app/src/main/res/layout/vertical_24.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_25.xml b/app/src/main/res/layout/vertical_25.xml new file mode 100644 index 000000000..453972b16 --- /dev/null +++ b/app/src/main/res/layout/vertical_25.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_26.xml b/app/src/main/res/layout/vertical_26.xml new file mode 100644 index 000000000..a8783f463 --- /dev/null +++ b/app/src/main/res/layout/vertical_26.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_27.xml b/app/src/main/res/layout/vertical_27.xml new file mode 100644 index 000000000..726c5e363 --- /dev/null +++ b/app/src/main/res/layout/vertical_27.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_28.xml b/app/src/main/res/layout/vertical_28.xml new file mode 100644 index 000000000..3d7a3981d --- /dev/null +++ b/app/src/main/res/layout/vertical_28.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_29.xml b/app/src/main/res/layout/vertical_29.xml new file mode 100644 index 000000000..8ba4c59ad --- /dev/null +++ b/app/src/main/res/layout/vertical_29.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_3.xml b/app/src/main/res/layout/vertical_3.xml new file mode 100644 index 000000000..2ba9bd489 --- /dev/null +++ b/app/src/main/res/layout/vertical_3.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_30.xml b/app/src/main/res/layout/vertical_30.xml new file mode 100644 index 000000000..ff8de134c --- /dev/null +++ b/app/src/main/res/layout/vertical_30.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_31.xml b/app/src/main/res/layout/vertical_31.xml new file mode 100644 index 000000000..601d5e383 --- /dev/null +++ b/app/src/main/res/layout/vertical_31.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_32.xml b/app/src/main/res/layout/vertical_32.xml new file mode 100644 index 000000000..e25b8a686 --- /dev/null +++ b/app/src/main/res/layout/vertical_32.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_33.xml b/app/src/main/res/layout/vertical_33.xml new file mode 100644 index 000000000..9ff755a40 --- /dev/null +++ b/app/src/main/res/layout/vertical_33.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_34.xml b/app/src/main/res/layout/vertical_34.xml new file mode 100644 index 000000000..cb0b9f1f1 --- /dev/null +++ b/app/src/main/res/layout/vertical_34.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_35.xml b/app/src/main/res/layout/vertical_35.xml new file mode 100644 index 000000000..07b107b6f --- /dev/null +++ b/app/src/main/res/layout/vertical_35.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_36.xml b/app/src/main/res/layout/vertical_36.xml new file mode 100644 index 000000000..90f0b95eb --- /dev/null +++ b/app/src/main/res/layout/vertical_36.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_37.xml b/app/src/main/res/layout/vertical_37.xml new file mode 100644 index 000000000..c4a566e89 --- /dev/null +++ b/app/src/main/res/layout/vertical_37.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_38.xml b/app/src/main/res/layout/vertical_38.xml new file mode 100644 index 000000000..8ae17a6c0 --- /dev/null +++ b/app/src/main/res/layout/vertical_38.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_39.xml b/app/src/main/res/layout/vertical_39.xml new file mode 100644 index 000000000..d91590bb4 --- /dev/null +++ b/app/src/main/res/layout/vertical_39.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_4.xml b/app/src/main/res/layout/vertical_4.xml new file mode 100644 index 000000000..a62a6d541 --- /dev/null +++ b/app/src/main/res/layout/vertical_4.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_40.xml b/app/src/main/res/layout/vertical_40.xml new file mode 100644 index 000000000..f025908d0 --- /dev/null +++ b/app/src/main/res/layout/vertical_40.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_41.xml b/app/src/main/res/layout/vertical_41.xml new file mode 100644 index 000000000..da10e316c --- /dev/null +++ b/app/src/main/res/layout/vertical_41.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_42.xml b/app/src/main/res/layout/vertical_42.xml new file mode 100644 index 000000000..cfbf8c5f0 --- /dev/null +++ b/app/src/main/res/layout/vertical_42.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_43.xml b/app/src/main/res/layout/vertical_43.xml new file mode 100644 index 000000000..0cce68287 --- /dev/null +++ b/app/src/main/res/layout/vertical_43.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_44.xml b/app/src/main/res/layout/vertical_44.xml new file mode 100644 index 000000000..916da7eec --- /dev/null +++ b/app/src/main/res/layout/vertical_44.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_45.xml b/app/src/main/res/layout/vertical_45.xml new file mode 100644 index 000000000..0c6bbe880 --- /dev/null +++ b/app/src/main/res/layout/vertical_45.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_46.xml b/app/src/main/res/layout/vertical_46.xml new file mode 100644 index 000000000..866f8d288 --- /dev/null +++ b/app/src/main/res/layout/vertical_46.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_47.xml b/app/src/main/res/layout/vertical_47.xml new file mode 100644 index 000000000..a7589995f --- /dev/null +++ b/app/src/main/res/layout/vertical_47.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_48.xml b/app/src/main/res/layout/vertical_48.xml new file mode 100644 index 000000000..84c2dddb5 --- /dev/null +++ b/app/src/main/res/layout/vertical_48.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_49.xml b/app/src/main/res/layout/vertical_49.xml new file mode 100644 index 000000000..fe680a3a7 --- /dev/null +++ b/app/src/main/res/layout/vertical_49.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_5.xml b/app/src/main/res/layout/vertical_5.xml new file mode 100644 index 000000000..a4363d96e --- /dev/null +++ b/app/src/main/res/layout/vertical_5.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_50.xml b/app/src/main/res/layout/vertical_50.xml new file mode 100644 index 000000000..b883773c8 --- /dev/null +++ b/app/src/main/res/layout/vertical_50.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_51.xml b/app/src/main/res/layout/vertical_51.xml new file mode 100644 index 000000000..f8b78c454 --- /dev/null +++ b/app/src/main/res/layout/vertical_51.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_52.xml b/app/src/main/res/layout/vertical_52.xml new file mode 100644 index 000000000..9fdcab405 --- /dev/null +++ b/app/src/main/res/layout/vertical_52.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_53.xml b/app/src/main/res/layout/vertical_53.xml new file mode 100644 index 000000000..51555209a --- /dev/null +++ b/app/src/main/res/layout/vertical_53.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_54.xml b/app/src/main/res/layout/vertical_54.xml new file mode 100644 index 000000000..7bb40d2dc --- /dev/null +++ b/app/src/main/res/layout/vertical_54.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_55.xml b/app/src/main/res/layout/vertical_55.xml new file mode 100644 index 000000000..549246956 --- /dev/null +++ b/app/src/main/res/layout/vertical_55.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_56.xml b/app/src/main/res/layout/vertical_56.xml new file mode 100644 index 000000000..e40f2a828 --- /dev/null +++ b/app/src/main/res/layout/vertical_56.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_57.xml b/app/src/main/res/layout/vertical_57.xml new file mode 100644 index 000000000..4743e3558 --- /dev/null +++ b/app/src/main/res/layout/vertical_57.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_58.xml b/app/src/main/res/layout/vertical_58.xml new file mode 100644 index 000000000..d08accfab --- /dev/null +++ b/app/src/main/res/layout/vertical_58.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_59.xml b/app/src/main/res/layout/vertical_59.xml new file mode 100644 index 000000000..2e49dc33f --- /dev/null +++ b/app/src/main/res/layout/vertical_59.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_6.xml b/app/src/main/res/layout/vertical_6.xml new file mode 100644 index 000000000..a36adebde --- /dev/null +++ b/app/src/main/res/layout/vertical_6.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_60.xml b/app/src/main/res/layout/vertical_60.xml new file mode 100644 index 000000000..cfa9a6b1c --- /dev/null +++ b/app/src/main/res/layout/vertical_60.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_61.xml b/app/src/main/res/layout/vertical_61.xml new file mode 100644 index 000000000..09b4f3533 --- /dev/null +++ b/app/src/main/res/layout/vertical_61.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_62.xml b/app/src/main/res/layout/vertical_62.xml new file mode 100644 index 000000000..f0270e547 --- /dev/null +++ b/app/src/main/res/layout/vertical_62.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_63.xml b/app/src/main/res/layout/vertical_63.xml new file mode 100644 index 000000000..5fd374810 --- /dev/null +++ b/app/src/main/res/layout/vertical_63.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_64.xml b/app/src/main/res/layout/vertical_64.xml new file mode 100644 index 000000000..ffb825e74 --- /dev/null +++ b/app/src/main/res/layout/vertical_64.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_65.xml b/app/src/main/res/layout/vertical_65.xml new file mode 100644 index 000000000..4b5e2b31d --- /dev/null +++ b/app/src/main/res/layout/vertical_65.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_66.xml b/app/src/main/res/layout/vertical_66.xml new file mode 100644 index 000000000..d8cb15492 --- /dev/null +++ b/app/src/main/res/layout/vertical_66.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_67.xml b/app/src/main/res/layout/vertical_67.xml new file mode 100644 index 000000000..34bd25762 --- /dev/null +++ b/app/src/main/res/layout/vertical_67.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_68.xml b/app/src/main/res/layout/vertical_68.xml new file mode 100644 index 000000000..e618f8ae1 --- /dev/null +++ b/app/src/main/res/layout/vertical_68.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_69.xml b/app/src/main/res/layout/vertical_69.xml new file mode 100644 index 000000000..ef5a38f45 --- /dev/null +++ b/app/src/main/res/layout/vertical_69.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_7.xml b/app/src/main/res/layout/vertical_7.xml new file mode 100644 index 000000000..ccf8675fa --- /dev/null +++ b/app/src/main/res/layout/vertical_7.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_70.xml b/app/src/main/res/layout/vertical_70.xml new file mode 100644 index 000000000..6902954ce --- /dev/null +++ b/app/src/main/res/layout/vertical_70.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_71.xml b/app/src/main/res/layout/vertical_71.xml new file mode 100644 index 000000000..00489aab4 --- /dev/null +++ b/app/src/main/res/layout/vertical_71.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_72.xml b/app/src/main/res/layout/vertical_72.xml new file mode 100644 index 000000000..c81a96ae5 --- /dev/null +++ b/app/src/main/res/layout/vertical_72.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_73.xml b/app/src/main/res/layout/vertical_73.xml new file mode 100644 index 000000000..5abf02f24 --- /dev/null +++ b/app/src/main/res/layout/vertical_73.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_74.xml b/app/src/main/res/layout/vertical_74.xml new file mode 100644 index 000000000..46b436057 --- /dev/null +++ b/app/src/main/res/layout/vertical_74.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_75.xml b/app/src/main/res/layout/vertical_75.xml new file mode 100644 index 000000000..776fb0ce7 --- /dev/null +++ b/app/src/main/res/layout/vertical_75.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_76.xml b/app/src/main/res/layout/vertical_76.xml new file mode 100644 index 000000000..d7444cd6e --- /dev/null +++ b/app/src/main/res/layout/vertical_76.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_77.xml b/app/src/main/res/layout/vertical_77.xml new file mode 100644 index 000000000..eb51403b2 --- /dev/null +++ b/app/src/main/res/layout/vertical_77.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_78.xml b/app/src/main/res/layout/vertical_78.xml new file mode 100644 index 000000000..7cc84d33d --- /dev/null +++ b/app/src/main/res/layout/vertical_78.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_79.xml b/app/src/main/res/layout/vertical_79.xml new file mode 100644 index 000000000..7001ae681 --- /dev/null +++ b/app/src/main/res/layout/vertical_79.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_8.xml b/app/src/main/res/layout/vertical_8.xml new file mode 100644 index 000000000..7fef4f158 --- /dev/null +++ b/app/src/main/res/layout/vertical_8.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_80.xml b/app/src/main/res/layout/vertical_80.xml new file mode 100644 index 000000000..6600dea39 --- /dev/null +++ b/app/src/main/res/layout/vertical_80.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_81.xml b/app/src/main/res/layout/vertical_81.xml new file mode 100644 index 000000000..2cde4eb41 --- /dev/null +++ b/app/src/main/res/layout/vertical_81.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_82.xml b/app/src/main/res/layout/vertical_82.xml new file mode 100644 index 000000000..75bbda7ff --- /dev/null +++ b/app/src/main/res/layout/vertical_82.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_83.xml b/app/src/main/res/layout/vertical_83.xml new file mode 100644 index 000000000..9329f2b97 --- /dev/null +++ b/app/src/main/res/layout/vertical_83.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_84.xml b/app/src/main/res/layout/vertical_84.xml new file mode 100644 index 000000000..569d5a794 --- /dev/null +++ b/app/src/main/res/layout/vertical_84.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_85.xml b/app/src/main/res/layout/vertical_85.xml new file mode 100644 index 000000000..2aaec2278 --- /dev/null +++ b/app/src/main/res/layout/vertical_85.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_86.xml b/app/src/main/res/layout/vertical_86.xml new file mode 100644 index 000000000..e71a173c4 --- /dev/null +++ b/app/src/main/res/layout/vertical_86.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_87.xml b/app/src/main/res/layout/vertical_87.xml new file mode 100644 index 000000000..5a1089195 --- /dev/null +++ b/app/src/main/res/layout/vertical_87.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_88.xml b/app/src/main/res/layout/vertical_88.xml new file mode 100644 index 000000000..7cdde28d4 --- /dev/null +++ b/app/src/main/res/layout/vertical_88.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_89.xml b/app/src/main/res/layout/vertical_89.xml new file mode 100644 index 000000000..6ec66969f --- /dev/null +++ b/app/src/main/res/layout/vertical_89.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_9.xml b/app/src/main/res/layout/vertical_9.xml new file mode 100644 index 000000000..4fc1156cc --- /dev/null +++ b/app/src/main/res/layout/vertical_9.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_90.xml b/app/src/main/res/layout/vertical_90.xml new file mode 100644 index 000000000..4572a4b7e --- /dev/null +++ b/app/src/main/res/layout/vertical_90.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_91.xml b/app/src/main/res/layout/vertical_91.xml new file mode 100644 index 000000000..00b1a1a82 --- /dev/null +++ b/app/src/main/res/layout/vertical_91.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_92.xml b/app/src/main/res/layout/vertical_92.xml new file mode 100644 index 000000000..3e0bbb55b --- /dev/null +++ b/app/src/main/res/layout/vertical_92.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_93.xml b/app/src/main/res/layout/vertical_93.xml new file mode 100644 index 000000000..972f61edb --- /dev/null +++ b/app/src/main/res/layout/vertical_93.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_94.xml b/app/src/main/res/layout/vertical_94.xml new file mode 100644 index 000000000..fc1c3d8c0 --- /dev/null +++ b/app/src/main/res/layout/vertical_94.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_95.xml b/app/src/main/res/layout/vertical_95.xml new file mode 100644 index 000000000..70652e772 --- /dev/null +++ b/app/src/main/res/layout/vertical_95.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_96.xml b/app/src/main/res/layout/vertical_96.xml new file mode 100644 index 000000000..76bcc5cfc --- /dev/null +++ b/app/src/main/res/layout/vertical_96.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/vertical_line.xml b/app/src/main/res/layout/vertical_line.xml new file mode 100644 index 000000000..e4c0bce0c --- /dev/null +++ b/app/src/main/res/layout/vertical_line.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/layout/widget_config_weekly.xml b/app/src/main/res/layout/widget_config_weekly.xml new file mode 100644 index 000000000..f77b23fb0 --- /dev/null +++ b/app/src/main/res/layout/widget_config_weekly.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + +