forked from github/wulkanowy-mirror
Add notifications center (#1524)
This commit is contained in:
parent
dc90549b9d
commit
d69118b085
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,7 @@
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
@ -74,6 +75,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -83,6 +85,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -95,11 +98,20 @@
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
<service
|
||||
android:name=".services.piggyback.VulcanNotificationListenerService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".services.messaging.AppMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
|
||||
<receiver
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||
@ -114,6 +126,7 @@
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/lucky_number_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -8,8 +8,8 @@ import androidx.preference.PreferenceManager
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@ -202,4 +202,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||
@ -23,8 +22,10 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
@ -38,7 +39,6 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||
@ -51,9 +51,11 @@ import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
@ -95,6 +97,7 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
@ -134,6 +137,7 @@ import javax.inject.Singleton
|
||||
StudentInfo::class,
|
||||
TimetableHeader::class,
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -142,7 +146,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 39
|
||||
const val VERSION_SCHEMA = 40
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -183,6 +187,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration37(),
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
Migration40()
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
@ -252,4 +257,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||
|
||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||
|
||||
abstract val notificationDao: NotificationDao
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
interface NotificationDao : BaseDao<Notification> {
|
||||
|
||||
@Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1")
|
||||
fun loadAll(studentId: Long): Flow<List<Notification>>
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity(tableName = "Notifications")
|
||||
data class Notification(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Long,
|
||||
|
||||
val title: String,
|
||||
|
||||
val content: String,
|
||||
|
||||
val type: NotificationType,
|
||||
|
||||
val date: LocalDateTime,
|
||||
|
||||
val data: String? = null
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration40 : Migration(39, 40) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `Notifications` (
|
||||
`student_id` INTEGER NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`type` TEXT NOT NULL,
|
||||
`date` INTEGER NOT NULL,
|
||||
`data` TEXT,
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import androidx.annotation.StringRes
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
|
||||
sealed interface Notification {
|
||||
sealed interface NotificationData {
|
||||
val type: NotificationType
|
||||
val startMenu: MainView.Section
|
||||
val icon: Int
|
||||
@ -14,7 +14,7 @@ sealed interface Notification {
|
||||
val contentStringRes: Int
|
||||
}
|
||||
|
||||
data class MultipleNotifications(
|
||||
data class MultipleNotificationsData(
|
||||
override val type: NotificationType,
|
||||
override val startMenu: MainView.Section,
|
||||
@DrawableRes override val icon: Int,
|
||||
@ -23,9 +23,9 @@ data class MultipleNotifications(
|
||||
|
||||
@PluralsRes val summaryStringRes: Int,
|
||||
val lines: List<String>,
|
||||
) : Notification
|
||||
) : NotificationData
|
||||
|
||||
data class OneNotification(
|
||||
data class OneNotificationData(
|
||||
override val type: NotificationType,
|
||||
override val startMenu: MainView.Section,
|
||||
@DrawableRes override val icon: Int,
|
||||
@ -33,4 +33,4 @@ data class OneNotification(
|
||||
@StringRes override val contentStringRes: Int,
|
||||
|
||||
val contentValues: List<String>,
|
||||
) : Notification
|
||||
) : NotificationData
|
@ -0,0 +1,15 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NotificationRepository @Inject constructor(private val notificationDao: NotificationDao) {
|
||||
|
||||
fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId)
|
||||
|
||||
suspend fun saveNotification(notification: Notification) =
|
||||
notificationDao.insertAll(listOf(notification))
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.data.pojos.NotificationData
|
||||
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.getCompatBitmap
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
class AppNotificationManager @Inject constructor(
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val appInfo: AppInfo,
|
||||
private val notificationRepository: NotificationRepository
|
||||
) {
|
||||
|
||||
suspend fun sendNotification(notificationData: NotificationData, student: Student) =
|
||||
when (notificationData) {
|
||||
is OneNotificationData -> sendOneNotification(notificationData, student)
|
||||
is MultipleNotificationsData -> sendMultipleNotifications(notificationData, student)
|
||||
}
|
||||
|
||||
private suspend fun sendOneNotification(
|
||||
notificationData: OneNotificationData,
|
||||
student: Student
|
||||
) {
|
||||
val content = context.getString(
|
||||
notificationData.contentStringRes,
|
||||
*notificationData.contentValues.toTypedArray()
|
||||
)
|
||||
|
||||
val title = context.getString(notificationData.titleStringRes)
|
||||
|
||||
val notification = getDefaultNotificationBuilder(notificationData)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student.nickOrName)
|
||||
.bigText(content)
|
||||
)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||
|
||||
saveNotification(title, content, notificationData, student)
|
||||
}
|
||||
|
||||
private suspend fun sendMultipleNotifications(
|
||||
notificationData: MultipleNotificationsData,
|
||||
student: Student
|
||||
) {
|
||||
val groupType = notificationData.type.group ?: return
|
||||
val group = "${groupType}_${student.id}"
|
||||
val groupId = student.id * 100 + notificationData.type.ordinal
|
||||
|
||||
notificationData.lines.forEach { item ->
|
||||
val title = context.resources.getQuantityString(notificationData.titleStringRes, 1)
|
||||
|
||||
val notification = getDefaultNotificationBuilder(notificationData)
|
||||
.setContentTitle(title)
|
||||
.setContentText(item)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student.nickOrName)
|
||||
.bigText(item)
|
||||
)
|
||||
.setGroup(group)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification)
|
||||
|
||||
saveNotification(title, item, notificationData, student)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||
|
||||
val summaryNotification = getDefaultNotificationBuilder(notificationData)
|
||||
.setSmallIcon(notificationData.icon)
|
||||
.setGroup(group)
|
||||
.setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||
.setGroupSummary(true)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(groupId.toInt(), summaryNotification)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun getDefaultNotificationBuilder(notificationData: NotificationData): NotificationCompat.Builder {
|
||||
val pendingIntentsFlags = if (appInfo.systemVersion >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
|
||||
return NotificationCompat.Builder(context, notificationData.type.channel)
|
||||
.setLargeIcon(context.getCompatBitmap(notificationData.icon, R.color.colorPrimary))
|
||||
.setSmallIcon(R.drawable.ic_stat_all)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
notificationData.startMenu.id,
|
||||
MainActivity.getStartIntent(context, notificationData.startMenu, true),
|
||||
pendingIntentsFlags
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun saveNotification(
|
||||
title: String,
|
||||
content: String,
|
||||
notificationData: NotificationData,
|
||||
student: Student
|
||||
) {
|
||||
val notificationEntity = Notification(
|
||||
studentId = student.id,
|
||||
title = title,
|
||||
content = content,
|
||||
type = notificationData.type,
|
||||
date = LocalDateTime.now()
|
||||
)
|
||||
|
||||
notificationRepository.saveNotification(notificationEntity)
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.Notification
|
||||
import io.github.wulkanowy.data.pojos.OneNotification
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.utils.getCompatBitmap
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import kotlin.random.Random
|
||||
|
||||
abstract class BaseNotification(
|
||||
private val context: Context,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
) {
|
||||
|
||||
protected fun sendNotification(notification: Notification, student: Student) =
|
||||
when (notification) {
|
||||
is OneNotification -> sendOneNotification(notification, student)
|
||||
is MultipleNotifications -> sendMultipleNotifications(notification, student)
|
||||
}
|
||||
|
||||
private fun sendOneNotification(notification: OneNotification, student: Student?) {
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
getNotificationBuilder(notification).apply {
|
||||
val content = context.getString(
|
||||
notification.contentStringRes,
|
||||
*notification.contentValues.toTypedArray()
|
||||
)
|
||||
setContentTitle(context.getString(notification.titleStringRes))
|
||||
setContentText(content)
|
||||
setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student?.nickOrName)
|
||||
.bigText(content)
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
|
||||
val group = notification.type.group + student.id
|
||||
val groupId = student.id * 100 + notification.type.ordinal
|
||||
|
||||
notification.lines.forEach { item ->
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
getNotificationBuilder(notification).apply {
|
||||
setContentTitle(getQuantityString(notification.titleStringRes, 1))
|
||||
setContentText(item)
|
||||
setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setSummaryText(student.nickOrName)
|
||||
.bigText(item)
|
||||
)
|
||||
setGroup(group)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||
|
||||
notificationManager.notify(
|
||||
groupId.toInt(),
|
||||
getNotificationBuilder(notification).apply {
|
||||
setSmallIcon(notification.icon)
|
||||
setGroup(group)
|
||||
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
||||
setGroupSummary(true)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
|
||||
.Builder(context, notification.type.channel)
|
||||
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
|
||||
.setSmallIcon(R.drawable.ic_stat_all)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, notification.startMenu.id,
|
||||
MainActivity.getStartIntent(context, notification.startMenu, true),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
|
||||
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
|
||||
return context.resources.getQuantityString(id, value, value)
|
||||
}
|
||||
}
|
@ -1,29 +1,25 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewConferenceNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Conference>, student: Student) {
|
||||
suspend fun notify(items: List<Conference>, student: Student) {
|
||||
val today = LocalDateTime.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_CONFERENCE,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewConferenceNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewExamNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Exam>, student: Student) {
|
||||
suspend fun notify(items: List<Exam>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_EXAM,
|
||||
icon = R.drawable.ic_main_exam,
|
||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewExamNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewGradeNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_DETAILS,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items,
|
||||
@ -29,11 +25,11 @@ class NewGradeNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
|
||||
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||
@ -45,11 +41,11 @@ class NewGradeNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
|
||||
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_GRADE_FINAL,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_final,
|
||||
@ -61,6 +57,6 @@ class NewGradeNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewHomeworkNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Homework>, student: Student) {
|
||||
suspend fun notify(items: List<Homework>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||
}.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_HOMEWORK,
|
||||
icon = R.drawable.ic_more_homework,
|
||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||
@ -33,6 +29,6 @@ class NewHomeworkNotification @Inject constructor(
|
||||
lines = lines
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.OneNotification
|
||||
import io.github.wulkanowy.data.pojos.OneNotificationData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewLuckyNumberNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotification(
|
||||
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotificationData(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
@ -25,6 +21,6 @@ class NewLuckyNumberNotification @Inject constructor(
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewMessageNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_MESSAGE,
|
||||
icon = R.drawable.ic_stat_message,
|
||||
titleStringRes = R.plurals.message_new_items,
|
||||
@ -28,6 +24,6 @@ class NewMessageNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewNoteNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_NOTE,
|
||||
icon = R.drawable.ic_stat_note,
|
||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||
@ -41,6 +37,6 @@ class NewNoteNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotificationsData
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager
|
||||
) {
|
||||
|
||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotificationsData(
|
||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
@ -28,6 +24,6 @@ class NewSchoolAnnouncementNotification @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendNotification(notification, student)
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||
|
||||
enum class NotificationType(val group: String, val channel: String) {
|
||||
enum class NotificationType(val group: String?, val channel: String) {
|
||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||
@ -20,4 +21,5 @@ enum class NotificationType(val group: String, val channel: String) {
|
||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||
PUSH(null, PushChannel.CHANNEL_ID)
|
||||
}
|
||||
|
@ -9,9 +9,12 @@ import io.github.wulkanowy.utils.waitForResult
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
|
||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
||||
class AttendanceWork @Inject constructor(
|
||||
private val attendanceRepository: AttendanceRepository
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester) {
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true)
|
||||
.waitForResult()
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
@ -120,6 +121,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
|
||||
R.id.dashboard_menu_notifaction_list -> presenter.onNotificationsCenterSelected()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -182,6 +184,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||
}
|
||||
|
||||
override fun openNotificationsCenterView() {
|
||||
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
dashboardAdapter.clearTimers()
|
||||
presenter.onDetachView()
|
||||
|
@ -209,6 +209,11 @@ class DashboardPresenter @Inject constructor(
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onNotificationsCenterSelected(): Boolean {
|
||||
view?.openNotificationsCenterView()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onDashboardTileSettingsSelected(): Boolean {
|
||||
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
|
||||
return true
|
||||
|
@ -23,4 +23,6 @@ interface DashboardView : BaseView {
|
||||
fun resetView()
|
||||
|
||||
fun popViewToRoot()
|
||||
|
||||
fun openNotificationsCenterView()
|
||||
}
|
@ -87,7 +87,7 @@ class NotificationDebugPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun withStudent(block: (Student) -> Unit) {
|
||||
private fun withStudent(block: suspend (Student) -> Unit) {
|
||||
launch {
|
||||
block(studentRepository.getCurrentStudent(false))
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationsCenterAdapter @Inject constructor() :
|
||||
ListAdapter<Notification, NotificationsCenterAdapter.ViewHolder>(DiffUtilCallback()) {
|
||||
|
||||
var onItemClickListener: (NotificationType) -> Unit = {}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemNotificationsCenterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
|
||||
with(holder.binding) {
|
||||
notificationsCenterItemTitle.text = item.title
|
||||
notificationsCenterItemContent.text = item.content
|
||||
notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM")
|
||||
notificationsCenterItemIcon.setImageResource(item.type.toDrawableResId())
|
||||
|
||||
root.setOnClickListener { onItemClickListener(item.type) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun NotificationType.toDrawableResId() = when (this) {
|
||||
NotificationType.NEW_CONFERENCE -> R.drawable.ic_more_conferences
|
||||
NotificationType.NEW_EXAM -> R.drawable.ic_main_exam
|
||||
NotificationType.NEW_GRADE_DETAILS -> R.drawable.ic_stat_grade
|
||||
NotificationType.NEW_GRADE_PREDICTED -> R.drawable.ic_stat_grade
|
||||
NotificationType.NEW_GRADE_FINAL -> R.drawable.ic_stat_grade
|
||||
NotificationType.NEW_HOMEWORK -> R.drawable.ic_more_homework
|
||||
NotificationType.NEW_LUCKY_NUMBER -> R.drawable.ic_stat_luckynumber
|
||||
NotificationType.NEW_MESSAGE -> R.drawable.ic_stat_message
|
||||
NotificationType.NEW_NOTE -> R.drawable.ic_stat_note
|
||||
NotificationType.NEW_ANNOUNCEMENT -> R.drawable.ic_all_about
|
||||
NotificationType.PUSH -> R.drawable.ic_stat_all
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: ItemNotificationsCenterBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class DiffUtilCallback : DiffUtil.ItemCallback<Notification>() {
|
||||
|
||||
override fun areContentsTheSame(oldItem: Notification, newItem: Notification) =
|
||||
oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: Notification, newItem: Notification) =
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotificationsCenterFragment :
|
||||
BaseFragment<FragmentNotificationsCenterBinding>(R.layout.fragment_notifications_center),
|
||||
NotificationsCenterView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: NotificationsCenterPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var notificationsCenterAdapter: NotificationsCenterAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = NotificationsCenterFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.notifications_center_title
|
||||
|
||||
override val isViewEmpty: Boolean
|
||||
get() = notificationsCenterAdapter.itemCount == 0
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentNotificationsCenterBinding.bind(view)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
notificationsCenterAdapter.onItemClickListener = { notificationType ->
|
||||
notificationType.toDestinationFragment()
|
||||
?.let { (requireActivity() as MainActivity).pushView(it) }
|
||||
}
|
||||
|
||||
with(binding.notificationsCenterRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = notificationsCenterAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<Notification>) {
|
||||
notificationsCenterAdapter.submitList(data)
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
binding.notificationsCenterEmpty.isVisible = show
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.notificationsCenterProgress.isVisible = show
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.notificationsCenterRecycler.isVisible = show
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.notificationCenterError.isVisible = show
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.notificationCenterErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun NotificationType.toDestinationFragment(): Fragment? = when (this) {
|
||||
NotificationType.NEW_CONFERENCE -> ConferenceFragment.newInstance()
|
||||
NotificationType.NEW_EXAM -> ExamFragment.newInstance()
|
||||
NotificationType.NEW_GRADE_DETAILS -> GradeFragment.newInstance()
|
||||
NotificationType.NEW_GRADE_PREDICTED -> GradeFragment.newInstance()
|
||||
NotificationType.NEW_GRADE_FINAL -> GradeFragment.newInstance()
|
||||
NotificationType.NEW_HOMEWORK -> HomeworkFragment.newInstance()
|
||||
NotificationType.NEW_LUCKY_NUMBER -> LuckyNumberFragment.newInstance()
|
||||
NotificationType.NEW_MESSAGE -> MessageFragment.newInstance()
|
||||
NotificationType.NEW_NOTE -> NoteFragment.newInstance()
|
||||
NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance()
|
||||
NotificationType.PUSH -> null
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||
|
||||
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationsCenterPresenter @Inject constructor(
|
||||
private val notificationRepository: NotificationRepository,
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
) : BasePresenter<NotificationsCenterView>(errorHandler, studentRepository) {
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
override fun onAttachView(view: NotificationsCenterView) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Notifications centre view was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDetailsClick() {
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
Timber.i("Loading notifications data started")
|
||||
|
||||
flow {
|
||||
val studentId = studentRepository.getCurrentStudent(false).id
|
||||
emitAll(notificationRepository.getNotifications(studentId))
|
||||
}
|
||||
.map { notificationList -> notificationList.sortedByDescending { it.date } }
|
||||
.catch { Timber.i("Loading notifications result: An exception occurred") }
|
||||
.onEach {
|
||||
Timber.i("Loading notifications result: Success")
|
||||
|
||||
if (it.isEmpty()) {
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(false)
|
||||
showEmpty(true)
|
||||
}
|
||||
} else {
|
||||
view?.run {
|
||||
showContent(true)
|
||||
showProgress(false)
|
||||
showEmpty(false)
|
||||
updateData(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launch()
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
if (isViewEmpty) {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showEmpty(false)
|
||||
} else showError(message, error)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package io.github.wulkanowy.ui.modules.notificationscenter
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface NotificationsCenterView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<Notification>)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
}
|
@ -184,6 +184,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
setNotificationPiggybackPreferenceChecked(false)
|
||||
}
|
||||
.setOnDismissListener { setNotificationPiggybackPreferenceChecked(false) }
|
||||
.show()
|
||||
}
|
||||
|
||||
|
106
app/src/main/res/layout/fragment_notifications_center.xml
Normal file
106
app/src/main/res/layout/fragment_notifications_center.xml
Normal file
@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/notifications_center_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="16dp"
|
||||
android:visibility="gone"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/item_notifications_center"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/notifications_center_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/notifications_center_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
android:visibility="gone"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
app:srcCompat="@drawable/ic_settings_notifications"
|
||||
app:tint="?colorOnBackground"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/all_no_data"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/notification_center_error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="invisible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
app:srcCompat="@drawable/ic_error"
|
||||
app:tint="?colorOnBackground"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_center_error_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="@string/error_unknown"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/notification_center_error_details"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/all_details" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/notification_center_error_retry"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/all_retry" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
70
app/src/main/res/layout/item_notifications_center.xml
Normal file
70
app/src/main/res/layout/item_notifications_center.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginTop="12dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notifications_center_item_date"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notifications_center_item_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/notifications_center_item_date"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notifications_center_item_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="5"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/notifications_center_item_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/notifications_center_item_title"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/notifications_center_item_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?colorPrimary"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
@ -1,10 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/dashboard_menu_notifaction_list"
|
||||
android:icon="@drawable/ic_settings_notifications"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/notifications_center_title"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/dashboard_menu_tiles"
|
||||
android:icon="@drawable/ic_more_settings"
|
||||
android:orderInCategory="1"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/pref_dashboard_appearance_tiles_title"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/mainMenuAccount"
|
||||
android:orderInCategory="2"
|
||||
android:orderInCategory="10"
|
||||
app:showAsAction="always"
|
||||
tools:ignore="MenuTitle" />
|
||||
</menu>
|
||||
|
@ -24,6 +24,7 @@
|
||||
<string name="account_details_title">Account details</string>
|
||||
<string name="student_info_title">Student info</string>
|
||||
<string name="dashboard_title">Dashboard</string>
|
||||
<string name="notifications_center_title">Notifications center</string>
|
||||
|
||||
|
||||
<!--Subtitles-->
|
||||
|
@ -0,0 +1,57 @@
|
||||
package io.github.wulkanowy.services.messaging
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
|
||||
@AndroidEntryPoint
|
||||
class AppMessagingService : FirebaseMessagingService() {
|
||||
|
||||
@Inject
|
||||
lateinit var notificationRepository: NotificationRepository
|
||||
|
||||
private val job = Job()
|
||||
|
||||
private val serviceScope = CoroutineScope(Dispatchers.Main + job)
|
||||
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
val remoteMessageData = remoteMessage.data
|
||||
val title = remoteMessageData["title"] ?: return
|
||||
val content = remoteMessageData["content"] ?: return
|
||||
val customData = remoteMessageData["custom_data"]
|
||||
|
||||
val notification = Notification(
|
||||
title = title,
|
||||
content = content,
|
||||
data = customData,
|
||||
date = LocalDateTime.now(),
|
||||
type = NotificationType.PUSH,
|
||||
studentId = -1
|
||||
)
|
||||
|
||||
serviceScope.launch {
|
||||
try {
|
||||
notificationRepository.saveNotification(notification)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
job.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user