forked from github/wulkanowy-mirror
Add conferences and announcements notifications (#1330)
This commit is contained in:
@ -93,6 +93,7 @@ import io.github.wulkanowy.data.db.migrations.Migration35
|
||||
import io.github.wulkanowy.data.db.migrations.Migration36
|
||||
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.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
@ -141,7 +142,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 38
|
||||
const val VERSION_SCHEMA = 39
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -181,6 +182,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration36(),
|
||||
Migration37(),
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
|
@ -32,4 +32,7 @@ data class Conference(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
var isNotified: Boolean = true
|
||||
}
|
||||
|
@ -21,4 +21,7 @@ data class SchoolAnnouncement(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
var isNotified: Boolean = true
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration39 : Migration(38, 39) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
|
||||
database.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
@ -10,6 +11,8 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -25,19 +28,46 @@ class ConferenceRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "conference"
|
||||
|
||||
fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getConferences(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
||||
query = { conferenceDb.loadAll(semester.diaryId, student.studentId) },
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
},
|
||||
query = {
|
||||
conferenceDb.loadAll(
|
||||
semester.diaryId,
|
||||
student.studentId
|
||||
)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getConferences()
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val conferencesToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
conferenceDb.deleteAll(old uniqueSubtract new)
|
||||
conferenceDb.insertAll(new uniqueSubtract old)
|
||||
conferenceDb.insertAll(conferencesToSave)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
fun getNotNotifiedConference(semester: Semester): Flow<List<Conference>> {
|
||||
return conferenceDb.loadAll(
|
||||
diaryId = semester.diaryId,
|
||||
studentId = semester.studentId
|
||||
).map {
|
||||
it.filter { conference -> !conference.isNotified }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
@ -9,6 +12,8 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -24,20 +29,42 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "school_announcement"
|
||||
|
||||
fun getSchoolAnnouncements(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getSchoolAnnouncements(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, student)
|
||||
)
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
},
|
||||
query = {
|
||||
schoolAnnouncementDb.loadAll(
|
||||
student.studentId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.getDirectorInformation()
|
||||
.mapToEntities(student)
|
||||
},
|
||||
query = { schoolAnnouncementDb.loadAll(student.studentId) },
|
||||
fetch = { sdk.init(student).getDirectorInformation().mapToEntities(student) },
|
||||
saveFetchResult = { old, new ->
|
||||
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
|
||||
schoolAnnouncementDb.insertAll(new uniqueSubtract old)
|
||||
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
|
||||
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
fun getNotNotifiedSchoolAnnouncement(semester: Semester): Flow<List<SchoolAnnouncement>> {
|
||||
return schoolAnnouncementDb.loadAll(
|
||||
studentId = semester.studentId
|
||||
).map {
|
||||
it.filter { schoolAnnouncement -> !schoolAnnouncement.isNotified }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||
}
|
||||
|
@ -15,16 +15,19 @@ import dagger.multibindings.IntoSet
|
||||
import io.github.wulkanowy.services.sync.channels.Channel
|
||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
||||
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
||||
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
|
||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
|
||||
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
||||
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
||||
import io.github.wulkanowy.services.sync.works.CompletedLessonWork
|
||||
import io.github.wulkanowy.services.sync.works.ConferenceWork
|
||||
import io.github.wulkanowy.services.sync.works.ExamWork
|
||||
import io.github.wulkanowy.services.sync.works.GradeStatisticsWork
|
||||
import io.github.wulkanowy.services.sync.works.GradeWork
|
||||
@ -73,6 +76,10 @@ abstract class ServicesModule {
|
||||
@IntoSet
|
||||
abstract fun provideAttendanceWork(work: AttendanceWork): Work
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun provideConferenceWork(work: ConferenceWork): Work
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun provideExamWork(work: ExamWork): Work
|
||||
@ -125,6 +132,10 @@ abstract class ServicesModule {
|
||||
@IntoSet
|
||||
abstract fun provideLuckyNumberChannel(channel: LuckyNumberChannel): Channel
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun provideNewConferenceChannel(channel: NewConferencesChannel): Channel
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun provideNewExamChannel(channel: NewExamChannel): Channel
|
||||
@ -145,6 +156,10 @@ abstract class ServicesModule {
|
||||
@IntoSet
|
||||
abstract fun provideNewNotesChannel(channel: NewNotesChannel): Channel
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun provideNewSchoolAnnouncementChannel(channel: NewSchoolAnnouncementsChannel): Channel
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun providePushChannel(channel: PushChannel): Channel
|
||||
|
@ -0,0 +1,32 @@
|
||||
package io.github.wulkanowy.services.sync.channels
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import javax.inject.Inject
|
||||
|
||||
@TargetApi(26)
|
||||
class NewConferencesChannel @Inject constructor(
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
@ApplicationContext private val context: Context
|
||||
) : Channel {
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "new_conferences_channel"
|
||||
}
|
||||
|
||||
override fun create() {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_conference), NotificationManager.IMPORTANCE_HIGH)
|
||||
.apply {
|
||||
enableLights(true)
|
||||
enableVibration(true)
|
||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.github.wulkanowy.services.sync.channels
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import javax.inject.Inject
|
||||
|
||||
@TargetApi(26)
|
||||
class NewSchoolAnnouncementsChannel @Inject constructor(
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
@ApplicationContext private val context: Context
|
||||
) : Channel {
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "new_schoolAnnouncements_channel"
|
||||
}
|
||||
|
||||
override fun create() {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_school_announcement), NotificationManager.IMPORTANCE_HIGH)
|
||||
.apply {
|
||||
enableLights(true)
|
||||
enableVibration(true)
|
||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package io.github.wulkanowy.services.sync.works
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
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.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getCompatBitmap
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.waitForResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
class ConferenceWork @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val conferenceRepository: ConferenceRepository,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester) {
|
||||
conferenceRepository.getConferences(
|
||||
student = student,
|
||||
semester = semester,
|
||||
forceRefresh = true,
|
||||
notify = preferencesRepository.isNotificationsEnable
|
||||
).waitForResult()
|
||||
|
||||
conferenceRepository.getNotNotifiedConference(semester).first().let {
|
||||
if (it.isNotEmpty()) notify(it)
|
||||
|
||||
conferenceRepository.updateConference(it.onEach { conference -> conference.isNotified = true })
|
||||
}
|
||||
}
|
||||
|
||||
private fun notify(conference: List<Conference>) {
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
NotificationCompat.Builder(context, NewConferencesChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.resources.getQuantityString(R.plurals.conference_notify_new_item_title, conference.size, conference.size))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.conference_notify_new_items, conference.size, conference.size))
|
||||
.setSmallIcon(R.drawable.ic_stat_all)
|
||||
.setLargeIcon(context.getCompatBitmap(R.drawable.ic_more_conferences, R.color.colorPrimary))
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, MainView.Section.CONFERENCE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.CONFERENCE, true),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.setStyle(NotificationCompat.InboxStyle().run {
|
||||
setSummaryText(
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.conference_number_item,
|
||||
conference.size,
|
||||
conference.size
|
||||
)
|
||||
)
|
||||
conference.forEach { addLine("${it.title}: ${it.subject}") }
|
||||
this
|
||||
})
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
@ -1,16 +1,83 @@
|
||||
package io.github.wulkanowy.services.sync.works
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
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.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.getCompatBitmap
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.waitForResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
class SchoolAnnouncementWork @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) : Work {
|
||||
|
||||
override suspend fun doWork(student: Student, semester: Semester) {
|
||||
schoolAnnouncementRepository.getSchoolAnnouncements(student, true).waitForResult()
|
||||
schoolAnnouncementRepository.getSchoolAnnouncements(
|
||||
student,
|
||||
true,
|
||||
notify = preferencesRepository.isNotificationsEnable
|
||||
).waitForResult()
|
||||
|
||||
|
||||
schoolAnnouncementRepository.getNotNotifiedSchoolAnnouncement(semester).first().let {
|
||||
if (it.isNotEmpty()) notify(it)
|
||||
|
||||
schoolAnnouncementRepository.updateSchoolAnnouncement(it.onEach { schoolAnnouncement -> schoolAnnouncement.isNotified = true })
|
||||
}
|
||||
}
|
||||
|
||||
private fun notify(schoolAnnouncement: List<SchoolAnnouncement>) {
|
||||
notificationManager.notify(
|
||||
Random.nextInt(Int.MAX_VALUE),
|
||||
NotificationCompat.Builder(context, NewSchoolAnnouncementsChannel.CHANNEL_ID)
|
||||
.setContentTitle(context.resources.getQuantityString(
|
||||
R.plurals.school_announcement_notify_new_item_title,
|
||||
schoolAnnouncement.size,
|
||||
schoolAnnouncement.size
|
||||
))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.school_announcement_notify_new_items, schoolAnnouncement.size, schoolAnnouncement.size))
|
||||
.setSmallIcon(R.drawable.ic_stat_all)
|
||||
.setLargeIcon(context.getCompatBitmap(R.drawable.ic_all_about, R.color.colorPrimary))
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, MainView.Section.SCHOOL_ANNOUNCEMENT.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.SCHOOL_ANNOUNCEMENT, true),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.setStyle(NotificationCompat.InboxStyle().run {
|
||||
setSummaryText(
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.school_announcement_number_item,
|
||||
schoolAnnouncement.size,
|
||||
schoolAnnouncement.size
|
||||
)
|
||||
)
|
||||
schoolAnnouncement.forEach { addLine("${it.subject}: ${it.content}") }
|
||||
this
|
||||
})
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,8 @@ interface MainView : BaseView {
|
||||
ABOUT(10),
|
||||
SCHOOL(11),
|
||||
ACCOUNT(12),
|
||||
STUDENT_INFO(13)
|
||||
STUDENT_INFO(13),
|
||||
CONFERENCE(14),
|
||||
SCHOOL_ANNOUNCEMENT(15)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user