diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7870902d..d115460d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+### Added
+- Widget for weekly view ([#470])
## [1.10.2] - 2026-02-04
### Fixed
diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml
index 65084db15..95c34e10c 100644
--- a/app/detekt-baseline.xml
+++ b/app/detekt-baseline.xml
@@ -16,7 +16,6 @@
ComplexCondition:SettingsActivity.kt$SettingsActivity$requestCode == PICK_SETTINGS_IMPORT_SOURCE_INTENT && resultCode == RESULT_OK && resultData != null && resultData.data != null
ComplexCondition:TaskActivity.kt$TaskActivity$config.wasAlarmWarningShown || (mReminder1Minutes == REMINDER_OFF && mReminder2Minutes == REMINDER_OFF && mReminder3Minutes == REMINDER_OFF)
ComplexCondition:TaskActivity.kt$TaskActivity$day == MONDAY_BIT || day == TUESDAY_BIT || day == WEDNESDAY_BIT || day == THURSDAY_BIT || day == FRIDAY_BIT || day == SATURDAY_BIT || day == SUNDAY_BIT
- ComplexCondition:WeekFragment.kt$WeekFragment$doesEventFit && (!isRepeatingOverlappingEvent || isAllDayEvent || isRowValidForEvent)
ConstructorParameterNaming:Task.kt$Task$@ColumnInfo(name = "task_id") var task_id: Long
CyclomaticComplexMethod:CalDAVHelper.kt$CalDAVHelper$@SuppressLint("MissingPermission") private fun fetchCalDAVCalendarEvents( calendar: CalDAVCalendar, localCalendarId: Long, showToasts: Boolean, )
CyclomaticComplexMethod:Context.kt$@SuppressLint("NewApi") fun Context.getNotification( pendingIntent: PendingIntent, event: Event, content: String, publicVersion: Boolean = false ): Notification?
@@ -45,8 +44,8 @@
CyclomaticComplexMethod:TaskActivity.kt$TaskActivity$private fun getOrderString(repeatRule: Int): String
CyclomaticComplexMethod:TaskActivity.kt$TaskActivity$private fun gotTask(savedInstanceState: Bundle?, localCalendar: CalendarEntity?, task: Event?)
CyclomaticComplexMethod:TaskActivity.kt$TaskActivity$private fun saveTask()
- CyclomaticComplexMethod:WeekFragment.kt$WeekFragment$@SuppressLint("NewApi") private fun addAllDayEvent(event: Event)
- CyclomaticComplexMethod:WeekFragment.kt$WeekFragment$private fun addEvents(events: ArrayList<Event>)
+ CyclomaticComplexMethod:WeekFragment.kt$WeekFragment$@SuppressLint("NewApi") private fun addAllDayEvent(dayOfWeek: Int, event: Event)
+ CyclomaticComplexMethod:WeekFragment.kt$WeekFragment$private fun addDays(days: ArrayList<DayWeekly>)
EmptyCatchBlock:EventsHelper.kt$EventsHelper${ }
EmptyCatchBlock:MainActivity.kt$MainActivity${ }
EmptyFunctionBlock:DayEventsAdapter.kt$DayEventsAdapter${}
@@ -79,7 +78,7 @@
LongMethod:EventActivity.kt$EventActivity$private fun saveEvent()
LongMethod:IcsImporter.kt$IcsImporter$fun importEvents( path: String, defaultCalendarId: Long, calDAVCalendarId: Int, overrideFileCalendars: Boolean, eventReminders: ArrayList<Int>? = null, loadFromAssets: Boolean = false, ): ImportResult
LongMethod:MyTimeZones.kt$fun getAllTimeZones()
- LongMethod:WeekFragment.kt$WeekFragment$private fun addEvents(events: ArrayList<Event>)
+ LongMethod:WeekFragment.kt$WeekFragment$private fun addDays(days: ArrayList<DayWeekly>)
LongParameterList:SelectCalendarDialog.kt$SelectCalendarDialog$( val activity: Activity, val currCalendar: Long, val showCalDAVCalendars: Boolean, val showNewCalendarOption: Boolean, val addLastUsedOneAsFirstOption: Boolean, val showOnlyWritable: Boolean, var showManageCalendars: Boolean, val callback: (calendar: CalendarEntity) -> Unit )
LoopWithTooManyJumpStatements:IcsImporter.kt$IcsImporter$while
LoopWithTooManyJumpStatements:ManageCalendarsAdapter.kt$ManageCalendarsAdapter$for
@@ -173,6 +172,7 @@
MagicNumber:MyWidgetMonthlyProvider.kt$MyWidgetMonthlyProvider$7
MagicNumber:MyWidgetMonthlyProvider.kt$MyWidgetMonthlyProvider.<no name provided>$3f
MagicNumber:MyWidgetMonthlyProvider.kt$MyWidgetMonthlyProvider.<no name provided>$6
+ MagicNumber:MyWidgetWeeklyProvider.kt$MyWidgetWeeklyProvider$3
MagicNumber:NotificationReceiver.kt$NotificationReceiver$3000
MagicNumber:Parser.kt$Parser$14
MagicNumber:Parser.kt$Parser$24
@@ -242,6 +242,9 @@
MagicNumber:WidgetMonthlyConfigureActivity.kt$WidgetMonthlyConfigureActivity$255f
MagicNumber:WidgetMonthlyConfigureActivity.kt$WidgetMonthlyConfigureActivity$3
MagicNumber:WidgetMonthlyConfigureActivity.kt$WidgetMonthlyConfigureActivity$7
+ MagicNumber:WidgetWeeklyConfigureActivity.kt$WidgetWeeklyConfigureActivity$100
+ MagicNumber:WidgetWeeklyConfigureActivity.kt$WidgetWeeklyConfigureActivity$100f
+ MagicNumber:WidgetWeeklyConfigureActivity.kt$WidgetWeeklyConfigureActivity$255f
MagicNumber:YearFragment.kt$YearFragment$12
MagicNumber:YearFragmentsHolder.kt$YearFragmentsHolder$100000
MagicNumber:YearFragmentsHolder.kt$YearFragmentsHolder$61
@@ -363,11 +366,7 @@
MaxLineLength:TaskActivity.kt$TaskActivity$if (usePreviousEventReminders && lastEventReminderMinutes1 >= -1) lastEventReminderMinutes1 else defaultReminder1
MaxLineLength:TaskActivity.kt$TaskActivity$if (usePreviousEventReminders && lastEventReminderMinutes2 >= -1) lastEventReminderMinutes2 else defaultReminder2
MaxLineLength:TaskActivity.kt$TaskActivity$if (usePreviousEventReminders && lastEventReminderMinutes3 >= -1) lastEventReminderMinutes3 else defaultReminder3
- MaxLineLength:WeekFragment.kt$WeekFragment$((currentEventWeeklyView.range.upper - currentEventWeeklyView.range.lower) * minuteHeight).toInt() - 1
- MaxLineLength:WeekFragment.kt$WeekFragment$// fix a visual glitch with all-day events or events lasting multiple days starting at midnight on monday, being shown the previous week too
MaxLineLength:WeekFragment.kt$WeekFragment$// we need to refresh all fragments because they can contain future occurrences
- MaxLineLength:WeekFragment.kt$WeekFragment$dayColumns.indexOfFirst { it.tag.toInt() == dayCodeStart || it.tag.toInt() in (dayCodeStart + 1)..dayCodeEnd }
- MaxLineLength:WeekFragment.kt$WeekFragment$eventWeeklyView.range.upper > eventWeeklyViewToCheck.range.lower
MaxLineLength:WeekFragmentsHolder.kt$WeekFragmentsHolder$binding.weekViewDaysCount.text = requireContext().resources.getQuantityString(org.fossify.commons.R.plurals.days, cnt, cnt)
MaxLineLength:WidgetDateConfigureActivity.kt$WidgetDateConfigureActivity$if
MaxLineLength:WidgetListConfigureActivity.kt$WidgetListConfigureActivity$EventListAdapter(this@WidgetListConfigureActivity, getListItems(), false, null, configWidgetPreview.configEventsList) {}
@@ -396,11 +395,11 @@
NestedBlockDepth:MainActivity.kt$MainActivity$private fun checkIsViewIntent()
NestedBlockDepth:MonthView.kt$MonthView$override fun onDraw(canvas: Canvas)
NestedBlockDepth:MyWidgetMonthlyProvider.kt$MyWidgetMonthlyProvider$private fun updateDays(context: Context, views: RemoteViews, days: List<DayMonthly>)
+ NestedBlockDepth:MyWidgetWeeklyProvider.kt$MyWidgetWeeklyProvider$private fun updateDays(context: Context, views: RemoteViews)
NestedBlockDepth:Parser.kt$Parser$fun parseRepeatInterval(fullString: String, startTS: Long): EventRepetition
NestedBlockDepth:SettingsActivity.kt$SettingsActivity$private fun parseFile(inputStream: InputStream?)
NestedBlockDepth:SmallMonthView.kt$SmallMonthView$override fun onDraw(canvas: Canvas)
- NestedBlockDepth:WeekFragment.kt$WeekFragment$@SuppressLint("NewApi") private fun addAllDayEvent(event: Event)
- NestedBlockDepth:WeekFragment.kt$WeekFragment$private fun addEvents(events: ArrayList<Event>)
+ NestedBlockDepth:WeekFragment.kt$WeekFragment$private fun addDays(days: ArrayList<DayWeekly>)
ReturnCount:HsvColorComparator.kt$HsvColorComparator$override fun compare(lhs: Int?, rhs: Int?): Int
SwallowedException:CalDAVHelper.kt$CalDAVHelper$e: Exception
SwallowedException:Converters.kt$Converters$e: Exception
@@ -447,6 +446,7 @@
TooManyFunctions:WeekFragmentsHolder.kt$WeekFragmentsHolder : MyFragmentHolderWeekFragmentListener
TooManyFunctions:WidgetListConfigureActivity.kt$WidgetListConfigureActivity : SimpleActivity
TooManyFunctions:WidgetMonthlyConfigureActivity.kt$WidgetMonthlyConfigureActivity : SimpleActivityMonthlyCalendar
+ TooManyFunctions:WidgetWeeklyConfigureActivity.kt$WidgetWeeklyConfigureActivity : SimpleActivityWeeklyCalendar
TooManyFunctions:YearFragmentsHolder.kt$YearFragmentsHolder : MyFragmentHolderNavigationListener
UnusedPrivateProperty:YearlyCalendarImpl.kt$YearlyCalendarImpl$i
VariableNaming:CalendarPickerActivity.kt$CalendarPickerActivity$private val TYPE_EVENT = 0
@@ -503,6 +503,8 @@
WildcardImport:MyWidgetListProvider.kt$import org.fossify.commons.extensions.*
WildcardImport:MyWidgetMonthlyProvider.kt$import org.fossify.calendar.extensions.*
WildcardImport:MyWidgetMonthlyProvider.kt$import org.fossify.commons.extensions.*
+ WildcardImport:MyWidgetWeeklyProvider.kt$import org.fossify.calendar.extensions.*
+ WildcardImport:MyWidgetWeeklyProvider.kt$import org.fossify.commons.extensions.*
WildcardImport:Parser.kt$import org.fossify.commons.helpers.*
WildcardImport:ReminderWarningDialog.kt$import org.fossify.commons.extensions.*
WildcardImport:RepeatLimitTypePickerDialog.kt$import org.fossify.commons.extensions.*
@@ -513,5 +515,6 @@
WildcardImport:WidgetListConfigureActivity.kt$import org.fossify.commons.extensions.*
WildcardImport:WidgetListConfigureActivity.kt$import org.fossify.commons.helpers.*
WildcardImport:WidgetMonthlyConfigureActivity.kt$import org.fossify.commons.extensions.*
+ WildcardImport:WidgetWeeklyConfigureActivity.kt$import org.fossify.commons.extensions.*
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= ArrayList()
+ private var earliestEventStartHour = 0
+ private var latestEventEndHour = WeeklyCalendarImpl.HOURS_PER_DAY
+
+ 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)
+ }
+ setupDayLabels()
+ setupDayColumns()
+ }
+
+ 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 setupDayLabels() {
+ val dayLetters = resources.getStringArray(org.fossify.commons.R.array.week_days_short)
+ .toMutableList() as ArrayList
+ binding.configCalendar.weekLettersHolder.apply {
+ removeAllViews()
+ val smallerFontSize = context.getWidgetFontSize()
+ var curDay = context.getFirstDayOfWeekDt(DateTime())
+ for (i in 0 until config.weeklyViewDays) {
+ val dayLetter = dayLetters[curDay.dayOfWeek - 1]
+
+ val newView = WidgetWeekDayLetterBinding.inflate(
+ layoutInflater,
+ binding.configCalendar.weekLettersHolder,
+ false
+ ).root
+ newView.text = dayLetter
+ newView.setTextColor(mTextColor)
+ newView.textSize = smallerFontSize
+ addView(newView)
+ curDay = curDay.plusDays(1)
+ }
+ }
+ }
+
+ private fun setupDayColumns() {
+ binding.configCalendar.weekEventsDayLines.removeAllViews()
+ // columns that will contain events
+ binding.configCalendar.weekEventsColumnsHolder.apply {
+ removeAllViews()
+ for (i in 0 until context.config.weeklyViewDays) {
+ addView(WidgetWeekColumnBinding.inflate(layoutInflater,
+ binding.configCalendar.weekEventsColumnsHolder, false).root)
+ }
+ }
+ // column on the left showing the time
+ binding.configCalendar.timeColumn.apply {
+ removeAllViews()
+ addView(Vertical1Binding.inflate(layoutInflater,
+ binding.configCalendar.timeColumn, false).root)
+ for (i in earliestEventStartHour + 1 until latestEventEndHour) {
+ val time = DateTime().withHourOfDay(i)
+ addView(WidgetWeekHourBinding.inflate(layoutInflater,
+ binding.configCalendar.timeColumn, false).root.apply {
+ text = time.toString(Formatter.getHourPattern(context))
+ setTextColor(mTextColor)
+ })
+ }
+ addView(Vertical1Binding.inflate(layoutInflater, binding.configCalendar.timeColumn, false).root)
+ }
+ binding.configCalendar.weekEventsHourLines.apply {
+ removeAllViews()
+ addView(Vertical1Binding.inflate(layoutInflater, binding.configCalendar.weekEventsHourLines, false).root)
+ for (i in earliestEventStartHour + 1 until latestEventEndHour) {
+ addView(HorizontalLineBinding.inflate(layoutInflater,
+ binding.configCalendar.weekEventsHourLines, false).root)
+ addView(Vertical1Binding.inflate(layoutInflater,
+ binding.configCalendar.weekEventsHourLines, false).root)
+ }
+ }
+ }
+
+ 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() {
+ mDays.forEach {
+ binding.configCalendar.weekEventsDayLines.apply {
+ addView(VerticalLineBinding.inflate(layoutInflater,
+ binding.configCalendar.weekEventsDayLines, false).root)
+ addView(Horizontal1Binding.inflate(layoutInflater,
+ binding.configCalendar.weekEventsDayLines, false).root)
+ }
+ }
+ }
+
+ override fun updateWeeklyCalendar(
+ context: Context,
+ days: ArrayList,
+ earliestStartHour: Int,
+ latestEndHour: Int,
+ ) {
+ runOnUiThread {
+ mDays = days
+ earliestEventStartHour = earliestStartHour
+ latestEventEndHour = latestEndHour
+ setupDayLabels()
+ setupDayColumns()
+ 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/fragments/WeekFragment.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/WeekFragment.kt
index 67fe929d0..86d0b0fac 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,23 @@ 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,
+ earliestStartHour: Int,
+ latestEndHour: Int,
+ ) {
+ 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 +568,108 @@ 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
- }
- }
+ val durationMinutes = ews.endMinute - ews.startMinute
+ minHeight = minimalHeight.coerceAtLeast((durationMinutes * 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 +677,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 +732,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 +768,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 +837,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/MyWidgetWeeklyProvider.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetWeeklyProvider.kt
new file mode 100644
index 000000000..ee6ea998b
--- /dev/null
+++ b/app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetWeeklyProvider.kt
@@ -0,0 +1,431 @@
+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()
+ private var earliestEventStartHour = 0
+ private var latestEventEndHour = WeeklyCalendarImpl.HOURS_PER_DAY
+
+ 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
+ for (i in 0 until context.config.weeklyViewDays) {
+ 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) {
+ 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 mDays.withIndex()) {
+ for (event in day.topBarEvents) {
+ addAllDayEvent(context, packageName, dayOfWeek, event, allDayEventRows, allDayEventNextStart)
+ }
+ val dayColumn = dayColumns[dayOfWeek]
+ var subColumnStartMinute = earliestEventStartHour * WeeklyCalendarImpl.MINUTES_PER_HOUR
+ 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
+ for (i in 0 until ews.slotMax) {
+ val column = RemoteViews(packageName, R.layout.widget_week_column)
+ subColumns.add(column)
+ }
+ }
+ for (i in 0 until ews.slotMax) {
+ 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 * WeeklyCalendarImpl.MINUTES_PER_HOUR,
+ 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,
+ earliestStartHour: Int,
+ latestEndHour: Int,
+ ) {
+ val textColor = context.config.widgetTextColor
+ val resources = context.resources
+ mDays = days
+ earliestEventStartHour = earliestStartHour
+ latestEventEndHour = latestEndHour
+
+ 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)
+
+ 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
+ val dayLetters = resources.getStringArray(org.fossify.commons.R.array.week_days_short)
+ .toMutableList() as ArrayList
+
+ views.removeAllViews(R.id.week_letters_holder)
+ for (i in 0 until config.weeklyViewDays) {
+ val curDay = mDays[i].start
+ 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)
+ }
+ }
+
+ 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/kotlin/org/fossify/calendar/helpers/WeeklyCalendarImpl.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/WeeklyCalendarImpl.kt
index 284e9ce70..6637ac376 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,217 @@
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)
+ companion object {
+ const val MINUTES_PER_HOUR = 60
+ const val HOURS_PER_DAY = 24
+ const val MIN_SHOWN_HOURS_PER_DAY = 6
+ const val EMPTY_START_HOUR = 6
+ const val EMPTY_END_HOUR = 18
+ }
+
+ 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
+
+ val days = generateDaysInitial(weekStart, sortedEvents)
+ fixEventOverlap(days)
+ callbackWithHourRange(days)
+ }
+ }
+
+ fun getWeek(targetDate: DateTime) {
+ updateWeeklyCalendar(targetDate)
+ }
+
+ private fun generateDaysInitial(weekStart: DateTime, sortedEvents: ArrayList): 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 {
+ addNormalEventToDays(days, event, eventStart, eventEnd)
+ }
+ }
+ return days
+ }
+
+ private fun addNormalEventToDays(
+ days: ArrayList,
+ event: Event,
+ eventStart: DateTime,
+ eventEnd: DateTime,
+ ) {
+ // the event gets added to all days it spans
+ for (day in days) {
+ val dayEnd = day.start.plusDays(1)
+ val eventIsDuringThisDay = eventStart < dayEnd && eventEnd > day.start
+ val eventStartsAndEndsWithDay = eventStart == day.start && eventEnd == day.start
+ if (eventIsDuringThisDay || eventStartsAndEndsWithDay) {
+ val startM = eventStart.coerceAtLeast(day.start).minuteOfDay
+ val endM = eventEnd.coerceAtMost(dayEnd).minuteOfDay
+ // round to timeStep
+ val startMinute = divRound(startM, timeStepMinutes) * timeStepMinutes
+ val endMinute = divRound(endM, timeStepMinutes) * timeStepMinutes
+ day.dayEvents.add(
+ EventWeeklyView(
+ event,
+ startMinute,
+ endMinute.coerceAtLeast(startMinute + timeStepMinutes),
+ )
+ )
+ }
+ }
+ }
+
+ private fun fixEventOverlap(days: ArrayList) {
+ // make sure that events don't overlap visually even if their timing overlaps
+ 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 }
+ )
+ fixEventOverlapDay(day, sapPoints)
+ }
+ }
+
+ private fun fixEventOverlapDay(day: DayWeekly, sapPoints: ArrayList) {
+ // make sure that events don't overlap visually even if their timing overlaps
+ var startOfCurrentBlock = 0
+ var neededSlots = 0
+ val currentEvents = ArrayList()
+ 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
+ distributeEventSlots(day, neededSlots, startOfCurrentBlock, i, sapPoints)
+ // reset needed slots for the next block
+ neededSlots = 0
+ }
+ }
+ // at least as many slots as concurrent events are needed
+ neededSlots = neededSlots.coerceAtLeast(currentEvents.size)
+ }
+ }
+
+ private fun distributeEventSlots(
+ day: DayWeekly,
+ neededSlots: Int,
+ startOfCurrentBlock: Int,
+ endOfCurrentBlock: Int,
+ sapPoints: ArrayList,
+ ) {
+ var slot = 0
+ val slotUsage = (0 until neededSlots).map { false }.toMutableList();
+ for (i in startOfCurrentBlock until endOfCurrentBlock) {
+ // 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
+ }
+ }
+ }
+
+ private fun callbackWithHourRange(days: ArrayList) {
+ // allows hiding hours in which nothing happens
+ var earliestEventStartHour = HOURS_PER_DAY
+ var latestEventEndHour = 0
+ for (day in days) {
+ val startHour = day.dayEvents.minOfOrNull { it.startMinute / MINUTES_PER_HOUR }
+ earliestEventStartHour = earliestEventStartHour.coerceAtMost(startHour ?: earliestEventStartHour)
+ val endHour = day.dayEvents.maxOfOrNull {
+ it.endMinute / MINUTES_PER_HOUR + if (it.endMinute % MINUTES_PER_HOUR > 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 = EMPTY_START_HOUR
+ latestEventEndHour = EMPTY_END_HOUR
+ } else if (latestEventEndHour - earliestEventStartHour < MIN_SHOWN_HOURS_PER_DAY ) {
+ // make sure that more than one hour is shown
+ val hoursToAdd = MIN_SHOWN_HOURS_PER_DAY - latestEventEndHour + earliestEventStartHour
+ earliestEventStartHour = (earliestEventStartHour - hoursToAdd / 2).coerceAtLeast(0)
+ latestEventEndHour = (latestEventEndHour + hoursToAdd - (hoursToAdd / 2)).coerceAtMost(HOURS_PER_DAY)
+ }
+ callback.updateWeeklyCalendar(context, days, earliestEventStartHour, latestEventEndHour)
+ }
+
+ 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 dayCodeStart = Formatter.getDayCodeFromDateTime(startDateTime)
+ val dayCodeEnd = Formatter.getDayCodeFromDateTime(endDateTime)
+ val spansMultipleDays = dayCodeStart != dayCodeEnd
+ 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..090c7c50d 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, earliestStartHour: Int, latestEndHour: Int)
}
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,
)
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 000000000..f93837426
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/img_widget_weekly_preview.png differ
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/widget_week_all_day_row.xml b/app/src/main/res/layout/widget_week_all_day_row.xml
new file mode 100644
index 000000000..c07baae42
--- /dev/null
+++ b/app/src/main/res/layout/widget_week_all_day_row.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/app/src/main/res/layout/widget_week_column.xml b/app/src/main/res/layout/widget_week_column.xml
new file mode 100644
index 000000000..c14d87be1
--- /dev/null
+++ b/app/src/main/res/layout/widget_week_column.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/app/src/main/res/layout/widget_week_day_letter.xml b/app/src/main/res/layout/widget_week_day_letter.xml
new file mode 100644
index 000000000..02b6b1a59
--- /dev/null
+++ b/app/src/main/res/layout/widget_week_day_letter.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/app/src/main/res/layout/widget_week_event_marker.xml b/app/src/main/res/layout/widget_week_event_marker.xml
new file mode 100644
index 000000000..e04c01d32
--- /dev/null
+++ b/app/src/main/res/layout/widget_week_event_marker.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/widget_week_hour.xml b/app/src/main/res/layout/widget_week_hour.xml
new file mode 100644
index 000000000..93c1bfb26
--- /dev/null
+++ b/app/src/main/res/layout/widget_week_hour.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 09cedfaee..fd8317f77 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,6 +13,7 @@
Go to date
+ Calendar weekly
Calendar monthly
Calendar event list
Calendar today\'s date
diff --git a/app/src/main/res/xml/widget_weekly_info.xml b/app/src/main/res/xml/widget_weekly_info.xml
new file mode 100644
index 000000000..f2fc47074
--- /dev/null
+++ b/app/src/main/res/xml/widget_weekly_info.xml
@@ -0,0 +1,13 @@
+
+
diff --git a/generate_weighted_layouts.py b/generate_weighted_layouts.py
new file mode 100644
index 000000000..84aee0e57
--- /dev/null
+++ b/generate_weighted_layouts.py
@@ -0,0 +1,29 @@
+import re
+import os
+
+# arguments
+filename_in = "app/src/main/res/layout/horizontal_1.xml"
+limit = 14 # split the day into 15 minute segments
+
+# constants
+filename_regex = r'(.*)_1(.*)'
+content_regex = r'(.*)layout_weight="1"(.*)'
+
+# variables
+rid_base = os.path.splitext(os.path.basename(filename_in))[0]
+rids = "R.layout." + rid_base + ","
+
+# read first file's content
+with open(filename_in, "r") as file:
+ content = file.read()
+ for i in range(2, limit + 1):
+ filename_out = re.sub(filename_regex, r"\g<1>_" + str(i) + r"\g<2>", filename_in)
+ rid = re.sub(filename_regex, r"\g<1>_" + str(i) + r"\g<2>", rid_base)
+ content_out = re.sub(content_regex, r'\g<1>layout_weight="' + str(i) + r'"\g<2>', content)
+ with open(filename_out, "w") as f:
+ f.write(content_out)
+ rids += "\nR.layout." + rid + ","
+
+# print R.layouts so they can easily be pasted into code
+print(rids)
+