Skip to content
Merged
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
47 changes: 47 additions & 0 deletions app/src/main/java/com/gatecontrol/android/mail/MailApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,53 @@ class MailApi @Inject constructor(

suspend fun dismissInvitation(id: Long, decline: Boolean): MResult<Unit> = post("invitations/$id/dismiss", MailPimJson.dismissBody(decline), timeoutMs = 60_000L, parse = unit)

// ─── free/busy, sharing, resources (since Mail 1.9.0) ───────────

/** `POST freebusy`: the day's free/busy of the person, [attendees] and [resources] (never titles). */
suspend fun freeBusy(from: Long, to: Long, attendees: List<String>, resources: List<Long>): MResult<MFreeBusy> =
post("freebusy", MailPimJson.freeBusyBody(from, to, attendees, resources), parse = MailPimJson::freeBusy)

/** `POST freebusy/suggest`: up to [limit] slots of [duration] minutes from [from] when everybody known is free. */
suspend fun suggest(duration: Int, from: Long?, attendees: List<String>, resources: List<Long>, limit: Int = 4): MResult<MSuggestions> =
post("freebusy/suggest", MailPimJson.suggestBody(duration, from, attendees, resources, limit), parse = MailPimJson::suggestions)

suspend fun shares(calendarId: Long): MResult<MShares> = get("calendars/$calendarId/shares", parse = MailPimJson::shares)

suspend fun addShare(calendarId: Long, type: String, principalId: Long, level: String): MResult<MShare> =
post("calendars/$calendarId/shares", MailPimJson.shareBody(type, principalId, level), parse = MailPimJson::shareAnswer)

suspend fun patchShare(calendarId: Long, shareId: Long, level: String): MResult<MShare> =
patch("calendars/$calendarId/shares/$shareId", MailPimJson.levelBody(level), parse = MailPimJson::shareAnswer)

suspend fun deleteShare(calendarId: Long, shareId: Long): MResult<Unit> = del("calendars/$calendarId/shares/$shareId", unit)

suspend fun shared(): MResult<MSharedList> = get("shared", parse = MailPimJson::sharedList)

suspend fun patchShared(calendarId: Long, visible: Boolean? = null, color: String? = null): MResult<MSharedCalendar> =
patch("shared/$calendarId", MailPimJson.sharedPatch(visible, color), parse = MailPimJson::sharedAnswer)

/** People (who may use Mail) and groups by name, to share with ([q] ≥ 2 characters). */
suspend fun principals(q: String): MResult<List<MPrincipal>> = get("principals", mapOf("q" to q.trim().take(60)), MailPimJson::principals)

/** `GET resources` (with [from]/[to]: each one's taken times in it, ≤ 31 days). */
suspend fun resources(from: Long? = null, to: Long? = null): MResult<MResources> =
get("resources", buildMap {
if (from != null && to != null) {
put("from", from.toString())
put("to", to.toString())
}
}, MailPimJson::resources)

suspend fun book(resourceId: Long, body: JsonObject): MResult<MEventSaved> = post("resources/$resourceId/book", body, timeoutMs = 60_000L, parse = MailPimJson::eventSaved)

/** `GET bookings?state=open|done|mine`. */
suspend fun bookings(state: String): MResult<MBookings> = get("bookings", mapOf("state" to state), MailPimJson::bookings)

suspend fun decideBooking(id: Long, approve: Boolean, override: Boolean = false, comment: String? = null): MResult<MBooking> =
post("bookings/$id/decide", MailPimJson.decideBody(approve, override, comment), timeoutMs = 60_000L, parse = MailPimJson::bookingAnswer)

suspend fun cancelBooking(id: Long): MResult<Unit> = del("bookings/$id", unit)

// ─── contacts (since Mail 1.2.0, path `cards`) ──────────────────

suspend fun addressbooks(): MResult<Pair<MBooks, JsonObject>> = get("addressbooks") { o -> MailPimJson.books(o)?.let { it to o } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ data class MCalendar(
val errorCode: String?,
val syncedAt: Long?,
val version: Long,
/** Since Mail 1.9.0: a Microsoft 365 calendar of another mailbox the account sees (its owner's address). */
val sharedBy: String? = null,
) {
val local: Boolean get() = source == SOURCE_LOCAL

Expand Down Expand Up @@ -77,6 +79,10 @@ data class MCalPrefs(
val defaultReminder: Int?,
/** Minutes before midnight of an all-day event (360 → 18:00 the day before, 1080 → 06:00 the day before), null: none. */
val allDayReminder: Int?,
/** Since Mail 1.9.0: „Frei/Gebucht für alle in GateControl“ (default on). */
val freeBusy: Boolean = true,
/** Since Mail 1.9.0: the working hours (free/busy's window and the suggestions). */
val workHours: MWorkHours = MWorkHours.DEFAULT,
) {
companion object {
const val VIEW_DAY = "day"
Expand All @@ -92,9 +98,17 @@ data class MCalPrefs(
/** A Microsoft account whose calendar needs the person to sign in again. */
data class MConsent(val accountId: Long, val missing: List<String>)

/** `GET calendars`. */
data class MCalendars(val calendars: List<MCalendar>, val overlays: List<MOverlay>, val prefs: MCalPrefs, val consent: List<MConsent>) {
/** `GET calendars` (since Mail 1.9.0 also the GateControl calendars shared with the person, [shared]). */
data class MCalendars(
val calendars: List<MCalendar>,
val overlays: List<MOverlay>,
val prefs: MCalPrefs,
val consent: List<MConsent>,
val shared: List<MSharedCalendar> = emptyList(),
) {
fun calendar(id: Long?): MCalendar? = id?.let { c -> calendars.firstOrNull { it.id == c } }

fun sharedCalendar(id: Long?): MSharedCalendar? = id?.let { c -> shared.firstOrNull { it.id == c } }
}

data class MOnline(val url: String, /** teams | zoom | meet | webex | other */ val provider: String)
Expand Down Expand Up @@ -127,8 +141,15 @@ data class MOccurrence(
val attendees: Int,
val private: Boolean,
val reminder: Int?,
/**
* Since Mail 1.9.0: an entry of a calendar shared with the person — `freebusy` | `read` | `write`; null: an own one.
* Free/busy and private entries come as „Gebucht“ / „Vorläufig“ without an event ([eventId] 0, never opened).
*/
val shared: String? = null,
) {
val cancelled: Boolean get() = status == STATUS_CANCELLED
/** Only a time („Gebucht“ of a shared calendar): nothing to open. */
val busyOnly: Boolean get() = eventId <= 0L
/** An invitation not answered yet (mockup: dashed). */
val open: Boolean get() = myResponse == RESPONSE_NEEDS_ACTION

Expand Down Expand Up @@ -258,8 +279,8 @@ data class MSeries(val start: Long, val end: Long, val allDay: Boolean, val star
/** The new times of a series moved from one of its occurrences ([MailPimLogic.shiftSeries]). */
data class MShift(val start: Long? = null, val end: Long? = null, val startDate: String? = null, val endDate: String? = null)

/** `POST/PATCH events` → `{ event, sent }`. */
data class MEventSaved(val event: MEvent, val sentMail: Int, val sentInternal: Int)
/** `POST/PATCH events` → `{ event, sent }` (since Mail 1.9.0 with resources among the attendees also their [bookings]). */
data class MEventSaved(val event: MEvent, val sentMail: Int, val sentInternal: Int, val bookings: List<MBooking> = emptyList())

data class MInviteCounts(val accepted: Int, val tentative: Int, val declined: Int, val needsAction: Int) {
val total: Int get() = accepted + tentative + declined + needsAction
Expand Down Expand Up @@ -365,11 +386,15 @@ data class MCalChanged(
val invitationId: Long? = null,
val messageId: Long? = null,
val eventId: Long? = null,
/** Since Mail 1.9.0: a change in a calendar shared with the person. */
val shared: Boolean = false,
) {
companion object {
const val CHANGED = "changed"
const val CALENDARS = "calendars"
const val INVITATION = "invitation"
const val CONFLICT = "conflict"
/** Since Mail 1.9.0: the shares of a calendar changed (its owner and the people it is shared with). */
const val SHARES = "shares"
}
}
5 changes: 5 additions & 0 deletions app/src/main/java/com/gatecontrol/android/mail/MailModels.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ data class MMe(
/** Since Mail 1.2.0: calendars and contacts (the administrator switched them on). */
const val FEATURE_CALENDAR = "calendar"
const val FEATURE_CONTACTS = "contacts"
/** Since Mail 1.9.0: free/busy in the event form, sharing GateControl calendars, resources, deciding requests. */
const val FEATURE_FREE_BUSY = "freeBusy"
const val FEATURE_SHARING = "calendarSharing"
const val FEATURE_RESOURCES = "resources"
const val FEATURE_BOOKING_REQUESTS = "bookingRequests"
}
}

Expand Down
Loading
Loading