Skip to content

Commit 1962587

Browse files
Othmane BencheqrounOthmane Bencheqroun
Othmane Bencheqroun
authored and
Othmane Bencheqroun
committed
feat(model): Add model classes for calendar functionality
- Implement model classes to support calendar feature integration - Define necessary structures for handling iCal data and event processing
1 parent ebc2112 commit 1962587

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.github.lookupgroup27.lookup.model.calendar
2+
3+
import android.util.Log
4+
import androidx.compose.runtime.mutableStateListOf
5+
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.viewModelScope
7+
import java.io.StringReader
8+
import java.util.Calendar as JavaCalendar
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.launch
11+
import kotlinx.coroutines.withContext
12+
import net.fortuna.ical4j.data.CalendarBuilder
13+
import net.fortuna.ical4j.model.Calendar
14+
import net.fortuna.ical4j.model.Date
15+
import net.fortuna.ical4j.model.DateTime
16+
import net.fortuna.ical4j.model.Period
17+
import net.fortuna.ical4j.model.Property
18+
import net.fortuna.ical4j.model.component.VEvent
19+
import net.fortuna.ical4j.model.property.DtEnd
20+
import net.fortuna.ical4j.model.property.DtStart
21+
import net.fortuna.ical4j.model.property.RRule
22+
import net.fortuna.ical4j.util.MapTimeZoneCache
23+
import okhttp3.OkHttpClient
24+
import okhttp3.Request
25+
26+
class CalendarViewModel : ViewModel() {
27+
28+
private val client = OkHttpClient()
29+
private val _icalEvents = mutableStateListOf<VEvent>()
30+
val icalEvents: List<VEvent> = _icalEvents
31+
32+
init {
33+
System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache::class.java.name)
34+
fetchICalData()
35+
}
36+
37+
fun fetchICalData() {
38+
viewModelScope.launch {
39+
val icalData =
40+
fetchIcalFromUrl(
41+
"https://p127-caldav.icloud.com/published/2/MTE0OTM4OTk2MTExNDkzOIzDRaBjEGa9_1mlmgjzcdlka5HK6EzMiIdOswU-0rZBZMDBibtH_M7CDyMpDQRQJPdGOSM0hTsS2qFNGOObsTc")
42+
icalData?.let { parseICalData(it) }
43+
}
44+
}
45+
46+
private suspend fun fetchIcalFromUrl(url: String): String? =
47+
withContext(Dispatchers.IO) {
48+
return@withContext try {
49+
val request = Request.Builder().url(url).build()
50+
val response = client.newCall(request).execute()
51+
response.body?.string()
52+
} catch (e: Exception) {
53+
Log.e("CalendarViewModel", "Error fetching iCal data: ${e.localizedMessage}", e)
54+
null
55+
}
56+
}
57+
58+
private fun parseICalData(icalData: String) {
59+
try {
60+
val reader = StringReader(icalData)
61+
val calendar: Calendar = CalendarBuilder().build(reader)
62+
val start = DateTime(System.currentTimeMillis())
63+
val end = DateTime(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000)
64+
val period = Period(start, end)
65+
66+
val allEvents = mutableListOf<VEvent>()
67+
68+
for (component in calendar.getComponents<VEvent>(VEvent.VEVENT)) {
69+
val dtStart = component.getProperty<DtStart>(Property.DTSTART)
70+
val dtEnd = component.getProperty<DtEnd>(Property.DTEND)
71+
72+
dtStart?.let { startDate ->
73+
val endDate = dtEnd?.date ?: startDate.date
74+
val rrule = component.getProperty<RRule>(Property.RRULE)
75+
76+
if (rrule != null) {
77+
handleRecurringEvents(component, period, startDate.date, endDate, allEvents)
78+
} else if (startDate.date.before(end) && endDate.after(start)) {
79+
handleNonRecurringEvents(component, startDate.date, endDate, allEvents)
80+
}
81+
}
82+
}
83+
84+
_icalEvents.clear()
85+
_icalEvents.addAll(allEvents)
86+
Log.d("CalendarViewModel", "Total events parsed: ${_icalEvents.size}")
87+
} catch (e: Exception) {
88+
Log.e("CalendarViewModel", "Error parsing iCal data: ${e.localizedMessage}", e)
89+
}
90+
}
91+
92+
private fun handleRecurringEvents(
93+
component: VEvent,
94+
period: Period,
95+
eventStartDate: Date,
96+
eventEndDate: Date,
97+
allEvents: MutableList<VEvent>
98+
) {
99+
val recurrenceSet = component.calculateRecurrenceSet(period)
100+
for (recurrence in recurrenceSet) {
101+
val eventInstance = component.copy() as VEvent
102+
eventInstance.getProperty<DtStart>(Property.DTSTART)?.date = recurrence.start
103+
if (eventEndDate.after(recurrence.start)) {
104+
var current = recurrence.start
105+
while (current.before(eventEndDate)) {
106+
val multiDayEventInstance = eventInstance.copy() as VEvent
107+
multiDayEventInstance.getProperty<DtStart>(Property.DTSTART)?.date =
108+
DateTime(current.time)
109+
allEvents.add(multiDayEventInstance)
110+
111+
val calendarInstance =
112+
JavaCalendar.getInstance().apply {
113+
time = current
114+
add(JavaCalendar.DATE, 1)
115+
}
116+
current = DateTime(calendarInstance.time)
117+
}
118+
} else {
119+
allEvents.add(eventInstance)
120+
}
121+
}
122+
}
123+
124+
private fun handleNonRecurringEvents(
125+
component: VEvent,
126+
eventStartDate: Date,
127+
eventEndDate: Date,
128+
allEvents: MutableList<VEvent>
129+
) {
130+
if (eventEndDate.after(eventStartDate)) {
131+
var current = eventStartDate
132+
while (current.before(eventEndDate)) {
133+
val eventInstance = component.copy() as VEvent
134+
eventInstance.getProperty<DtStart>(Property.DTSTART)?.date = DateTime(current.time)
135+
allEvents.add(eventInstance)
136+
137+
val calendarInstance =
138+
JavaCalendar.getInstance().apply {
139+
time = current
140+
add(JavaCalendar.DATE, 1)
141+
}
142+
current = DateTime(calendarInstance.time)
143+
}
144+
} else {
145+
allEvents.add(component)
146+
}
147+
}
148+
}

0 commit comments

Comments
 (0)