Skip to content

Commit

Permalink
feat: Notifications Preferences Screen
Browse files Browse the repository at this point in the history
Fixes: LEARNER-10345
  • Loading branch information
HamzaIsrar12 committed Jan 8, 2025
1 parent dfc37d6 commit 8ea50d1
Show file tree
Hide file tree
Showing 28 changed files with 549 additions and 16 deletions.
3 changes: 2 additions & 1 deletion app/src/main/java/org/openedx/app/AnalyticsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.openedx.course.presentation.CourseAnalytics
import org.openedx.dashboard.presentation.DashboardAnalytics
import org.openedx.discovery.presentation.DiscoveryAnalytics
import org.openedx.discussion.presentation.DiscussionAnalytics
import org.openedx.notifications.presentation.NotificationsAnalytics
import org.openedx.profile.presentation.ProfileAnalytics
import org.openedx.whatsnew.presentation.WhatsNewAnalytics

Expand All @@ -22,7 +23,7 @@ class AnalyticsManager(
config: Config,
) : AppAnalytics, AppReviewAnalytics, AuthAnalytics, CoreAnalytics, CourseAnalytics,
DashboardAnalytics, DiscoveryAnalytics, DiscussionAnalytics, ProfileAnalytics,
WhatsNewAnalytics, IAPAnalytics {
WhatsNewAnalytics, IAPAnalytics, NotificationsAnalytics {

private val services: ArrayList<Analytics> = arrayListOf()

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import org.openedx.discussion.presentation.search.DiscussionSearchThreadFragment
import org.openedx.discussion.presentation.threads.DiscussionAddThreadFragment
import org.openedx.discussion.presentation.threads.DiscussionThreadsFragment
import org.openedx.notifications.presentation.inbox.NotificationsInboxFragment
import org.openedx.notifications.presentation.settings.NotificationsSettingsFragment
import org.openedx.profile.domain.model.Account
import org.openedx.profile.presentation.ProfileRouter
import org.openedx.profile.presentation.anothersaccount.AnothersProfileFragment
Expand Down Expand Up @@ -425,6 +426,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, VideoSettingsFragment())
}

override fun navigateToPushNotificationsSettings(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, NotificationsSettingsFragment())
}

override fun navigateToVideoQuality(fm: FragmentManager, videoQualityType: VideoQualityType) {
replaceFragmentWithBackStack(fm, VideoQualityFragment.newInstance(videoQualityType.name))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import org.openedx.core.domain.model.VideoQuality
import org.openedx.core.domain.model.VideoSettings
import org.openedx.core.extension.replaceSpace
import org.openedx.course.data.storage.CoursePreferences
import org.openedx.notifications.data.storage.NotificationsPreferences
import org.openedx.notifications.domain.model.NotificationsConfiguration
import org.openedx.profile.data.model.Account
import org.openedx.profile.data.storage.ProfilePreferences
import org.openedx.whatsnew.data.storage.WhatsNewPreferences

class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences,
WhatsNewPreferences, InAppReviewPreferences, CoursePreferences {
WhatsNewPreferences, InAppReviewPreferences, CoursePreferences, NotificationsPreferences {

private val sharedPreferences =
context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE)
Expand Down Expand Up @@ -186,6 +188,17 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
override fun isCalendarSyncEventsDialogShown(courseName: String): Boolean =
getBoolean(courseName.replaceSpace("_"))

override var notifications: NotificationsConfiguration
set(value) {
val notificationsJson = Gson().toJson(value)
saveString(NOTIFICATIONS_CONFIGURATION, notificationsJson)
}
get() {
val notificationsString = getString(NOTIFICATIONS_CONFIGURATION)
return Gson().fromJson(notificationsString, NotificationsConfiguration::class.java)
?: NotificationsConfiguration.default
}

companion object {
private const val ACCESS_TOKEN = "access_token"
private const val REFRESH_TOKEN = "refresh_token"
Expand All @@ -203,5 +216,6 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
private const val APP_CONFIG = "app_config"
private const val RESET_APP_DIRECTORY = "reset_app_directory"
private const val LAST_SIGN_IN_TYPE = "last_sign_in_type"
private const val NOTIFICATIONS_CONFIGURATION = "notifications_configuration"
}
}
6 changes: 5 additions & 1 deletion app/src/main/java/org/openedx/app/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRouter
import org.openedx.core.system.AppCookieManager
import org.openedx.core.system.CalendarManager
import org.openedx.core.system.PushGlobalManager
import org.openedx.core.system.notifier.PushNotifier
import org.openedx.core.system.ResourceManager
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.CourseNotifier
import org.openedx.core.system.notifier.DiscoveryNotifier
import org.openedx.core.system.notifier.DownloadNotifier
import org.openedx.core.system.notifier.IAPNotifier
import org.openedx.core.system.notifier.PushNotifier
import org.openedx.core.system.notifier.VideoNotifier
import org.openedx.core.system.notifier.app.AppNotifier
import org.openedx.core.utils.FileUtil
Expand All @@ -68,6 +68,8 @@ import org.openedx.discussion.presentation.DiscussionAnalytics
import org.openedx.discussion.presentation.DiscussionRouter
import org.openedx.discussion.system.notifier.DiscussionNotifier
import org.openedx.notifications.PushManager
import org.openedx.notifications.data.storage.NotificationsPreferences
import org.openedx.notifications.presentation.NotificationsAnalytics
import org.openedx.profile.data.storage.ProfilePreferences
import org.openedx.profile.presentation.ProfileAnalytics
import org.openedx.profile.presentation.ProfileRouter
Expand All @@ -87,6 +89,7 @@ val appModule = module {
single<WhatsNewPreferences> { get<PreferencesManager>() }
single<InAppReviewPreferences> { get<PreferencesManager>() }
single<CoursePreferences> { get<PreferencesManager>() }
single<NotificationsPreferences> { get<PreferencesManager>() }

single { ResourceManager(get()) }
single { AppCookieManager(get(), get()) }
Expand Down Expand Up @@ -207,6 +210,7 @@ val appModule = module {
single<ProfileAnalytics> { get<AnalyticsManager>() }
single<WhatsNewAnalytics> { get<AnalyticsManager>() }
single<IAPAnalytics> { get<AnalyticsManager>() }
single<NotificationsAnalytics> { get<AnalyticsManager>() }

single { PushManager(get()) }
single<PushGlobalManager> { get<PushManager>() }
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import org.openedx.learn.presentation.LearnViewModel
import org.openedx.notifications.data.repository.NotificationsRepository
import org.openedx.notifications.domain.interactor.NotificationsInteractor
import org.openedx.notifications.presentation.inbox.NotificationsInboxViewModel
import org.openedx.notifications.presentation.settings.NotificationsSettingsViewModel
import org.openedx.profile.data.repository.ProfileRepository
import org.openedx.profile.domain.interactor.ProfileInteractor
import org.openedx.profile.domain.model.Account
Expand Down Expand Up @@ -487,6 +488,7 @@ val screenModule = module {
factory { NotificationsInteractor(get()) }

viewModel { NotificationsInboxViewModel(get(), get()) }
viewModel { NotificationsSettingsViewModel(get(), get(), get()) }

single { IAPRepository(get()) }
factory { IAPInteractor(get(), get(), get(), get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.openedx.notifications.data.api

object APIConstants {
const val NOTIFICATION_COUNT = "/api/notifications/count/"
const val NOTIFICATIONS_COUNT = "/api/notifications/count/"
const val NOTIFICATIONS_INBOX = "/api/notifications/"
const val NOTIFICATIONS_SEEN = "/api/notifications/mark-seen/{app_name}/"
const val NOTIFICATION_READ = "/api/notifications/read/"

const val NOTIFICATIONS_CONFIGURATION = "/api/notifications/configurations/"
const val NOTIFICATION_UPDATE_CONFIGURATION = "/api/notifications/preferences/update-all/"

const val APP_NAME_DISCUSSION = "discussion"
const val NOTIFICATION_TYPE = "core"
const val NOTIFICATION_CHANNEL = "push"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ package org.openedx.notifications.data.api

import org.openedx.notifications.data.model.InboxNotificationsResponse
import org.openedx.notifications.data.model.MarkNotificationReadBody
import org.openedx.notifications.data.model.NotificationsConfiguration
import org.openedx.notifications.data.model.NotificationsCountResponse
import org.openedx.notifications.data.model.NotificationsMarkResponse
import org.openedx.notifications.data.model.NotificationsUpdateBody
import org.openedx.notifications.data.model.NotificationsUpdateResponse
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query

interface NotificationsApi {
@GET(APIConstants.NOTIFICATION_COUNT)
@GET(APIConstants.NOTIFICATIONS_COUNT)
suspend fun getUnreadNotificationsCount(): NotificationsCountResponse

@GET(APIConstants.NOTIFICATIONS_INBOX)
Expand All @@ -30,4 +34,12 @@ interface NotificationsApi {
suspend fun markNotificationAsRead(
@Body markNotification: MarkNotificationReadBody,
): NotificationsMarkResponse

@GET(APIConstants.NOTIFICATIONS_CONFIGURATION)
suspend fun fetchNotificationsConfiguration(): NotificationsConfiguration

@POST(APIConstants.NOTIFICATION_UPDATE_CONFIGURATION)
suspend fun updateNotificationsConfiguration(
@Body notificationsUpdateBody: NotificationsUpdateBody,
): NotificationsUpdateResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.openedx.notifications.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.notifications.domain.model.NotificationsConfiguration

data class NotificationsConfiguration(
@SerializedName("status") val status: String,
@SerializedName("message") val message: String,
@SerializedName("data") val data: NotificationData,
) {
fun mapToDomain(): NotificationsConfiguration {
return NotificationsConfiguration(
discussionsPushEnabled = data.discussion
.notificationTypes[CORE_NOTIFICATION_TYPE]?.push ?: false
)
}

companion object {
const val CORE_NOTIFICATION_TYPE = "core"
}
}

data class NotificationData(
@SerializedName("discussion") val discussion: NotificationCategory,
)

data class NotificationCategory(
@SerializedName("enabled") val enabled: Boolean,
@SerializedName("notification_types") val notificationTypes: Map<String, NotificationType>,
@SerializedName("core_notification_types") val coreNotificationTypes: List<String>,
)

data class NotificationType(
@SerializedName("push") val push: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.openedx.notifications.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.notifications.domain.model.NotificationsUpdateResponse

data class NotificationsUpdateBody(
@SerializedName("notification_app") val notificationApp: String,
@SerializedName("notification_type") val notificationType: String,
@SerializedName("notification_channel") val notificationChannel: String,
@SerializedName("value") val value: Boolean,
)

data class NotificationsUpdateResponse(
@SerializedName("status") val status: String,
@SerializedName("data") val data: NotificationUpdateData,
) {
fun mapToDomain(): NotificationsUpdateResponse {
return NotificationsUpdateResponse(
status = status,
updatedValue = data.updatedValue,
notificationType = data.notificationType,
channel = data.channel,
app = data.app
)
}
}

data class NotificationUpdateData(
@SerializedName("updated_value") val updatedValue: Boolean,
@SerializedName("notification_type") val notificationType: String,
@SerializedName("channel") val channel: String,
@SerializedName("app") val app: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import org.openedx.core.extension.isNotNull
import org.openedx.notifications.data.api.APIConstants
import org.openedx.notifications.data.api.NotificationsApi
import org.openedx.notifications.data.model.MarkNotificationReadBody
import org.openedx.notifications.data.model.NotificationsUpdateBody
import org.openedx.notifications.domain.model.InboxNotifications
import org.openedx.notifications.domain.model.NotificationsConfiguration
import org.openedx.notifications.domain.model.NotificationsCount
import org.openedx.notifications.domain.model.NotificationsUpdateResponse

class NotificationsRepository(private val api: NotificationsApi) {
class NotificationsRepository(
private val api: NotificationsApi,
) {
suspend fun getUnreadNotificationsCount(): NotificationsCount {
return api.getUnreadNotificationsCount().mapToDomain()
}
Expand All @@ -32,4 +37,21 @@ class NotificationsRepository(private val api: NotificationsApi) {
)
).message.isNotNull()
}

suspend fun fetchNotificationsConfiguration(): NotificationsConfiguration {
return api.fetchNotificationsConfiguration().mapToDomain()
}

suspend fun updateNotificationsConfiguration(
isDiscussionPushEnabled: Boolean,
): NotificationsUpdateResponse {
return api.updateNotificationsConfiguration(
NotificationsUpdateBody(
notificationApp = APIConstants.APP_NAME_DISCUSSION,
notificationType = APIConstants.NOTIFICATION_TYPE,
notificationChannel = APIConstants.NOTIFICATION_CHANNEL,
value = isDiscussionPushEnabled,
)
).mapToDomain()
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
package org.openedx.notifications.data.storage

import org.openedx.notifications.domain.model.NotificationsConfiguration

interface NotificationsPreferences {
var notifications: NotificationsConfiguration
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package org.openedx.notifications.domain.interactor

import org.openedx.notifications.data.repository.NotificationsRepository
import org.openedx.notifications.domain.model.InboxNotifications
import org.openedx.notifications.domain.model.NotificationsConfiguration
import org.openedx.notifications.domain.model.NotificationsCount
import org.openedx.notifications.domain.model.NotificationsUpdateResponse

class NotificationsInteractor(private val repository: NotificationsRepository) {

class NotificationsInteractor(
private val repository: NotificationsRepository,
) {
suspend fun getUnreadNotificationsCount(): NotificationsCount {
return repository.getUnreadNotificationsCount()
}
Expand All @@ -21,4 +24,16 @@ class NotificationsInteractor(private val repository: NotificationsRepository) {
suspend fun markNotificationAsRead(notificationId: Int): Boolean {
return repository.markNotificationAsRead(notificationId = notificationId)
}

suspend fun fetchNotificationsConfiguration(): NotificationsConfiguration {
return repository.fetchNotificationsConfiguration()
}

suspend fun updateNotificationsConfiguration(
isDiscussionPushEnabled: Boolean,
): NotificationsUpdateResponse {
return repository.updateNotificationsConfiguration(
isDiscussionPushEnabled = isDiscussionPushEnabled,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.openedx.notifications.domain.model

data class NotificationsConfiguration(
val discussionsPushEnabled: Boolean,
) {
companion object {
val default = NotificationsConfiguration(
discussionsPushEnabled = false,
)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.openedx.notifications.domain.model

data class NotificationsUpdateResponse(
val status: String,
val updatedValue: Boolean,
val notificationType: String,
val channel: String,
val app: String,
)
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
package org.openedx.notifications.presentation

interface NotificationsAnalytics {
fun logEvent(event: String, params: Map<String, Any?>)
fun logScreenEvent(screenName: String, params: Map<String, Any?>)
}

enum class NotificationsAnalyticsEvent(val eventName: String, val biValue: String) {
DISCUSSION_PERMISSION_TOGGLE(
eventName = "Notification:Discussion Permission Toggle",
biValue = "edx.bi.app.notification.discussion.permission.toggle"
)
}

enum class NotificationsAnalyticsKey(val key: String) {
NAME("name"),
ACTION("action"),
CATEGORY("category"),
NOTIFICATIONS("notifications"),
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 8ea50d1

Please sign in to comment.