Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/java/at/techbee/jtx/database/ICalDatabaseDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,16 @@ interface ICalDatabaseDao {
suspend fun makeSeriesDirty(uid: String, lastModified: Long = System.currentTimeMillis())


@Query("SELECT * FROM $TABLE_NAME_ICALOBJECT WHERE $COLUMN_RRULE IS NOT NULL AND $COLUMN_RECURID IS NULL AND $COLUMN_DELETED = 0")
fun getAllSeriesSync(): List<ICalObject>

/**
* Rebuilds the instances of every series, needed when the recurrence window changes. Instances
* are inserted with dirty = false and changed ones are kept, so nothing is uploaded.
*/
@Transaction
fun recreateAllRecurring() = getAllSeriesSync().forEach { recreateRecurring(it) }

@Transaction
fun recreateRecurring(iCalObject: ICalObject) {

Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/at/techbee/jtx/database/ICalObject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,14 @@ data class ICalObject(
private fun buildPeriod(
start: ZonedDateTime,
frequency: Frequency
): Period<ZonedDateTime> = when (val window = RecurrenceWindow.current) {
is RecurrenceWindow.Legacy -> legacyPeriod(start, frequency)
is RecurrenceWindow.AroundToday -> periodAroundToday(start, frequency, window)
}

private fun legacyPeriod(
start: ZonedDateTime,
frequency: Frequency
): Period<ZonedDateTime> {

val from = when (frequency) {
Expand All @@ -1021,6 +1029,29 @@ data class ICalObject(
return Period(from, to)
}

private fun periodAroundToday(
start: ZonedDateTime,
frequency: Frequency,
window: RecurrenceWindow.AroundToday
): Period<ZonedDateTime> {

// A series starting in the future would materialise nothing, and the list hides a series
// that has an RRULE and a DTSTART in favour of its instances - so it would vanish.
val now = ZonedDateTime.now(start.zone)
val reference = if (start.isAfter(now)) start else now

return when (frequency) {
// Three months of a SECONDLY rule is millions of rows wherever the window sits.
Frequency.SECONDLY -> Period(reference.minusHours(1), reference.plusHours(1))
Frequency.MINUTELY -> Period(reference.minusDays(1), reference.plusDays(1))
Frequency.HOURLY -> Period(reference.minusDays(30), reference.plusDays(30))
else -> Period(
reference.minusMonths(window.monthsBack),
reference.plusMonths(window.monthsAhead)
)
}
}

private fun normalizeRruleForAllDay() {
if (rrule == null)
return
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/at/techbee/jtx/database/RecurrenceWindow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) Techbee e.U.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/

package at.techbee.jtx.database

/**
* How far around its reference point recurrence instances are materialised.
*
* [Legacy] centres the window on DTSTART, and since nothing is generated before DTSTART that
* reaches forward from the start of the series: for a weekly series older than a year the window
* ends before today and no current occurrence is created at all.
*/
sealed interface RecurrenceWindow {

data object Legacy : RecurrenceWindow

data class AroundToday(val monthsBack: Long, val monthsAhead: Long) : RecurrenceWindow

companion object {
// Read from getInstancesFromRrule(), which is reached from Room DAO methods and from the
// sync content provider - neither carries a Context, so passing the value in would thread a
// parameter up into the ViewModels.
@Volatile
var current: RecurrenceWindow = Legacy
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/at/techbee/jtx/ui/settings/DropdownSetting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.os.Build
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Alarm
import androidx.compose.material.icons.outlined.EditCalendar
import androidx.compose.material.icons.outlined.EventRepeat
import androidx.compose.material.icons.outlined.Fingerprint
import androidx.compose.material.icons.outlined.FontDownload
import androidx.compose.material.icons.outlined.FormatPaint
Expand All @@ -34,6 +35,19 @@ enum class DropdownSetting(
val options: List<DropdownSettingOption>,
val default: DropdownSettingOption
) {
SETTING_RECUR_WINDOW(
key = "setting_recur_window",
icon = Icons.Outlined.EventRepeat,
title = R.string.settings_recur_window,
subtitle = R.string.settings_recur_window_sub,
options = listOf(
DropdownSettingOption.RECUR_WINDOW_LEGACY,
DropdownSettingOption.RECUR_WINDOW_1_MONTH,
DropdownSettingOption.RECUR_WINDOW_3_MONTHS,
DropdownSettingOption.RECUR_WINDOW_1_YEAR
),
default = DropdownSettingOption.RECUR_WINDOW_LEGACY
),
SETTING_THEME(
key = "settings_theme",
icon = Icons.Outlined.FormatPaint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ enum class DropdownSettingOption(
val key: String,
@StringRes val text: Int
) {
RECUR_WINDOW_LEGACY("recur_window_legacy", R.string.settings_recur_window_legacy),
RECUR_WINDOW_1_MONTH("recur_window_1_month", R.string.settings_recur_window_1_month),
RECUR_WINDOW_3_MONTHS("recur_window_3_months", R.string.settings_recur_window_3_months),
RECUR_WINDOW_1_YEAR("recur_window_1_year", R.string.settings_recur_window_1_year),
THEME_SYSTEM("system", R.string.settings_select_theme_system),
THEME_LIGHT("light", R.string.settings_select_theme_light),
THEME_DARK("dark", R.string.settings_select_theme_dark),
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/at/techbee/jtx/ui/settings/SettingsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import at.techbee.jtx.ui.settings.DropdownSetting.SETTING_FONT
import at.techbee.jtx.ui.settings.DropdownSetting.SETTING_MAPS_PROVIDER
import at.techbee.jtx.ui.settings.DropdownSetting.SETTING_PROGRESS_STEP
import at.techbee.jtx.ui.settings.DropdownSetting.SETTING_PROTECT_BIOMETRIC
import at.techbee.jtx.database.ICalDatabase
import at.techbee.jtx.ui.settings.DropdownSetting.SETTING_RECUR_WINDOW
import at.techbee.jtx.ui.settings.DropdownSetting.SETTING_THEME
import at.techbee.jtx.ui.settings.SwitchSetting.SETTING_ACCESSIBILITY_MODE
import at.techbee.jtx.ui.settings.SwitchSetting.SETTING_AUTO_EXPAND_ATTACHMENTS
Expand Down Expand Up @@ -703,6 +705,19 @@ fun SettingsScreen(
onToggle = { expandOrCollapse(SettingsScreenSection.ITEM_LIST) },
modifier = Modifier.fillMaxWidth()
) {
DropdownSettingElement(
setting = SETTING_RECUR_WINDOW,
selected = settingsStateHolder.settingRecurWindow.value,
onSelectionChanged = { selection ->
settingsStateHolder.settingRecurWindow.value = selection
SETTING_RECUR_WINDOW.saveSetting(selection, settingsStateHolder.prefs)
settingsStateHolder.applyRecurrenceWindow()
// The stored instances were materialised for the previous window.
scope.launch(Dispatchers.IO) {
ICalDatabase.getInstance(context).iCalDatabaseDao().recreateAllRecurring()
}
}
)
SwitchSettingElement(
setting = SETTING_AUTO_EXPAND_SUBTASKS,
checked = settingsStateHolder.settingAutoExpandSubtasks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import at.techbee.jtx.database.Module
import at.techbee.jtx.database.RecurrenceWindow
import at.techbee.jtx.ui.detail.DetailTopAppBarMode

class SettingsStateHolder(val context: Context) {
Expand Down Expand Up @@ -57,6 +58,7 @@ class SettingsStateHolder(val context: Context) {
var settingKeepStatusProgressCompletedInSync = mutableStateOf(SwitchSetting.SETTING_KEEP_STATUS_PROGRESS_COMPLETED_IN_SYNC.getSetting(prefs))
var settingProtectBiometric = mutableStateOf(DropdownSetting.SETTING_PROTECT_BIOMETRIC.getSetting(prefs))
var settingDisplayTimezone = mutableStateOf(DropdownSetting.SETTING_DISPLAY_TIMEZONE.getSetting(prefs))
var settingRecurWindow = mutableStateOf(DropdownSetting.SETTING_RECUR_WINDOW.getSetting(prefs))

var settingSetDefaultCurrentLocationJournals = mutableStateOf(SwitchSetting.SETTING_JOURNALS_SET_DEFAULT_CURRENT_LOCATION.getSetting(prefs))
var settingSetDefaultCurrentLocationNotes = mutableStateOf(SwitchSetting.SETTING_NOTES_SET_DEFAULT_CURRENT_LOCATION.getSetting(prefs))
Expand Down Expand Up @@ -110,6 +112,21 @@ class SettingsStateHolder(val context: Context) {
field = newValue
}
*/

init {
applyRecurrenceWindow()
}

fun applyRecurrenceWindow() {
// A month of history keeps a recently missed occurrence visible.
RecurrenceWindow.current = when (settingRecurWindow.value) {
DropdownSettingOption.RECUR_WINDOW_1_MONTH -> RecurrenceWindow.AroundToday(monthsBack = 1, monthsAhead = 1)
DropdownSettingOption.RECUR_WINDOW_3_MONTHS -> RecurrenceWindow.AroundToday(monthsBack = 1, monthsAhead = 3)
DropdownSettingOption.RECUR_WINDOW_1_YEAR -> RecurrenceWindow.AroundToday(monthsBack = 1, monthsAhead = 12)
else -> RecurrenceWindow.Legacy
}
}

}


6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@
<string name="settings_export_error">"An error occurred while exporting settings"</string>
<string name="more">"More"</string>
<string name="navigation_drawer_buypro">"Buy Pro"</string>
<string name="settings_recur_window">"Occurrences of recurring entries"</string>
<string name="settings_recur_window_sub">"Which single occurrences of a series are created. From the start of the series reaches one year ahead for daily and weekly rules, ten years for monthly and a hundred for yearly - so a weekly series that began more than a year ago only gets occurrences from its first year, and none around today."</string>
<string name="settings_recur_window_legacy">"From the start of the series"</string>
<string name="settings_recur_window_1_month">"Around today (1 month)"</string>
<string name="settings_recur_window_3_months">"Around today (3 months)"</string>
<string name="settings_recur_window_1_year">"Around today (1 year)"</string>
<string name="settings_select_theme">"Select theme"</string>
<string name="settings_select_theme_dark">"Dark"</string>
<string name="settings_select_theme_light">"Light"</string>
Expand Down