From a4493ec964de9fe33c080950b9d9b47febd8fc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 22 Feb 2020 00:07:22 +0100 Subject: [PATCH] [Notifications] Add filtering notifications to show during sync. --- .../edziennik/config/ProfileConfig.kt | 4 +- .../edziennik/config/ProfileConfigSync.kt | 15 +++ .../edziennik/data/api/task/SzkolnyTask.kt | 11 +++ .../data/firebase/SzkolnyAppFirebase.kt | 63 +++++++----- .../dialogs/sync/NotificationFilterDialog.kt | 98 +++++++++++++++++++ .../modules/settings/SettingsNewFragment.java | 52 +++++----- app/src/main/res/values/strings.xml | 7 +- 7 files changed, 195 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt index 3e9ef09a..6281225d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -29,8 +29,8 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List? = null + var notificationFilter: List + get() { mNotificationFilter = mNotificationFilter ?: config.values.get("notificationFilter", listOf()); return mNotificationFilter ?: listOf() } + set(value) { config.set("notificationFilter", value); mNotificationFilter = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt index 497efdee..5171e1f7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/SzkolnyTask.kt @@ -44,6 +44,17 @@ class SzkolnyTask(val app: App, val syncingProfiles: List) : IApiTask(- } d(TAG, "Created ${notificationList.count()} notifications.") + // filter notifications + notificationList + .mapNotNull { it.profileId } + .distinct() + .map { app.config.getFor(it).sync.notificationFilter } + .forEach { filter -> + filter.forEach { type -> + notificationList.removeAll { it.type == type } + } + } + // update the database app.db.metadataDao().setAllNotified(true) if (notificationList.isNotEmpty()) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index a79e2790..39b5396e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -145,25 +145,31 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: event.topic )*/ val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT - val notification = Notification( - id = Notification.buildId(event.profileId, type, event.id), - title = app.getNotificationTitle(type), - text = message, - type = type, - profileId = profile?.id, - profileName = profile?.name, - viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, - addedDate = metadata.addedDate - ).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) + val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter + + if (!notificationFilter.contains(type)) { + val notification = Notification( + id = Notification.buildId(event.profileId, type, event.id), + title = app.getNotificationTitle(type), + text = message, + type = type, + profileId = profile?.id, + profileName = profile?.name, + viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, + addedDate = metadata.addedDate + ).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) + notificationList += notification + } events += event metadataList += metadata - notificationList += notification } app.db.eventDao().addAll(events) app.db.metadataDao().addAllReplace(metadataList) - app.db.notificationDao().addAll(notificationList) - PostNotifications(app, notificationList) + if (notificationList.isNotEmpty()) { + app.db.notificationDao().addAll(notificationList) + PostNotifications(app, notificationList) + } } private fun unsharedEvent(teamCode: String, eventId: Long, message: String) { @@ -172,19 +178,26 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team -> val profile = profiles.firstOrNull { it.id == team.profileId } - val notification = Notification( - id = Notification.buildId(profile?.id ?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId), - title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT), - text = message, - type = Notification.TYPE_REMOVED_SHARED_EVENT, - profileId = profile?.id, - profileName = profile?.name, - viewId = MainActivity.DRAWER_ITEM_AGENDA - ) - notificationList += notification + val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter + + if (!notificationFilter.contains(Notification.TYPE_REMOVED_SHARED_EVENT)) { + val notification = Notification( + id = Notification.buildId(profile?.id + ?: 0, Notification.TYPE_REMOVED_SHARED_EVENT, eventId), + title = app.getNotificationTitle(Notification.TYPE_REMOVED_SHARED_EVENT), + text = message, + type = Notification.TYPE_REMOVED_SHARED_EVENT, + profileId = profile?.id, + profileName = profile?.name, + viewId = MainActivity.DRAWER_ITEM_AGENDA + ) + notificationList += notification + } app.db.eventDao().remove(team.profileId, eventId) } - app.db.notificationDao().addAll(notificationList) - PostNotifications(app, notificationList) + if (notificationList.isNotEmpty()) { + app.db.notificationDao().addAll(notificationList) + PostNotifications(app, notificationList) + } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt new file mode 100644 index 00000000..eb8aecd3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.sync + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.onClick +import kotlin.coroutines.CoroutineContext + +// TODO refactor dialog to allow configuring other profiles +// than the selected one in UI +class NotificationFilterDialog( + val activity: AppCompatActivity, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "NotificationFilterDialog" + private val notificationTypes = listOf( + Notification.TYPE_TIMETABLE_LESSON_CHANGE to R.string.notification_type_timetable_lesson_change, + Notification.TYPE_NEW_GRADE to R.string.notification_type_new_grade, + Notification.TYPE_NEW_EVENT to R.string.notification_type_new_event, + Notification.TYPE_NEW_HOMEWORK to R.string.notification_type_new_homework, + Notification.TYPE_NEW_MESSAGE to R.string.notification_type_new_message, + Notification.TYPE_LUCKY_NUMBER to R.string.notification_type_lucky_number, + Notification.TYPE_NEW_NOTICE to R.string.notification_type_notice, + Notification.TYPE_NEW_ATTENDANCE to R.string.notification_type_attendance, + Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement, + Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event, + Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework, + Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event + ) + } + + private lateinit var app: App + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val notificationFilter = mutableListOf() + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + + notificationFilter.clear() + notificationFilter += app.config.forProfile().sync.notificationFilter + val items = notificationTypes.map { app.getString(it.second) }.toTypedArray() + val checkedItems = notificationTypes.map { !notificationFilter.contains(it.first) }.toBooleanArray() + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_notification_filter_title) + //.setMessage(R.string.dialog_notification_filter_text) + .setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> + val type = notificationTypes[which].first + notificationFilter.remove(type) + if (!isChecked) + notificationFilter += type + } + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick { + if (notificationFilter.isEmpty()) { + app.config.forProfile().sync.notificationFilter = notificationFilter + dialog.dismiss() + return@onClick + } + // warn user when he tries to disable some notifications + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(R.string.notification_filter_warning) + .setPositiveButton(R.string.ok) { _, _ -> + app.config.forProfile().sync.notificationFilter = notificationFilter + dialog.dismiss() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java index 5865dde8..1fd274f3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java @@ -54,6 +54,7 @@ import pl.szczodrzynski.edziennik.sync.UpdateWorker; import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog; +import pl.szczodrzynski.edziennik.ui.dialogs.sync.NotificationFilterDialog; import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Utils; import pl.szczodrzynski.edziennik.utils.models.Date; @@ -217,6 +218,24 @@ public class SettingsNewFragment extends MaterialAboutFragment { }) );*/ + items.add( + new MaterialAboutActionItem( + getString(R.string.settings_profile_notifications_text), + getString(R.string.settings_profile_notifications_subtext), + new IconicsDrawable(activity) + .icon(CommunityMaterial.Icon.cmd_filter_outline) + .size(IconicsSize.dp(iconSizeDp)) + .color(IconicsColor.colorInt(iconColor)) + ) + .setOnClickAction(() -> { + new NotificationFilterDialog(activity, null, null); + }) + ); + + items.add(getMoreItem(() -> addCardItems(CARD_PROFILE, getProfileCard(true)))); + } + else { + items.add( new MaterialAboutSwitchItem( getString(R.string.settings_profile_sync_text), @@ -226,35 +245,14 @@ public class SettingsNewFragment extends MaterialAboutFragment { .size(IconicsSize.dp(iconSizeDp)) .color(IconicsColor.colorInt(iconColor)) ) - .setChecked(app.getProfile().getSyncEnabled()) - .setOnChangeAction(((isChecked, tag) -> { - app.getProfile().setSyncEnabled(isChecked); - app.profileSave(); - return true; - })) + .setChecked(app.getProfile().getSyncEnabled()) + .setOnChangeAction(((isChecked, tag) -> { + app.getProfile().setSyncEnabled(isChecked); + app.profileSave(); + return true; + })) ); - items.add(getMoreItem(() -> addCardItems(CARD_PROFILE, getProfileCard(true)))); - } - else { - - /*items.add( - new MaterialAboutSwitchItem( - getString(R.string.settings_profile_notify_text), - getString(R.string.settings_profile_notify_subtext), - new IconicsDrawable(activity) - .icon(CommunityMaterial.Icon.cmd_bell_ring) - .size(IconicsSize.dp(iconSizeDp)) - .color(IconicsColor.colorInt(iconColor)) - ) - .setChecked(app.getProfile().getSyncNotifications()) - .setOnChangeAction(((isChecked, tag) -> { - app.getProfile().setSyncNotifications(isChecked); - app.profileSave(); - return true; - })) - );*/ - items.add( new MaterialAboutActionItem( getString(R.string.settings_profile_remove_text), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89bd6870..50e2a72c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -567,7 +567,7 @@ Nowe zadanie domowe Nowa wiadomość Udostępniono wydarzenie - Ocena ucznia + Wpis zachowania Wiadomość z serwera Zmiana planu zajęć Zmiana lekcji @@ -1181,5 +1181,10 @@ Wymagane działanie w aplikacji Problem, który uniemożliwia synchronizację musi być rozwiązany przez użytkownika. Kliknij, aby uzyskać więcej informacji. Librus: wymagane rozwiązanie zadania Captcha. Kliknij, aby kontynuować logowanie do dziennika. + Filtruj powiadomienia + Wyłącz określone rodzaje powiadomień + Pokazuj wybrane powiadomienia + + Czy na pewno chcesz zastosować te ustawienia?\n\nNie będziesz widział informacji o niektórych danych, przez co możesz przeoczyć ważne komunikaty, wiadomości lub oceny.\n\nUstawienia zostaną zastosowane dla aktualnie otwartego profilu. %s - %s (%s lekcji - %s godzin %s minut)