Add support for messages plus API (#1945)

This commit is contained in:
Mikołaj Pich 2022-08-22 14:30:50 +02:00 committed by GitHub
parent 08a3bd77bd
commit 9d47127921
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 3065 additions and 575 deletions

View File

@ -186,7 +186,7 @@ ext {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:9032e33686" implementation "io.github.wulkanowy:sdk:dbe87aac"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,6 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -110,7 +109,6 @@ internal class DataModule {
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
@Provides @Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) = fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
@ -197,7 +195,7 @@ internal class DataModule {
@Singleton @Singleton
@Provides @Provides
fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao fun provideMailboxesDao(database: AppDatabase) = database.mailboxDao
@Singleton @Singleton
@Provides @Provides

View File

@ -30,7 +30,7 @@ import javax.inject.Singleton
Subject::class, Subject::class,
LuckyNumber::class, LuckyNumber::class,
CompletedLesson::class, CompletedLesson::class,
ReportingUnit::class, Mailbox::class,
Recipient::class, Recipient::class,
MobileDevice::class, MobileDevice::class,
Teacher::class, Teacher::class,
@ -55,7 +55,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 50 const val VERSION_SCHEMA = 51
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -103,7 +103,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration44(), Migration44(),
Migration46(), Migration46(),
Migration49(), Migration49(),
Migration50() Migration50(),
Migration51(),
) )
fun newInstance( fun newInstance(
@ -154,7 +155,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val completedLessonsDao: CompletedLessonsDao abstract val completedLessonsDao: CompletedLessonsDao
abstract val reportingUnitDao: ReportingUnitDao abstract val mailboxDao: MailboxDao
abstract val recipientDao: RecipientDao abstract val recipientDao: RecipientDao

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Mailbox
import javax.inject.Singleton
@Singleton
@Dao
interface MailboxDao : BaseDao<Mailbox> {
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId ")
suspend fun loadAll(userLoginId: Int): List<Mailbox>
@Query("SELECT * FROM Mailboxes WHERE userLoginId = :userLoginId AND studentName = :studentName ")
suspend fun load(userLoginId: Int, studentName: String): Mailbox?
}

View File

@ -11,9 +11,9 @@ import kotlinx.coroutines.flow.Flow
interface MessagesDao : BaseDao<Message> { interface MessagesDao : BaseDao<Message> {
@Transaction @Transaction
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId") @Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?> fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE student_id = :studentId 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(studentId: Int, folder: Int): Flow<List<Message>> fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
} }

View File

@ -2,6 +2,7 @@ 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.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import javax.inject.Singleton import javax.inject.Singleton
@ -9,6 +10,6 @@ import javax.inject.Singleton
@Dao @Dao
interface RecipientDao : BaseDao<Recipient> { interface RecipientDao : BaseDao<Recipient> {
@Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role") @Query("SELECT * FROM Recipients WHERE type = :type AND studentMailboxGlobalKey = :studentMailboxGlobalKey")
suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient> suspend fun loadAll(type: MailboxType, studentMailboxGlobalKey: String): List<Recipient>
} }

View File

@ -1,17 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.ReportingUnit
import javax.inject.Singleton
@Singleton
@Dao
interface ReportingUnitDao : BaseDao<ReportingUnit> {
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId")
suspend fun load(studentId: Int): List<ReportingUnit>
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId AND real_id = :unitId")
suspend fun loadOne(studentId: Int, unitId: Int): ReportingUnit?
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "Mailboxes")
data class Mailbox(
@PrimaryKey
val globalKey: String,
val fullName: String,
val userName: String,
val userLoginId: Int,
val studentName: String,
val schoolNameShort: String,
val type: MailboxType,
)
enum class MailboxType {
STUDENT,
PARENT,
GUARDIAN,
EMPLOYEE,
UNKNOWN,
}

View File

@ -9,23 +9,16 @@ import java.time.Instant
@Entity(tableName = "Messages") @Entity(tableName = "Messages")
data class Message( data class Message(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "message_global_key")
val studentId: Long, val messageGlobalKey: String,
@ColumnInfo(name = "real_id") @ColumnInfo(name = "mailbox_key")
val realId: Int, val mailboxKey: String,
@ColumnInfo(name = "message_id") @ColumnInfo(name = "message_id")
val messageId: Int, val messageId: Int,
@ColumnInfo(name = "sender_name") val correspondents: String,
val sender: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "recipient_name")
val recipient: String,
val subject: String, val subject: String,
@ -36,8 +29,6 @@ data class Message(
var unread: Boolean, var unread: Boolean,
val removed: Boolean,
@ColumnInfo(name = "has_attachments") @ColumnInfo(name = "has_attachments")
val hasAttachments: Boolean val hasAttachments: Boolean
) : Serializable { ) : Serializable {
@ -48,11 +39,7 @@ data class Message(
@ColumnInfo(name = "is_notified") @ColumnInfo(name = "is_notified")
var isNotified: Boolean = true var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = "" var content: String = ""
var sender: String? = null
var recipients: String? = null
} }

View File

@ -12,11 +12,8 @@ data class MessageAttachment(
@ColumnInfo(name = "real_id") @ColumnInfo(name = "real_id")
val realId: Int, val realId: Int,
@ColumnInfo(name = "message_id") @ColumnInfo(name = "message_global_key")
val messageId: Int, val messageGlobalKey: String,
@ColumnInfo(name = "one_drive_id")
val oneDriveId: String,
@ColumnInfo(name = "url") @ColumnInfo(name = "url")
val url: String, val url: String,

View File

@ -7,6 +7,6 @@ data class MessageWithAttachment(
@Embedded @Embedded
val message: Message, val message: Message,
@Relation(parentColumn = "message_id", entityColumn = "message_id") @Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
val attachments: List<MessageAttachment> val attachments: List<MessageAttachment>
) )

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.data.db.entities package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
@ -8,32 +7,16 @@ import java.io.Serializable
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
@Entity(tableName = "Recipients") @Entity(tableName = "Recipients")
data class Recipient( data class Recipient(
val mailboxGlobalKey: String,
@ColumnInfo(name = "student_id") val studentMailboxGlobalKey: String,
val studentId: Int, val fullName: String,
val userName: String,
@ColumnInfo(name = "real_id") val schoolShortName: String,
val realId: String, val type: MailboxType,
val name: String,
@ColumnInfo(name = "real_name")
val realName: String,
@ColumnInfo(name = "login_id")
val loginId: Int,
@ColumnInfo(name = "unit_id")
val unitId: Int,
val role: Int,
val hash: String
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
override fun toString() = name override fun toString() = userName
} }

View File

@ -1,32 +0,0 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "ReportingUnits")
data class ReportingUnit(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "real_id")
val unitId: Int,
@ColumnInfo(name = "short")
val shortName: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "sender_name")
val senderName: String,
val roles: List<Int>
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,88 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration51 : Migration(50, 51) {
override fun migrate(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
recreateMessageAttachmentsTable(database)
recreateRecipientsTable(database)
deleteReportingUnitTable(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,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`userLoginId` INTEGER 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` (
`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,
`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()
)
}
private fun recreateMessageAttachmentsTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MessageAttachments")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MessageAttachments` (
`real_id` INTEGER NOT NULL,
`message_global_key` TEXT NOT NULL,
`url` TEXT NOT NULL,
`filename` TEXT NOT NULL,
PRIMARY KEY(`real_id`)
)""".trimIndent()
)
}
private fun recreateRecipientsTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Recipients")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Recipients` (
`mailboxGlobalKey` TEXT NOT NULL,
`studentMailboxGlobalKey` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`schoolShortName` TEXT NOT NULL,
`type` TEXT NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
)""".trimIndent()
)
}
private fun deleteReportingUnitTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS ReportingUnits")
}
}

View File

@ -0,0 +1,18 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.Mailbox as SdkMailbox
fun List<SdkMailbox>.mapToEntities(student: Student) = map {
Mailbox(
globalKey = it.globalKey,
fullName = it.fullName,
userName = it.userName,
userLoginId = student.userLoginId,
studentName = it.studentName,
schoolNameShort = it.schoolNameShort,
type = MailboxType.valueOf(it.type.name),
)
}

View File

@ -1,40 +1,31 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.sdk.pojo.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage 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(student: Student) = map { fun List<SdkMessage>.mapToEntities(mailbox: Mailbox) = map {
Message( Message(
studentId = student.id, messageGlobalKey = it.globalKey,
realId = it.id ?: 0, mailboxKey = mailbox.globalKey,
messageId = it.messageId ?: 0, messageId = it.id,
sender = it.sender?.name.orEmpty(), correspondents = it.correspondents,
senderId = it.sender?.loginId ?: 0,
recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów",
subject = it.subject.trim(), subject = it.subject.trim(),
date = it.dateZoned?.toInstant() ?: Instant.now(), date = it.dateZoned.toInstant(),
folderId = it.folderId, folderId = it.folderId,
unread = it.unread ?: false, unread = it.unread,
removed = it.removed,
hasAttachments = it.hasAttachments hasAttachments = it.hasAttachments
).apply { ).apply {
content = it.content.orEmpty() content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
} }
} }
fun List<SdkMessageAttachment>.mapToEntities() = map { fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
MessageAttachment( MessageAttachment(
realId = it.id, messageGlobalKey = messageGlobalKey,
messageId = it.messageId, realId = it.url.hashCode(),
oneDriveId = it.oneDriveId,
url = it.url, url = it.url,
filename = it.filename filename = it.filename
) )
@ -42,12 +33,11 @@ fun List<SdkMessageAttachment>.mapToEntities() = map {
fun List<Recipient>.mapFromEntities() = map { fun List<Recipient>.mapFromEntities() = map {
SdkRecipient( SdkRecipient(
id = it.realId, fullName = it.fullName,
name = it.realName, userName = it.userName,
loginId = it.loginId, studentName = it.userName,
reportingUnitId = it.unitId, mailboxGlobalKey = it.mailboxGlobalKey,
role = it.role, schoolNameShort = it.schoolShortName,
hash = it.hash, type = MailboxType.valueOf(it.type.name),
shortName = it.name
) )
} }

View File

@ -1,17 +1,16 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map { fun List<SdkRecipient>.mapToEntities(studentMailboxGlobalKey: String) = map {
Recipient( Recipient(
studentId = userLoginId, mailboxGlobalKey = it.mailboxGlobalKey,
realId = it.id, fullName = it.fullName,
realName = it.name, userName = it.userName,
name = it.shortName, studentMailboxGlobalKey = studentMailboxGlobalKey,
hash = it.hash, schoolShortName = it.schoolNameShort,
loginId = it.loginId, type = MailboxType.valueOf(it.type.name),
role = it.role,
unitId = it.reportingUnitId ?: 0
) )
} }

View File

@ -1,16 +0,0 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit
fun List<SdkReportingUnit>.mapToEntities(student: Student) = map {
ReportingUnit(
studentId = student.id.toInt(),
unitId = it.id,
roles = it.roles,
senderId = it.senderId,
senderName = it.senderName,
shortName = it.short
)
}

View File

@ -0,0 +1,48 @@
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 mailbox = mailboxDao.load(student.userLoginId, student.studentName)
return if (isExpired || mailbox == null) {
refreshMailboxes(student)
val newMailbox = mailboxDao.load(student.userLoginId, student.studentName)
requireNotNull(newMailbox) {
"Mailbox for ${student.userName} - ${student.studentName} not found!"
}
newMailbox
} else mailbox
}
}

View File

@ -10,24 +10,24 @@ import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities 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.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.sdk.pojo.SentMessage
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime.now
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -49,7 +49,7 @@ class MessageRepository @Inject constructor(
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun getMessages( fun getMessages(
student: Student, student: Student,
semester: Semester, mailbox: Mailbox,
folder: MessageFolder, folder: MessageFolder,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
@ -62,42 +62,20 @@ class MessageRepository @Inject constructor(
) )
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { messagesDb.loadAll(student.id.toInt(), folder.id) }, query = { messagesDb.loadAll(mailbox.globalKey, folder.id) },
fetch = { fetch = {
sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()) sdk.init(student).getMessages(Folder.valueOf(folder.name)).mapToEntities(mailbox)
.mapToEntities(student)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new) messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach { messagesDb.insertAll((new uniqueSubtract old).onEach {
it.isNotified = !notify it.isNotified = !notify
}) })
messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify))
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
} }
) )
private fun getMessagesWithReadByChange(
old: List<Message>,
new: List<Message>,
setNotified: Boolean
): List<Message> {
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
val newMeta = new.map { Triple(it, it.readBy, it.unreadBy) }
val updatedItems = newMeta uniqueSubtract oldMeta
return updatedItems.map {
val oldItem = old.find { item -> item.messageId == it.first.messageId }
it.first.apply {
id = oldItem?.id ?: 0
isNotified = oldItem?.isNotified ?: setNotified
content = oldItem?.content.orEmpty()
}
}
}
fun getMessage( fun getMessage(
student: Student, student: Student,
message: Message, message: Message,
@ -106,34 +84,34 @@ class MessageRepository @Inject constructor(
isResultEmpty = { it?.message?.content.isNullOrBlank() }, isResultEmpty = { it?.message?.content.isNullOrBlank() },
shouldFetch = { shouldFetch = {
checkNotNull(it) { "This message no longer exist!" } checkNotNull(it) { "This message no longer exist!" }
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
it.message.unread || it.message.content.isEmpty() it.message.unread || it.message.content.isBlank()
}, },
query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) }, query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
fetch = { fetch = {
sdk.init(student).getMessageDetails( sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey)
messageId = it!!.message.messageId,
folderId = message.folderId,
read = markAsRead,
id = message.realId
).let { details ->
details.content to details.attachments.mapToEntities()
}
}, },
saveFetchResult = { old, (downloadedMessage, attachments) -> saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" } checkNotNull(old) { "Fetched message no longer exist!" }
messagesDb.updateAll(listOf(old.message.apply { messagesDb.updateAll(
id = old.message.id listOf(old.message.apply {
id = message.id
unread = !markAsRead unread = !markAsRead
content = content.ifBlank { downloadedMessage } sender = new.sender
})) recipients = new.recipients.firstOrNull() ?: "Wielu adresoatów"
messageAttachmentDao.insertAttachments(attachments) content = content.ifBlank { new.content }
})
)
messageAttachmentDao.insertAttachments(
items = new.attachments.mapToEntities(message.messageGlobalKey),
)
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read") Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
} }
) )
fun getMessagesFromDatabase(student: Student): Flow<List<Message>> { fun getMessagesFromDatabase(mailbox: Mailbox): Flow<List<Message>> {
return messagesDb.loadAll(student.id.toInt(), RECEIVED.id) return messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
} }
suspend fun updateMessages(messages: List<Message>) { suspend fun updateMessages(messages: List<Message>) {
@ -145,32 +123,48 @@ class MessageRepository @Inject constructor(
subject: String, subject: String,
content: String, content: String,
recipients: List<Recipient>, recipients: List<Recipient>,
): SentMessage = sdk.init(student).sendMessage( mailboxId: String,
) {
sdk.init(student).sendMessage(
subject = subject, subject = subject,
content = content, content = content,
recipients = recipients.mapFromEntities() recipients = recipients.mapFromEntities(),
mailboxId = mailboxId,
)
}
suspend fun deleteMessages(student: Student, mailbox: Mailbox, messages: List<Message>) {
val firstMessage = messages.first()
sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey },
removeForever = firstMessage.folderId == TRASHED.id,
) )
suspend fun deleteMessages(student: Student, messages: List<Message>) { if (firstMessage.folderId != TRASHED.id) {
val folderId = messages.first().folderId
val isDeleted = sdk.init(student)
.deleteMessages(messages = messages.map { it.messageId }, folderId = folderId)
if (folderId != MessageFolder.TRASHED.id && isDeleted) {
val deletedMessages = messages.map { val deletedMessages = messages.map {
it.copy(folderId = MessageFolder.TRASHED.id) it.copy(folderId = TRASHED.id)
.apply { .apply {
id = it.id id = it.id
content = it.content content = it.content
sender = it.sender
recipients = it.recipients
} }
} }
messagesDb.updateAll(deletedMessages) messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages) } else messagesDb.deleteAll(messages)
getMessages(
student = student,
mailbox = mailbox,
folder = TRASHED,
forceRefresh = true,
).first()
} }
suspend fun deleteMessage(student: Student, message: Message) = suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
deleteMessages(student, listOf(message)) deleteMessages(student, mailbox, listOf(message))
}
var draftMessage: MessageDraft? var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft)) get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))

View File

@ -1,10 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
@ -23,9 +20,10 @@ class RecipientRepository @Inject constructor(
private val cacheKey = "recipient" private val cacheKey = "recipient"
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) { suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId) val new = sdk.init(student).getRecipients(mailbox.globalKey)
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role) .mapToEntities(mailbox.globalKey)
val old = recipientDb.loadAll(type, mailbox.globalKey)
recipientDb.deleteAll(old uniqueSubtract new) recipientDb.deleteAll(old uniqueSubtract new)
recipientDb.insertAll(new uniqueSubtract old) recipientDb.insertAll(new uniqueSubtract old)
@ -33,18 +31,27 @@ class RecipientRepository @Inject constructor(
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> { suspend fun getRecipients(
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role) student: Student,
mailbox: Mailbox,
type: MailboxType
): List<Recipient> {
val cached = recipientDb.loadAll(type, mailbox.globalKey)
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
return if (cached.isEmpty() || isExpired) { return if (cached.isEmpty() || isExpired) {
refreshRecipients(student, unit, role) refreshRecipients(student, mailbox, type)
recipientDb.loadAll(unit.studentId, unit.unitId, role) recipientDb.loadAll(type, mailbox.globalKey)
} else cached } else cached
} }
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> { suspend fun getMessageSender(
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId) student: Student,
.mapToEntities(student.studentId) mailbox: Mailbox,
} message: Message
): List<Recipient> = sdk.init(student)
.getMessageReplayDetails(message.messageGlobalKey)
.sender
.let(::listOf)
.mapToEntities(mailbox.globalKey)
} }

View File

@ -1,53 +0,0 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.entities.ReportingUnit
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 ReportingUnitRepository @Inject constructor(
private val reportingUnitDb: ReportingUnitDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val cacheKey = "reporting_unit"
suspend fun refreshReportingUnits(student: Student) {
val new = sdk.init(student).getReportingUnits().mapToEntities(student)
val old = reportingUnitDb.load(student.id.toInt())
reportingUnitDb.deleteAll(old.uniqueSubtract(new))
reportingUnitDb.insertAll(new.uniqueSubtract(old))
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
suspend fun getReportingUnits(student: Student): List<ReportingUnit> {
val cached = reportingUnitDb.load(student.id.toInt())
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
return if (cached.isEmpty() || isExpired) {
refreshReportingUnits(student)
reportingUnitDb.load(student.id.toInt())
} else cached
}
suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? {
val cached = reportingUnitDb.loadOne(student.id.toInt(), unitId)
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
return if (cached == null || isExpired) {
refreshReportingUnits(student)
reportingUnitDb.loadOne(student.id.toInt(), unitId)
} else cached
}
}

View File

@ -21,7 +21,7 @@ class NewMessageNotification @Inject constructor(
val notificationDataList = items.map { val notificationDataList = items.map {
NotificationData( NotificationData(
title = context.getPlural(R.plurals.message_new_items, 1), title = context.getPlural(R.plurals.message_new_items, 1),
content = "${it.sender}: ${it.subject}", content = "${it.correspondents}: ${it.subject}",
destination = Destination.Message, destination = Destination.Message,
) )
} }

View File

@ -3,6 +3,7 @@ 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
@ -11,19 +12,21 @@ 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)
messageRepository.getMessages( messageRepository.getMessages(
student = student, student = student,
semester = semester, mailbox = mailbox,
folder = RECEIVED, folder = RECEIVED,
forceRefresh = true, forceRefresh = true,
notify = notify notify = notify
).waitForResult() ).waitForResult()
messageRepository.getMessagesFromDatabase(student).first() messageRepository.getMessagesFromDatabase(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,23 +1,22 @@
package io.github.wulkanowy.services.sync.works package io.github.wulkanowy.services.sync.works
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.RecipientRepository import io.github.wulkanowy.data.repositories.RecipientRepository
import io.github.wulkanowy.data.repositories.ReportingUnitRepository
import javax.inject.Inject import javax.inject.Inject
class RecipientWork @Inject constructor( class RecipientWork @Inject constructor(
private val reportingUnitRepository: ReportingUnitRepository, private val mailboxRepository: MailboxRepository,
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) {
reportingUnitRepository.refreshReportingUnits(student) mailboxRepository.refreshMailboxes(student)
reportingUnitRepository.getReportingUnits(student).let { units -> val mailbox = mailboxRepository.getMailbox(student)
units.map {
recipientRepository.refreshRecipients(student, it, 2) recipientRepository.refreshRecipients(student, mailbox, MailboxType.EMPLOYEE)
}
}
} }
} }

View File

@ -25,6 +25,7 @@ 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,
@ -227,6 +228,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 selectedTiles = preferencesRepository.selectedDashboardTiles val selectedTiles = preferencesRepository.selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null)) val flowSuccess = flowOf(Resource.Success(null))
@ -238,7 +240,7 @@ class DashboardPresenter @Inject constructor(
val messageFLow = messageRepository.getMessages( val messageFLow = messageRepository.getMessages(
student = student, student = student,
semester = semester, mailbox = mailbox,
folder = MessageFolder.RECEIVED, folder = MessageFolder.RECEIVED,
forceRefresh = forceRefresh forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess

View File

@ -17,16 +17,13 @@ val debugMessageItems = listOf(
) )
private fun generateMessage(sender: String, subject: String) = Message( private fun generateMessage(sender: String, subject: String) = Message(
sender = sender,
subject = subject, subject = subject,
studentId = 0, messageId = 123,
realId = 0,
messageId = 0,
senderId = 0,
recipient = "",
date = Instant.now(), date = Instant.now(),
folderId = 0, folderId = 0,
unread = true, unread = true,
removed = false, hasAttachments = false,
hasAttachments = false messageGlobalKey = "",
correspondents = sender,
mailboxKey = "",
) )

View File

@ -4,6 +4,8 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT
import androidx.core.text.parseAsHtml
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
@ -75,29 +77,25 @@ class MessagePreviewAdapter @Inject constructor() :
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun bindMessage(holder: MessageViewHolder, message: Message) { private fun bindMessage(holder: MessageViewHolder, message: Message) {
val context = holder.binding.root.context val context = holder.binding.root.context
val recipientCount = message.unreadBy + message.readBy
val readText = when { val readTextValue = when {
recipientCount > 1 -> { !message.unread -> R.string.all_yes
context.getString(R.string.message_read_by, message.readBy, recipientCount) else -> R.string.all_no
}
message.readBy == 1 -> {
context.getString(R.string.message_read, context.getString(R.string.all_yes))
}
else -> context.getString(R.string.message_read, context.getString(R.string.all_no))
} }
val readText = context.getString(R.string.message_read, context.getString(readTextValue))
with(holder.binding) { with(holder.binding) {
messagePreviewSubject.text = messagePreviewSubject.text = message.subject.ifBlank {
message.subject.ifBlank { root.context.getString(R.string.message_no_subject) } context.getString(R.string.message_no_subject)
messagePreviewDate.text = root.context.getString( }
messagePreviewDate.text = context.getString(
R.string.message_date, R.string.message_date,
message.date.toFormattedString("yyyy-MM-dd HH:mm:ss") message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
) )
messagePreviewRead.text = readText messagePreviewRead.text = readText
messagePreviewContent.text = message.content messagePreviewContent.text = message.content.parseAsHtml(FROM_HTML_MODE_COMPACT)
messagePreviewFromSender.text = message.sender messagePreviewFromSender.text = message.sender
messagePreviewToRecipient.text = message.recipient messagePreviewToRecipient.text = message.recipients
} }
} }

View File

@ -135,8 +135,8 @@ class MessagePreviewFragment :
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
} }
override fun showOptions(show: Boolean) { override fun showOptions(show: Boolean, isReplayable: Boolean) {
menuReplyButton?.isVisible = show menuReplyButton?.isVisible = isReplayable
menuForwardButton?.isVisible = show menuForwardButton?.isVisible = show
menuDeleteButton?.isVisible = show menuDeleteButton?.isVisible = show
menuShareButton?.isVisible = show menuShareButton?.isVisible = show

View File

@ -1,10 +1,12 @@
package io.github.wulkanowy.ui.modules.message.preview package io.github.wulkanowy.ui.modules.message.preview
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.core.text.parseAsHtml
import io.github.wulkanowy.data.* 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
@ -19,6 +21,7 @@ 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) {
@ -52,7 +55,7 @@ class MessagePreviewPresenter @Inject constructor(
private fun loadData(messageToLoad: Message) { private fun loadData(messageToLoad: Message) {
flatResourceFlow { flatResourceFlow {
val student = studentRepository.getStudentById(messageToLoad.studentId) val student = studentRepository.getCurrentStudent()
messageRepository.getMessage(student, messageToLoad, true) messageRepository.getMessage(student, messageToLoad, true)
} }
.logResourceStatus("message ${messageToLoad.messageId} preview") .logResourceStatus("message ${messageToLoad.messageId} preview")
@ -104,63 +107,70 @@ class MessagePreviewPresenter @Inject constructor(
} }
fun onShare(): Boolean { fun onShare(): Boolean {
message?.let { val message = message ?: return false
var text = val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
"Temat: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}\n" + when (it.sender.isNotEmpty()) {
true -> "Od: ${it.sender}\n"
false -> "Do: ${it.recipient}\n"
} + "Data: ${it.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${it.content}"
attachments?.let { attachments -> val text = buildString {
if (attachments.isNotEmpty()) { appendLine("Temat: $subject")
text += "\n\nZałączniki:" appendLine("Od: ${message.sender}")
appendLine("Do: ${message.recipients}")
appendLine("Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}")
attachments.forEach { attachment -> appendLine()
text += "\n${attachment.filename}: ${attachment.url}"
} appendLine(message.content.parseAsHtml())
if (!attachments.isNullOrEmpty()) {
appendLine()
appendLine("Załączniki:")
append(attachments.orEmpty().joinToString(separator = "\n") { attachment ->
"${attachment.filename}: ${attachment.url}"
})
} }
} }
view?.shareText( view?.shareText(
text, subject = "FW: $subject",
"FW: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }}" text = text,
) )
return true return true
} }
return false
}
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun onPrint(): Boolean { fun onPrint(): Boolean {
message?.let { val message = message ?: return false
val dateString = it.date.toFormattedString("yyyy-MM-dd HH:mm:ss") val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
val infoContent = "<div><h4>Data wysłania</h4>$dateString</div>" + when {
it.sender.isNotEmpty() -> "<div><h4>Od</h4>${it.sender}</div>"
else -> "<div><h4>Do</h4>${it.recipient}</div>"
}
val messageContent = "<p>${it.content}</p>" val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
val infoContent = buildString {
append("<div><h4>Data wysłania</h4>$dateString</div>")
append("<div><h4>Od</h4>${message.sender}</div>")
append("<div><h4>DO</h4>${message.recipients}</div>")
}
val messageContent = "<p>${message.content}</p>"
.replace(Regex("[\\n\\r]{2,}"), "</p><p>") .replace(Regex("[\\n\\r]{2,}"), "</p><p>")
.replace(Regex("[\\n\\r]"), "<br>") .replace(Regex("[\\n\\r]"), "<br>")
val jobName = "Wiadomość " + when { val jobName = buildString {
it.sender.isNotEmpty() -> "od ${it.sender}" append("Wiadomość ")
else -> "do ${it.recipient}" append("od ${message.correspondents}")
} + " $dateString: ${it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }} | Wulkanowy" append("do ${message.correspondents}")
append(" $dateString: $subject | Wulkanowy")
}
view?.apply { view?.apply {
val html = printHTML val html = printHTML
.replace( .replace("%SUBJECT%", subject)
"%SUBJECT%",
it.subject.ifBlank { view?.messageNoSubjectString.orEmpty() })
.replace("%CONTENT%", messageContent) .replace("%CONTENT%", messageContent)
.replace("%INFO%", infoContent) .replace("%INFO%", infoContent)
printDocument(html, jobName) printDocument(html, jobName)
} }
return true return true
} }
return false
}
private fun deleteMessage() { private fun deleteMessage() {
message ?: return message ?: return
@ -168,16 +178,17 @@ class MessagePreviewPresenter @Inject constructor(
view?.run { view?.run {
showContent(false) showContent(false)
showProgress(true) showProgress(true)
showOptions(false) showOptions(show = false, isReplayable = false)
showErrorView(false) showErrorView(false)
} }
Timber.i("Delete message ${message?.id}") Timber.i("Delete message ${message?.messageGlobalKey}")
presenterScope.launch { presenterScope.launch {
runCatching { runCatching {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent(decryptPass = true)
messageRepository.deleteMessage(student, message!!) val mailbox = mailboxRepository.getMailbox(student)
messageRepository.deleteMessage(student, mailbox, message!!)
} }
.onFailure { .onFailure {
retryCallback = { onMessageDelete() } retryCallback = { onMessageDelete() }
@ -211,7 +222,10 @@ class MessagePreviewPresenter @Inject constructor(
private fun initOptions() { private fun initOptions() {
view?.apply { view?.apply {
showOptions(message != null) showOptions(
show = message != null,
isReplayable = message?.folderId != MessageFolder.SENT.id,
)
message?.let { message?.let {
when (it.folderId == MessageFolder.TRASHED.id) { when (it.folderId == MessageFolder.TRASHED.id) {
true -> setDeletedOptionsLabels() true -> setDeletedOptionsLabels()

View File

@ -28,7 +28,7 @@ interface MessagePreviewView : BaseView {
fun setErrorRetryCallback(callback: () -> Unit) fun setErrorRetryCallback(callback: () -> Unit)
fun showOptions(show: Boolean) fun showOptions(show: Boolean, isReplayable: Boolean)
fun setDeletedOptionsLabels() fun setDeletedOptionsLabels()

View File

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.text.Spanned
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.TouchDelegate import android.view.TouchDelegate
@ -13,11 +14,12 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.core.text.parseAsHtml
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.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.ReportingUnit
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.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
@ -72,17 +74,32 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
override val messageSuccess: String override val messageSuccess: String
get() = getString(R.string.message_send_successful) get() = getString(R.string.message_send_successful)
override val mailboxStudent: String
get() = getString(R.string.message_mailbox_type_student)
override val mailboxParent: String
get() = getString(R.string.message_mailbox_type_parent)
override val mailboxGuardian: String
get() = getString(R.string.message_mailbox_type_guardian)
override val mailboxEmployee: String
get() = getString(R.string.message_mailbox_type_employee)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivitySendMessageBinding.inflate(layoutInflater).apply { binding = this }.root) setContentView(
ActivitySendMessageBinding.inflate(layoutInflater).apply { binding = this }.root
)
setSupportActionBar(binding.sendMessageToolbar) setSupportActionBar(binding.sendMessageToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
messageContainer = binding.sendMessageContainer messageContainer = binding.sendMessageContainer
formRecipientsData = binding.sendMessageTo.addedChipItems as List<RecipientChipItem> formRecipientsData = binding.sendMessageTo.addedChipItems as List<RecipientChipItem>
formSubjectValue = binding.sendMessageSubject.text.toString() formSubjectValue = binding.sendMessageSubject.text.toString()
formContentValue = binding.sendMessageMessageContent.text.toString() formContentValue =
binding.sendMessageMessageContent.text.toString().parseAsHtml().toString()
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
@ -110,7 +127,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
} }
private fun onMessageContentChange(text: CharSequence?) { private fun onMessageContentChange(text: CharSequence?) {
formContentValue = text.toString() formContentValue = (text as Spanned).toHtml()
presenter.onMessageContentChange() presenter.onMessageContentChange()
} }
@ -132,8 +149,8 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
return presenter.onUpNavigate() return presenter.onUpNavigate()
} }
override fun setReportingUnit(unit: ReportingUnit) { override fun setMailbox(mailbox: String) {
binding.sendMessageFrom.text = unit.senderName binding.sendMessageFrom.text = mailbox
} }
override fun setRecipients(recipients: List<RecipientChipItem>) { override fun setRecipients(recipients: List<RecipientChipItem>) {
@ -165,7 +182,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
} }
override fun setContent(content: String) { override fun setContent(content: String) {
binding.sendMessageMessageContent.setText(content) binding.sendMessageMessageContent.setText(content.parseAsHtml())
} }
override fun showMessage(text: String) { override fun showMessage(text: String) {

View File

@ -1,6 +1,8 @@
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.Resource
import io.github.wulkanowy.data.db.entities.Mailbox
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.logResourceStatus
@ -25,9 +27,8 @@ import javax.inject.Inject
class SendMessagePresenter @Inject constructor( class SendMessagePresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val reportingUnitRepository: ReportingUnitRepository, 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
@ -52,20 +53,21 @@ class SendMessagePresenter @Inject constructor(
message?.let { message?.let {
setSubject( setSubject(
when (reply) { when (reply) {
true -> "Re: " true -> "RE: "
else -> "FW: " else -> "FW: "
} + message.subject } + message.subject
) )
if (preferencesRepository.fillMessageContent || reply != true) { if (preferencesRepository.fillMessageContent || reply != true) {
setContent( setContent(buildString {
when (reply) { if (reply == true) {
true -> "\n\n" append("<br><br>")
else -> "" }
} + when (message.sender.isNotEmpty()) {
true -> "Od: ${message.sender}\n" append("Od: ${message.sender}<br>")
false -> "Do: ${message.recipient}\n" append("Do: ${message.recipients}<br>")
} + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}" append("Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}<br><br>")
) append(message.content)
})
} }
} }
} }
@ -111,21 +113,24 @@ class SendMessagePresenter @Inject constructor(
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 semester = semesterRepository.getCurrentSemester(student) val mailbox = mailboxRepository.getMailbox(student)
val unit = reportingUnitRepository.getReportingUnit(student, semester.unitId)
Timber.i("Loading recipients started") Timber.i("Loading recipients started")
val recipients = when { val recipients = createChips(
unit != null -> recipientRepository.getRecipients(student, unit, 2) recipients = recipientRepository.getRecipients(
else -> listOf() student = student,
}.let { createChips(it) } mailbox = mailbox,
type = MailboxType.EMPLOYEE,
)
)
Timber.i("Loading recipients result: Success, fetched %d recipients", recipients.size) Timber.i("Loading recipients result: Success, fetched %d recipients", recipients.size)
Timber.i("Loading message recipients started") Timber.i("Loading message recipients started")
val messageRecipients = when { val messageRecipients = when {
message != null && reply == true -> recipientRepository.getMessageRecipients( message != null && reply == true -> recipientRepository.getMessageSender(
student, student = student,
message message = message,
mailbox = mailbox,
) )
else -> emptyList() else -> emptyList()
}.let { createChips(it) } }.let { createChips(it) }
@ -134,7 +139,7 @@ class SendMessagePresenter @Inject constructor(
messageRecipients.size messageRecipients.size
) )
Triple(unit, recipients, messageRecipients) Triple(mailbox, recipients, messageRecipients)
} }
.logResourceStatus("load recipients") .logResourceStatus("load recipients")
.onEach { .onEach {
@ -143,19 +148,14 @@ class SendMessagePresenter @Inject constructor(
showProgress(true) showProgress(true)
showContent(false) showContent(false)
} }
is Resource.Success -> it.data.let { (reportingUnit, recipientChips, selectedRecipientChips) -> is Resource.Success -> it.data.let { (mailbox, recipientChips, selectedRecipientChips) ->
view?.run { view?.run {
if (reportingUnit != null) { setMailbox(getMailboxName(mailbox))
setReportingUnit(reportingUnit)
setRecipients(recipientChips) setRecipients(recipientChips)
if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients( if (selectedRecipientChips.isNotEmpty()) setSelectedRecipients(
selectedRecipientChips selectedRecipientChips
) )
showContent(true) showContent(true)
} else {
Timber.i("Loading recipients result: Can't find the reporting unit")
view?.showEmpty(true)
}
} }
} }
is Resource.Error -> { is Resource.Error -> {
@ -171,7 +171,14 @@ class SendMessagePresenter @Inject constructor(
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) { private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
resourceFlow { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
messageRepository.sendMessage(student, subject, content, recipients) val mailbox = mailboxRepository.getMailbox(student)
messageRepository.sendMessage(
student = student,
subject = subject,
content = content,
recipients = recipients,
mailboxId = mailbox.globalKey,
)
}.logResourceStatus("sending message").onEach { }.logResourceStatus("sending message").onEach {
when (it) { when (it) {
is Resource.Loading -> view?.run { is Resource.Loading -> view?.run {
@ -201,31 +208,44 @@ class SendMessagePresenter @Inject constructor(
} }
private fun createChips(recipients: List<Recipient>): List<RecipientChipItem> { private fun createChips(recipients: List<Recipient>): List<RecipientChipItem> {
fun generateCorrectSummary(recipientRealName: String): String {
val substring = recipientRealName.substringBeforeLast("-")
return when {
substring == recipientRealName -> recipientRealName
substring.indexOf("(") != -1 -> {
recipientRealName.indexOf("(")
.let { recipientRealName.substring(if (it != -1) it else 0) }
}
substring.indexOf("[") != -1 -> {
recipientRealName.indexOf("[")
.let { recipientRealName.substring(if (it != -1) it else 0) }
}
else -> recipientRealName.substringAfter("-")
}.trim()
}
return recipients.map { return recipients.map {
RecipientChipItem( RecipientChipItem(
title = it.name, title = it.userName,
summary = generateCorrectSummary(it.realName), summary = buildString {
getMailboxType(it.type)?.let(::append)
if (isNotBlank()) append(" ")
append("(${it.schoolShortName})")
},
recipient = it recipient = it
) )
} }
} }
private fun getMailboxName(mailbox: Mailbox): String {
return buildString {
append(mailbox.userName)
append(" - ")
append(getMailboxType(mailbox.type))
if (mailbox.type == MailboxType.PARENT) {
append(" - ")
append(mailbox.studentName)
}
append(" - ")
append("(${mailbox.schoolNameShort})")
}
}
private fun getMailboxType(type: MailboxType): String? = when (type) {
MailboxType.STUDENT -> view?.mailboxStudent
MailboxType.PARENT -> view?.mailboxParent
MailboxType.GUARDIAN -> view?.mailboxGuardian
MailboxType.EMPLOYEE -> view?.mailboxEmployee
MailboxType.UNKNOWN -> null
}
fun onMessageContentChange() { fun onMessageContentChange() {
presenterScope.launch { presenterScope.launch {
messageUpdateChannel.send(Unit) messageUpdateChannel.send(Unit)
@ -263,7 +283,7 @@ class SendMessagePresenter @Inject constructor(
fun getRecipientsNames(): String { fun getRecipientsNames(): String {
return messageRepository.draftMessage?.recipients.orEmpty() return messageRepository.draftMessage?.recipients.orEmpty()
.joinToString { it.recipient.name } .joinToString { it.recipient.userName }
} }
fun clearDraft() { fun clearDraft() {

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.message.send package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface SendMessageView : BaseView { interface SendMessageView : BaseView {
@ -18,9 +18,17 @@ interface SendMessageView : BaseView {
val messageSuccess: String val messageSuccess: String
val mailboxStudent: String
val mailboxParent: String
val mailboxGuardian: String
val mailboxEmployee: String
fun initView() fun initView()
fun setReportingUnit(unit: ReportingUnit) fun setMailbox(mailbox: String)
fun setRecipients(recipients: List<RecipientChipItem>) fun setRecipients(recipients: List<RecipientChipItem>)

View File

@ -8,7 +8,6 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.MessageFolder
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.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@ -88,12 +87,8 @@ class MessageTabAdapter @Inject constructor() :
with(holder.binding) { with(holder.binding) {
val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL
messageItemAuthor.run { with(messageItemAuthor) {
text = if (message.folderId == MessageFolder.SENT.id) { text = message.correspondents
message.recipient
} else {
message.sender
}
setTypeface(null, style) setTypeface(null, style)
} }
messageItemSubject.run { messageItemSubject.run {
@ -145,7 +140,7 @@ class MessageTabAdapter @Inject constructor() :
val newItem = new[newItemPosition] val newItem = new[newItemPosition]
return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) { return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) {
oldItem.message.id == newItem.message.id oldItem.message.messageGlobalKey == newItem.message.messageGlobalKey
} else { } else {
oldItem.viewType == newItem.viewType oldItem.viewType == newItem.viewType
} }

View File

@ -3,8 +3,8 @@ 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.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.SemesterRepository
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
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -26,7 +26,7 @@ class MessageTabPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val messageRepository: MessageRepository, private val messageRepository: MessageRepository,
private val semesterRepository: SemesterRepository, private val mailboxRepository: MailboxRepository,
private val analytics: AnalyticsHelper private val analytics: AnalyticsHelper
) : BasePresenter<MessageTabView>(errorHandler, studentRepository) { ) : BasePresenter<MessageTabView>(errorHandler, studentRepository) {
@ -122,7 +122,8 @@ class MessageTabPresenter @Inject constructor(
runCatching { runCatching {
val student = studentRepository.getCurrentStudent(true) val student = studentRepository.getCurrentStudent(true)
messageRepository.deleteMessages(student, messageList) val mailbox = mailboxRepository.getMailbox(student)
messageRepository.deleteMessages(student, mailbox, messageList)
} }
.onFailure(errorHandler::dispatch) .onFailure(errorHandler::dispatch)
.onSuccess { view?.showMessagesDeleted() } .onSuccess { view?.showMessagesDeleted() }
@ -159,7 +160,7 @@ class MessageTabPresenter @Inject constructor(
} }
fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) { fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) {
Timber.i("Select message ${messageItem.message.id} item (position: $position)") Timber.i("Select message ${messageItem.message.messageGlobalKey} item (position: $position)")
if (!isActionMode) { if (!isActionMode) {
view?.run { view?.run {
@ -206,8 +207,8 @@ class MessageTabPresenter @Inject constructor(
flatResourceFlow { flatResourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val mailbox = mailboxRepository.getMailbox(student)
messageRepository.getMessages(student, semester, folder, forceRefresh) messageRepository.getMessages(student, mailbox, folder, forceRefresh)
} }
.logResourceStatus("load $folder message") .logResourceStatus("load $folder message")
.onResourceData { .onResourceData {
@ -333,7 +334,7 @@ class MessageTabPresenter @Inject constructor(
addAll(data.map { message -> addAll(data.map { message ->
MessageTabDataItem.MessageItem( MessageTabDataItem.MessageItem(
message = message, message = message,
isSelected = messagesToDelete.any { it.id == message.id }, isSelected = messagesToDelete.any { it.messageGlobalKey == message.messageGlobalKey },
isActionMode = isActionMode isActionMode = isActionMode
) )
}) })
@ -345,10 +346,9 @@ class MessageTabPresenter @Inject constructor(
private fun calculateMatchRatio(message: Message, query: String): Int { private fun calculateMatchRatio(message: Message, query: String): Int {
val subjectRatio = FuzzySearch.tokenSortPartialRatio(query.lowercase(), message.subject) val subjectRatio = FuzzySearch.tokenSortPartialRatio(query.lowercase(), message.subject)
val senderOrRecipientRatio = FuzzySearch.tokenSortPartialRatio( val correspondentsRatio = FuzzySearch.tokenSortPartialRatio(
query.lowercase(), query.lowercase(),
if (message.sender.isNotEmpty()) message.sender.lowercase() message.correspondents
else message.recipient.lowercase()
) )
val dateRatio = listOf( val dateRatio = listOf(
@ -364,7 +364,7 @@ class MessageTabPresenter @Inject constructor(
return (subjectRatio.toDouble().pow(2) return (subjectRatio.toDouble().pow(2)
+ senderOrRecipientRatio.toDouble().pow(2) + correspondentsRatio.toDouble().pow(2)
+ dateRatio.toDouble().pow(2) * 2 + dateRatio.toDouble().pow(2) * 2
).toInt() ).toInt()
} }

View File

@ -16,8 +16,7 @@
app:layout_constraintBottom_toTopOf="@id/sendMessageScroll" app:layout_constraintBottom_toTopOf="@id/sendMessageScroll"
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" />
tools:targetApi="lollipop" />
<io.github.wulkanowy.materialchipsinput.ConsumedNestedScrollView <io.github.wulkanowy.materialchipsinput.ConsumedNestedScrollView
android:id="@+id/sendMessageScroll" android:id="@+id/sendMessageScroll"

View File

@ -289,6 +289,10 @@
<string name="message_move_to_trash">Move to trash</string> <string name="message_move_to_trash">Move to trash</string>
<string name="message_delete_forever">Delete permanently</string> <string name="message_delete_forever">Delete permanently</string>
<string name="message_delete_success">Message deleted successfully</string> <string name="message_delete_success">Message deleted successfully</string>
<string name="message_mailbox_type_student">student</string>
<string name="message_mailbox_type_parent">parent</string>
<string name="message_mailbox_type_guardian">guardian</string>
<string name="message_mailbox_type_employee">employee</string>
<string name="message_share">Share</string> <string name="message_share">Share</string>
<string name="message_print">Print</string> <string name="message_print">Print</string>
<string name="message_subject">Subject</string> <string name="message_subject">Subject</string>
@ -300,7 +304,6 @@
<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>
<string name="message_read_by">Read by: %1$d of %2$d people</string>
<plurals name="message_number_item"> <plurals name="message_number_item">
<item quantity="one">%1$d message</item> <item quantity="one">%1$d message</item>
<item quantity="other">%1$d messages</item> <item quantity="other">%1$d messages</item>

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy package io.github.wulkanowy
import io.github.wulkanowy.data.db.entities.Mailbox
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.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
@ -21,6 +23,16 @@ fun getSemesterEntity(diaryId: Int = 1, semesterId: Int = 1, start: LocalDate =
end = end end = end
) )
fun getMailboxEntity() = Mailbox(
globalKey = "v4",
fullName = "",
userName = "",
userLoginId = 0,
studentName = "",
schoolNameShort = "",
type = MailboxType.UNKNOWN,
)
fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalDate, semesterName: Int = 1) = SdkSemester( fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalDate, semesterName: Int = 1) = SdkSemester(
diaryId = diaryId, diaryId = diaryId,
kindergartenDiaryId = 0, kindergartenDiaryId = 0,

View File

@ -10,12 +10,10 @@ 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.getSemesterEntity 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
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.sdk.pojo.MessageDetails
import io.github.wulkanowy.sdk.pojo.Sender
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.Status import io.github.wulkanowy.utils.Status
import io.github.wulkanowy.utils.status import io.github.wulkanowy.utils.status
@ -23,7 +21,6 @@ import io.mockk.*
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -60,7 +57,7 @@ class MessageRepositoryTest {
private val student = getStudentEntity() private val student = getStudentEntity()
private val semester = getSemesterEntity() private val mailbox = getMailboxEntity()
private lateinit var repository: MessageRepository private lateinit var repository: MessageRepository
@ -80,59 +77,18 @@ class MessageRepositoryTest {
) )
} }
@Test
fun `get messages when read by values was changed on already read message`() = runTest {
every { messageDb.loadAll(any(), any()) } returns flow {
val dbMessage = getMessageEntity(3, "", false).apply {
unreadBy = 10
readBy = 5
isNotified = true
}
emit(listOf(dbMessage))
}
coEvery { sdk.getMessages(Folder.RECEIVED, any(), any()) } returns listOf(
getMessageDto(messageId = 3, content = "", unread = false).copy(
unreadBy = 5,
readBy = 10,
)
)
coEvery { messageDb.deleteAll(any()) } just Runs
coEvery { messageDb.insertAll(any()) } returns listOf()
repository.getMessages(
student = student,
semester = semester,
folder = MessageFolder.RECEIVED,
forceRefresh = true,
notify = true, // all new messages will be marked as not notified
).toFirstResult().dataOrNull.orEmpty()
coVerify(exactly = 1) { messageDb.deleteAll(emptyList()) }
coVerify(exactly = 1) { messageDb.insertAll(emptyList()) }
coVerify(exactly = 1) {
messageDb.updateAll(withArg {
assertEquals(1, it.size)
assertEquals(5, it.single().unreadBy)
assertEquals(10, it.single().readBy)
})
}
}
@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()) every { messageDb.loadAll(any(), any()) } returns flowOf(emptyList())
coEvery { sdk.getMessages(Folder.RECEIVED, any(), any()) } returns listOf( coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf(
getMessageDto(messageId = 4, content = "Test", unread = true).copy( getMessageDto()
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( repository.getMessages(
student = student, student = student,
semester = semester, mailbox = mailbox,
folder = MessageFolder.RECEIVED, folder = MessageFolder.RECEIVED,
forceRefresh = true, forceRefresh = true,
notify = false, notify = false,
@ -151,7 +107,7 @@ class MessageRepositoryTest {
fun `throw error when message is not in the db`() { fun `throw error when message is not in the db`() {
val testMessage = getMessageEntity(1, "", false) val testMessage = getMessageEntity(1, "", false)
coEvery { coEvery {
messageDb.loadMessageWithAttachment(1, 1) messageDb.loadMessageWithAttachment("v4")
} throws NoSuchElementException("No message in database") } throws NoSuchElementException("No message in database")
runBlocking { repository.getMessage(student, testMessage).toFirstResult() } runBlocking { repository.getMessage(student, testMessage).toFirstResult() }
@ -162,7 +118,7 @@ class MessageRepositoryTest {
val testMessage = getMessageEntity(123, "Test", false) val testMessage = getMessageEntity(123, "Test", false)
val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) val messageWithAttachment = MessageWithAttachment(testMessage, emptyList())
coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } returns flowOf( coEvery { messageDb.loadMessageWithAttachment("v4") } returns flowOf(
messageWithAttachment messageWithAttachment
) )
@ -174,7 +130,7 @@ class MessageRepositoryTest {
} }
@Test @Test
fun `get message when content in db is empty`() { fun `get message when content in db is empty`() = runTest {
val testMessage = getMessageEntity(123, "", true) val testMessage = getMessageEntity(123, "", true)
val testMessageWithContent = testMessage.copy().apply { content = "Test" } val testMessageWithContent = testMessage.copy().apply { content = "Test" }
@ -182,23 +138,19 @@ class MessageRepositoryTest {
val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList()) val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList())
coEvery { coEvery {
messageDb.loadMessageWithAttachment( messageDb.loadMessageWithAttachment("v4")
1,
testMessage.messageId
)
} returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent)) } returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent))
coEvery { coEvery {
sdk.getMessageDetails( sdk.getMessageDetails("v4")
messageId = testMessage.messageId, } returns mockk {
folderId = 1, every { sender } returns ""
read = false, every { recipients } returns listOf("")
id = testMessage.realId every { attachments } returns listOf()
) }
} returns MessageDetails("Test", emptyList())
coEvery { messageDb.updateAll(any()) } just Runs coEvery { messageDb.updateAll(any()) } just Runs
coEvery { messageAttachmentDao.insertAttachments(any()) } returns listOf(1) coEvery { messageAttachmentDao.insertAttachments(any()) } returns listOf(1)
val res = runBlocking { repository.getMessage(student, testMessage).toFirstResult() } val res = repository.getMessage(student, testMessage).toFirstResult()
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(Status.SUCCESS, res.status) assertEquals(Status.SUCCESS, res.status)
@ -211,7 +163,7 @@ class MessageRepositoryTest {
val testMessage = getMessageEntity(123, "", false) val testMessage = getMessageEntity(123, "", false)
coEvery { coEvery {
messageDb.loadMessageWithAttachment(1, testMessage.messageId) messageDb.loadMessageWithAttachment("v4")
} throws UnknownHostException() } throws UnknownHostException()
runBlocking { repository.getMessage(student, testMessage).toFirstResult() } runBlocking { repository.getMessage(student, testMessage).toFirstResult() }
@ -222,7 +174,7 @@ class MessageRepositoryTest {
val testMessage = getMessageEntity(123, "", true) val testMessage = getMessageEntity(123, "", true)
coEvery { coEvery {
messageDb.loadMessageWithAttachment(1, testMessage.messageId) messageDb.loadMessageWithAttachment("v4")
} throws UnknownHostException() } throws UnknownHostException()
runBlocking { repository.getMessage(student, testMessage).toList()[1] } runBlocking { repository.getMessage(student, testMessage).toList()[1] }
@ -233,42 +185,30 @@ class MessageRepositoryTest {
content: String, content: String,
unread: Boolean unread: Boolean
) = Message( ) = Message(
studentId = 1, messageGlobalKey = "v4",
realId = 1, mailboxKey = "",
correspondents = "",
messageId = messageId, messageId = messageId,
sender = "",
senderId = 0,
recipient = "Wielu adresatów",
subject = "", subject = "",
date = Instant.EPOCH, date = Instant.EPOCH,
folderId = 1, folderId = 1,
unread = unread, unread = unread,
removed = false,
hasAttachments = false hasAttachments = false
).apply { ).apply {
this.content = content this.content = content
unreadBy = 1
readBy = 1
} }
private fun getMessageDto( private fun getMessageDto() = io.github.wulkanowy.sdk.pojo.Message(
messageId: Int, globalKey = "v4",
content: String, mailbox = "",
unread: Boolean, correspondents = "",
) = io.github.wulkanowy.sdk.pojo.Message( id = 4,
id = 1,
messageId = messageId,
sender = Sender("", "", 0, 0, 0, ""),
recipients = listOf(), recipients = listOf(),
subject = "", subject = "",
content = content, content = "Test",
date = Instant.EPOCH.atZone(ZoneOffset.UTC).toLocalDateTime(),
dateZoned = Instant.EPOCH.atZone(ZoneOffset.UTC), dateZoned = Instant.EPOCH.atZone(ZoneOffset.UTC),
folderId = 1, folderId = 1,
unread = unread, unread = true,
unreadBy = 0,
readBy = 0,
removed = false,
hasAttachments = false, hasAttachments = false,
) )
} }

View File

@ -1,19 +1,15 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
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
import io.github.wulkanowy.sdk.pojo.MailboxType
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.mockk.MockKAnnotations import io.mockk.*
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
@ -36,9 +32,30 @@ class RecipientLocalTest {
private lateinit var recipientRepository: RecipientRepository private lateinit var recipientRepository: RecipientRepository
private val remoteList = listOf( private val remoteList = listOf(
SdkRecipient("2rPracownik", "Kowalski Jan", 3, 4, 2, "hash", "Kowalski Jan [KJ] - Pracownik (Fake123456)"), SdkRecipient(
SdkRecipient("3rPracownik", "Kowalska Karolina", 4, 4, 2, "hash", "Kowalska Karolina [KK] - Pracownik (Fake123456)"), mailboxGlobalKey = "2rPracownik",
SdkRecipient("4rPracownik", "Krupa Stanisław", 5, 4, 1, "hash", "Krupa Stanisław [KS] - Uczeń (Fake123456)") userName = "Kowalski Jan",
fullName = "Kowalski Jan [KJ] - Pracownik (Fake123456)",
studentName = "",
schoolNameShort = "",
type = MailboxType.UNKNOWN,
),
SdkRecipient(
mailboxGlobalKey = "3rPracownik",
userName = "Kowalska Karolina",
fullName = "Kowalska Karolina [KK] - Pracownik (Fake123456)",
studentName = "",
schoolNameShort = "",
type = MailboxType.UNKNOWN,
),
SdkRecipient(
mailboxGlobalKey = "4rPracownik",
userName = "Krupa Stanisław",
fullName = "Krupa Stanisław [KS] - Uczeń (Fake123456)",
studentName = "",
schoolNameShort = "",
type = MailboxType.UNKNOWN,
)
) )
@Before @Before
@ -52,39 +69,61 @@ class RecipientLocalTest {
@Test @Test
fun `load recipients when items already in database`() { fun `load recipients when items already in database`() {
// prepare // prepare
coEvery { recipientDb.loadAll(4, 123, 7) } returnsMany listOf( coEvery { recipientDb.loadAll(io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, "v4") } returnsMany listOf(
remoteList.mapToEntities(4), remoteList.mapToEntities("v4"),
remoteList.mapToEntities(4) remoteList.mapToEntities("v4")
) )
coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { recipientDb.deleteAll(any()) } just Runs coEvery { recipientDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { recipientRepository.getRecipients(student, ReportingUnit(4, 123, "", 4, "", listOf()), 7) } val res = runBlocking {
recipientRepository.getRecipients(
student = student,
mailbox = getMailboxEntity(),
type = io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN,
)
}
// verify // verify
assertEquals(3, res.size) assertEquals(3, res.size)
coVerify { recipientDb.loadAll(4, 123, 7) } coVerify {
recipientDb.loadAll(
type = io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN,
studentMailboxGlobalKey = "v4"
)
}
} }
@Test @Test
fun `load recipients when database is empty`() { fun `load recipients when database is empty`() {
// prepare // prepare
coEvery { sdk.getRecipients(123, 7) } returns remoteList coEvery { sdk.getRecipients("v4") } returns remoteList
coEvery { recipientDb.loadAll(4, 123, 7) } returnsMany listOf( coEvery {
recipientDb.loadAll(
io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN,
"v4"
)
} returnsMany listOf(
emptyList(), emptyList(),
remoteList.mapToEntities(4) remoteList.mapToEntities("v4")
) )
coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { recipientDb.deleteAll(any()) } just Runs coEvery { recipientDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { recipientRepository.getRecipients(student, ReportingUnit(4, 123, "", 4, "", listOf()), 7) } val res = runBlocking {
recipientRepository.getRecipients(
student = student,
mailbox = getMailboxEntity(),
type = io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN,
)
}
// verify // verify
assertEquals(3, res.size) assertEquals(3, res.size)
coVerify { sdk.getRecipients(123, 7) } coVerify { sdk.getRecipients("v4") }
coVerify { recipientDb.loadAll(4, 123, 7) } coVerify { recipientDb.loadAll(io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, "v4") }
coVerify { recipientDb.insertAll(match { it.isEmpty() }) } coVerify { recipientDb.insertAll(match { it.isEmpty() }) }
coVerify { recipientDb.deleteAll(match { it.isEmpty() }) } coVerify { recipientDb.deleteAll(match { it.isEmpty() }) }
} }