Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mup committed Feb 4, 2025
1 parent 7400adf commit 4ff0a1d
Show file tree
Hide file tree
Showing 18 changed files with 648 additions and 445 deletions.
2 changes: 1 addition & 1 deletion app-android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ android {
buildTypes.each {
it.buildConfigField 'String', 'FILE_PROVIDER_AUTHORITY', '"' + it.manifestPlaceholders['contentProviderAuthority'] + '"'
// keep in sync with src/native/main/NativePushServiceApp.ts
it.buildConfigField 'String', "SYS_MODEL_VERSION", '"118"'
it.buildConfigField 'String', "SYS_MODEL_VERSION", '"119"'
it.buildConfigField 'String', "TUTANOTA_MODEL_VERSION", '"80"'
it.buildConfigField 'String', 'RES_ADDRESS', '"tutanota"'
}
Expand Down
2 changes: 1 addition & 1 deletion app-android/calendar/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ android {
"\"" + it.manifestPlaceholders["contentProviderAuthority"] + "\""
)
// keep in sync with src/native/main/NativePushServiceApp.ts
it.buildConfigField("String", "SYS_MODEL_VERSION", "\"118\"")
it.buildConfigField("String", "SYS_MODEL_VERSION", "\"119\"")
it.buildConfigField("String", "TUTANOTA_MODEL_VERSION", "\"80\"")
it.buildConfigField("String", "RES_ADDRESS", "\"tutanota\"")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ object AlarmModel {
null
}
val calendar = Calendar.getInstance(if (isAllDayEvent) localTimeZone else timeZone)
val setPosRules = byRules.filter { rule -> rule.byRule == ByRuleType.BYSETPOS }
val setPosRules = byRules.filter { rule -> rule.byRule == ByRuleType.BY_SET_POS }
val eventFacade = EventFacade()

var occurrences = 0
Expand All @@ -69,7 +69,7 @@ object AlarmModel {
incrementByRepeatPeriod(calendar, frequency, interval * intervalOccurrences)

var expandedEvents: List<DateTime> = eventFacade.generateFutureInstances(
calendar.timeInMillis.toULong(),
(calendar.timeInMillis / 1000).toULong(),
EventRepeatRule(frequency.toSdkPeriod(), byRules)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.tutao.tutashared.alarms

import android.util.Log
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import de.tutao.tutasdk.ByRule
Expand Down Expand Up @@ -85,14 +84,14 @@ fun EncryptedRepeatRule.decrypt(crypto: AndroidNativeCryptoFacade, sessionKey: B

fun ByRuleType.Companion.fromValue(value: Int) = run {
when (value) {
0 -> ByRuleType.BYMINUTE
1 -> ByRuleType.BYHOUR
2 -> ByRuleType.BYDAY
3 -> ByRuleType.BYMONTHDAY
4 -> ByRuleType.BYYEARDAY
5 -> ByRuleType.BYWEEKNO
6 -> ByRuleType.BYMONTH
7 -> ByRuleType.BYSETPOS
0 -> ByRuleType.BY_MINUTE
1 -> ByRuleType.BY_HOUR
2 -> ByRuleType.BY_DAY
3 -> ByRuleType.BY_MONTHDAY
4 -> ByRuleType.BY_YEAR_DAY
5 -> ByRuleType.BY_WEEK_NO
6 -> ByRuleType.BY_MONTH
7 -> ByRuleType.BY_SET_POS
8 -> ByRuleType.WKST

else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ public struct AdvancedRule: Codable, Hashable {
extension ByRuleType {
func toSDKType() -> tutasdk.ByRuleType {
switch self {
case .byminute: return tutasdk.ByRuleType.byminute
case .byhour: return tutasdk.ByRuleType.byhour
case .byday: return tutasdk.ByRuleType.byday
case .bymonth: return tutasdk.ByRuleType.bymonth
case .bymonthday: return tutasdk.ByRuleType.bymonthday
case .byyearday: return tutasdk.ByRuleType.byyearday
case .byweekno: return tutasdk.ByRuleType.byweekno
case .bysetpos: return tutasdk.ByRuleType.bysetpos
case .byminute: return tutasdk.ByRuleType.byMinute
case .byhour: return tutasdk.ByRuleType.byHour
case .byday: return tutasdk.ByRuleType.byDay
case .bymonth: return tutasdk.ByRuleType.byMonth
case .bymonthday: return tutasdk.ByRuleType.byMonthday
case .byyearday: return tutasdk.ByRuleType.byYearDay
case .byweekno: return tutasdk.ByRuleType.byWeekNo
case .bysetpos: return tutasdk.ByRuleType.bySetPos
case .wkst: return tutasdk.ByRuleType.wkst
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/calendar-app/calendar/export/CalendarExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import {
import { assertNotNull, downcast, incrementDate, isNotEmpty, mapAndFilterNull, neverNull, pad, stringToUtf8Uint8Array } from "@tutao/tutanota-utils"
import { calendarAttendeeStatusToParstat, iCalReplacements, repeatPeriodToIcalFrequency } from "./CalendarParser"
import { getAllDayDateLocal, isAllDayEvent } from "../../../common/api/common/utils/CommonCalendarUtils"
import { AlarmIntervalUnit, generateUid, getTimeZone, parseAlarmInterval } from "../../../common/calendar/date/CalendarUtils"
import { AlarmIntervalUnit, ByRule, generateUid, getTimeZone, parseAlarmInterval } from "../../../common/calendar/date/CalendarUtils"
import type { CalendarEvent } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { createFile } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { convertToDataFile, DataFile } from "../../../common/api/common/DataFile"
import type { CalendarAdvancedRepeatRule, DateWrapper, RepeatRule, UserAlarmInfo } from "../../../common/api/entities/sys/TypeRefs.js"
import { DateTime } from "luxon"
import { getLetId } from "../../../common/api/common/utils/EntityUtils"
import { CALENDAR_MIME_TYPE } from "../../../common/file/FileController.js"
import { ByRule } from "../../../common/calendar/import/ImportExportUtils.js"

/** create an ical data file that can be attached to an invitation/update/cancellation/response mail */
export function makeInvitationCalendarFile(event: CalendarEvent, method: CalendarMethod, now: Date, zone: string): DataFile {
Expand Down Expand Up @@ -165,8 +164,11 @@ export function serializeRepeatRule(repeatRule: RepeatRule | null, isAllDayEvent
const advancedRepeatRules = serializeAdvancedRepeatRules(repeatRule.advancedRules)

return [
`RRULE:FREQ=${repeatPeriodToIcalFrequency(assertEnumValue(RepeatPeriod, repeatRule.frequency))}` + `;INTERVAL=${repeatRule.interval}` + endType,
].concat(advancedRepeatRules, excludedDates)
`RRULE:FREQ=${repeatPeriodToIcalFrequency(assertEnumValue(RepeatPeriod, repeatRule.frequency))}` +
`;INTERVAL=${repeatRule.interval}` +
endType +
advancedRepeatRules.trim(),
].concat(excludedDates)
} else {
return []
}
Expand Down
3 changes: 1 addition & 2 deletions src/calendar-app/calendar/export/CalendarParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ import WindowsZones from "./WindowsZones"
import type { ParsedCalendarData } from "../../../common/calendar/import/CalendarImporter.js"
import { isMailAddress } from "../../../common/misc/FormatValidator"
import { CalendarAttendeeStatus, CalendarMethod, EndType, RepeatPeriod, reverse } from "../../../common/api/common/TutanotaConstants"
import { AlarmInterval, AlarmIntervalUnit } from "../../../common/calendar/date/CalendarUtils.js"
import { AlarmInterval, AlarmIntervalUnit, BYRULE_MAP } from "../../../common/calendar/date/CalendarUtils.js"
import { AlarmInfoTemplate } from "../../../common/api/worker/facades/lazy/CalendarFacade.js"
import { serializeAlarmInterval } from "../../../common/api/common/utils/CommonCalendarUtils.js"
import { BYRULE_MAP } from "../../../common/calendar/import/ImportExportUtils.js"

function parseDateString(dateString: string): {
year: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import stream from "mithril/stream"
import { Divider } from "../../../../common/gui/Divider.js"
import { theme } from "../../../../common/gui/theme.js"
import { isApp } from "../../../../common/api/common/Env.js"
import { ByRule } from "../../../../common/calendar/import/ImportExportUtils.js"
import { BannerType, InfoBanner, InfoBannerAttrs } from "../../../../common/gui/base/InfoBanner.js"
import { Icons } from "../../../../common/gui/base/icons/Icons.js"
import { ByRule } from "../../../../common/calendar/date/CalendarUtils.js"

export type RepeatRuleEditorAttrs = {
model: CalendarEventWhenModel
Expand Down
3 changes: 1 addition & 2 deletions src/calendar-app/calendar/gui/eventpopup/EventPreviewView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AllIcons, Icon, IconSize } from "../../../../common/gui/base/Icon.js"
import { theme } from "../../../../common/gui/theme.js"
import { BootIcons } from "../../../../common/gui/base/icons/BootIcons.js"
import { Icons } from "../../../../common/gui/base/icons/Icons.js"
import { getRepeatEndTimeForDisplay, getTimeZone } from "../../../../common/calendar/date/CalendarUtils.js"
import { ByRule, getRepeatEndTimeForDisplay, getTimeZone } from "../../../../common/calendar/date/CalendarUtils.js"
import { CalendarAttendeeStatus, EndType, getAttendeeStatus, RepeatPeriod } from "../../../../common/api/common/TutanotaConstants.js"
import { downcast, memoized } from "@tutao/tutanota-utils"
import { lang, TranslationKey } from "../../../../common/misc/LanguageViewModel.js"
Expand All @@ -27,7 +27,6 @@ import { ExternalLink } from "../../../../common/gui/base/ExternalLink.js"

import { createRepeatRuleFrequencyValues, formatEventDuration, getDisplayEventTitle, iconForAttendeeStatus } from "../CalendarGuiUtils.js"
import { hasError } from "../../../../common/api/common/utils/ErrorUtils.js"
import { ByRule } from "../../../../common/calendar/import/ImportExportUtils.js"

export type EventPreviewViewAttrs = {
event: Omit<CalendarEvent, "description">
Expand Down
102 changes: 58 additions & 44 deletions src/common/calendar/date/CalendarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
downcast,
filterInt,
findAllAndRemove,
freezeMap,
getFirstOrThrow,
getFromMap,
getStartOfDay,
Expand Down Expand Up @@ -51,7 +52,6 @@ import { CalendarEventUidIndexEntry } from "../../api/worker/facades/lazy/Calend
import { ParserError } from "../../misc/parsing/ParserCombinator.js"
import { LoginController } from "../../api/main/LoginController.js"
import { BirthdayEventRegistry } from "./CalendarEventsRepository.js"
import { ByRule } from "../import/ImportExportUtils.js"

export type CalendarTimeRange = {
start: number
Expand Down Expand Up @@ -180,45 +180,6 @@ const WEEKDAY_TO_NUMBER = {
SU: 7,
} as Record<string, WeekdayNumbers>

function applyMinuteRules(dates: DateTime[], parsedRules: CalendarAdvancedRepeatRule[]): DateTime[] {
if (parsedRules.length === 0) {
return dates
}

const newDates: DateTime[] = []
for (const date of dates) {
for (const rule of parsedRules) {
newDates.push(
date.set({
//FIXME Check if rule accepts negative values
minute: Number.parseInt(rule.interval),
}),
)
}
}

return newDates
}

function applyHourRules(dates: DateTime[], parsedRules: CalendarAdvancedRepeatRule[]) {
if (parsedRules.length === 0) {
return dates
}

const newDates: DateTime[] = []
for (const date of dates) {
for (const rule of parsedRules) {
newDates.push(
date.set({
hour: Number.parseInt(rule.interval),
}),
)
}
}

return newDates
}

function applyByDayRules(
dates: DateTime[],
parsedRules: CalendarAdvancedRepeatRule[],
Expand Down Expand Up @@ -1237,6 +1198,11 @@ function* generateEventOccurrences(event: CalendarEvent, timeZone: string, maxDa
let eventCount = 0

for (const event of events) {
if (iteration === 1 && event.toJSDate().getTime() === eventStartTime.getTime()) {
// Already yielded
continue
}

const newStartTime = event.toJSDate()
const newEndTime = allDay
? incrementByRepeatPeriod(newStartTime, RepeatPeriod.DAILY, calcDuration, repeatTimeZone)
Expand Down Expand Up @@ -1284,7 +1250,7 @@ export function calendarEventHasMoreThanOneOccurrencesLeft({ progenitor, altered
return true
} else {
// we need to count occurrences and match them up against altered instances & exclusions.
const excludedTimestamps = excludedDates.map(({ date }) => date.getTime())
const excludedTimestamps = excludedDates.map(({ date }) => date.getTime()).sort()
let i = 0
// in our model, we have an extra exclusion for each altered instance. this code
// assumes that this invariant is upheld here and does not match each recurrenceId
Expand Down Expand Up @@ -1345,9 +1311,9 @@ export function findNextAlarmOccurrence(
repeatRule: RepeatRule,
): AlarmOccurrence | null {
let occurrenceNumber = 0
const exclusions = repeatRule.excludedDates.map(({ date }) => date)
const isAllDayEvent = isAllDayEventByTimes(eventStart, eventEnd)
const calcEventStart = isAllDayEvent ? getAllDayDateForTimezone(eventStart, localTimeZone) : eventStart
const exclusions = repeatRule.excludedDates.map(({ date }) => date)
let calcEventStart = isAllDayEvent ? getAllDayDateForTimezone(eventStart, localTimeZone) : eventStart
assertDateIsValid(calcEventStart)

const endDate =
Expand Down Expand Up @@ -1375,7 +1341,7 @@ export function findNextAlarmOccurrence(
endTime: eventEnd,
repeatRule,
} as StrippedEntity<CalendarEvent>),
timeZone,
localTimeZone,
maxDate,
)

Expand Down Expand Up @@ -1675,3 +1641,51 @@ export function extractContactIdFromEvent(id: string | null | undefined): string

return decodeBase64("utf-8", id)
}

export enum ByRule {
BYMINUTE = "0",
BYHOUR = "1",
BYDAY = "2",
BYMONTHDAY = "3",
BYYEARDAY = "4",
BYWEEKNO = "5",
BYMONTH = "6",
BYSETPOS = "7",
WKST = "8",
}

export const BYRULE_MAP = freezeMap(
new Map([
["BYMINUTE", ByRule.BYMINUTE],
["BYHOUR", ByRule.BYHOUR],
["BYDAY", ByRule.BYDAY],
["BYMONTHDAY", ByRule.BYMONTHDAY],
["BYYEARDAY", ByRule.BYYEARDAY],
["BYWEEKNO", ByRule.BYWEEKNO],
["BYMONTH", ByRule.BYMONTH],
["BYSETPOS", ByRule.BYSETPOS],
["WKST", ByRule.WKST],
]),
)

export const enum WeekDaysJsValue {
SU,
MO,
TU,
WE,
TH,
FR,
SA,
}

export const BYRULE_WEEKDAYS_JS_VALUE = freezeMap(
new Map([
["SU", WeekDaysJsValue.SU],
["MO", WeekDaysJsValue.MO],
["TU", WeekDaysJsValue.TU],
["WE", WeekDaysJsValue.WE],
["TH", WeekDaysJsValue.TH],
["FR", WeekDaysJsValue.FR],
["SA", WeekDaysJsValue.SA],
]),
)
50 changes: 1 addition & 49 deletions src/common/calendar/import/ImportExportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CalendarEvent, CalendarGroupRoot } from "../../api/entities/tutanota/Ty
import type { AlarmInfoTemplate } from "../../api/worker/facades/lazy/CalendarFacade.js"
import { assignEventId, CalendarEventValidity, checkEventValidity, getTimeZone } from "../date/CalendarUtils.js"
import { ParsedCalendarData, ParsedEvent } from "./CalendarImporter.js"
import { freezeMap, getFromMap, groupBy, insertIntoSortedArray } from "@tutao/tutanota-utils"
import { getFromMap, groupBy, insertIntoSortedArray } from "@tutao/tutanota-utils"
import { generateEventElementId } from "../../api/common/utils/CommonCalendarUtils.js"
import { createDateWrapper } from "../../api/entities/sys/TypeRefs.js"
import { parseCalendarEvents, parseICalendar } from "../../../calendar-app/calendar/export/CalendarParser.js"
Expand Down Expand Up @@ -152,51 +152,3 @@ export function checkURLString(url: string): TranslationKey | URL {
export function hasValidProtocol(url: URL, validProtocols: string[]) {
return validProtocols.includes(url.protocol)
}

export enum ByRule {
BYMINUTE = "0",
BYHOUR = "1",
BYDAY = "2",
BYMONTHDAY = "3",
BYYEARDAY = "4",
BYWEEKNO = "5",
BYMONTH = "6",
BYSETPOS = "7",
WKST = "8",
}

export const BYRULE_MAP = freezeMap(
new Map([
["BYMINUTE", ByRule.BYMINUTE],
["BYHOUR", ByRule.BYHOUR],
["BYDAY", ByRule.BYDAY],
["BYMONTHDAY", ByRule.BYMONTHDAY],
["BYYEARDAY", ByRule.BYYEARDAY],
["BYWEEKNO", ByRule.BYWEEKNO],
["BYMONTH", ByRule.BYMONTH],
["BYSETPOS", ByRule.BYSETPOS],
["WKST", ByRule.WKST],
]),
)

export const enum WeekDaysJsValue {
SU,
MO,
TU,
WE,
TH,
FR,
SA,
}

export const BYRULE_WEEKDAYS_JS_VALUE = freezeMap(
new Map([
["SU", WeekDaysJsValue.SU],
["MO", WeekDaysJsValue.MO],
["TU", WeekDaysJsValue.TU],
["WE", WeekDaysJsValue.WE],
["TH", WeekDaysJsValue.TH],
["FR", WeekDaysJsValue.FR],
["SA", WeekDaysJsValue.SA],
]),
)
2 changes: 1 addition & 1 deletion src/common/native/main/NativePushServiceApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { AppType } from "../../misc/ClientConstants.js"
// keep in sync with SYS_MODEL_VERSION in app-android/app/build.gradle
// keep in sync with SYS_MODEL_VERSION in app-android/calendar/build.gradle.kts
// keep in sync with app-ios/TutanotaSharedFramework/Utils/Utils.swift
const MOBILE_SYS_MODEL_VERSION = 118
const MOBILE_SYS_MODEL_VERSION = 119

function effectiveModelVersion(): number {
// on desktop we use generated classes
Expand Down
1 change: 1 addition & 0 deletions src/mail-app/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,7 @@ export default {
"lastOfPeriod_label": "Am letzten {day} des Monats",
"withCustomRules_label": "Mit benutzerdefinierten Wiederholungsregeln",
"unsupportedAdvancedRules_msg": "Dieses Ereignis enthält eine oder mehrere nicht unterstützte erweiterte Wiederholungsregeln; jede Änderung führt zum Verlust dieser Regeln.",
"onDays_label": "Am {days}",
// Put in temporarily, will be removed soon
"localAdminGroup_label": "Local admin group",
"assignAdminRightsToLocallyAdministratedUserError_msg": "You can't assign global admin rights to a locally administrated user.",
Expand Down
Loading

0 comments on commit 4ff0a1d

Please sign in to comment.