forked from github/wulkanowy-mirror
Add messages (#148)
This commit is contained in:
parent
48f96b5932
commit
92baecbd0d
@ -1,4 +1,4 @@
|
||||
image: circleci/android:api-27-alpha
|
||||
image: circleci/android:api-28-alpha
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||
|
@ -52,6 +52,7 @@ script:
|
||||
- ./gradlew createDebugCoverageReport --stacktrace -PdisableCrashlytics --daemon
|
||||
- ./gradlew jacocoTestReport --stacktrace --daemon
|
||||
- if [ "$TRAVIS_PULL_REQUEST" != "false" ] || [ "$TRAVIS_BRANCH" == "master" ]; then
|
||||
git fetch --unshallow;
|
||||
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} -PdisableCrashlytics --stacktrace --daemon;
|
||||
fi
|
||||
- |
|
||||
|
@ -1,12 +1,13 @@
|
||||
# Wulkanowy
|
||||
|
||||
[![CircleCI](https://img.shields.io/circleci/project/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://circleci.com/gh/wulkanowy/wulkanowy)
|
||||
[![Travis](https://img.shields.io/travis/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
|
||||
[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
|
||||
[![Bitrise](https://img.shields.io/bitrise/daeff1893f3c8128/master.svg?token=Hjm1ACamk86JDeVVJHOeqQ&style=flat-square)](https://www.bitrise.io/app/daeff1893f3c8128)
|
||||
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||
[![BCH compliance](https://bettercodehub.com/edge/badge/wulkanowy/wulkanowy?branch=master)](https://bettercodehub.com/)
|
||||
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
|
||||
[![Sonarcloud](https://sonarcloud.io/api/project_badges/measure?project=io.github.wulkanowy%3Aapp&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=io.github.wulkanowy%3Aapp)
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/custom%2B5644%2Fgit%40github.com%3Awulkanowy%2Fwulkanowy.git.svg?type=shield)](https://app.fossa.io/projects/custom%2B5644%2Fgit%40github.com%3Awulkanowy%2Fwulkanowy.git?ref=badge_shield)
|
||||
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
|
||||
|
||||
[Pobierz wersję beta z Google Play](https://play.google.com/store/apps/details?id=io.github.wulkanowy&utm_source=vcs)
|
||||
|
||||
|
@ -51,7 +51,7 @@ android {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
buildConfigField "boolean", "FABRIC_ENABLED", fabricApiKey == "null" ? "false" : "true"
|
||||
buildConfigField "boolean", "FABRIC_ENABLED", fabricApiKey != "null" && !project.hasProperty("disableCrashlytics") ? "true" : "false"
|
||||
applicationIdSuffix ".dev"
|
||||
versionNameSuffix "-dev"
|
||||
testCoverageEnabled = true
|
||||
@ -81,7 +81,7 @@ configurations.all {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation('com.github.wulkanowy:api:0ac961607b') { exclude module: "threetenbp" }
|
||||
implementation('com.github.wulkanowy:api:ba17abc') { exclude module: "threetenbp" }
|
||||
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.appcompat:appcompat:1.0.2"
|
||||
|
@ -45,7 +45,7 @@ class WulkanowyApp : DaggerApplication() {
|
||||
|
||||
private fun initializeFabric() {
|
||||
Fabric.with(Fabric.Builder(this).kits(
|
||||
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG || !BuildConfig.FABRIC_ENABLED).build()).build(),
|
||||
Crashlytics.Builder().core(CrashlyticsCore.Builder().disabled(!BuildConfig.FABRIC_ENABLED).build()).build(),
|
||||
Answers()
|
||||
).debuggable(BuildConfig.DEBUG).build())
|
||||
Timber.plant(CrashlyticsTree())
|
||||
|
@ -65,6 +65,10 @@ internal class RepositoryModule {
|
||||
@Provides
|
||||
fun provideGradeSummaryDao(database: AppDatabase) = database.gradeSummaryDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideExamDao(database: AppDatabase) = database.examsDao
|
||||
|
@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.dao.HomeworkDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
@ -19,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
@ -36,6 +38,7 @@ import javax.inject.Singleton
|
||||
Attendance::class,
|
||||
Grade::class,
|
||||
GradeSummary::class,
|
||||
Message::class,
|
||||
Note::class,
|
||||
Homework::class
|
||||
],
|
||||
@ -67,6 +70,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract val gradeSummaryDao: GradeSummaryDao
|
||||
|
||||
abstract val messagesDao: MessagesDao
|
||||
|
||||
abstract val noteDao: NoteDao
|
||||
|
||||
abstract val homeworkDao: HomeworkDao
|
||||
|
@ -0,0 +1,37 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.reactivex.Maybe
|
||||
|
||||
@Dao
|
||||
interface MessagesDao {
|
||||
|
||||
@Insert
|
||||
fun insertAll(messages: List<Message>): List<Long>
|
||||
|
||||
@Delete
|
||||
fun deleteAll(messages: List<Message>)
|
||||
|
||||
@Update
|
||||
fun update(message: Message)
|
||||
|
||||
@Update
|
||||
fun updateAll(messages: List<Message>)
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND real_id = :id")
|
||||
fun loadOne(studentId: Int, id: Int): Maybe<Message>
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
|
||||
fun load(studentId: Int, folder: Int): Maybe<List<Message>>
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND removed = 1 ORDER BY date DESC")
|
||||
fun loadDeleted(studentId: Int): Maybe<List<Message>>
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE unread = 1 AND student_id = :studentId")
|
||||
fun loadNewMessages(studentId: Int): Maybe<List<Message>>
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity(tableName = "Messages")
|
||||
data class Message(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
var studentId: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "real_id")
|
||||
val realId: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "message_id")
|
||||
val messageId: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "sender_name")
|
||||
val sender: String? = null,
|
||||
|
||||
@ColumnInfo(name = "sender_id")
|
||||
val senderId: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "recipient_id")
|
||||
val recipientId: Int? = null,
|
||||
|
||||
@ColumnInfo(name = "recipient_name")
|
||||
val recipient: String? = "",
|
||||
|
||||
val subject: String = "",
|
||||
|
||||
val date: LocalDateTime? = null,
|
||||
|
||||
@ColumnInfo(name = "folder_id")
|
||||
val folderId: Int = 0,
|
||||
|
||||
var unread: Boolean? = false,
|
||||
|
||||
val unreadBy: Int? = 0,
|
||||
|
||||
val readBy: Int? = 0,
|
||||
|
||||
val removed: Boolean = false
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
var isNotified: Boolean = true
|
||||
|
||||
var content: String? = null
|
||||
}
|
@ -16,30 +16,30 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ExamRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: ExamLocal,
|
||||
private val remote: ExamRemote
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: ExamLocal,
|
||||
private val remote: ExamRemote
|
||||
) {
|
||||
|
||||
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Exam>> {
|
||||
return Single.fromCallable { startDate.monday to endDate.friday }
|
||||
.flatMap { dates ->
|
||||
local.getExams(semester, dates.first, dates.second).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getExams(semester, dates.first, dates.second)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { newExams ->
|
||||
local.getExams(semester, dates.first, dates.second)
|
||||
.toSingle(emptyList())
|
||||
.doOnSuccess { oldExams ->
|
||||
local.deleteExams(oldExams - newExams)
|
||||
local.saveExams(newExams - oldExams)
|
||||
}
|
||||
}.flatMap {
|
||||
local.getExams(semester, dates.first, dates.second)
|
||||
.toSingle(emptyList())
|
||||
}).map { list -> list.filter { it.date in startDate..endDate } }
|
||||
}
|
||||
.flatMap { dates ->
|
||||
local.getExams(semester, dates.first, dates.second).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getExams(semester, dates.first, dates.second)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { newExams ->
|
||||
local.getExams(semester, dates.first, dates.second)
|
||||
.toSingle(emptyList())
|
||||
.doOnSuccess { oldExams ->
|
||||
local.deleteExams(oldExams - newExams)
|
||||
local.saveExams(newExams - oldExams)
|
||||
}
|
||||
}.flatMap {
|
||||
local.getExams(semester, dates.first, dates.second)
|
||||
.toSingle(emptyList())
|
||||
}).map { list -> list.filter { it.date in startDate..endDate } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.local.MessagesLocal
|
||||
import io.github.wulkanowy.data.repositories.remote.MessagesRemote
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MessagesRepository @Inject constructor(
|
||||
private val settings: InternetObservingSettings,
|
||||
private val local: MessagesLocal,
|
||||
private val remote: MessagesRemote
|
||||
) {
|
||||
|
||||
enum class MessageFolder(val id: Int = 1) {
|
||||
RECEIVED(1),
|
||||
SENT(2),
|
||||
TRASHED(3)
|
||||
}
|
||||
|
||||
fun getMessages(studentId: Int, folder: MessageFolder, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Message>> {
|
||||
return local.getMessages(studentId, folder).filter { !forceRefresh }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) remote.getMessages(studentId, folder)
|
||||
else Single.error(UnknownHostException())
|
||||
}.flatMap { new ->
|
||||
local.getMessages(studentId, folder).toSingle(emptyList())
|
||||
.doOnSuccess { old ->
|
||||
local.deleteMessages(old - new)
|
||||
local.saveMessages((new - old)
|
||||
.onEach {
|
||||
it.isNotified = !notify
|
||||
})
|
||||
}
|
||||
}.flatMap { local.getMessages(studentId, folder).toSingle(emptyList()) }
|
||||
)
|
||||
}
|
||||
|
||||
fun getMessage(studentId: Int, messageId: Int, markAsRead: Boolean = false): Single<Message> {
|
||||
return local.getMessage(studentId, messageId)
|
||||
.filter { !it.content.isNullOrEmpty() }
|
||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
||||
.flatMap {
|
||||
if (it) local.getMessage(studentId, messageId).toSingle()
|
||||
else Single.error(UnknownHostException())
|
||||
}
|
||||
.flatMap { dbMessage ->
|
||||
remote.getMessagesContent(dbMessage, markAsRead).doOnSuccess {
|
||||
local.updateMessage(dbMessage.copy(unread = false).apply {
|
||||
id = dbMessage.id
|
||||
content = it
|
||||
})
|
||||
}
|
||||
}.flatMap {
|
||||
local.getMessage(studentId, messageId).toSingle()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun getNewMessages(student: Student): Single<List<Message>> {
|
||||
return local.getNewMessages(student).toSingle(emptyList())
|
||||
}
|
||||
|
||||
fun updateMessage(message: Message): Completable {
|
||||
return Completable.fromCallable { local.updateMessage(message) }
|
||||
}
|
||||
|
||||
fun updateMessages(messages: List<Message>): Completable {
|
||||
return Completable.fromCallable { local.updateMessages(messages) }
|
||||
}
|
||||
}
|
@ -27,8 +27,8 @@ class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
|
||||
return Completable.fromCallable { gradeDb.update(grade) }
|
||||
}
|
||||
|
||||
fun updateGrades(grade: List<Grade>): Completable {
|
||||
return Completable.fromCallable { gradeDb.updateAll(grade) }
|
||||
fun updateGrades(grades: List<Grade>): Completable {
|
||||
return Completable.fromCallable { gradeDb.updateAll(grades) }
|
||||
}
|
||||
|
||||
fun deleteGrades(grades: List<Grade>) {
|
||||
|
@ -0,0 +1,44 @@
|
||||
package io.github.wulkanowy.data.repositories.local
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository
|
||||
import io.reactivex.Maybe
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MessagesLocal @Inject constructor(private val messagesDb: MessagesDao) {
|
||||
|
||||
fun getMessage(studentId: Int, id: Int): Maybe<Message> {
|
||||
return messagesDb.loadOne(studentId, id)
|
||||
}
|
||||
|
||||
fun getMessages(studentId: Int, folder: MessagesRepository.MessageFolder): Maybe<List<Message>> {
|
||||
return when (folder) {
|
||||
MessagesRepository.MessageFolder.TRASHED -> messagesDb.loadDeleted(studentId)
|
||||
else -> messagesDb.load(studentId, folder.id)
|
||||
}.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun getNewMessages(student: Student): Maybe<List<Message>> {
|
||||
return messagesDb.loadNewMessages(student.studentId)
|
||||
}
|
||||
|
||||
fun saveMessages(messages: List<Message>): List<Long> {
|
||||
return messagesDb.insertAll(messages)
|
||||
}
|
||||
|
||||
fun updateMessage(message: Message) {
|
||||
return messagesDb.update(message)
|
||||
}
|
||||
|
||||
fun updateMessages(messages: List<Message>) {
|
||||
return messagesDb.updateAll(messages)
|
||||
}
|
||||
|
||||
fun deleteMessages(messages: List<Message>) {
|
||||
messagesDb.deleteAll(messages)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package io.github.wulkanowy.data.repositories.remote
|
||||
|
||||
import io.github.wulkanowy.api.Api
|
||||
import io.github.wulkanowy.api.messages.Folder
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
import io.github.wulkanowy.api.messages.Message as ApiMessage
|
||||
|
||||
class MessagesRemote @Inject constructor(private val api: Api) {
|
||||
|
||||
fun getMessages(studentId: Int, folder: MessagesRepository.MessageFolder): Single<List<Message>> {
|
||||
return api.getMessages(Folder.valueOf(folder.name)).map { messages ->
|
||||
messages.map {
|
||||
Message(
|
||||
studentId = studentId,
|
||||
realId = it.id,
|
||||
messageId = it.messageId,
|
||||
sender = it.sender,
|
||||
senderId = it.senderId,
|
||||
recipient = it.recipient,
|
||||
recipientId = it.recipientId,
|
||||
subject = it.subject.trim(),
|
||||
date = it.date?.toLocalDateTime(),
|
||||
folderId = it.folderId,
|
||||
unread = it.unread,
|
||||
unreadBy = it.unreadBy,
|
||||
readBy = it.readBy,
|
||||
removed = it.removed
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessagesContent(message: Message, markAsRead: Boolean = false): Single<String> {
|
||||
return api.getMessageContent(message.messageId ?: 0, message.folderId, markAsRead, message.realId ?: 0)
|
||||
}
|
||||
}
|
@ -8,12 +8,15 @@ import io.github.wulkanowy.data.repositories.ExamRepository
|
||||
import io.github.wulkanowy.data.repositories.GradeRepository
|
||||
import io.github.wulkanowy.data.repositories.GradeSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.repositories.NoteRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.services.notification.GradeNotification
|
||||
import io.github.wulkanowy.services.notification.MessageNotification
|
||||
import io.github.wulkanowy.services.notification.NoteNotification
|
||||
import io.github.wulkanowy.utils.friday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
@ -47,6 +50,9 @@ class SyncWorker : SimpleJobService() {
|
||||
@Inject
|
||||
lateinit var timetable: TimetableRepository
|
||||
|
||||
@Inject
|
||||
lateinit var message: MessagesRepository
|
||||
|
||||
@Inject
|
||||
lateinit var note: NoteRepository
|
||||
|
||||
@ -87,6 +93,7 @@ class SyncWorker : SimpleJobService() {
|
||||
attendance.getAttendance(it, start, end, true),
|
||||
exam.getExams(it, start, end, true),
|
||||
timetable.getTimetable(it, start, end, true),
|
||||
message.getMessages(it.studentId, RECEIVED, true, true),
|
||||
note.getNotes(it, true, true),
|
||||
homework.getHomework(it, LocalDate.now(), true),
|
||||
homework.getHomework(it, LocalDate.now().plusDays(1), true)
|
||||
@ -107,35 +114,57 @@ class SyncWorker : SimpleJobService() {
|
||||
|
||||
private fun sendNotifications() {
|
||||
sendGradeNotifications()
|
||||
sendMessageNotification()
|
||||
sendNoteNotification()
|
||||
}
|
||||
|
||||
private fun sendGradeNotifications() {
|
||||
disposable.add(student.getCurrentStudent()
|
||||
.flatMap { semester.getCurrentSemester(it, true) }
|
||||
.flatMap { semester.getCurrentSemester(it) }
|
||||
.flatMap { gradesDetails.getNewGrades(it) }
|
||||
.map { it.filter { grade -> !grade.isNotified } }
|
||||
.subscribe({
|
||||
.doOnSuccess {
|
||||
if (it.isNotEmpty()) {
|
||||
Timber.d("Found ${it.size} unread grades")
|
||||
GradeNotification(applicationContext).sendNotification(it)
|
||||
gradesDetails.updateGrades(it.map { grade -> grade.apply { isNotified = true } }).subscribe()
|
||||
}
|
||||
}) { Timber.e("Notifications sending failed") })
|
||||
}
|
||||
.map { it.map { grade -> grade.apply { isNotified = true } } }
|
||||
.flatMapCompletable { gradesDetails.updateGrades(it) }
|
||||
.subscribe({}, { Timber.e(it, "Grade notifications sending failed") }))
|
||||
}
|
||||
|
||||
private fun sendMessageNotification() {
|
||||
disposable.add(student.getCurrentStudent()
|
||||
.flatMap { message.getNewMessages(it) }
|
||||
.map { it.filter { message -> !message.isNotified } }
|
||||
.doOnSuccess{
|
||||
if (it.isNotEmpty()) {
|
||||
Timber.d("Found ${it.size} unread messages")
|
||||
MessageNotification(applicationContext).sendNotification(it)
|
||||
}
|
||||
}
|
||||
.map { it.map { message -> message.apply { isNotified = true } } }
|
||||
.flatMapCompletable { message.updateMessages(it) }
|
||||
.subscribe({}, { Timber.e(it, "Message notifications sending failed") })
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendNoteNotification() {
|
||||
disposable.add(student.getCurrentStudent()
|
||||
.flatMap { semester.getCurrentSemester(it, true) }
|
||||
.flatMap { semester.getCurrentSemester(it) }
|
||||
.flatMap { note.getNewNotes(it) }
|
||||
.map { it.filter { note -> !note.isNotified } }
|
||||
.subscribe({
|
||||
.doOnSuccess {
|
||||
if (it.isNotEmpty()) {
|
||||
Timber.d("Found ${it.size} unread notes")
|
||||
NoteNotification(applicationContext).sendNotification(it)
|
||||
note.updateNotes(it.map { note -> note.apply { isNotified = true } }).subscribe()
|
||||
}
|
||||
}) { Timber.e("Notifications sending failed") })
|
||||
}
|
||||
.map { it.map { note -> note.apply { isNotified = true } } }
|
||||
.flatMapCompletable { note.updateNotes(it) }
|
||||
.subscribe({}, { Timber.e("Notifications sending failed") })
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -0,0 +1,58 @@
|
||||
package io.github.wulkanowy.services.notification
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import timber.log.Timber
|
||||
|
||||
class MessageNotification(context: Context) : BaseNotification(context) {
|
||||
|
||||
private val channelId = "Message_Notify"
|
||||
|
||||
@TargetApi(26)
|
||||
override fun createChannel(channelId: String) {
|
||||
notificationManager.createNotificationChannel(NotificationChannel(
|
||||
channelId, context.getString(R.string.notify_message_channel), NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
enableLights(true)
|
||||
enableVibration(true)
|
||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
})
|
||||
}
|
||||
|
||||
fun sendNotification(items: List<Message>) {
|
||||
notify(notificationBuilder(channelId)
|
||||
.setContentTitle(context.resources.getQuantityString(R.plurals.message_new_items, items.size, items.size))
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.notify_message_new_items, items.size, items.size))
|
||||
.setSmallIcon(R.drawable.ic_stat_notify_message)
|
||||
.setAutoCancel(true)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setColor(ContextCompat.getColor(context, R.color.colorPrimary))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(context, 0,
|
||||
MainActivity.getStartIntent(context).putExtra(MainActivity.EXTRA_START_MENU_INDEX, 4),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.setStyle(NotificationCompat.InboxStyle().run {
|
||||
setSummaryText(context.resources.getQuantityString(R.plurals.message_number_item, items.size, items.size))
|
||||
items.forEach {
|
||||
addLine("${it.sender}: ${it.subject}")
|
||||
}
|
||||
this
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
||||
Timber.d("Notification sent")
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ abstract class GradeModule {
|
||||
}
|
||||
|
||||
@PerChildFragment
|
||||
@ContributesAndroidInjector()
|
||||
@ContributesAndroidInjector
|
||||
abstract fun bindGradeDetailsFragment(): GradeDetailsFragment
|
||||
|
||||
@PerChildFragment
|
||||
|
@ -15,6 +15,9 @@ import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeModule
|
||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.message.MessageModule
|
||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
||||
import io.github.wulkanowy.ui.modules.more.MoreFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||
@ -46,6 +49,14 @@ abstract class MainModule {
|
||||
@ContributesAndroidInjector(modules = [GradeModule::class])
|
||||
abstract fun bindGradeFragment(): GradeFragment
|
||||
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector(modules = [MessageModule::class])
|
||||
abstract fun bindMessagesFragment(): MessageFragment
|
||||
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun bindMessagePreviewFragment(): MessagePreviewFragment
|
||||
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun bindMoreFragment(): MoreFragment
|
||||
|
@ -0,0 +1,83 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.SENT
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.base.BasePagerAdapter
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
|
||||
import io.github.wulkanowy.utils.setOnSelectPageListener
|
||||
import kotlinx.android.synthetic.main.fragment_message.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MessagePresenter
|
||||
|
||||
@Inject
|
||||
lateinit var pagerAdapter: BasePagerAdapter
|
||||
|
||||
companion object {
|
||||
fun newInstance() = MessageFragment()
|
||||
}
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.message_title
|
||||
|
||||
override val currentPageIndex: Int
|
||||
get() = messageViewPager.currentItem
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_message, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
pagerAdapter.fragments.putAll(mapOf(
|
||||
getString(R.string.message_inbox) to MessageTabFragment.newInstance(RECEIVED),
|
||||
getString(R.string.message_sent) to MessageTabFragment.newInstance(SENT),
|
||||
getString(R.string.message_trash) to MessageTabFragment.newInstance(TRASHED)
|
||||
))
|
||||
messageViewPager.run {
|
||||
adapter = pagerAdapter
|
||||
offscreenPageLimit = 2
|
||||
setOnSelectPageListener { presenter.onPageSelected(it) }
|
||||
}
|
||||
messageTabLayout.setupWithViewPager(messageViewPager)
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
messageViewPager.visibility = if (show) VISIBLE else INVISIBLE
|
||||
messageTabLayout.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
messageProgress.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
fun onChildFragmentLoaded() {
|
||||
presenter.onChildViewLoaded()
|
||||
}
|
||||
|
||||
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
|
||||
(childFragmentManager.fragments[index] as MessageView.MessageChildView).onParentLoadData(forceRefresh)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import android.graphics.Typeface.BOLD
|
||||
import android.graphics.Typeface.NORMAL
|
||||
import android.view.View
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_message.*
|
||||
|
||||
class MessageItem(val message: Message, private val noSubjectString: String) :
|
||||
AbstractFlexibleItem<MessageItem.ViewHolder>() {
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
||||
return ViewHolder(view, adapter)
|
||||
}
|
||||
|
||||
override fun getLayoutRes(): Int = R.layout.item_message
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
|
||||
position: Int, payloads: MutableList<Any>?
|
||||
) {
|
||||
holder.apply {
|
||||
val style = if (message.unread == true) BOLD else NORMAL
|
||||
|
||||
messageItemAuthor.run {
|
||||
text = if (message.recipient?.isNotBlank() == true) message.recipient else message.sender
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemSubject.run {
|
||||
text = if (message.subject.isNotBlank()) message.subject else noSubjectString
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemDate.run {
|
||||
text = message.date?.toFormattedString()
|
||||
setTypeface(null, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MessageItem
|
||||
|
||||
if (message != other.message) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return message.hashCode()
|
||||
}
|
||||
|
||||
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View
|
||||
get() = contentView
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import io.github.wulkanowy.di.scopes.PerChildFragment
|
||||
import io.github.wulkanowy.di.scopes.PerFragment
|
||||
import io.github.wulkanowy.ui.base.BasePagerAdapter
|
||||
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
|
||||
|
||||
@Module
|
||||
abstract class MessageModule {
|
||||
|
||||
@Module
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
@PerFragment
|
||||
@Provides
|
||||
fun provideGradePagerAdapter(fragment: MessageFragment) = BasePagerAdapter(fragment.childFragmentManager)
|
||||
}
|
||||
|
||||
@PerChildFragment
|
||||
@ContributesAndroidInjector
|
||||
abstract fun bindMessageTabFragment(): MessageTabFragment
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import io.github.wulkanowy.data.ErrorHandler
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.reactivex.Completable
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessagePresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
private val schedulers: SchedulersProvider
|
||||
) : BasePresenter<MessageView>(errorHandler) {
|
||||
|
||||
override fun onAttachView(view: MessageView) {
|
||||
super.onAttachView(view)
|
||||
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread)
|
||||
.subscribe {
|
||||
view.initView()
|
||||
loadData()
|
||||
})
|
||||
}
|
||||
|
||||
fun onPageSelected(index: Int) {
|
||||
loadChild(index)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
view?.run { loadChild(currentPageIndex) }
|
||||
}
|
||||
|
||||
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
||||
view?.notifyChildLoadData(index, forceRefresh)
|
||||
}
|
||||
|
||||
fun onChildViewLoaded() {
|
||||
view?.apply {
|
||||
showContent(true)
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MessageView : BaseView {
|
||||
|
||||
val currentPageIndex: Int
|
||||
|
||||
fun initView()
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun notifyChildLoadData(index: Int, forceRefresh: Boolean)
|
||||
|
||||
interface MessageChildView {
|
||||
|
||||
fun onParentLoadData(forceRefresh: Boolean)
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import kotlinx.android.synthetic.main.fragment_message_preview.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MessagePreviewPresenter
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.message_title
|
||||
|
||||
override val noSubjectString: String
|
||||
get() = getString(R.string.message_no_subject)
|
||||
|
||||
companion object {
|
||||
const val MESSAGE_ID_KEY = "message_id"
|
||||
|
||||
fun newInstance(messageId: Int?): MessagePreviewFragment {
|
||||
return MessagePreviewFragment().apply {
|
||||
arguments = Bundle().apply { putInt(MESSAGE_ID_KEY, messageId ?: 0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_message_preview, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
messageContainer = message
|
||||
presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getInt(MESSAGE_ID_KEY) ?: 0)
|
||||
}
|
||||
|
||||
override fun setSubject(subject: String) {
|
||||
messageSubject.text = subject
|
||||
}
|
||||
|
||||
override fun setRecipient(recipient: String?) {
|
||||
messageAuthor.text = getString(R.string.message_to, recipient)
|
||||
}
|
||||
|
||||
override fun setSender(sender: String?) {
|
||||
messageAuthor.text = getString(R.string.message_from, sender)
|
||||
}
|
||||
|
||||
override fun setDate(date: String?) {
|
||||
messageDate.text = getString(R.string.message_date, date)
|
||||
}
|
||||
|
||||
override fun setContent(content: String?) {
|
||||
messageContent.text = content
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
messageProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showMessageError() {
|
||||
messageError.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putInt(MESSAGE_ID_KEY, presenter.messageId)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import io.github.wulkanowy.data.ErrorHandler
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.logEvent
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessagePreviewPresenter @Inject constructor(
|
||||
private val errorHandler: ErrorHandler,
|
||||
private val schedulers: SchedulersProvider,
|
||||
private val messagesRepository: MessagesRepository,
|
||||
private val studentRepository: StudentRepository
|
||||
) : BasePresenter<MessagePreviewView>(errorHandler) {
|
||||
|
||||
var messageId: Int = 0
|
||||
|
||||
fun onAttachView(view: MessagePreviewView, id: Int) {
|
||||
super.onAttachView(view)
|
||||
loadData(id)
|
||||
}
|
||||
|
||||
private fun loadData(id: Int) {
|
||||
messageId = id
|
||||
disposable.apply {
|
||||
clear()
|
||||
add(studentRepository.getCurrentStudent()
|
||||
.flatMap { messagesRepository.getMessage(it.studentId, messageId, true) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally { view?.showProgress(false) }
|
||||
.subscribe({ messages ->
|
||||
view?.run {
|
||||
messages.let {
|
||||
setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString)
|
||||
setDate(it.date?.toFormattedString("yyyy-MM-dd HH:mm:ss"))
|
||||
setContent(it.content)
|
||||
|
||||
if (it.recipient?.isNotBlank() == true) setRecipient(it.recipient)
|
||||
else setSender(it.sender)
|
||||
}
|
||||
}
|
||||
logEvent("Message load", mapOf("length" to messages.content?.length))
|
||||
}) {
|
||||
view?.showMessageError()
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MessagePreviewView : BaseView {
|
||||
|
||||
val noSubjectString: String
|
||||
|
||||
fun setSubject(subject: String)
|
||||
|
||||
fun setRecipient(recipient: String?)
|
||||
|
||||
fun setSender(sender: String?)
|
||||
|
||||
fun setDate(date: String?)
|
||||
|
||||
fun setContent(content: String?)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showMessageError()
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.message.MessageItem
|
||||
import io.github.wulkanowy.ui.modules.message.MessageView
|
||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.fragment_message_tab.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessageTabFragment : BaseFragment(), MessageTabView, MessageView.MessageChildView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: MessageTabPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var tabAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
||||
|
||||
companion object {
|
||||
const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id"
|
||||
|
||||
fun newInstance(folder: MessagesRepository.MessageFolder): MessageTabFragment {
|
||||
return MessageTabFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(MESSAGE_TAB_FOLDER_ID, folder.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val noSubjectString: String
|
||||
get() = getString(R.string.message_no_subject)
|
||||
|
||||
override val isViewEmpty
|
||||
get() = tabAdapter.isEmpty
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_message_tab, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
messageContainer = messageTabRecycler
|
||||
presenter.onAttachView(this, MessagesRepository.MessageFolder.valueOf(
|
||||
(savedInstanceState ?: arguments)?.getString(MessageTabFragment.MESSAGE_TAB_FOLDER_ID) ?: ""
|
||||
))
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
tabAdapter.setOnItemClickListener { presenter.onMessageItemSelected(it) }
|
||||
|
||||
messageTabRecycler.run {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
adapter = tabAdapter
|
||||
}
|
||||
messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||
}
|
||||
|
||||
override fun updateData(data: List<MessageItem>) {
|
||||
tabAdapter.updateDataSet(data, true)
|
||||
}
|
||||
|
||||
override fun updateItem(item: AbstractFlexibleItem<*>) {
|
||||
tabAdapter.updateItem(item)
|
||||
}
|
||||
|
||||
override fun clearView() {
|
||||
tabAdapter.clear()
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
messageTabProgress.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
messageTabRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
override fun showRefresh(show: Boolean) {
|
||||
messageTabSwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun openMessage(messageId: Int?) {
|
||||
(activity as? MainActivity)?.pushView(MessagePreviewFragment.newInstance(messageId))
|
||||
}
|
||||
|
||||
override fun notifyParentDataLoaded() {
|
||||
(parentFragment as? MessageFragment)?.onChildFragmentLoaded()
|
||||
}
|
||||
|
||||
override fun onParentLoadData(forceRefresh: Boolean) {
|
||||
presenter.onParentViewLoadData(forceRefresh)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString(MessageTabFragment.MESSAGE_TAB_FOLDER_ID, presenter.folder.name)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.data.ErrorHandler
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.repositories.MessagesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.message.MessageItem
|
||||
import io.github.wulkanowy.utils.SchedulersProvider
|
||||
import io.github.wulkanowy.utils.logEvent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessageTabPresenter @Inject constructor(
|
||||
private val errorHandler: ErrorHandler,
|
||||
private val schedulers: SchedulersProvider,
|
||||
private val messagesRepository: MessagesRepository,
|
||||
private val studentRepository: StudentRepository
|
||||
) : BasePresenter<MessageTabView>(errorHandler) {
|
||||
|
||||
lateinit var folder: MessagesRepository.MessageFolder
|
||||
|
||||
fun onAttachView(view: MessageTabView, folder: MessagesRepository.MessageFolder) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
this.folder = folder
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
onParentViewLoadData(true)
|
||||
}
|
||||
|
||||
fun onParentViewLoadData(forceRefresh: Boolean) {
|
||||
disposable.apply {
|
||||
clear()
|
||||
add(studentRepository.getCurrentStudent()
|
||||
.flatMap { messagesRepository.getMessages(it.studentId, folder, forceRefresh) }
|
||||
.map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doFinally {
|
||||
view?.run {
|
||||
showRefresh(false)
|
||||
showProgress(false)
|
||||
notifyParentDataLoaded()
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
view?.run {
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(it.isNotEmpty())
|
||||
updateData(it)
|
||||
}
|
||||
logEvent("Message tab load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
|
||||
}) {
|
||||
view?.run { showEmpty(isViewEmpty) }
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageItemSelected(item: AbstractFlexibleItem<*>) {
|
||||
if (item is MessageItem) {
|
||||
view?.run {
|
||||
openMessage(item.message.realId)
|
||||
if (item.message.unread == true) {
|
||||
item.message.unread = false
|
||||
updateItem(item)
|
||||
updateMessage(item.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMessage(message: Message) {
|
||||
disposable.add(messagesRepository.updateMessage(message)
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.subscribe({
|
||||
Timber.d("Message ${message.realId} updated")
|
||||
}) { error -> errorHandler.dispatch(error) }
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.message.MessageItem
|
||||
|
||||
interface MessageTabView : BaseView {
|
||||
|
||||
val noSubjectString: String
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<MessageItem>)
|
||||
|
||||
fun updateItem(item: AbstractFlexibleItem<*>)
|
||||
|
||||
fun clearView()
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showRefresh(show: Boolean)
|
||||
|
||||
fun openMessage(messageId: Int?)
|
||||
|
||||
fun notifyParentDataLoaded()
|
||||
}
|
@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.about.AboutFragment
|
||||
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
@ -36,6 +37,15 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
|
||||
override val titleStringId: Int
|
||||
get() = R.string.more_title
|
||||
|
||||
|
||||
override val messagesRes: Pair<String, Drawable?>?
|
||||
get() {
|
||||
return context?.run {
|
||||
getString(R.string.message_title) to
|
||||
ContextCompat.getDrawable(this, R.drawable.ic_more_messages_24dp)
|
||||
}
|
||||
}
|
||||
|
||||
override val homeworkRes: Pair<String, Drawable?>?
|
||||
get() {
|
||||
return context?.run {
|
||||
@ -92,6 +102,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
|
||||
moreAdapter.updateDataSet(data)
|
||||
}
|
||||
|
||||
override fun openMessagesView() {
|
||||
(activity as? MainActivity)?.pushView(MessageFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openHomeworkView() {
|
||||
(activity as? MainActivity)?.pushView(HomeworkFragment.newInstance())
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen
|
||||
if (item is MoreItem) {
|
||||
view?.run {
|
||||
when (item.title) {
|
||||
messagesRes?.first -> openMessagesView()
|
||||
homeworkRes?.first -> openHomeworkView()
|
||||
noteRes?.first -> openNoteView()
|
||||
settingsRes?.first -> openSettingsView()
|
||||
@ -33,6 +34,7 @@ class MorePresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresen
|
||||
private fun loadData() {
|
||||
view?.run {
|
||||
updateData(listOfNotNull(
|
||||
messagesRes?.let { MoreItem(it.first, it.second) },
|
||||
homeworkRes?.let { MoreItem(it.first, it.second) },
|
||||
noteRes?.let { MoreItem(it.first, it.second) },
|
||||
settingsRes?.let { MoreItem(it.first, it.second) },
|
||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MoreView : BaseView {
|
||||
|
||||
val messagesRes: Pair<String, Drawable?>?
|
||||
|
||||
val homeworkRes: Pair<String, Drawable?>?
|
||||
|
||||
val noteRes: Pair<String, Drawable?>?
|
||||
@ -23,6 +25,8 @@ interface MoreView : BaseView {
|
||||
|
||||
fun popView()
|
||||
|
||||
fun openMessagesView()
|
||||
|
||||
fun openHomeworkView()
|
||||
|
||||
fun openNoteView()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import org.threeten.bp.DateTimeUtils
|
||||
import org.threeten.bp.DayOfWeek.*
|
||||
import org.threeten.bp.Instant
|
||||
import org.threeten.bp.LocalDate
|
||||
|
BIN
app/src/main/res/drawable-hdpi/ic_stat_notify_message.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_stat_notify_message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 271 B |
BIN
app/src/main/res/drawable-mdpi/ic_stat_notify_message.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_stat_notify_message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 214 B |
BIN
app/src/main/res/drawable-xhdpi/ic_stat_notify_message.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_stat_notify_message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 268 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_stat_notify_message.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_stat_notify_message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 401 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_stat_notify_message.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_stat_notify_message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 458 B |
9
app/src/main/res/drawable/ic_more_messages_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_more_messages_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z" />
|
||||
</vector>
|
@ -57,4 +57,4 @@
|
||||
android:text="@string/grade_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
33
app/src/main/res/layout/fragment_message.xml
Normal file
33
app/src/main/res/layout/fragment_message.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/messageTabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:elevation="5dp"
|
||||
android:visibility="invisible"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="@android:color/white"
|
||||
app:tabMaxWidth="0dp"
|
||||
app:tabMode="fixed"
|
||||
app:tabTextColor="@android:color/white" />
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/messageViewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="48dp"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/messageProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
90
app/src/main/res/layout/fragment_message_preview.xml
Normal file
90
app/src/main/res/layout/fragment_message_preview.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageSubject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textSize="22sp"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageAuthor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/messageError"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
android:visibility="invisible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_more_messages_24dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/message_preview_error"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/messageProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
</FrameLayout>
|
50
app/src/main/res/layout/fragment_message_tab.xml
Normal file
50
app/src/main/res/layout/fragment_message_tab.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/messageTabSwipe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/messageTabRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/messageTabProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/messageTabEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_more_messages_24dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/message_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
52
app/src/main/res/layout/item_message.xml
Normal file
52
app/src/main/res/layout/item_message.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tool="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageItemAuthor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginRight="40dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="15sp"
|
||||
tool:text="@tools:sample/full_names" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageItemDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_toEndOf="@id/messageItemAuthor"
|
||||
android:layout_toRightOf="@id/messageItemAuthor"
|
||||
android:gravity="end"
|
||||
android:textSize="13sp"
|
||||
tool:text="@tools:sample/date/mmddyy" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageItemSubject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/messageItemAuthor"
|
||||
android:layout_alignStart="@id/messageItemAuthor"
|
||||
android:layout_alignLeft="@id/messageItemAuthor"
|
||||
android:layout_marginTop="5dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tool:text="@tools:sample/lorem" />
|
||||
|
||||
</RelativeLayout>
|
@ -11,6 +11,7 @@
|
||||
<string name="settings_title">Ustawienia</string>
|
||||
<string name="more_title">Więcej</string>
|
||||
<string name="about_title">O aplikacji</string>
|
||||
<string name="message_title">Wiadomości</string>
|
||||
<string name="note_title">Uwagi i osiągnięcia</string>
|
||||
<string name="homework_title">Zadania domowe</string>
|
||||
<string name="account_title">Wybierz konto</string>
|
||||
@ -68,8 +69,7 @@
|
||||
<plurals name="grade_new_items">
|
||||
<item quantity="one">Nowa ocena</item>
|
||||
<item quantity="few">Nowe oceny</item>
|
||||
<item quantity="many">Nowych ocen</item>
|
||||
<item quantity="other">Nowych ocen</item>
|
||||
<item quantity="many">Nowe oceny</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
@ -124,6 +124,38 @@
|
||||
<string name="exam_type">Typ</string>
|
||||
<string name="exam_entry_date">Data wpisu</string>
|
||||
|
||||
|
||||
<!--Message-->
|
||||
<string name="message_inbox">Odebrane</string>
|
||||
<string name="message_sent">Wysłane</string>
|
||||
<string name="message_trash">Kosz</string>
|
||||
<string name="message_no_subject">(brak tematu)</string>
|
||||
<string name="message_no_items">Brak wiadomości</string>
|
||||
<string name="message_preview_error">Wystąpił błąd podczas pobierania treści wiadomości</string>
|
||||
<string name="message_from">Od: %s</string>
|
||||
<string name="message_to">Do: %s</string>
|
||||
<string name="message_date">Data: %s</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d wiadomość</item>
|
||||
<item quantity="few">%d wiadomości</item>
|
||||
<item quantity="many">%d wiadomości</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="message_new_items">
|
||||
<item quantity="one">Nowa wiadomość</item>
|
||||
<item quantity="few">Nowe wiadomości</item>
|
||||
<item quantity="many">Nowe wiadomości</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
<!--Message notify-->
|
||||
<string name="notify_message_channel">Nowe wiadomości</string>
|
||||
<plurals name="notify_message_new_items">
|
||||
<item quantity="one">Dostałeś %1$d wiadomość</item>
|
||||
<item quantity="few">"Dostałeś %1$d wiadomości</item>
|
||||
<item quantity="many">Dostałeś %1$d wiadomości</item>
|
||||
</plurals>
|
||||
|
||||
<!--About-->
|
||||
<string name="about_source_code">Kod źródłowy</string>
|
||||
<string name="about_feedback">Zgłoś błąd</string>
|
||||
|
@ -11,6 +11,7 @@
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="more_title">More</string>
|
||||
<string name="about_title">About</string>
|
||||
<string name="message_title">Messages</string>
|
||||
<string name="note_title">Notes and achievements</string>
|
||||
<string name="homework_title">Homework</string>
|
||||
<string name="account_title">Choose account</string>
|
||||
@ -113,6 +114,34 @@
|
||||
<string name="exam_type">Type</string>
|
||||
<string name="exam_entry_date">Entry date</string>
|
||||
|
||||
|
||||
<!--Message-->
|
||||
<string name="message_inbox">Inbox</string>
|
||||
<string name="message_sent">Sent</string>
|
||||
<string name="message_trash">Trash</string>
|
||||
<string name="message_no_subject">(no subject)</string>
|
||||
<string name="message_no_items">No messages</string>
|
||||
<string name="message_preview_error">An error occurred while downloading message content</string>
|
||||
<string name="message_from">From: %s</string>
|
||||
<string name="message_to">To: %s</string>
|
||||
<string name="message_date">Date: %s</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d message</item>
|
||||
<item quantity="other">%d messages</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="message_new_items">
|
||||
<item quantity="one">New message</item>
|
||||
<item quantity="other">New messages</item>
|
||||
</plurals>
|
||||
|
||||
<!--Message notify-->
|
||||
<string name="notify_message_channel">New messages</string>
|
||||
<plurals name="notify_message_new_items">
|
||||
<item quantity="one">You received %1$d message</item>
|
||||
<item quantity="other">You received %1$d messages</item>
|
||||
</plurals>
|
||||
|
||||
<!--About-->
|
||||
<string name="about_source_code">Source code</string>
|
||||
<string name="about_feedback">Report a bug</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user