Skip to content

Commit 3c40556

Browse files
fix: switching between local and google calendars. Google calendars options.
1 parent 2f1d938 commit 3c40556

File tree

11 files changed

+389
-68
lines changed

11 files changed

+389
-68
lines changed

app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.openedx.core.data.storage.CalendarPreferences
88
import org.openedx.core.data.storage.CorePreferences
99
import org.openedx.core.data.storage.InAppReviewPreferences
1010
import org.openedx.core.domain.model.AppConfig
11+
import org.openedx.core.domain.model.CalendarType
1112
import org.openedx.core.domain.model.VideoQuality
1213
import org.openedx.core.domain.model.VideoSettings
1314
import org.openedx.core.system.CalendarManager
@@ -69,6 +70,7 @@ class PreferencesManager(context: Context) :
6970
override fun clearCalendarPreferences() {
7071
sharedPreferences.edit().apply {
7172
remove(CALENDAR_ID)
73+
remove(CALENDAR_TYPE)
7274
remove(IS_CALENDAR_SYNC_ENABLED)
7375
remove(HIDE_INACTIVE_COURSES)
7476
}.apply()
@@ -104,6 +106,17 @@ class PreferencesManager(context: Context) :
104106
}
105107
get() = getLong(CALENDAR_ID, CalendarManager.CALENDAR_DOES_NOT_EXIST)
106108

109+
override var calendarType: CalendarType
110+
set(value) {
111+
saveString(CALENDAR_TYPE, value.name)
112+
}
113+
get() {
114+
val storedType = getString(CALENDAR_TYPE, CalendarType.LOCAL.name)
115+
return runCatching {
116+
CalendarType.valueOf(storedType)
117+
}.getOrDefault(CalendarType.LOCAL)
118+
}
119+
107120
override var user: User?
108121
set(value) {
109122
val userJson = Gson().toJson(value)
@@ -234,6 +247,7 @@ class PreferencesManager(context: Context) :
234247
private const val VIDEO_SETTINGS_DOWNLOAD_QUALITY = "video_settings_download_quality"
235248
private const val APP_CONFIG = "app_config"
236249
private const val CALENDAR_ID = "CALENDAR_ID"
250+
private const val CALENDAR_TYPE = "CALENDAR_TYPE"
237251
private const val RESET_APP_DIRECTORY = "reset_app_directory"
238252
private const val IS_CALENDAR_SYNC_ENABLED = "IS_CALENDAR_SYNC_ENABLED"
239253
private const val IS_RELATIVE_DATES_ENABLED = "IS_RELATIVE_DATES_ENABLED"

auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import org.openedx.core.config.MicrosoftConfig
3737
import org.openedx.core.data.storage.CalendarPreferences
3838
import org.openedx.core.data.storage.CorePreferences
3939
import org.openedx.core.domain.interactor.CalendarInteractor
40+
import org.openedx.core.domain.model.CalendarType
4041
import org.openedx.core.presentation.global.WhatsNewGlobalManager
4142
import org.openedx.core.system.EdxError
4243
import org.openedx.core.system.notifier.app.AppNotifier
@@ -91,6 +92,7 @@ class SignInViewModelTest {
9192
every { config.getGoogleConfig() } returns GoogleConfig()
9293
every { config.getMicrosoftConfig() } returns MicrosoftConfig()
9394
every { calendarPreferences.calendarUser } returns ""
95+
every { calendarPreferences.calendarType } returns CalendarType.LOCAL
9496
every { calendarPreferences.clearCalendarPreferences() } returns Unit
9597
coEvery { calendarInteractor.clearCalendarCachedData() } returns Unit
9698
every { analytics.logScreenEvent(any(), any()) } returns Unit

core/src/main/java/org/openedx/core/data/storage/CalendarPreferences.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.openedx.core.data.storage
22

3+
import org.openedx.core.domain.model.CalendarType
4+
35
interface CalendarPreferences {
46
var calendarId: Long
57
var calendarUser: String
8+
var calendarType: CalendarType
69
var isCalendarSyncEnabled: Boolean
710
var isHideInactiveCourses: Boolean
811

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.openedx.core.domain.model
2+
3+
enum class CalendarType {
4+
LOCAL,
5+
GOOGLE,
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.openedx.core.domain.model
2+
3+
data class UserCalendar(
4+
val id: Long,
5+
val title: String,
6+
val color: Int
7+
)

core/src/main/java/org/openedx/core/system/CalendarManager.kt

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.openedx.core.system
33
import android.content.ContentUris
44
import android.content.ContentValues
55
import android.content.Context
6+
import android.content.Intent
67
import android.content.pm.PackageManager
78
import android.database.Cursor
89
import android.net.Uri
@@ -15,7 +16,9 @@ import kotlinx.coroutines.delay
1516
import kotlinx.coroutines.runBlocking
1617
import org.openedx.core.data.storage.CorePreferences
1718
import org.openedx.core.domain.model.CalendarData
19+
import org.openedx.core.domain.model.CalendarType
1820
import org.openedx.core.domain.model.CourseDateBlock
21+
import org.openedx.core.domain.model.UserCalendar
1922
import org.openedx.core.utils.Logger
2023
import org.openedx.core.utils.toCalendar
2124
import java.util.TimeZone
@@ -63,28 +66,36 @@ class CalendarManager(
6366
fun createOrUpdateCalendar(
6467
calendarId: Long = CALENDAR_DOES_NOT_EXIST,
6568
calendarTitle: String,
66-
calendarColor: Long
69+
calendarColor: Long,
70+
calendarType: CalendarType
6771
): Long {
68-
if (calendarId != CALENDAR_DOES_NOT_EXIST) {
72+
if (calendarId != CALENDAR_DOES_NOT_EXIST && calendarType == CalendarType.LOCAL) {
6973
deleteCalendar(calendarId = calendarId)
7074
}
7175

7276
return createCalendar(
7377
calendarTitle = calendarTitle,
74-
calendarColor = calendarColor
78+
calendarColor = calendarColor,
79+
calendarType = calendarType
7580
)
7681
}
7782

7883
private fun createCalendar(
7984
calendarTitle: String,
80-
calendarColor: Long
85+
calendarColor: Long,
86+
calendarType: CalendarType
8187
): Long {
82-
val existingGoogleCalendar = findOrCreateGoogleCalendar()
83-
if (existingGoogleCalendar != CALENDAR_DOES_NOT_EXIST) {
84-
return existingGoogleCalendar
88+
if (calendarType == CalendarType.GOOGLE) {
89+
val existingGoogleCalendar = findOrCreateGoogleCalendar()
90+
if (existingGoogleCalendar != CALENDAR_DOES_NOT_EXIST) {
91+
return existingGoogleCalendar
92+
}
8593
}
8694

87-
val calendarAccount = getCalendarOwnerAccount()
95+
val calendarAccount = when (calendarType) {
96+
CalendarType.LOCAL -> CalendarAccount(accountName, CalendarContract.ACCOUNT_TYPE_LOCAL)
97+
CalendarType.GOOGLE -> getCalendarOwnerAccount()
98+
}
8899
val contentValues = ContentValues().apply {
89100
put(CalendarContract.Calendars.NAME, calendarTitle)
90101
put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, calendarTitle)
@@ -177,6 +188,62 @@ class CalendarManager(
177188
}
178189
}
179190

191+
fun getGoogleCalendars(): List<UserCalendar> {
192+
val projection = arrayOf(
193+
CalendarContract.Calendars._ID,
194+
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
195+
CalendarContract.Calendars.CALENDAR_COLOR
196+
)
197+
val selection = "${CalendarContract.Calendars.ACCOUNT_TYPE} = ? AND " +
198+
"${CalendarContract.Calendars.SYNC_EVENTS} = 1 AND " +
199+
"${CalendarContract.Calendars.VISIBLE} = 1 AND " +
200+
"${CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL} >= ?"
201+
val selectionArgs = arrayOf(
202+
GOOGLE_ACCOUNT_TYPE,
203+
CalendarContract.Calendars.CAL_ACCESS_CONTRIBUTOR.toString()
204+
)
205+
val sortOrder =
206+
"${CalendarContract.Calendars.IS_PRIMARY} DESC, ${CalendarContract.Calendars.CALENDAR_DISPLAY_NAME} ASC"
207+
208+
return try {
209+
val cursor = context.contentResolver.query(
210+
CalendarContract.Calendars.CONTENT_URI,
211+
projection,
212+
selection,
213+
selectionArgs,
214+
sortOrder
215+
)
216+
217+
cursor?.use {
218+
val idIndex = it.getColumnIndexOrThrow(CalendarContract.Calendars._ID)
219+
val titleIndex =
220+
it.getColumnIndexOrThrow(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME)
221+
val colorIndex = it.getColumnIndexOrThrow(CalendarContract.Calendars.CALENDAR_COLOR)
222+
223+
buildList {
224+
while (it.moveToNext()) {
225+
add(
226+
UserCalendar(
227+
id = it.getLong(idIndex),
228+
title = it.getString(titleIndex),
229+
color = it.getInt(colorIndex)
230+
)
231+
)
232+
}
233+
}
234+
} ?: emptyList()
235+
} catch (e: SecurityException) {
236+
logger.d { "Failed to load Google calendars: ${e.message}" }
237+
emptyList()
238+
}
239+
}
240+
241+
fun hasAlternativeCalendarApp(): Boolean {
242+
val intent = Intent(Intent.ACTION_INSERT).setData(CalendarContract.Events.CONTENT_URI)
243+
val activities = context.packageManager.queryIntentActivities(intent, 0)
244+
return activities.any { it.activityInfo.packageName != GOOGLE_CALENDAR_PACKAGE }
245+
}
246+
180247
fun addEventsIntoCalendar(
181248
calendarId: Long,
182249
courseId: String,
@@ -507,6 +574,7 @@ class CalendarManager(
507574
private const val TAG = "CalendarManager"
508575
private const val LOCAL_USER = "local_user"
509576
private const val GOOGLE_ACCOUNT_TYPE = "com.google"
577+
private const val GOOGLE_CALENDAR_PACKAGE = "com.google.android.calendar"
510578

511579
private const val ACTION_RETRY_DELAY = 500L
512580
private const val EVENT_ATTEMPTS = 3

profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,16 @@ class CalendarViewModel(
124124
}
125125

126126
private fun getCalendarData() {
127-
if (calendarManager.hasPermissions()) {
128-
val calendarData = calendarManager.getCalendarData(calendarId = calendarPreferences.calendarId)
129-
_uiState.update { it.copy(calendarData = calendarData) }
127+
if (!calendarManager.hasPermissions()) return
128+
129+
val calendarId = calendarPreferences.calendarId
130+
if (calendarId == CalendarManager.CALENDAR_DOES_NOT_EXIST) {
131+
_uiState.update { it.copy(calendarData = null) }
132+
return
130133
}
134+
135+
val calendarData = calendarManager.getCalendarData(calendarId = calendarId)
136+
_uiState.update { it.copy(calendarData = calendarData) }
131137
}
132138

133139
private fun updateSyncedCoursesCount() {

profile/src/main/java/org/openedx/profile/presentation/calendar/DisableCalendarSyncDialogViewModel.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.coroutines.launch
99
import kotlinx.coroutines.withContext
1010
import org.openedx.core.data.storage.CalendarPreferences
1111
import org.openedx.core.domain.interactor.CalendarInteractor
12+
import org.openedx.core.domain.model.CalendarType
1213
import org.openedx.core.system.CalendarManager
1314
import org.openedx.core.system.notifier.calendar.CalendarNotifier
1415
import org.openedx.core.system.notifier.calendar.CalendarSyncDisabled
@@ -34,7 +35,10 @@ class DisableCalendarSyncDialogViewModel(
3435
calendarManager.deleteEvents(eventIds)
3536
_deletionState.value = DeletionState.DELETED
3637
calendarInteractor.clearCalendarCachedData()
37-
calendarManager.deleteCalendar(calendarPreferences.calendarId)
38+
val calendarId = calendarPreferences.calendarId
39+
if (calendarPreferences.calendarType == CalendarType.LOCAL) {
40+
calendarManager.deleteCalendar(calendarId)
41+
}
3842
calendarPreferences.clearCalendarPreferences()
3943
calendarNotifier.send(CalendarSyncDisabled)
4044
}

0 commit comments

Comments
 (0)