1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-19 22:49:08 -05:00

Add mailbox chooser to messages (#2002)

This commit is contained in:
Mikołaj Pich 2022-11-16 13:46:47 +01:00 committed by GitHub
parent db4f172fb8
commit 51a1097bb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 3209 additions and 235 deletions

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 52 const val VERSION_SCHEMA = 53
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -106,6 +106,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration49(), Migration49(),
Migration50(), Migration50(),
Migration51(), Migration51(),
Migration53(),
) )
fun newInstance( fun newInstance(

View File

@ -3,12 +3,16 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Mailbox
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface MailboxDao : BaseDao<Mailbox> { interface MailboxDao : BaseDao<Mailbox> {
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId ") @Query("SELECT * FROM Mailboxes WHERE email = :email")
suspend fun loadAll(userLoginId: Int): List<Mailbox> suspend fun loadAll(email: String): List<Mailbox>
@Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId")
fun loadAll(email: String, symbol: String, schoolId: String): Flow<List<Mailbox>>
} }

View File

@ -16,4 +16,7 @@ interface MessagesDao : BaseDao<Message> {
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>> fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
fun loadAll(folder: Int, email: String): Flow<List<Message>>
} }

View File

@ -1,20 +1,27 @@
package io.github.wulkanowy.data.db.entities package io.github.wulkanowy.data.db.entities
import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Parcelize
@Entity(tableName = "Mailboxes") @Entity(tableName = "Mailboxes")
data class Mailbox( data class Mailbox(
@PrimaryKey @PrimaryKey
val globalKey: String, val globalKey: String,
val email: String,
val symbol: String,
val schoolId: String,
val fullName: String, val fullName: String,
val userName: String, val userName: String,
val userLoginId: Int,
val studentName: String, val studentName: String,
val schoolNameShort: String, val schoolNameShort: String,
val type: MailboxType, val type: MailboxType,
) ) : java.io.Serializable, Parcelable
enum class MailboxType { enum class MailboxType {
STUDENT, STUDENT,

View File

@ -9,6 +9,9 @@ import java.time.Instant
@Entity(tableName = "Messages") @Entity(tableName = "Messages")
data class Message( data class Message(
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "message_global_key") @ColumnInfo(name = "message_global_key")
val messageGlobalKey: String, val messageGlobalKey: String,

View File

@ -0,0 +1,57 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration53 : Migration(52, 53) {
override fun migrate(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
}
private fun createMailboxTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Mailboxes")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Mailboxes` (
`globalKey` TEXT NOT NULL,
`email` TEXT NOT NULL,
`symbol` TEXT NOT NULL,
`schoolId` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`studentName` TEXT NOT NULL,
`schoolNameShort` TEXT NOT NULL,
`type` TEXT NOT NULL,
PRIMARY KEY(`globalKey`)
)""".trimIndent()
)
}
private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Messages` (
`email` TEXT NOT NULL,
`message_global_key` TEXT NOT NULL,
`mailbox_key` TEXT NOT NULL,
`message_id` INTEGER NOT NULL,
`correspondents` TEXT NOT NULL,
`subject` TEXT NOT NULL,
`date` INTEGER NOT NULL,
`folder_id` INTEGER NOT NULL,
`unread` INTEGER NOT NULL,
`read_by` INTEGER,
`unread_by` INTEGER,
`has_attachments` INTEGER NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`is_notified` INTEGER NOT NULL,
`content` TEXT NOT NULL,
`sender` TEXT,
`recipients` TEXT
)""".trimIndent()
)
}
}

View File

@ -10,9 +10,11 @@ fun List<SdkMailbox>.mapToEntities(student: Student) = map {
globalKey = it.globalKey, globalKey = it.globalKey,
fullName = it.fullName, fullName = it.fullName,
userName = it.userName, userName = it.userName,
userLoginId = student.userLoginId,
studentName = it.studentName, studentName = it.studentName,
schoolNameShort = it.schoolNameShort, schoolNameShort = it.schoolNameShort,
type = MailboxType.valueOf(it.type.name), type = MailboxType.valueOf(it.type.name),
email = student.email,
symbol = student.symbol,
schoolId = student.schoolSymbol,
) )
} }

View File

@ -6,10 +6,13 @@ import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkMessage>.mapToEntities(mailbox: Mailbox) = map { fun List<SdkMessage>.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List<Mailbox>) = map {
Message( Message(
messageGlobalKey = it.globalKey, messageGlobalKey = it.globalKey,
mailboxKey = mailbox.globalKey, mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box ->
box.fullName == it.mailbox
}?.globalKey!!,
email = student.email,
messageId = it.id, messageId = it.id,
correspondents = it.correspondents, correspondents = it.correspondents,
subject = it.subject.trim(), subject = it.subject.trim(),

View File

@ -1,85 +0,0 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MailboxRepository @Inject constructor(
private val mailboxDao: MailboxDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val cacheKey = "mailboxes"
suspend fun refreshMailboxes(student: Student) {
val new = sdk.init(student).getMailboxes().mapToEntities(student)
val old = mailboxDao.loadAll(student.userLoginId)
mailboxDao.deleteAll(old uniqueSubtract new)
mailboxDao.insertAll(new uniqueSubtract old)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
suspend fun getMailbox(student: Student): Mailbox {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
val mailboxes = mailboxDao.loadAll(student.userLoginId)
val mailbox = mailboxes.filterByStudent(student)
return if (isExpired || mailbox == null) {
refreshMailboxes(student)
val newMailbox = mailboxDao.loadAll(student.userLoginId).filterByStudent(student)
requireNotNull(newMailbox) {
"Mailbox for ${student.userName} - ${student.studentName} not found! Saved mailboxes: $mailboxes"
}
newMailbox
} else mailbox
}
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
val normalizedStudentName = student.studentName.normalizeStudentName()
return find {
it.studentName.normalizeStudentName() == normalizedStudentName
} ?: singleOrNull {
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
} ?: singleOrNull {
it.studentName.getUnauthorizedVersion() == normalizedStudentName
}
}
private fun String.normalizeStudentName(): String {
return trim().split(" ")
.filter { it.isNotBlank() }
.joinToString(" ") { part ->
part.lowercase().replaceFirstChar { it.uppercase() }
}
}
private fun String.getFirstAndLastPart(): String {
val parts = normalizeStudentName().split(" ")
val endParts = parts.filterIndexed { i, _ ->
i == 0 || parts.size - 1 == i
}
return endParts.joinToString(" ")
}
private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ")
.joinToString(" ") {
it.first() + "*".repeat(it.length - 1)
}
}
}

View File

@ -5,6 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.*
@ -15,6 +16,8 @@ import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
@ -40,16 +43,18 @@ class MessageRepository @Inject constructor(
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider, private val sharedPrefProvider: SharedPrefProvider,
private val json: Json, private val json: Json,
private val mailboxDao: MailboxDao,
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val cacheKey = "message" private val messagesCacheKey = "message"
private val mailboxCacheKey = "mailboxes"
@Suppress("UNUSED_PARAMETER")
fun getMessages( fun getMessages(
student: Student, student: Student,
mailbox: Mailbox, mailbox: Mailbox?,
folder: MessageFolder, folder: MessageFolder,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
@ -58,16 +63,20 @@ class MessageRepository @Inject constructor(
isResultEmpty = { it.isEmpty() }, isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student, folder) key = getRefreshKey(messagesCacheKey, mailbox, folder)
) )
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { messagesDb.loadAll(mailbox.globalKey, folder.id) }, query = {
if (mailbox == null) {
messagesDb.loadAll(folder.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, folder.id)
},
fetch = { fetch = {
sdk.init(student).getMessages( sdk.init(student).getMessages(
folder = Folder.valueOf(folder.name), folder = Folder.valueOf(folder.name),
mailboxKey = mailbox.globalKey, mailboxKey = mailbox?.globalKey,
).mapToEntities(mailbox) ).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new) messagesDb.deleteAll(old uniqueSubtract new)
@ -75,7 +84,9 @@ class MessageRepository @Inject constructor(
it.isNotified = !notify it.isNotified = !notify
}) })
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder)) refreshHelper.updateLastRefreshTimestamp(
getRefreshKey(messagesCacheKey, mailbox, folder)
)
} }
) )
@ -90,7 +101,9 @@ class MessageRepository @Inject constructor(
Timber.d("Message content in db empty: ${it.message.content.isBlank()}") Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
it.message.unread || it.message.content.isBlank() it.message.unread || it.message.content.isBlank()
}, },
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) }, query = {
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
},
fetch = { fetch = {
sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead) sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead)
}, },
@ -113,8 +126,10 @@ class MessageRepository @Inject constructor(
} }
) )
fun getMessagesFromDatabase(mailbox: Mailbox): Flow<List<Message>> { fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow<List<Message>> {
return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id) return if (mailbox == null) {
messagesDb.loadAll(RECEIVED.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
} }
suspend fun updateMessages(messages: List<Message>) { suspend fun updateMessages(messages: List<Message>) {
@ -136,7 +151,7 @@ class MessageRepository @Inject constructor(
) )
} }
suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List<Message>) { suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
val firstMessage = messages.first() val firstMessage = messages.first()
sdk.init(student).deleteMessages( sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey }, messages = messages.map { it.messageGlobalKey },
@ -169,6 +184,34 @@ class MessageRepository @Inject constructor(
deleteMessages(student, mailbox, listOf(message)) deleteMessages(student, mailbox, listOf(message))
} }
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(mailboxCacheKey, student),
)
it.isEmpty() || isExpired || forceRefresh
},
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
fetch = { sdk.init(student).getMailboxes().mapToEntities(student) },
saveFetchResult = { old, new ->
mailboxDao.deleteAll(old uniqueSubtract new)
mailboxDao.insertAll(new uniqueSubtract old)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(mailboxCacheKey, student))
}
)
suspend fun getMailboxByStudent(student: Student): Mailbox? {
val mailbox = getMailboxByStudentUseCase(student)
return if (mailbox == null) {
getMailboxes(student, forceRefresh = true).toFirstResult()
getMailboxByStudentUseCase(student)
} else mailbox
}
var draftMessage: MessageDraft? var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft)) get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_draft))
?.let { json.decodeFromString(it) } ?.let { json.decodeFromString(it) }

View File

@ -33,9 +33,11 @@ class RecipientRepository @Inject constructor(
suspend fun getRecipients( suspend fun getRecipients(
student: Student, student: Student,
mailbox: Mailbox, mailbox: Mailbox?,
type: MailboxType type: MailboxType,
): List<Recipient> { ): List<Recipient> {
mailbox ?: return emptyList()
val cached = recipientDb.loadAll(type, mailbox.globalKey) val cached = recipientDb.loadAll(type, mailbox.globalKey)
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
@ -47,11 +49,15 @@ class RecipientRepository @Inject constructor(
suspend fun getMessageSender( suspend fun getMessageSender(
student: Student, student: Student,
mailbox: Mailbox, mailbox: Mailbox?,
message: Message message: Message,
): List<Recipient> = sdk.init(student) ): List<Recipient> {
.getMessageReplayDetails(message.messageGlobalKey) mailbox ?: return emptyList()
.sender
.let(::listOf) return sdk.init(student)
.mapToEntities(mailbox.globalKey) .getMessageReplayDetails(message.messageGlobalKey)
.sender
.let(::listOf)
.mapToEntities(mailbox.globalKey)
}
} }

View File

@ -0,0 +1,52 @@
package io.github.wulkanowy.domain.messages
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Student
import javax.inject.Inject
class GetMailboxByStudentUseCase @Inject constructor(
private val mailboxDao: MailboxDao,
) {
suspend operator fun invoke(student: Student): Mailbox? {
return mailboxDao.loadAll(student.email)
.filterByStudent(student)
}
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
val normalizedStudentName = student.studentName.normalizeStudentName()
return find {
it.studentName.normalizeStudentName() == normalizedStudentName
} ?: singleOrNull {
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
} ?: singleOrNull {
it.studentName.getUnauthorizedVersion() == normalizedStudentName
}
}
private fun String.normalizeStudentName(): String {
return trim().split(" ")
.filter { it.isNotBlank() }
.joinToString(" ") { part ->
part.lowercase().replaceFirstChar { it.uppercase() }
}
}
private fun String.getFirstAndLastPart(): String {
val parts = normalizeStudentName().split(" ")
val endParts = parts.filterIndexed { i, _ ->
i == 0 || parts.size - 1 == i
}
return endParts.joinToString(" ")
}
private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ")
.joinToString(" ") {
it.first() + "*".repeat(it.length - 1)
}
}
}

View File

@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getPlural
import javax.inject.Inject import javax.inject.Inject

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.repositories.MailboxRepository
import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewMessageNotification import io.github.wulkanowy.services.sync.notifications.NewMessageNotification
@ -12,12 +11,11 @@ import javax.inject.Inject
class MessageWork @Inject constructor( class MessageWork @Inject constructor(
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val mailboxRepository: MailboxRepository,
private val newMessageNotification: NewMessageNotification, private val newMessageNotification: NewMessageNotification,
) : Work { ) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
val mailbox = mailboxRepository.getMailbox(student) val mailbox = messageRepository.getMailboxByStudent(student)
messageRepository.getMessages( messageRepository.getMessages(
student = student, student = student,
mailbox = mailbox, mailbox = mailbox,
@ -26,7 +24,7 @@ class MessageWork @Inject constructor(
notify = notify notify = notify
).waitForResult() ).waitForResult()
messageRepository.getMessagesFromDatabase(mailbox).first() messageRepository.getMessagesFromDatabase(student, mailbox).first()
.filter { !it.isNotified && it.unread }.let { .filter { !it.isNotified && it.unread }.let {
if (it.isNotEmpty()) newMessageNotification.notify(it, student) if (it.isNotEmpty()) newMessageNotification.notify(it, student)
messageRepository.updateMessages(it.onEach { message -> message.isNotified = true }) messageRepository.updateMessages(it.onEach { message -> message.isNotified = true })

View File

@ -1,22 +1,23 @@
package io.github.wulkanowy.services.sync.works package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.MailboxRepository import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.RecipientRepository import io.github.wulkanowy.data.repositories.RecipientRepository
import io.github.wulkanowy.data.toFirstResult
import javax.inject.Inject import javax.inject.Inject
class RecipientWork @Inject constructor( class RecipientWork @Inject constructor(
private val mailboxRepository: MailboxRepository, private val messageRepository: MessageRepository,
private val recipientRepository: RecipientRepository private val recipientRepository: RecipientRepository
) : Work { ) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
mailboxRepository.refreshMailboxes(student) val mailboxes = messageRepository.getMailboxes(student, forceRefresh = true).toFirstResult()
mailboxes.dataOrNull?.forEach {
val mailbox = mailboxRepository.getMailbox(student) recipientRepository.refreshRecipients(student, it, MailboxType.EMPLOYEE)
}
recipientRepository.refreshRecipients(student, mailbox, MailboxType.EMPLOYEE)
} }
} }

View File

@ -25,7 +25,6 @@ class DashboardPresenter @Inject constructor(
private val gradeRepository: GradeRepository, private val gradeRepository: GradeRepository,
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val mailboxRepository: MailboxRepository,
private val attendanceSummaryRepository: AttendanceSummaryRepository, private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val timetableRepository: TimetableRepository, private val timetableRepository: TimetableRepository,
private val homeworkRepository: HomeworkRepository, private val homeworkRepository: HomeworkRepository,
@ -228,7 +227,7 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow { flow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val mailbox = mailboxRepository.getMailbox(student) val mailbox = messageRepository.getMailboxByStudent(student)
val selectedTiles = preferencesRepository.selectedDashboardTiles val selectedTiles = preferencesRepository.selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null)) val flowSuccess = flowOf(Resource.Success(null))

View File

@ -19,6 +19,7 @@ val debugMessageItems = listOf(
private fun generateMessage(sender: String, subject: String) = Message( private fun generateMessage(sender: String, subject: String) = Message(
subject = subject, subject = subject,
messageId = 123, messageId = 123,
email = "",
date = Instant.now(), date = Instant.now(),
folderId = 0, folderId = 0,
unread = true, unread = true,

View File

@ -0,0 +1,81 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.databinding.ItemMailboxChooserBinding
import javax.inject.Inject
class MailboxChooserAdapter @Inject constructor() :
ListAdapter<MailboxChooserItem, MailboxChooserAdapter.ItemViewHolder>(Differ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMailboxChooserBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ItemViewHolder(
private val binding: ItemMailboxChooserBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: MailboxChooserItem) {
with(binding) {
mailboxItemName.text = item.mailbox?.getFirstLine()
?: root.resources.getString(R.string.message_chip_all_mailboxes)
mailboxItemSchool.text = item.mailbox?.getSecondLine()
mailboxItemSchool.isVisible = !item.isAll
root.setOnClickListener { item.onClickListener(item.mailbox) }
}
}
private fun Mailbox.getFirstLine() = buildString {
if (studentName.isNotBlank() && studentName != userName) {
append(studentName)
append(" - ")
}
append(userName)
}
private fun Mailbox.getSecondLine() = buildString {
append(schoolNameShort)
append(" - ")
append(getMailboxType(type))
}
private fun getMailboxType(type: MailboxType): String = when (type) {
MailboxType.STUDENT -> R.string.message_mailbox_type_student
MailboxType.PARENT -> R.string.message_mailbox_type_parent
MailboxType.GUARDIAN -> R.string.message_mailbox_type_guardian
MailboxType.EMPLOYEE -> R.string.message_mailbox_type_employee
MailboxType.UNKNOWN -> null
}.let { it?.let { it1 -> binding.root.resources.getString(it1) }.orEmpty() }
}
private object Differ : ItemCallback<MailboxChooserItem>() {
override fun areItemsTheSame(
oldItem: MailboxChooserItem,
newItem: MailboxChooserItem
): Boolean {
return oldItem.mailbox?.globalKey == newItem.mailbox?.globalKey
}
override fun areContentsTheSame(
oldItem: MailboxChooserItem,
newItem: MailboxChooserItem
): Boolean {
return oldItem == newItem
}
}
}

View File

@ -0,0 +1,75 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.databinding.DialogMailboxChooserBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import javax.inject.Inject
@AndroidEntryPoint
class MailboxChooserDialog : BaseDialogFragment<DialogMailboxChooserBinding>(), MailboxChooserView {
@Inject
lateinit var presenter: MailboxChooserPresenter
@Inject
lateinit var mailboxAdapter: MailboxChooserAdapter
companion object {
const val LISTENER_KEY = "mailbox_selected"
const val MAILBOX_KEY = "selected_mailbox"
const val REQUIRED_KEY = "is_mailbox_required"
fun newInstance(mailboxes: List<Mailbox>, isMailboxRequired: Boolean, folder: String) =
MailboxChooserDialog().apply {
arguments = bundleOf(
MAILBOX_KEY to mailboxes.toTypedArray(),
REQUIRED_KEY to isMailboxRequired,
LISTENER_KEY to folder,
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root
@Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
presenter.onAttachView(
view = this,
requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false),
mailboxes = requireArguments().getParcelableArray(MAILBOX_KEY).orEmpty()
.toList() as List<Mailbox>,
)
}
override fun initView() {
binding.accountQuickDialogRecycler.adapter = mailboxAdapter
}
override fun submitData(items: List<MailboxChooserItem>) {
mailboxAdapter.submitList(items)
}
override fun onMailboxSelected(item: Mailbox?) {
setFragmentResult(
requestKey = requireArguments().getString(LISTENER_KEY).orEmpty(),
result = bundleOf(MAILBOX_KEY to item),
)
dismiss()
}
}

View File

@ -0,0 +1,9 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import io.github.wulkanowy.data.db.entities.Mailbox
data class MailboxChooserItem(
val mailbox: Mailbox? = null,
val isAll: Boolean = false,
val onClickListener: (Mailbox?) -> Unit,
)

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject
class MailboxChooserPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository
) : BasePresenter<MailboxChooserView>(errorHandler, studentRepository) {
fun onAttachView(view: MailboxChooserView, mailboxes: List<Mailbox>, requireMailbox: Boolean) {
super.onAttachView(view)
view.initView()
Timber.i("Mailbox chooser view was initialized")
view.submitData(getMailboxItems(mailboxes, requireMailbox))
}
private fun getMailboxItems(
mailboxes: List<Mailbox>,
requireMailbox: Boolean,
): List<MailboxChooserItem> = buildList {
if (!requireMailbox) {
add(MailboxChooserItem(isAll = true, onClickListener = ::onMailboxSelect))
}
addAll(mailboxes.map {
MailboxChooserItem(mailbox = it, isAll = false, onClickListener = ::onMailboxSelect)
})
}
fun onMailboxSelect(item: Mailbox?) {
view?.onMailboxSelected(item)
}
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.ui.base.BaseView
interface MailboxChooserView : BaseView {
fun initView()
fun submitData(items: List<MailboxChooserItem>)
fun onMailboxSelected(item: Mailbox?)
}

View File

@ -6,7 +6,6 @@ import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.MailboxRepository
import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -21,7 +20,6 @@ class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val mailboxRepository: MailboxRepository,
private val analytics: AnalyticsHelper private val analytics: AnalyticsHelper
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) { ) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
@ -187,8 +185,8 @@ class MessagePreviewPresenter @Inject constructor(
presenterScope.launch { presenterScope.launch {
runCatching { runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true) val student = studentRepository.getCurrentStudent(decryptPass = true)
val mailbox = mailboxRepository.getMailbox(student) val mailbox = messageRepository.getMailboxByStudent(student)
messageRepository.deleteMessage(student, mailbox, message!!) messageRepository.deleteMessage(student, mailbox!!, message!!)
} }
.onFailure { .onFailure {
retryCallback = { onMessageDelete() } retryCallback = { onMessageDelete() }

View File

@ -19,9 +19,13 @@ import androidx.core.text.toHtml
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.databinding.ActivitySendMessageBinding import io.github.wulkanowy.databinding.ActivitySendMessageBinding
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
@ -100,6 +104,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
formSubjectValue = binding.sendMessageSubject.text.toString() formSubjectValue = binding.sendMessageSubject.text.toString()
formContentValue = formContentValue =
binding.sendMessageMessageContent.text.toString().parseAsHtml().toString() binding.sendMessageMessageContent.text.toString().parseAsHtml().toString()
binding.sendMessageFrom.setOnClickListener { presenter.onOpenMailboxChooser() }
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
@ -107,6 +112,9 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
message = intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, message = intent.getSerializableExtra(EXTRA_MESSAGE) as? Message,
reply = intent.getSerializableExtra(EXTRA_REPLY) as? Boolean reply = intent.getSerializableExtra(EXTRA_REPLY) as? Boolean
) )
supportFragmentManager.setFragmentResultListener(LISTENER_KEY, this) { _, bundle ->
presenter.onMailboxSelected(bundle.getSerializable(MAILBOX_KEY) as? Mailbox)
}
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@ -205,6 +213,14 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
} }
} }
override fun showMailboxChooser(mailboxes: List<Mailbox>) {
MailboxChooserDialog.newInstance(
mailboxes = mailboxes,
isMailboxRequired = true,
folder = LISTENER_KEY,
).show(supportFragmentManager, "chooser")
}
override fun popView() { override fun popView() {
finish() finish()
} }

View File

@ -1,15 +1,15 @@
package io.github.wulkanowy.ui.modules.message.send package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.repositories.* import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.RecipientRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
@ -28,7 +28,6 @@ class SendMessagePresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val mailboxRepository: MailboxRepository,
private val recipientRepository: RecipientRepository, private val recipientRepository: RecipientRepository,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val analytics: AnalyticsHelper private val analytics: AnalyticsHelper
@ -36,10 +35,19 @@ class SendMessagePresenter @Inject constructor(
private val messageUpdateChannel = Channel<Unit>() private val messageUpdateChannel = Channel<Unit>()
private var message: Message? = null
private var isReplay: Boolean? = null
private var mailboxes: List<Mailbox> = emptyList()
private var selectedMailbox: Mailbox? = null
fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) { fun onAttachView(view: SendMessageView, reason: String?, message: Message?, reply: Boolean?) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
initializeSubjectStream() initializeSubjectStream()
this.message = message
this.isReplay = reply
Timber.i("Send message view was initialized") Timber.i("Send message view was initialized")
loadData(message, reply) loadData(message, reply)
with(view) { with(view) {
@ -110,16 +118,31 @@ class SendMessagePresenter @Inject constructor(
return false return false
} }
fun onOpenMailboxChooser() {
view?.showMailboxChooser(mailboxes)
}
fun onMailboxSelected(mailbox: Mailbox?) {
selectedMailbox = mailbox
loadData(message, isReplay)
}
private fun loadData(message: Message?, reply: Boolean?) { private fun loadData(message: Message?, reply: Boolean?) {
resourceFlow { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val mailbox = mailboxRepository.getMailbox(student)
if (selectedMailbox == null && mailboxes.isEmpty()) {
selectedMailbox = messageRepository.getMailboxByStudent(student)
mailboxes = messageRepository.getMailboxes(student, false).toFirstResult()
.dataOrNull.orEmpty()
}
Timber.i("Loading recipients started") Timber.i("Loading recipients started")
val recipients = createChips( val recipients = createChips(
recipients = recipientRepository.getRecipients( recipients = recipientRepository.getRecipients(
student = student, student = student,
mailbox = mailbox, mailbox = selectedMailbox,
type = MailboxType.EMPLOYEE, type = MailboxType.EMPLOYEE,
) )
) )
@ -130,7 +153,7 @@ class SendMessagePresenter @Inject constructor(
message != null && reply == true -> recipientRepository.getMessageSender( message != null && reply == true -> recipientRepository.getMessageSender(
student = student, student = student,
message = message, message = message,
mailbox = mailbox, mailbox = selectedMailbox,
) )
else -> emptyList() else -> emptyList()
}.let { createChips(it) } }.let { createChips(it) }
@ -139,39 +162,42 @@ class SendMessagePresenter @Inject constructor(
messageRecipients.size messageRecipients.size
) )
Triple(mailbox, recipients, messageRecipients) recipients to messageRecipients
} }
.logResourceStatus("load recipients") .logResourceStatus("load recipients")
.onEach { .onResourceLoading {
when (it) { view?.run {
is Resource.Loading -> view?.run { showProgress(true)
showProgress(true) showContent(false)
showContent(false) }
} }
is Resource.Success -> it.data.let { (mailbox, recipientChips, selectedRecipientChips) -> .onResourceNotLoading {
view?.run { view?.run { showProgress(false) }
setMailbox(getMailboxName(mailbox)) }
setRecipients(recipientChips) .onResourceError {
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( view?.showContent(true)
selectedRecipientChips errorHandler.dispatch(it)
) }
showContent(true) .onResourceSuccess {
} it.let { (recipientChips, selectedRecipientChips) ->
} view?.run {
is Resource.Error -> { setMailbox(getMailboxName(selectedMailbox))
view?.showContent(true) setRecipients(recipientChips)
errorHandler.dispatch(it.error) if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
selectedRecipientChips
)
showContent(true)
} }
} }
}.onResourceNotLoading { }
view?.run { showProgress(false) } .launch()
}.launch()
} }
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) { private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
val mailbox = selectedMailbox ?: return
resourceFlow { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val mailbox = mailboxRepository.getMailbox(student)
messageRepository.sendMessage( messageRepository.sendMessage(
student = student, student = student,
subject = subject, subject = subject,
@ -222,18 +248,21 @@ class SendMessagePresenter @Inject constructor(
} }
} }
private fun getMailboxName(mailbox: Mailbox): String { private fun getMailboxName(mailbox: Mailbox?): String {
mailbox ?: return ""
// username - accountType [\n student name - ] (school short name)
return buildString { return buildString {
append(mailbox.userName) append(mailbox.userName)
append(" - ") append(" - ")
append(getMailboxType(mailbox.type)) append(getMailboxType(mailbox.type))
appendLine()
if (mailbox.type == MailboxType.PARENT) { if (mailbox.type == MailboxType.PARENT) {
append(" - ")
append(mailbox.studentName) append(mailbox.studentName)
append(" - ")
} }
append(" - ")
append("(${mailbox.schoolNameShort})") append("(${mailbox.schoolNameShort})")
} }
} }
@ -267,9 +296,9 @@ class SendMessagePresenter @Inject constructor(
private fun saveDraftMessage() { private fun saveDraftMessage() {
messageRepository.draftMessage = MessageDraft( messageRepository.draftMessage = MessageDraft(
view?.formRecipientsData!!, recipients = view?.formRecipientsData!!,
view?.formSubjectValue!!, subject = view?.formSubjectValue!!,
view?.formContentValue!! content = view?.formContentValue!!,
) )
} }

View File

@ -61,4 +61,5 @@ interface SendMessageView : BaseView {
fun getMessageBackupDialogStringWithRecipients(recipients: String): String fun getMessageBackupDialogStringWithRecipients(recipients: String): String
fun clearDraft() fun clearDraft()
fun showMailboxChooser(mailboxes: List<Mailbox>)
} }

View File

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.ItemMessageBinding import io.github.wulkanowy.databinding.ItemMessageBinding
import io.github.wulkanowy.databinding.ItemMessageChipsBinding import io.github.wulkanowy.databinding.ItemMessageChipsBinding
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject import javax.inject.Inject
@ -19,13 +20,15 @@ import javax.inject.Inject
class MessageTabAdapter @Inject constructor() : class MessageTabAdapter @Inject constructor() :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit = { _, _ -> } lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit
var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit = {} lateinit var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit
var onHeaderClickListener: (CompoundButton, Boolean) -> Unit = { _, _ -> } lateinit var onHeaderClickListener: (CompoundButton, Boolean) -> Unit
var onChangesDetectedListener = {} lateinit var onMailboxClickListener: () -> Unit
lateinit var onChangesDetectedListener: () -> Unit
private var items = mutableListOf<MessageTabDataItem>() private var items = mutableListOf<MessageTabDataItem>()
@ -49,12 +52,12 @@ class MessageTabAdapter @Inject constructor() :
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (MessageItemViewType.values()[viewType]) { return when (MessageItemViewType.values()[viewType]) {
MessageItemViewType.MESSAGE -> ItemViewHolder(
ItemMessageBinding.inflate(inflater, parent, false)
)
MessageItemViewType.FILTERS -> HeaderViewHolder( MessageItemViewType.FILTERS -> HeaderViewHolder(
ItemMessageChipsBinding.inflate(inflater, parent, false) ItemMessageChipsBinding.inflate(inflater, parent, false)
) )
MessageItemViewType.MESSAGE -> ItemViewHolder(
ItemMessageBinding.inflate(inflater, parent, false)
)
} }
} }
@ -69,6 +72,20 @@ class MessageTabAdapter @Inject constructor() :
val item = items[position] as MessageTabDataItem.FilterHeader val item = items[position] as MessageTabDataItem.FilterHeader
with(holder.binding) { with(holder.binding) {
chipMailbox.text = item.selectedMailbox
?: root.context.getString(R.string.message_chip_all_mailboxes)
chipMailbox.chipBackgroundColor = ColorStateList.valueOf(
if (item.selectedMailbox == null) {
root.context.getCompatColor(R.color.mtrl_choice_chip_background_color)
} else root.context.getThemeAttrColor(android.R.attr.colorPrimary, 64)
)
chipMailbox.setTextColor(
if (item.selectedMailbox == null) {
root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
} else root.context.getThemeAttrColor(android.R.attr.colorPrimary)
)
chipMailbox.setOnClickListener { onMailboxClickListener() }
if (item.onlyUnread == null) { if (item.onlyUnread == null) {
chipUnread.isVisible = false chipUnread.isVisible = false
} else { } else {
@ -77,6 +94,7 @@ class MessageTabAdapter @Inject constructor() :
chipUnread.setOnCheckedChangeListener(onHeaderClickListener) chipUnread.setOnCheckedChangeListener(onHeaderClickListener)
} }
chipUnread.isEnabled = item.isEnabled chipUnread.isEnabled = item.isEnabled
chipAttachments.isEnabled = item.isEnabled chipAttachments.isEnabled = item.isEnabled
chipAttachments.isChecked = item.onlyWithAttachments chipAttachments.isChecked = item.onlyWithAttachments
chipAttachments.setOnCheckedChangeListener(onHeaderClickListener) chipAttachments.setOnCheckedChangeListener(onHeaderClickListener)

View File

@ -11,6 +11,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) {
) : MessageTabDataItem(MessageItemViewType.MESSAGE) ) : MessageTabDataItem(MessageItemViewType.MESSAGE)
data class FilterHeader( data class FilterHeader(
val selectedMailbox: String?,
val onlyUnread: Boolean?, val onlyUnread: Boolean?,
val onlyWithAttachments: Boolean, val onlyWithAttachments: Boolean,
val isEnabled: Boolean val isEnabled: Boolean

View File

@ -10,15 +10,18 @@ import android.widget.CompoundButton
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.databinding.FragmentMessageTabBinding import io.github.wulkanowy.databinding.FragmentMessageTabBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
@ -104,6 +107,7 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
onItemClickListener = presenter::onMessageItemSelected onItemClickListener = presenter::onMessageItemSelected
onLongItemClickListener = presenter::onMessageItemLongSelected onLongItemClickListener = presenter::onMessageItemLongSelected
onHeaderClickListener = ::onChipChecked onHeaderClickListener = ::onChipChecked
onMailboxClickListener = presenter::onMailboxFilterSelected
onChangesDetectedListener = ::resetListPosition onChangesDetectedListener = ::resetListPosition
} }
@ -123,6 +127,12 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
messageTabErrorRetry.setOnClickListener { presenter.onRetry() } messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
presenter.onMailboxSelected(
mailbox = bundle.getSerializable(MailboxChooserDialog.MAILBOX_KEY) as? Mailbox,
)
}
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -246,6 +256,16 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
) )
} }
override fun showMailboxChooser(mailboxes: List<Mailbox>) {
(activity as? MainActivity)?.showDialogFragment(
MailboxChooserDialog.newInstance(
mailboxes = mailboxes,
isMailboxRequired = false,
folder = requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!,
)
)
}
override fun hideKeyboard() { override fun hideKeyboard() {
activity?.hideSoftInput() activity?.hideSoftInput()
} }

View File

@ -1,9 +1,9 @@
package io.github.wulkanowy.ui.modules.message.tab package io.github.wulkanowy.ui.modules.message.tab
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.MailboxRepository
import io.github.wulkanowy.data.repositories.MessageRepository import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -26,7 +26,6 @@ class MessageTabPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val mailboxRepository: MailboxRepository,
private val analytics: AnalyticsHelper private val analytics: AnalyticsHelper
) : BasePresenter<MessageTabView>(errorHandler, studentRepository) { ) : BasePresenter<MessageTabView>(errorHandler, studentRepository) {
@ -36,6 +35,9 @@ class MessageTabPresenter @Inject constructor(
private var lastSearchQuery = "" private var lastSearchQuery = ""
private var mailboxes: List<Mailbox> = emptyList()
private var selectedMailbox: Mailbox? = null
private var messages = emptyList<Message>() private var messages = emptyList<Message>()
private val searchChannel = Channel<String>() private val searchChannel = Channel<String>()
@ -122,8 +124,7 @@ class MessageTabPresenter @Inject constructor(
runCatching { runCatching {
val student = studentRepository.getCurrentStudent(true) val student = studentRepository.getCurrentStudent(true)
val mailbox = mailboxRepository.getMailbox(student) messageRepository.deleteMessages(student, selectedMailbox, messageList)
messageRepository.deleteMessages(student, mailbox, messageList)
} }
.onFailure(errorHandler::dispatch) .onFailure(errorHandler::dispatch)
.onSuccess { view?.showMessagesDeleted() } .onSuccess { view?.showMessagesDeleted() }
@ -202,13 +203,28 @@ class MessageTabPresenter @Inject constructor(
} }
} }
fun onMailboxFilterSelected() {
view?.showMailboxChooser(mailboxes)
}
fun onMailboxSelected(mailbox: Mailbox?) {
selectedMailbox = mailbox
loadData(false)
}
private fun loadData(forceRefresh: Boolean) { private fun loadData(forceRefresh: Boolean) {
Timber.i("Loading $folder message data started") Timber.i("Loading $folder message data started")
flatResourceFlow { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val mailbox = mailboxRepository.getMailbox(student)
messageRepository.getMessages(student, mailbox, folder, forceRefresh) if (selectedMailbox == null && mailboxes.isEmpty()) {
selectedMailbox = messageRepository.getMailboxByStudent(student)
mailboxes = messageRepository.getMailboxes(student, forceRefresh).toFirstResult()
.dataOrNull.orEmpty()
}
messageRepository.getMessages(student, selectedMailbox, folder, forceRefresh)
} }
.logResourceStatus("load $folder message") .logResourceStatus("load $folder message")
.onResourceData { .onResourceData {
@ -327,7 +343,16 @@ class MessageTabPresenter @Inject constructor(
MessageTabDataItem.FilterHeader( MessageTabDataItem.FilterHeader(
onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT }, onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT },
onlyWithAttachments = onlyWithAttachments, onlyWithAttachments = onlyWithAttachments,
isEnabled = !isActionMode isEnabled = !isActionMode,
selectedMailbox = selectedMailbox?.let {
buildString {
if (it.studentName.isNotBlank() && it.studentName != it.userName) {
append(it.studentName)
append(" - ")
}
append(it.userName)
}
},
) )
) )

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.message.tab package io.github.wulkanowy.ui.modules.message.tab
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
@ -46,4 +47,6 @@ interface MessageTabView : BaseView {
fun showActionMode(show: Boolean) fun showActionMode(show: Boolean)
fun showRecyclerBottomPadding(show: Boolean) fun showRecyclerBottomPadding(show: Boolean)
fun showMailboxChooser(mailboxes: List<Mailbox>)
} }

View File

@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
@ -25,8 +26,8 @@ fun getRefreshKey(name: String, student: Student): String {
return "${name}_${student.userLoginId}" return "${name}_${student.userLoginId}"
} }
fun getRefreshKey(name: String, student: Student, folder: MessageFolder): String { fun getRefreshKey(name: String, mailbox: Mailbox?, folder: MessageFolder): String {
return "${name}_${student.id}_${folder.id}" return "${name}_${mailbox?.globalKey ?: "all"}_${folder.id}"
} }
class AutoRefreshHelper @Inject constructor( class AutoRefreshHelper @Inject constructor(

View File

@ -55,17 +55,29 @@
android:id="@+id/sendMessageFrom" android:id="@+id/sendMessageFrom"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="58dp" android:layout_height="58dp"
android:layout_marginStart="16dp" android:layout_marginStart="8dp"
android:layout_marginLeft="16dp" android:background="?selectableItemBackground"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="8dp"
android:paddingEnd="32dp"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/sendMessageFromHint" app:layout_constraintStart_toEndOf="@id/sendMessageFromHint"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Jan Kowalski" /> tools:text="Jan Kowalski" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:rotation="270"
android:src="@drawable/ic_chevron_left"
app:layout_constraintBottom_toBottomOf="@id/sendMessageFrom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/sendMessageFrom"
app:tint="?android:textColorSecondary"
tools:ignore="ContentDescription" />
<View <View
android:id="@+id/sendMessageFromDivider" android:id="@+id/sendMessageFromDivider"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UselessParent">
<TextView
android:id="@+id/account_quick_dialog_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/message_mailbox_chooser_title"
android:textSize="20sp"
android:textStyle="bold"
app:firstBaselineToTopHeight="40dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/account_quick_dialog_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="3"
tools:listitem="@layout/item_mailbox_chooser" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,43 @@
<?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="wrap_content"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
tools:context=".ui.modules.message.mailboxchooser.MailboxChooserAdapter">
<TextView
android:id="@+id/mailboxItemName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/mailboxItemSchool"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginTop="3dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mailboxItemName"
tools:text="@tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,21 +1,30 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/messageChipsLayout" android:id="@+id/messageChipsLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingTop="10dp"
android:paddingRight="16dp"
tools:context=".ui.modules.message.tab.MessageTabAdapter"> tools:context=".ui.modules.message.tab.MessageTabAdapter">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/messageChipGroup" android:id="@+id/messageChipGroup"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingLeft="16dp"
android:paddingTop="10dp"
android:paddingRight="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
app:singleLine="true">
<com.google.android.material.chip.Chip
android:id="@+id/chip_mailbox"
style="@style/Widget.MaterialComponents.Chip.Action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_chip_all_mailboxes" />
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/chip_unread" android:id="@+id/chip_unread"
@ -37,4 +46,4 @@
app:checkedIconEnabled="true" app:checkedIconEnabled="true"
app:checkedIconTint="@color/mtrl_choice_chip_text_color" /> app:checkedIconTint="@color/mtrl_choice_chip_text_color" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout> </HorizontalScrollView>

View File

@ -301,6 +301,7 @@
<string name="message_not_exists">Message does not exist</string> <string name="message_not_exists">Message does not exist</string>
<string name="message_required_recipients">You need to choose at least 1 recipient</string> <string name="message_required_recipients">You need to choose at least 1 recipient</string>
<string name="message_content_min_length">The message content must be at least 3 characters</string> <string name="message_content_min_length">The message content must be at least 3 characters</string>
<string name="message_chip_all_mailboxes">Wszystkie skrzynki</string>
<string name="message_chip_only_unread">Only unread</string> <string name="message_chip_only_unread">Only unread</string>
<string name="message_chip_only_with_attachments">Only with attachments</string> <string name="message_chip_only_with_attachments">Only with attachments</string>
<string name="message_read">Read: %s</string> <string name="message_read">Read: %s</string>
@ -324,6 +325,7 @@
<item quantity="other">%1$d selected</item> <item quantity="other">%1$d selected</item>
</plurals> </plurals>
<string name="message_messages_deleted">Messages deleted</string> <string name="message_messages_deleted">Messages deleted</string>
<string name="message_mailbox_chooser_title">Choose mailbox</string>
<!--Note--> <!--Note-->

View File

@ -27,7 +27,9 @@ fun getMailboxEntity() = Mailbox(
globalKey = "v4", globalKey = "v4",
fullName = "", fullName = "",
userName = "", userName = "",
userLoginId = 0, email = "test",
symbol = "powiatwulkanowy",
schoolId = "123456",
studentName = "", studentName = "",
schoolNameShort = "", schoolNameShort = "",
type = MailboxType.UNKNOWN, type = MailboxType.UNKNOWN,

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
@ -10,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.getMailboxEntity import io.github.wulkanowy.getMailboxEntity
import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
@ -55,6 +57,12 @@ class MessageRepositoryTest {
@MockK @MockK
private lateinit var sharedPrefProvider: SharedPrefProvider private lateinit var sharedPrefProvider: SharedPrefProvider
@MockK
private lateinit var mailboxDao: MailboxDao
@MockK
private lateinit var getMailboxByStudentUseCase: GetMailboxByStudentUseCase
private val student = getStudentEntity() private val student = getStudentEntity()
private val mailbox = getMailboxEntity() private val mailbox = getMailboxEntity()
@ -74,26 +82,33 @@ class MessageRepositoryTest {
refreshHelper = refreshHelper, refreshHelper = refreshHelper,
sharedPrefProvider = sharedPrefProvider, sharedPrefProvider = sharedPrefProvider,
json = Json, json = Json,
mailboxDao = mailboxDao,
getMailboxByStudentUseCase = getMailboxByStudentUseCase,
) )
} }
@Test @Test
fun `get messages when fetched completely new message without notify`() = runBlocking { fun `get messages when fetched completely new message without notify`() = runBlocking {
every { messageDb.loadAll(any(), any()) } returns flowOf(emptyList()) coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox)
every { messageDb.loadAll(mailbox.globalKey, any()) } returns flowOf(emptyList())
coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf( coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf(
getMessageDto() getMessageDto().copy(
unreadBy = 5,
readBy = 10,
)
) )
coEvery { messageDb.deleteAll(any()) } just Runs coEvery { messageDb.deleteAll(any()) } just Runs
coEvery { messageDb.insertAll(any()) } returns listOf() coEvery { messageDb.insertAll(any()) } returns listOf()
repository.getMessages( val res = repository.getMessages(
student = student, student = student,
mailbox = mailbox, mailbox = mailbox,
folder = MessageFolder.RECEIVED, folder = MessageFolder.RECEIVED,
forceRefresh = true, forceRefresh = true,
notify = false, notify = false,
).toFirstResult().dataOrNull.orEmpty() ).toFirstResult()
assertEquals(null, res.errorOrNull)
coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList<Message>()) }) } coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList<Message>()) }) }
coVerify { coVerify {
messageDb.insertAll(withArg { messageDb.insertAll(withArg {
@ -187,6 +202,7 @@ class MessageRepositoryTest {
) = Message( ) = Message(
messageGlobalKey = "v4", messageGlobalKey = "v4",
mailboxKey = "", mailboxKey = "",
email = "",
correspondents = "", correspondents = "",
messageId = messageId, messageId = messageId,
subject = "", subject = "",

View File

@ -1,65 +1,52 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.domain
import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
import io.mockk.just import io.mockk.just
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertNull
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class MailboxRepositoryTest { class GetMailboxByStudentUseCaseTest {
@SpyK
private var sdk = Sdk()
@MockK @MockK
private lateinit var mailboxDao: MailboxDao private lateinit var mailboxDao: MailboxDao
@MockK private lateinit var systemUnderTest: GetMailboxByStudentUseCase
private lateinit var refreshHelper: AutoRefreshHelper
private lateinit var systemUnderTest: MailboxRepository
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
coEvery { refreshHelper.shouldBeRefreshed(any()) } returns false
coEvery { refreshHelper.updateLastRefreshTimestamp(any()) } just Runs
coEvery { mailboxDao.deleteAll(any()) } just Runs coEvery { mailboxDao.deleteAll(any()) } just Runs
coEvery { mailboxDao.insertAll(any()) } returns emptyList() coEvery { mailboxDao.insertAll(any()) } returns emptyList()
coEvery { mailboxDao.loadAll(any()) } returns emptyList() coEvery { mailboxDao.loadAll(any()) } returns emptyList()
coEvery { sdk.getMailboxes() } returns emptyList()
systemUnderTest = MailboxRepository( systemUnderTest = GetMailboxByStudentUseCase(mailboxDao = mailboxDao)
mailboxDao = mailboxDao,
sdk = sdk,
refreshHelper = refreshHelper,
)
} }
@Test(expected = IllegalArgumentException::class) @Test
fun `get mailbox that doesn't exist`() = runTest { fun `get mailbox that doesn't exist`() = runTest {
val student = getStudentEntity( val student = getStudentEntity(
userName = "Stanisław Kowalski", userName = "Stanisław Kowalski",
studentName = "Jan Kowalski", studentName = "Jan Kowalski",
) )
coEvery { sdk.getMailboxes() } returns emptyList() coEvery { mailboxDao.loadAll(any()) } returns emptyList()
systemUnderTest.getMailbox(student) assertNull(systemUnderTest(student))
} }
@Test @Test
@ -73,7 +60,7 @@ class MailboxRepositoryTest {
expectedMailbox, expectedMailbox,
) )
val selectedMailbox = systemUnderTest.getMailbox(student) val selectedMailbox = systemUnderTest(student)
assertEquals(expectedMailbox, selectedMailbox) assertEquals(expectedMailbox, selectedMailbox)
} }
@ -88,7 +75,7 @@ class MailboxRepositoryTest {
expectedMailbox, expectedMailbox,
) )
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) assertEquals(expectedMailbox, systemUnderTest(student))
} }
@Test @Test
@ -102,10 +89,10 @@ class MailboxRepositoryTest {
expectedMailbox, expectedMailbox,
) )
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) assertEquals(expectedMailbox, systemUnderTest(student))
} }
@Test(expected = IllegalArgumentException::class) @Test
fun `get mailbox for not-unique non-authorized student`() = runTest { fun `get mailbox for not-unique non-authorized student`() = runTest {
val student = getStudentEntity( val student = getStudentEntity(
userName = "Stanisław Kowalski", userName = "Stanisław Kowalski",
@ -116,7 +103,7 @@ class MailboxRepositoryTest {
getMailboxEntity("Jan Kurowski"), getMailboxEntity("Jan Kurowski"),
) )
systemUnderTest.getMailbox(student) assertNull(systemUnderTest(student))
} }
@Test @Test
@ -130,7 +117,7 @@ class MailboxRepositoryTest {
expectedMailbox, expectedMailbox,
) )
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) assertEquals(expectedMailbox, systemUnderTest(student))
} }
@Test @Test
@ -144,7 +131,7 @@ class MailboxRepositoryTest {
expectedMailbox, expectedMailbox,
) )
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) assertEquals(expectedMailbox, systemUnderTest(student))
} }
@Test @Test
@ -158,7 +145,7 @@ class MailboxRepositoryTest {
expectedMailbox, expectedMailbox,
) )
assertEquals(expectedMailbox, systemUnderTest.getMailbox(student)) assertEquals(expectedMailbox, systemUnderTest(student))
} }
private fun getMailboxEntity( private fun getMailboxEntity(
@ -167,7 +154,9 @@ class MailboxRepositoryTest {
globalKey = "", globalKey = "",
fullName = "", fullName = "",
userName = "", userName = "",
userLoginId = 123, email = "",
schoolId = "",
symbol = "",
studentName = studentName, studentName = studentName,
schoolNameShort = "", schoolNameShort = "",
type = MailboxType.STUDENT, type = MailboxType.STUDENT,