mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-31 17:02:44 +01:00
Merge branch 'release/2.5.0'
This commit is contained in:
commit
7b2c839775
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: wulkanowy
|
||||
custom: https://www.paypal.com/paypalme/wulkanowy
|
@ -27,8 +27,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 148
|
||||
versionName "2.4.2"
|
||||
versionCode 149
|
||||
versionName "2.5.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
@ -164,8 +164,8 @@ play {
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.99d
|
||||
updatePriority = 2
|
||||
userFraction = 0.20d
|
||||
updatePriority = 1
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
@ -187,15 +187,15 @@ huaweiPublish {
|
||||
|
||||
ext {
|
||||
work_manager = "2.9.0"
|
||||
android_hilt = "1.1.0"
|
||||
android_hilt = "1.2.0"
|
||||
room = "2.6.1"
|
||||
chucker = "4.0.0"
|
||||
mockk = "1.13.9"
|
||||
mockk = "1.13.10"
|
||||
coroutines = "1.8.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.4.1'
|
||||
implementation 'io.github.wulkanowy:sdk:2.5.0'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
|
||||
@ -246,13 +246,13 @@ dependencies {
|
||||
implementation 'com.github.Faierbel:slf4j-timber:2.0'
|
||||
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||
implementation 'io.coil-kt:coil:2.5.0'
|
||||
implementation 'io.coil-kt:coil:2.6.0'
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.7.3')
|
||||
playImplementation 'com.google.firebase:firebase-analytics'
|
||||
playImplementation 'com.google.firebase:firebase-messaging'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
|
2527
app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
Normal file
2527
app/schemas/io.github.wulkanowy.data.db.AppDatabase/60.json
Normal file
File diff suppressed because it is too large
Load Diff
2533
app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json
Normal file
2533
app/schemas/io.github.wulkanowy.data.db.AppDatabase/61.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -54,5 +54,9 @@
|
||||
{
|
||||
"displayName": "Antoni Paduch",
|
||||
"githubUsername": "janAte1"
|
||||
},
|
||||
{
|
||||
"displayName": "Kamil Wąsik",
|
||||
"githubUsername": "JestemKamil"
|
||||
}
|
||||
]
|
||||
|
@ -38,13 +38,16 @@ internal class DataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) =
|
||||
Sdk().apply {
|
||||
fun provideSdk(
|
||||
chuckerInterceptor: ChuckerInterceptor,
|
||||
remoteConfig: RemoteConfigHelper,
|
||||
webkitCookieManagerProxy: WebkitCookieManagerProxy,
|
||||
) = Sdk().apply {
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
setAdditionalCookieManager(WebkitCookieManagerProxy())
|
||||
setAdditionalCookieManager(webkitCookieManagerProxy)
|
||||
|
||||
// for debug only
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
@ -254,6 +257,10 @@ internal class DataModule {
|
||||
@Provides
|
||||
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMutesDao(database: AppDatabase) = database.mutedMessageSendersDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao
|
||||
|
@ -25,6 +25,7 @@ import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
@ -56,6 +57,7 @@ import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.MutedMessageSender
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
@ -157,6 +159,7 @@ import javax.inject.Singleton
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class,
|
||||
AdminMessage::class,
|
||||
MutedMessageSender::class,
|
||||
GradeDescriptive::class,
|
||||
],
|
||||
autoMigrations = [
|
||||
@ -169,6 +172,8 @@ import javax.inject.Singleton
|
||||
AutoMigration(from = 56, to = 57, spec = Migration57::class),
|
||||
AutoMigration(from = 57, to = 58, spec = Migration58::class),
|
||||
AutoMigration(from = 58, to = 59),
|
||||
AutoMigration(from = 59, to = 60),
|
||||
AutoMigration(from = 60, to = 61),
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -177,7 +182,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 59
|
||||
const val VERSION_SCHEMA = 61
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -303,5 +308,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract val adminMessagesDao: AdminMessageDao
|
||||
|
||||
abstract val mutedMessageSendersDao: MutedMessageSendersDao
|
||||
|
||||
abstract val gradeDescriptiveDao: GradeDescriptiveDao
|
||||
}
|
||||
|
@ -2,24 +2,14 @@ package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Dao
|
||||
abstract class AdminMessageDao : BaseDao<AdminMessage> {
|
||||
interface AdminMessageDao : BaseDao<AdminMessage> {
|
||||
|
||||
@Query("SELECT * FROM AdminMessages")
|
||||
abstract fun loadAll(): Flow<List<AdminMessage>>
|
||||
|
||||
@Transaction
|
||||
open suspend fun removeOldAndSaveNew(
|
||||
oldMessages: List<AdminMessage>,
|
||||
newMessages: List<AdminMessage>
|
||||
) {
|
||||
deleteAll(oldMessages)
|
||||
insertAll(newMessages)
|
||||
}
|
||||
fun loadAll(): Flow<List<AdminMessage>>
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
|
||||
interface BaseDao<T> {
|
||||
@ -15,4 +16,10 @@ interface BaseDao<T> {
|
||||
|
||||
@Delete
|
||||
suspend fun deleteAll(items: List<T>)
|
||||
|
||||
@Transaction
|
||||
suspend fun removeOldAndSaveNew(oldItems: List<T>, newItems: List<T>) {
|
||||
deleteAll(oldItems)
|
||||
insertAll(newItems)
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,23 @@ import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface MessagesDao : BaseDao<Message> {
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
|
||||
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
||||
fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow<List<MessageWithMutedAuthor>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
|
||||
fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow<List<MessageWithMutedAuthor>>
|
||||
|
||||
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
|
||||
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.MutedMessageSender
|
||||
|
||||
@Dao
|
||||
interface MutedMessageSendersDao : BaseDao<MutedMessageSender> {
|
||||
|
||||
@Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author")
|
||||
suspend fun checkMute(author: String): Boolean
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertMute(mute: MutedMessageSender): Long
|
||||
|
||||
@Query("DELETE FROM MutedMessageSenders WHERE author = :author")
|
||||
suspend fun deleteMute(author: String)
|
||||
}
|
@ -15,5 +15,5 @@ interface TimetableDao : BaseDao<Timetable> {
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
|
||||
|
||||
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
|
||||
suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
|
||||
}
|
||||
|
@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
import java.io.Serializable
|
||||
|
||||
data class MessageWithAttachment(
|
||||
@Embedded
|
||||
val message: Message,
|
||||
|
||||
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
|
||||
val attachments: List<MessageAttachment>
|
||||
)
|
||||
val attachments: List<MessageAttachment>,
|
||||
|
||||
@Relation(parentColumn = "correspondents", entityColumn = "author")
|
||||
val mutedMessageSender: MutedMessageSender?,
|
||||
) : Serializable
|
||||
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
|
||||
data class MessageWithMutedAuthor(
|
||||
@Embedded
|
||||
val message: Message,
|
||||
|
||||
@Relation(parentColumn = "correspondents", entityColumn = "author")
|
||||
val mutedMessageSender: MutedMessageSender?,
|
||||
)
|
@ -0,0 +1,15 @@
|
||||
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 = "MutedMessageSenders")
|
||||
data class MutedMessageSender(
|
||||
@ColumnInfo(name = "author")
|
||||
val author: String,
|
||||
) : Serializable {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -16,7 +16,9 @@ data class SchoolAnnouncement(
|
||||
|
||||
val subject: String,
|
||||
|
||||
val content: String
|
||||
val content: String,
|
||||
|
||||
val author: String? = null,
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
|
@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
|
||||
enum class MessageFolder(val id: Int = 1) {
|
||||
RECEIVED(1),
|
||||
SENT(2),
|
||||
TRASHED(3)
|
||||
TRASHED(3),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun byId(id: Int) = entries.first { it.id == id }
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,26 @@ package io.github.wulkanowy.data.mappers
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
|
||||
import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement
|
||||
|
||||
@JvmName("mapDirectorInformationToEntities")
|
||||
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
|
||||
SchoolAnnouncement(
|
||||
userLoginId = student.userLoginId,
|
||||
date = it.date,
|
||||
subject = it.subject,
|
||||
content = it.content,
|
||||
author = null,
|
||||
)
|
||||
}
|
||||
|
||||
@JvmName("mapLastAnnouncementsToEntities")
|
||||
fun List<SdkLastAnnouncement>.mapToEntities(student: Student) = map {
|
||||
SchoolAnnouncement(
|
||||
userLoginId = student.userLoginId,
|
||||
date = it.date,
|
||||
subject = it.subject,
|
||||
content = it.content,
|
||||
author = it.author,
|
||||
)
|
||||
}
|
||||
|
@ -16,10 +16,8 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
@ -58,23 +56,22 @@ class AttendanceRepository @Inject constructor(
|
||||
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||
},
|
||||
fetch = {
|
||||
val lessons = withContext(Dispatchers.IO) {
|
||||
timetableDb.load(
|
||||
val lessons = timetableDb.load(
|
||||
semester.diaryId, semester.studentId, start.monday, end.sunday
|
||||
)
|
||||
}
|
||||
sdk.init(student)
|
||||
.switchSemester(semester)
|
||||
.getAttendance(start.monday, end.sunday)
|
||||
.mapToEntities(semester, lessons)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
||||
newAttendance.apply { if (notify) isNotified = false }
|
||||
}
|
||||
attendanceDb.insertAll(attendanceToAdd)
|
||||
|
||||
attendanceDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = attendanceToAdd,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
@ -20,6 +22,7 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
private val attendanceDb: AttendanceSummaryDao,
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val appDatabase: AppDatabase,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -46,8 +49,10 @@ class AttendanceSummaryRepository @Inject constructor(
|
||||
.mapToEntities(semester, subjectId)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
appDatabase.withTransaction {
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
attendanceDb.insertAll(new uniqueSubtract old)
|
||||
}
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -6,7 +6,13 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
@ -53,8 +59,10 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
completedLessonsDb.deleteAll(old uniqueSubtract new)
|
||||
completedLessonsDb.insertAll(new uniqueSubtract old)
|
||||
completedLessonsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
|
@ -53,12 +53,12 @@ class ConferenceRepository @Inject constructor(
|
||||
.filter { it.date >= startDate }
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val conferencesToSave = (new uniqueSubtract old).onEach {
|
||||
conferenceDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
conferenceDb.deleteAll(old uniqueSubtract new)
|
||||
conferenceDb.insertAll(conferencesToSave)
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -62,12 +62,12 @@ class ExamRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val examsToSave = (new uniqueSubtract old).onEach {
|
||||
examDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
examDb.deleteAll(old uniqueSubtract new)
|
||||
examDb.insertAll(examsToSave)
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
|
@ -87,10 +87,12 @@ class GradeRepository @Inject constructor(
|
||||
new: List<GradeDescriptive>,
|
||||
notify: Boolean
|
||||
) {
|
||||
gradeDescriptiveDb.deleteAll(old uniqueSubtract new)
|
||||
gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach {
|
||||
gradeDescriptiveDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshGradeDetails(
|
||||
@ -101,13 +103,16 @@ class GradeRepository @Inject constructor(
|
||||
) {
|
||||
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
|
||||
?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
|
||||
gradeDb.removeOldAndSaveNew(
|
||||
oldItems = oldGrades uniqueSubtract newDetails,
|
||||
newItems = (newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
isRead = false
|
||||
if (notify) isNotified = false
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshGradeSummaries(
|
||||
@ -115,9 +120,23 @@ class GradeRepository @Inject constructor(
|
||||
newSummary: List<GradeSummary>,
|
||||
notify: Boolean
|
||||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||
gradeSummaryDb.removeOldAndSaveNew(
|
||||
oldItems = oldSummaries uniqueSubtract newSummary,
|
||||
newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
getGradeSummaryWithUpdatedNotificationState(
|
||||
summary = summary,
|
||||
oldSummary = oldSummaries.find { it.subject == summary.subject },
|
||||
notify = notify,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getGradeSummaryWithUpdatedNotificationState(
|
||||
summary: GradeSummary,
|
||||
oldSummary: GradeSummary?,
|
||||
notify: Boolean,
|
||||
) {
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
@ -128,7 +147,6 @@ class GradeRepository @Inject constructor(
|
||||
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
summary.predictedGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
||||
@ -139,7 +157,6 @@ class GradeRepository @Inject constructor(
|
||||
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
|
||||
else -> oldSummary.finalGradeLastChange
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
|
||||
|
@ -19,7 +19,7 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -62,8 +62,10 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
gradePartialStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||
gradePartialStatisticsDb.insertAll(new uniqueSubtract old)
|
||||
gradePartialStatisticsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
|
||||
},
|
||||
mapResult = { items ->
|
||||
@ -80,6 +82,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
)
|
||||
listOf(summaryItem) + items
|
||||
}
|
||||
|
||||
else -> items.filter { it.subject == subjectName }
|
||||
}.mapPartialToStatisticItems()
|
||||
}
|
||||
@ -107,8 +110,10 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||
gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old)
|
||||
gradeSemesterStatisticsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
|
||||
},
|
||||
mapResult = { items ->
|
||||
@ -138,6 +143,7 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
}
|
||||
listOf(summaryItem) + itemsWithAverage
|
||||
}
|
||||
|
||||
else -> itemsWithAverage.filter { it.subject == subjectName }
|
||||
}.mapSemesterToStatisticItems()
|
||||
}
|
||||
@ -163,8 +169,10 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
gradePointsStatisticsDb.deleteAll(old uniqueSubtract new)
|
||||
gradePointsStatisticsDb.insertAll(new uniqueSubtract old)
|
||||
gradePointsStatisticsDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
|
||||
},
|
||||
mapResult = { items ->
|
||||
|
@ -61,14 +61,14 @@ class HomeworkRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
val filteredOld = old.filterNot { it.isAddedByUser }
|
||||
|
||||
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
||||
homeworkDb.insertAll(homeWorkToSave)
|
||||
|
||||
homeworkDb.removeOldAndSaveNew(
|
||||
oldItems = filteredOld uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||
}
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class LuckyNumberRepository @Inject constructor(
|
||||
private val luckyNumberDb: LuckyNumberDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -39,11 +39,10 @@ class LuckyNumberRepository @Inject constructor(
|
||||
newLuckyNumber ?: return@networkBoundResource
|
||||
|
||||
if (newLuckyNumber != oldLuckyNumber) {
|
||||
val updatedLuckNumberList =
|
||||
listOf(newLuckyNumber.apply { if (notify) isNotified = false })
|
||||
|
||||
oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
luckyNumberDb.insertAll(updatedLuckNumberList)
|
||||
luckyNumberDb.removeOldAndSaveNew(
|
||||
oldItems = listOfNotNull(oldLuckyNumber),
|
||||
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -8,13 +8,17 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
|
||||
import io.github.wulkanowy.data.db.entities.MutedMessageSender
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.SENT
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
@ -22,6 +26,7 @@ import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.onResourceError
|
||||
import io.github.wulkanowy.data.onResourceSuccess
|
||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
@ -31,7 +36,6 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@ -42,6 +46,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class MessageRepository @Inject constructor(
|
||||
private val messagesDb: MessagesDao,
|
||||
private val mutedMessageSendersDao: MutedMessageSendersDao,
|
||||
private val messageAttachmentDao: MessageAttachmentDao,
|
||||
private val sdk: Sdk,
|
||||
@ApplicationContext private val context: Context,
|
||||
@ -51,7 +56,6 @@ class MessageRepository @Inject constructor(
|
||||
private val mailboxDao: MailboxDao,
|
||||
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
private val messagesCacheKey = "message"
|
||||
@ -63,7 +67,7 @@ class MessageRepository @Inject constructor(
|
||||
folder: MessageFolder,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||
): Flow<Resource<List<MessageWithMutedAuthor>>> = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { it.isEmpty() },
|
||||
shouldFetch = {
|
||||
@ -74,8 +78,8 @@ class MessageRepository @Inject constructor(
|
||||
},
|
||||
query = {
|
||||
if (mailbox == null) {
|
||||
messagesDb.loadAll(folder.id, student.email)
|
||||
} else messagesDb.loadAll(mailbox.globalKey, folder.id)
|
||||
messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email)
|
||||
} else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student).getMessages(
|
||||
@ -83,12 +87,15 @@ class MessageRepository @Inject constructor(
|
||||
mailboxKey = mailbox?.globalKey,
|
||||
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
messagesDb.deleteAll(old uniqueSubtract new)
|
||||
messagesDb.insertAll((new uniqueSubtract old).onEach {
|
||||
it.isNotified = !notify
|
||||
})
|
||||
|
||||
saveFetchResult = { oldWithAuthors, new ->
|
||||
val old = oldWithAuthors.map { it.message }
|
||||
messagesDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
val muted = isMuted(it.correspondents)
|
||||
it.isNotified = !notify || muted
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(
|
||||
getRefreshKey(messagesCacheKey, mailbox, folder)
|
||||
)
|
||||
@ -106,9 +113,7 @@ class MessageRepository @Inject constructor(
|
||||
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
|
||||
(it.message.unread && markAsRead) || it.message.content.isBlank()
|
||||
},
|
||||
query = {
|
||||
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
|
||||
},
|
||||
query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
|
||||
fetch = {
|
||||
sdk.init(student).getMessageDetails(
|
||||
messageKey = it!!.message.messageGlobalKey,
|
||||
@ -152,17 +157,30 @@ class MessageRepository @Inject constructor(
|
||||
subject: String,
|
||||
content: String,
|
||||
recipients: List<Recipient>,
|
||||
mailboxId: String,
|
||||
mailbox: Mailbox,
|
||||
) {
|
||||
sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
recipients = recipients.mapFromEntities(),
|
||||
mailboxId = mailboxId,
|
||||
mailboxId = mailbox.globalKey,
|
||||
)
|
||||
refreshFolders(student, mailbox, listOf(SENT))
|
||||
}
|
||||
|
||||
suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||
sdk.init(student).restoreMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
)
|
||||
|
||||
refreshFolders(student, mailbox)
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, message: Message) {
|
||||
deleteMessages(student, listOf(message))
|
||||
}
|
||||
|
||||
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
||||
val firstMessage = messages.first()
|
||||
sdk.init(student).deleteMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
@ -181,18 +199,24 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
messagesDb.updateAll(deletedMessages)
|
||||
} else messagesDb.deleteAll(messages)
|
||||
} else {
|
||||
messagesDb.deleteAll(messages)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshFolders(
|
||||
student: Student,
|
||||
mailbox: Mailbox?,
|
||||
folders: List<MessageFolder> = MessageFolder.entries
|
||||
) {
|
||||
folders.forEach {
|
||||
getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = TRASHED,
|
||||
folder = it,
|
||||
forceRefresh = true,
|
||||
).first()
|
||||
).toFirstResult()
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
|
||||
deleteMessages(student, mailbox, listOf(message))
|
||||
}
|
||||
|
||||
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||
@ -236,4 +260,18 @@ class MessageRepository @Inject constructor(
|
||||
context.getString(R.string.pref_key_message_draft),
|
||||
value?.let { json.encodeToString(it) }
|
||||
)
|
||||
|
||||
private suspend fun isMuted(author: String): Boolean {
|
||||
return mutedMessageSendersDao.checkMute(author)
|
||||
}
|
||||
|
||||
suspend fun muteMessage(author: String) {
|
||||
if (isMuted(author)) return
|
||||
mutedMessageSendersDao.insertMute(MutedMessageSender(author))
|
||||
}
|
||||
|
||||
suspend fun unmuteMessage(author: String) {
|
||||
if (!isMuted(author)) return
|
||||
mutedMessageSendersDao.deleteMute(author)
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,10 @@ class MobileDeviceRepository @Inject constructor(
|
||||
.mapToEntities(student)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
mobileDb.deleteAll(old uniqueSubtract new)
|
||||
mobileDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
mobileDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
@ -7,7 +7,12 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.toLocalDate
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
@ -46,14 +51,16 @@ class NoteRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
noteDb.deleteAll(old uniqueSubtract new)
|
||||
noteDb.insertAll((new uniqueSubtract old).onEach {
|
||||
val notesToAdd = (new uniqueSubtract old).onEach {
|
||||
if (it.date >= student.registrationDate.toLocalDate()) it.apply {
|
||||
isRead = false
|
||||
if (notify) isNotified = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
noteDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = notesToAdd,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -1,7 +1,11 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.entities.*
|
||||
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.Recipient
|
||||
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
|
||||
@ -25,8 +29,10 @@ class RecipientRepository @Inject constructor(
|
||||
.mapToEntities(mailbox.globalKey)
|
||||
val old = recipientDb.loadAll(type, mailbox.globalKey)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
recipientDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
|
@ -41,17 +41,18 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
schoolAnnouncementDb.loadAll(student.userLoginId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
.getDirectorInformation()
|
||||
.mapToEntities(student)
|
||||
val sdk = sdk.init(student)
|
||||
val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student)
|
||||
val directorInformation = sdk.getDirectorInformation().mapToEntities(student)
|
||||
lastAnnouncements + directorInformation
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach {
|
||||
schoolAnnouncementDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
|
||||
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
@ -47,10 +47,10 @@ class SchoolRepository @Inject constructor(
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(schoolDb) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
schoolDb.removeOldAndSaveNew(
|
||||
oldItems = listOf(old),
|
||||
newItems = listOf(new)
|
||||
)
|
||||
} else if (old == null) {
|
||||
schoolDb.insertAll(listOf(new))
|
||||
}
|
||||
|
@ -5,7 +5,11 @@ import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.isCurrent
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -15,7 +19,7 @@ import javax.inject.Singleton
|
||||
class SemesterRepository @Inject constructor(
|
||||
private val semesterDb: SemesterDao,
|
||||
private val sdk: Sdk,
|
||||
private val dispatchers: DispatchersProvider
|
||||
private val dispatchers: DispatchersProvider,
|
||||
) {
|
||||
|
||||
suspend fun getSemesters(
|
||||
@ -45,6 +49,7 @@ class SemesterRepository @Inject constructor(
|
||||
0 == it.diaryId && 0 == it.kindergartenDiaryId
|
||||
} == true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
@ -59,8 +64,10 @@ class SemesterRepository @Inject constructor(
|
||||
if (new.isEmpty()) return Timber.i("Empty semester list!")
|
||||
|
||||
val old = semesterDb.loadAll(student.studentId, student.classId)
|
||||
semesterDb.deleteAll(old.uniqueSubtract(new))
|
||||
semesterDb.insertSemesters(new.uniqueSubtract(old))
|
||||
semesterDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||
|
@ -15,7 +15,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class StudentInfoRepository @Inject constructor(
|
||||
private val studentInfoDao: StudentInfoDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -36,10 +36,10 @@ class StudentInfoRepository @Inject constructor(
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
studentInfoDao.removeOldAndSaveNew(
|
||||
oldItems = listOf(old),
|
||||
newItems = listOf(new),
|
||||
)
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
|
@ -45,9 +45,10 @@ class SubjectRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
subjectDao.deleteAll(old uniqueSubtract new)
|
||||
subjectDao.insertAll(new uniqueSubtract old)
|
||||
|
||||
subjectDao.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -45,9 +45,10 @@ class TeacherRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
teacherDb.deleteAll(old uniqueSubtract new)
|
||||
teacherDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
teacherDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -3,13 +3,23 @@ package io.github.wulkanowy.data.repositories
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||
import io.github.wulkanowy.data.db.entities.*
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.pojos.TimetableFull
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.switchSemester
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -121,12 +131,12 @@ class TimetableRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun getTimetableFromDatabase(
|
||||
suspend fun getTimetableFromDatabase(
|
||||
semester: Semester,
|
||||
from: LocalDate,
|
||||
start: LocalDate,
|
||||
end: LocalDate
|
||||
): Flow<List<Timetable>> {
|
||||
return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
|
||||
): List<Timetable> {
|
||||
return timetableDb.load(semester.diaryId, semester.studentId, start, end)
|
||||
}
|
||||
|
||||
suspend fun updateTimetable(timetable: List<Timetable>) {
|
||||
@ -144,8 +154,10 @@ class TimetableRepository @Inject constructor(
|
||||
new.apply { if (notify) isNotified = false }
|
||||
}
|
||||
|
||||
timetableDb.deleteAll(lessonsToRemove)
|
||||
timetableDb.insertAll(lessonsToAdd)
|
||||
timetableDb.removeOldAndSaveNew(
|
||||
oldItems = lessonsToRemove,
|
||||
newItems = lessonsToAdd,
|
||||
)
|
||||
|
||||
schedulerHelper.cancelScheduled(lessonsToRemove, student)
|
||||
schedulerHelper.scheduleNotifications(lessonsToAdd, student)
|
||||
@ -156,13 +168,17 @@ class TimetableRepository @Inject constructor(
|
||||
new: List<TimetableAdditional>
|
||||
) {
|
||||
val oldFiltered = old.filter { !it.isAddedByUser }
|
||||
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
|
||||
timetableAdditionalDb.insertAll(new uniqueSubtract old)
|
||||
timetableAdditionalDb.removeOldAndSaveNew(
|
||||
oldItems = oldFiltered uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) {
|
||||
timetableHeaderDb.deleteAll(old uniqueSubtract new)
|
||||
timetableHeaderDb.insertAll(new uniqueSubtract old)
|
||||
timetableHeaderDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = new uniqueSubtract old,
|
||||
)
|
||||
}
|
||||
|
||||
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {
|
||||
|
@ -1,10 +1,7 @@
|
||||
package io.github.wulkanowy.domain.timetable
|
||||
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import java.time.LocalDate
|
||||
@ -16,18 +13,14 @@ class IsStudentHasLessonsOnWeekendUseCase @Inject constructor(
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
currentDate: LocalDate = LocalDate.now(),
|
||||
): Boolean {
|
||||
val lessons = timetableRepository.getTimetable(
|
||||
student = student,
|
||||
val lessons = timetableRepository.getTimetableFromDatabase(
|
||||
semester = semester,
|
||||
start = currentDate.monday,
|
||||
end = currentDate.sunday,
|
||||
forceRefresh = false,
|
||||
timetableType = TimetableRepository.TimetableType.NORMAL
|
||||
).toFirstResult().dataOrNull?.lessons.orEmpty()
|
||||
)
|
||||
return isWeekendHasLessonsUseCase(lessons)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -31,10 +30,9 @@ class TimetableWork @Inject constructor(
|
||||
|
||||
timetableRepository.getTimetableFromDatabase(
|
||||
semester = semester,
|
||||
from = startDate,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
)
|
||||
.first()
|
||||
.filterNot { it.isNotified }
|
||||
.let {
|
||||
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
|
||||
|
@ -17,6 +17,8 @@ import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import timber.log.Timber
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
@ -36,6 +38,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
|
||||
abstract var presenter: T
|
||||
|
||||
private var lastDialogOpenTime = mutableMapOf<String, Instant>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
inject()
|
||||
themeManager.applyActivityTheme(this)
|
||||
@ -70,6 +74,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun showExpiredCredentialsDialog() {
|
||||
if (!shouldShowDialog(DIALOG_ERROR_BAD_CREDENTIALS)) return
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.main_expired_credentials_title)
|
||||
.setMessage(R.string.main_expired_credentials_description)
|
||||
@ -83,6 +89,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun showDecryptionFailedDialog() {
|
||||
if (!shouldShowDialog(DIALOG_ERROR_DECRYPTION_FAILED)) return
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.main_session_expired)
|
||||
.setMessage(R.string.main_session_relogin)
|
||||
@ -119,4 +127,21 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
protected open fun inject() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun shouldShowDialog(name: String): Boolean {
|
||||
val lastOpenTime = lastDialogOpenTime[name]
|
||||
val now = Instant.now()
|
||||
|
||||
if (lastOpenTime != null && now.isBefore(lastOpenTime.plusSeconds(1))) {
|
||||
Timber.i("Dialog $name was shown less than a second ago. Skip")
|
||||
return false
|
||||
}
|
||||
lastDialogOpenTime[name] = Instant.now()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DIALOG_ERROR_BAD_CREDENTIALS = "dialog_error_bad_credentials"
|
||||
private const val DIALOG_ERROR_DECRYPTION_FAILED = "dialog_error_decryption_failed"
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||
}
|
||||
|
||||
protected open fun proceed(error: Throwable) {
|
||||
showErrorMessage(context.resources.getErrorString(error), error)
|
||||
showDefaultMessage(error)
|
||||
when (error) {
|
||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||
is ScramblerException -> onDecryptionFailed()
|
||||
@ -45,6 +45,10 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
||||
}
|
||||
}
|
||||
|
||||
fun showDefaultMessage(error: Throwable) {
|
||||
showErrorMessage(context.resources.getErrorString(error), error)
|
||||
}
|
||||
|
||||
open fun clear() {
|
||||
showErrorMessage = { _, _ -> }
|
||||
onExpiredCredentials = {}
|
||||
|
@ -4,18 +4,14 @@ import android.annotation.SuppressLint
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||
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.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.*
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.time.DayOfWeek
|
||||
@ -210,7 +206,7 @@ class AttendancePresenter @Inject constructor(
|
||||
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
checkInitialAndCurrentDate(student, semester)
|
||||
checkInitialAndCurrentDate(semester)
|
||||
attendanceRepository.getAttendance(
|
||||
student = student,
|
||||
semester = semester,
|
||||
@ -266,15 +262,13 @@ class AttendancePresenter @Inject constructor(
|
||||
.launch()
|
||||
}
|
||||
|
||||
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
|
||||
private suspend fun checkInitialAndCurrentDate(semester: Semester) {
|
||||
if (initialDate == null) {
|
||||
val lessons = attendanceRepository.getAttendance(
|
||||
student = student,
|
||||
val lessons = attendanceRepository.getAttendanceFromDatabase(
|
||||
semester = semester,
|
||||
start = now().monday,
|
||||
end = now().sunday,
|
||||
forceRefresh = false,
|
||||
).toFirstResult().dataOrNull.orEmpty()
|
||||
).firstOrNull().orEmpty()
|
||||
isWeekendHasLessons = isWeekendHasLessons(lessons)
|
||||
initialDate = getInitialDate(semester)
|
||||
}
|
||||
@ -316,6 +310,7 @@ class AttendancePresenter @Inject constructor(
|
||||
showContent(false)
|
||||
showExcuseButton(false)
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Excusing for absence result: Success")
|
||||
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
|
||||
@ -328,6 +323,7 @@ class AttendancePresenter @Inject constructor(
|
||||
}
|
||||
loadData(forceRefresh = true)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Excusing for absence result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
|
@ -62,7 +62,11 @@ class AuthPresenter @Inject constructor(
|
||||
}
|
||||
isSuccess
|
||||
}
|
||||
.onFailure { errorHandler.dispatch(it) }
|
||||
.onFailure {
|
||||
errorHandler.dispatch(it)
|
||||
view?.showProgress(false)
|
||||
view?.showContent(true)
|
||||
}
|
||||
.onSuccess {
|
||||
if (it) {
|
||||
view?.showSuccess(true)
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.DialogCaptchaBinding
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -22,6 +23,9 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||
@Inject
|
||||
lateinit var sdk: Sdk
|
||||
|
||||
@Inject
|
||||
lateinit var webkitCookieManagerProxy: WebkitCookieManagerProxy
|
||||
|
||||
private var webView: WebView? = null
|
||||
|
||||
companion object {
|
||||
@ -80,6 +84,7 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
webkitCookieManagerProxy.webkitCookieManager?.flush()
|
||||
webView?.destroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
@ -304,6 +304,7 @@ class DashboardPresenter @Inject constructor(
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
.mapResourceData { it.map { messageWithAuthor -> messageWithAuthor.message } }
|
||||
.onResourceError { errorHandler.dispatch(it) }
|
||||
.takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
|
||||
|
||||
@ -438,7 +439,7 @@ class DashboardPresenter @Inject constructor(
|
||||
private fun loadLessons(student: Student, forceRefresh: Boolean) {
|
||||
flatResourceFlow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val date = when (isStudentHasLessonsOnWeekendUseCase(student, semester)) {
|
||||
val date = when (isStudentHasLessonsOnWeekendUseCase(semester)) {
|
||||
true -> LocalDate.now()
|
||||
else -> LocalDate.now().nextOrSameSchoolDay
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ class GradeAverageProvider @Inject constructor(
|
||||
?.updateModifiers(student, config).orEmpty()
|
||||
|
||||
(updatedSecondSemesterGrades + updatedFirstSemesterGrades).calcAverage(
|
||||
config.isOptionalArithmeticAverage
|
||||
isOptionalArithmeticAverage = config.isOptionalArithmeticAverage,
|
||||
)
|
||||
} else {
|
||||
secondSemesterSubject.average
|
||||
@ -173,13 +173,21 @@ class GradeAverageProvider @Inject constructor(
|
||||
config: AverageCalcParams,
|
||||
): Double {
|
||||
return if (!isAnyVulcanAverage || config.forceAverageCalc) {
|
||||
val divider = if (secondSemesterSubject.grades.any { it.weightValue > .0 }) 2 else 1
|
||||
val isSecondSemesterHasWeightGrade = secondSemesterSubject.grades
|
||||
.any { it.weightValue > .0 }
|
||||
val isSecondSemesterHasArithmeticGrade = secondSemesterSubject.grades
|
||||
.all { it.weightValue == .0 } && config.isOptionalArithmeticAverage
|
||||
val isSecondSemesterHaveAverage =
|
||||
isSecondSemesterHasWeightGrade || isSecondSemesterHasArithmeticGrade
|
||||
|
||||
val divider = if (isSecondSemesterHaveAverage) 2 else 1
|
||||
val secondSemesterAverage = secondSemesterSubject.grades
|
||||
.updateModifiers(student, config)
|
||||
.calcAverage(config.isOptionalArithmeticAverage)
|
||||
.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage)
|
||||
val firstSemesterAverage = firstSemesterSubject?.grades
|
||||
?.updateModifiers(student, config)
|
||||
?.calcAverage(config.isOptionalArithmeticAverage) ?: secondSemesterAverage
|
||||
?.calcAverage(isOptionalArithmeticAverage = config.isOptionalArithmeticAverage)
|
||||
?: secondSemesterAverage
|
||||
|
||||
(secondSemesterAverage + firstSemesterAverage) / divider
|
||||
} else {
|
||||
@ -225,7 +233,7 @@ class GradeAverageProvider @Inject constructor(
|
||||
subject = summary.subject,
|
||||
average = if (!isAnyAverage || params.forceAverageCalc) {
|
||||
grades.updateModifiers(student, params)
|
||||
.calcAverage(params.isOptionalArithmeticAverage)
|
||||
.calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
|
||||
} else summary.average,
|
||||
points = summary.pointsSum,
|
||||
summary = summary,
|
||||
@ -286,8 +294,13 @@ class GradeAverageProvider @Inject constructor(
|
||||
proposedPoints = "",
|
||||
finalPoints = "",
|
||||
pointsSum = "",
|
||||
average = if (calcAverage) details.updateModifiers(student, params)
|
||||
.calcAverage(params.isOptionalArithmeticAverage) else .0
|
||||
average = when {
|
||||
calcAverage -> details
|
||||
.updateModifiers(student, params)
|
||||
.calcAverage(isOptionalArithmeticAverage = params.isOptionalArithmeticAverage)
|
||||
|
||||
else -> .0
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
||||
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
@ -204,6 +205,9 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
.onResourceError {
|
||||
loginErrorHandler.dispatch(it)
|
||||
if (it is InvalidSymbolException) {
|
||||
loginErrorHandler.showDefaultMessage(it)
|
||||
}
|
||||
lastError = it
|
||||
view?.showContact(true)
|
||||
analytics.logEvent(
|
||||
|
@ -50,12 +50,15 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
ViewType.MESSAGE.id -> MessageViewHolder(
|
||||
ItemMessagePreviewBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
|
||||
ViewType.DIVIDER.id -> DividerViewHolder(
|
||||
ItemMessageDividerBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
|
||||
ViewType.ATTACHMENT.id -> AttachmentViewHolder(
|
||||
ItemMessageAttachmentBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
@ -66,6 +69,7 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
holder,
|
||||
requireNotNull(messageWithAttachment).message
|
||||
)
|
||||
|
||||
is AttachmentViewHolder -> bindAttachment(
|
||||
holder,
|
||||
requireNotNull(messageWithAttachment).attachments[position - 2]
|
||||
@ -82,9 +86,11 @@ class MessagePreviewAdapter @Inject constructor() :
|
||||
recipientCount > 1 -> {
|
||||
context.getString(R.string.message_read_by, message.readBy, recipientCount)
|
||||
}
|
||||
|
||||
message.readBy == 1 || (isReceived && !message.unread) -> {
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -44,18 +44,33 @@ class MessagePreviewFragment :
|
||||
|
||||
private var menuForwardButton: MenuItem? = null
|
||||
|
||||
private var menuRestoreButton: MenuItem? = null
|
||||
|
||||
private var menuDeleteButton: MenuItem? = null
|
||||
|
||||
private var menuDeleteForeverButton: MenuItem? = null
|
||||
|
||||
private var menuShareButton: MenuItem? = null
|
||||
|
||||
private var menuPrintButton: MenuItem? = null
|
||||
|
||||
private var menuMuteButton: MenuItem? = null
|
||||
|
||||
override val titleStringId: Int
|
||||
get() = R.string.message_title
|
||||
|
||||
override val deleteMessageSuccessString: String
|
||||
get() = getString(R.string.message_delete_success)
|
||||
|
||||
override val muteMessageSuccessString: String
|
||||
get() = getString(R.string.message_mute_success)
|
||||
|
||||
override val unmuteMessageSuccessString: String
|
||||
get() = getString(R.string.message_unmute_success)
|
||||
|
||||
override val restoreMessageSuccessString: String
|
||||
get() = getString(R.string.message_restore_success)
|
||||
|
||||
override val messageNoSubjectString: String
|
||||
get() = getString(R.string.message_no_subject)
|
||||
|
||||
@ -103,9 +118,12 @@ class MessagePreviewFragment :
|
||||
inflater.inflate(R.menu.action_menu_message_preview, menu)
|
||||
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
|
||||
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
|
||||
menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore)
|
||||
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
|
||||
menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever)
|
||||
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
||||
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
||||
menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
|
||||
presenter.onCreateOptionsMenu()
|
||||
|
||||
menu.findItem(R.id.mainMenuAccount).isVisible = false
|
||||
@ -115,9 +133,12 @@ class MessagePreviewFragment :
|
||||
return when (item.itemId) {
|
||||
R.id.messagePreviewMenuReply -> presenter.onReply()
|
||||
R.id.messagePreviewMenuForward -> presenter.onForward()
|
||||
R.id.messagePreviewMenuRestore -> presenter.onMessageRestore()
|
||||
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
|
||||
R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete()
|
||||
R.id.messagePreviewMenuShare -> presenter.onShare()
|
||||
R.id.messagePreviewMenuPrint -> presenter.onPrint()
|
||||
R.id.messagePreviewMenuMute -> presenter.onMute()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -129,6 +150,11 @@ class MessagePreviewFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateMuteToggleButton(isMuted: Boolean) {
|
||||
menuMuteButton?.setTitle(if (isMuted) R.string.message_unmute else R.string.message_mute)
|
||||
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.messagePreviewProgress.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
@ -137,20 +163,15 @@ class MessagePreviewFragment :
|
||||
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showOptions(show: Boolean, isReplayable: Boolean) {
|
||||
menuReplyButton?.isVisible = isReplayable
|
||||
override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) {
|
||||
menuReplyButton?.isVisible = show && isReplayable
|
||||
menuForwardButton?.isVisible = show
|
||||
menuDeleteButton?.isVisible = show
|
||||
menuRestoreButton?.isVisible = show && isRestorable
|
||||
menuDeleteButton?.isVisible = show && !isRestorable
|
||||
menuDeleteForeverButton?.isVisible = show && isRestorable
|
||||
menuShareButton?.isVisible = show
|
||||
menuPrintButton?.isVisible = show
|
||||
}
|
||||
|
||||
override fun setDeletedOptionsLabels() {
|
||||
menuDeleteButton?.setTitle(R.string.message_delete_forever)
|
||||
}
|
||||
|
||||
override fun setNotDeletedOptionsLabels() {
|
||||
menuDeleteButton?.setTitle(R.string.message_move_to_trash)
|
||||
menuMuteButton?.isVisible = show && isReplayable
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
@ -213,7 +234,7 @@ class MessagePreviewFragment :
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putSerializable(MESSAGE_ID_KEY, presenter.message)
|
||||
outState.putSerializable(MESSAGE_ID_KEY, presenter.messageWithAttachments)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import androidx.core.text.parseAsHtml
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class MessagePreviewPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
@ -26,9 +28,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
||||
|
||||
var message: Message? = null
|
||||
|
||||
var attachments: List<MessageAttachment>? = null
|
||||
var messageWithAttachments: MessageWithAttachment? = null
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
@ -38,7 +38,6 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
this.message = message
|
||||
loadData(requireNotNull(message))
|
||||
}
|
||||
|
||||
@ -66,25 +65,24 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
.logResourceStatus("message ${messageToLoad.messageId} preview")
|
||||
.onResourceData {
|
||||
if (it != null) {
|
||||
message = it.message
|
||||
attachments = it.attachments
|
||||
messageWithAttachments = it
|
||||
view?.apply {
|
||||
setMessageWithAttachment(it)
|
||||
showContent(true)
|
||||
initOptions()
|
||||
|
||||
updateMuteToggleButton(isMuted = it.mutedMessageSender != null)
|
||||
if (preferencesRepository.isIncognitoMode && it.message.unread) {
|
||||
showMessage(R.string.message_incognito_description)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delay(1.seconds)
|
||||
view?.run {
|
||||
showMessage(messageNotExists)
|
||||
popView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onResourceSuccess {
|
||||
}.onResourceSuccess {
|
||||
if (it != null) {
|
||||
analytics.logEvent(
|
||||
"load_item",
|
||||
@ -92,31 +90,28 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
"length" to it.message.content.length
|
||||
)
|
||||
}
|
||||
}
|
||||
.onResourceNotLoading { view?.showProgress(false) }
|
||||
.onResourceError {
|
||||
}.onResourceNotLoading { view?.showProgress(false) }.onResourceError {
|
||||
retryCallback = { onMessageLoadRetry(messageToLoad) }
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
.launch()
|
||||
}.launch()
|
||||
}
|
||||
|
||||
fun onReply(): Boolean {
|
||||
return if (message != null) {
|
||||
view?.openMessageReply(message)
|
||||
return if (messageWithAttachments?.message != null) {
|
||||
view?.openMessageReply(messageWithAttachments?.message)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
fun onForward(): Boolean {
|
||||
return if (message != null) {
|
||||
view?.openMessageForward(message)
|
||||
return if (messageWithAttachments?.message != null) {
|
||||
view?.openMessageForward(messageWithAttachments?.message)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
fun onShare(): Boolean {
|
||||
val message = message ?: return false
|
||||
val message = messageWithAttachments?.message ?: return false
|
||||
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
||||
|
||||
val text = buildString {
|
||||
@ -129,11 +124,13 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
|
||||
appendLine(message.content.parseAsHtml())
|
||||
|
||||
if (!attachments.isNullOrEmpty()) {
|
||||
if (!messageWithAttachments?.attachments.isNullOrEmpty()) {
|
||||
appendLine()
|
||||
appendLine("Załączniki:")
|
||||
|
||||
append(attachments.orEmpty().joinToString(separator = "\n") { attachment ->
|
||||
append(
|
||||
messageWithAttachments?.attachments.orEmpty()
|
||||
.joinToString(separator = "\n") { attachment ->
|
||||
"${attachment.filename}: ${attachment.url}"
|
||||
})
|
||||
}
|
||||
@ -148,7 +145,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
fun onPrint(): Boolean {
|
||||
val message = message ?: return false
|
||||
val message = messageWithAttachments?.message ?: return false
|
||||
val subject = message.subject.ifBlank { view?.messageNoSubjectString.orEmpty() }
|
||||
|
||||
val dateString = message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")
|
||||
@ -159,8 +156,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
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>")
|
||||
val messageContent = "<p>${message.content}</p>".replace(Regex("[\\n\\r]{2,}"), "</p><p>")
|
||||
.replace(Regex("[\\n\\r]"), "<br>")
|
||||
|
||||
val jobName = buildString {
|
||||
@ -171,9 +167,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
view?.apply {
|
||||
val html = printHTML
|
||||
.replace("%SUBJECT%", subject)
|
||||
.replace("%CONTENT%", messageContent)
|
||||
val html = printHTML.replace("%SUBJECT%", subject).replace("%CONTENT%", messageContent)
|
||||
.replace("%INFO%", infoContent)
|
||||
printDocument(html, jobName)
|
||||
}
|
||||
@ -181,29 +175,64 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun deleteMessage() {
|
||||
message ?: return
|
||||
private fun restoreMessage() {
|
||||
val message = messageWithAttachments?.message ?: return
|
||||
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showOptions(show = false, isReplayable = false)
|
||||
showOptions(
|
||||
show = false,
|
||||
isReplayable = false,
|
||||
isRestorable = false,
|
||||
)
|
||||
showErrorView(false)
|
||||
}
|
||||
|
||||
Timber.i("Delete message ${message?.messageGlobalKey}")
|
||||
|
||||
Timber.i("Restore message ${message.messageGlobalKey}")
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||
messageRepository.deleteMessage(student, mailbox, message!!)
|
||||
messageRepository.restoreMessages(student, mailbox, listOfNotNull(message))
|
||||
}
|
||||
.onFailure {
|
||||
retryCallback = { onMessageDelete() }
|
||||
retryCallback = { onMessageRestore() }
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
.onSuccess {
|
||||
view?.run {
|
||||
showMessage(restoreMessageSuccessString)
|
||||
popView()
|
||||
}
|
||||
}
|
||||
view?.showProgress(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteMessage() {
|
||||
messageWithAttachments?.message ?: return
|
||||
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showOptions(
|
||||
show = false,
|
||||
isReplayable = false,
|
||||
isRestorable = false,
|
||||
)
|
||||
showErrorView(false)
|
||||
}
|
||||
|
||||
Timber.i("Delete message ${messageWithAttachments?.message?.messageGlobalKey}")
|
||||
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||
messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
|
||||
}.onFailure {
|
||||
retryCallback = { onMessageDelete() }
|
||||
errorHandler.dispatch(it)
|
||||
}.onSuccess {
|
||||
view?.run {
|
||||
showMessage(deleteMessageSuccessString)
|
||||
popView()
|
||||
@ -224,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageRestore(): Boolean {
|
||||
restoreMessage()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onMessageDelete(): Boolean {
|
||||
deleteMessage()
|
||||
return true
|
||||
@ -232,20 +266,39 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
private fun initOptions() {
|
||||
view?.apply {
|
||||
showOptions(
|
||||
show = message != null,
|
||||
isReplayable = message?.folderId != MessageFolder.SENT.id,
|
||||
show = messageWithAttachments?.message != null,
|
||||
isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id,
|
||||
isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id,
|
||||
)
|
||||
message?.let {
|
||||
when (it.folderId == MessageFolder.TRASHED.id) {
|
||||
true -> setDeletedOptionsLabels()
|
||||
false -> setNotDeletedOptionsLabels()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun onCreateOptionsMenu() {
|
||||
initOptions()
|
||||
}
|
||||
|
||||
fun onMute(): Boolean {
|
||||
val message = messageWithAttachments?.message ?: return false
|
||||
val isMuted = messageWithAttachments?.mutedMessageSender != null
|
||||
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
when (isMuted) {
|
||||
true -> {
|
||||
messageRepository.unmuteMessage(message.correspondents)
|
||||
view?.run { showMessage(unmuteMessageSuccessString) }
|
||||
}
|
||||
|
||||
false -> {
|
||||
messageRepository.muteMessage(message.correspondents)
|
||||
view?.run { showMessage(muteMessageSuccessString) }
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
}
|
||||
view?.updateMuteToggleButton(isMuted)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,12 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
val deleteMessageSuccessString: String
|
||||
|
||||
val muteMessageSuccessString: String
|
||||
|
||||
val unmuteMessageSuccessString: String
|
||||
|
||||
val restoreMessageSuccessString: String
|
||||
|
||||
val messageNoSubjectString: String
|
||||
|
||||
val printHTML: String
|
||||
@ -19,6 +25,8 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
fun setMessageWithAttachment(item: MessageWithAttachment)
|
||||
|
||||
fun updateMuteToggleButton(isMuted: Boolean)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
@ -29,11 +37,7 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
fun setErrorRetryCallback(callback: () -> Unit)
|
||||
|
||||
fun showOptions(show: Boolean, isReplayable: Boolean)
|
||||
|
||||
fun setDeletedOptionsLabels()
|
||||
|
||||
fun setNotDeletedOptionsLabels()
|
||||
fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean)
|
||||
|
||||
fun openMessageReply(message: Message?)
|
||||
|
||||
|
@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor(
|
||||
subject = subject,
|
||||
content = content,
|
||||
recipients = recipients,
|
||||
mailboxId = mailbox.globalKey,
|
||||
mailbox = mailbox,
|
||||
)
|
||||
}.logResourceStatus("sending message").onEach {
|
||||
when (it) {
|
||||
|
@ -18,8 +18,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessageTabAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
class MessageTabAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
lateinit var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit
|
||||
|
||||
@ -52,10 +51,11 @@ class MessageTabAdapter @Inject constructor() :
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
|
||||
return when (MessageItemViewType.values()[viewType]) {
|
||||
return when (MessageItemViewType.entries[viewType]) {
|
||||
MessageItemViewType.FILTERS -> HeaderViewHolder(
|
||||
ItemMessageChipsBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
|
||||
MessageItemViewType.MESSAGE -> ItemViewHolder(
|
||||
ItemMessageBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
@ -137,7 +137,12 @@ class MessageTabAdapter @Inject constructor() :
|
||||
ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(currentTextColor))
|
||||
isVisible = message.hasAttachments
|
||||
}
|
||||
messageItemUnreadIndicator.isVisible = message.unread
|
||||
messageItemUnreadIndicator.isVisible = message.unread || item.isMuted
|
||||
|
||||
when (item.isMuted) {
|
||||
true -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_notifications_off)
|
||||
else -> messageItemUnreadIndicator.setImageResource(R.drawable.ic_circle_notification)
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
holder.bindingAdapterPosition.let {
|
||||
@ -165,8 +170,7 @@ class MessageTabAdapter @Inject constructor() :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class MessageTabDiffUtil(
|
||||
private val old: List<MessageTabDataItem>,
|
||||
private val new: List<MessageTabDataItem>
|
||||
private val old: List<MessageTabDataItem>, private val new: List<MessageTabDataItem>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = old.size
|
||||
|
@ -6,6 +6,7 @@ sealed class MessageTabDataItem(val viewType: MessageItemViewType) {
|
||||
|
||||
data class MessageItem(
|
||||
val message: Message,
|
||||
val isMuted: Boolean,
|
||||
val isSelected: Boolean,
|
||||
val isActionMode: Boolean
|
||||
) : MessageTabDataItem(MessageItemViewType.MESSAGE)
|
||||
|
@ -5,7 +5,9 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.*
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.CompoundButton
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.view.ActionMode
|
||||
@ -64,10 +66,12 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
if (presenter.folder == MessageFolder.TRASHED) {
|
||||
val menuItem = menu.findItem(R.id.messageTabContextMenuDelete)
|
||||
menuItem.setTitle(R.string.message_delete_forever)
|
||||
}
|
||||
val isTrashFolder = presenter.folder == MessageFolder.TRASHED
|
||||
|
||||
menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder)
|
||||
menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder)
|
||||
menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder)
|
||||
|
||||
return presenter.onPrepareActionMode()
|
||||
}
|
||||
|
||||
@ -79,6 +83,8 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
||||
when (menu.itemId) {
|
||||
R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete()
|
||||
R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore()
|
||||
R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete()
|
||||
R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll()
|
||||
}
|
||||
return true
|
||||
|
@ -4,6 +4,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
@ -39,7 +40,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
private var mailboxes: List<Mailbox> = emptyList()
|
||||
private var selectedMailbox: Mailbox? = null
|
||||
|
||||
private var messages = emptyList<Message>()
|
||||
private var messages = emptyList<MessageWithMutedAuthor>()
|
||||
|
||||
private val searchChannel = Channel<String>()
|
||||
|
||||
@ -120,8 +121,27 @@ class MessageTabPresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
fun onActionModeSelectRestore() {
|
||||
Timber.i("Restore ${messagesToDelete.size} messages")
|
||||
val messageList = messagesToDelete.toList()
|
||||
|
||||
presenterScope.launch {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
showActionMode(false)
|
||||
}
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
messageRepository.restoreMessages(student, selectedMailbox, messageList)
|
||||
}
|
||||
.onFailure(errorHandler::dispatch)
|
||||
.onSuccess { view?.showMessage(R.string.message_messages_restored) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onActionModeSelectDelete() {
|
||||
Timber.i("Delete ${messagesToDelete.size} messages)")
|
||||
Timber.i("Delete ${messagesToDelete.size} messages")
|
||||
val messageList = messagesToDelete.toList()
|
||||
|
||||
presenterScope.launch {
|
||||
@ -133,7 +153,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
messageRepository.deleteMessages(student, selectedMailbox, messageList)
|
||||
messageRepository.deleteMessages(student, messageList)
|
||||
}
|
||||
.onFailure(errorHandler::dispatch)
|
||||
.onSuccess { view?.showMessage(R.string.message_messages_deleted) }
|
||||
@ -141,7 +161,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onActionModeSelectCheckAll() {
|
||||
val messagesToSelect = getFilteredData()
|
||||
val messagesToSelect = getFilteredData().map { it.message }
|
||||
val isAllSelected = messagesToDelete.containsAll(messagesToSelect)
|
||||
|
||||
if (isAllSelected) {
|
||||
@ -188,7 +208,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
view?.showActionMode(false)
|
||||
}
|
||||
|
||||
val filteredData = getFilteredData()
|
||||
val filteredData = getFilteredData().map { it.message }
|
||||
|
||||
view?.run {
|
||||
updateActionModeTitle(messagesToDelete.size)
|
||||
@ -320,25 +340,31 @@ class MessageTabPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFilteredData(): List<Message> {
|
||||
private fun getFilteredData(): List<MessageWithMutedAuthor> {
|
||||
if (lastSearchQuery.trim().isEmpty()) {
|
||||
val sortedMessages = messages.sortedByDescending { it.date }
|
||||
val sortedMessages = messages.sortedByDescending { it.message.date }
|
||||
return when {
|
||||
(onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
|
||||
(onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
|
||||
onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
|
||||
(onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter {
|
||||
it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments
|
||||
}
|
||||
|
||||
(onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread }
|
||||
onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments }
|
||||
else -> sortedMessages
|
||||
}
|
||||
} else {
|
||||
val sortedMessages = messages
|
||||
.map { it to calculateMatchRatio(it, lastSearchQuery) }
|
||||
.sortedWith(compareBy<Pair<Message, Int>> { -it.second }.thenByDescending { it.first.date })
|
||||
.map { it to calculateMatchRatio(it.message, lastSearchQuery) }
|
||||
.sortedWith(compareBy<Pair<MessageWithMutedAuthor, Int>> { -it.second }.thenByDescending { it.first.message.date })
|
||||
.filter { it.second > 6000 }
|
||||
.map { it.first }
|
||||
return when {
|
||||
(onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
|
||||
(onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
|
||||
onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
|
||||
(onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter {
|
||||
it.message.unread == onlyUnread && it.message.hasAttachments == onlyWithAttachments
|
||||
}
|
||||
|
||||
(onlyUnread == true) -> sortedMessages.filter { it.message.unread == onlyUnread }
|
||||
onlyWithAttachments -> sortedMessages.filter { it.message.hasAttachments == onlyWithAttachments }
|
||||
else -> sortedMessages
|
||||
}
|
||||
}
|
||||
@ -367,8 +393,9 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
addAll(data.map { message ->
|
||||
MessageTabDataItem.MessageItem(
|
||||
message = message,
|
||||
isSelected = messagesToDelete.any { it.messageGlobalKey == message.messageGlobalKey },
|
||||
message = message.message,
|
||||
isMuted = message.mutedMessageSender != null,
|
||||
isSelected = messagesToDelete.any { it.messageGlobalKey == message.message.messageGlobalKey },
|
||||
isActionMode = isActionMode
|
||||
)
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.schoolannouncement
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding
|
||||
@ -29,6 +30,10 @@ class SchoolAnnouncementAdapter @Inject constructor() :
|
||||
schoolAnnouncementItemDate.text = item.date.toFormattedString()
|
||||
schoolAnnouncementItemType.text = item.subject
|
||||
schoolAnnouncementItemContent.text = item.content.parseUonetHtml()
|
||||
with(schoolAnnouncementItemAuthor) {
|
||||
text = item.author
|
||||
isVisible = !item.author.isNullOrBlank()
|
||||
}
|
||||
|
||||
root.setOnClickListener { onItemClickListener(item) }
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.timetable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
|
||||
@ -150,7 +149,7 @@ class TimetablePresenter @Inject constructor(
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
checkInitialAndCurrentDate(student, semester)
|
||||
checkInitialAndCurrentDate(semester)
|
||||
timetableRepository.getTimetable(
|
||||
student = student,
|
||||
semester = semester,
|
||||
@ -194,9 +193,9 @@ class TimetablePresenter @Inject constructor(
|
||||
.launch()
|
||||
}
|
||||
|
||||
private suspend fun checkInitialAndCurrentDate(student: Student, semester: Semester) {
|
||||
private suspend fun checkInitialAndCurrentDate(semester: Semester) {
|
||||
if (initialDate == null) {
|
||||
isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(student, semester)
|
||||
isWeekendHasLessons = isStudentHasLessonsOnWeekendUseCase(semester)
|
||||
initialDate = getInitialDate(semester)
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,13 @@ package io.github.wulkanowy.utils
|
||||
import android.content.res.Resources
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.AccountInactiveException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.VulcanException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
@ -33,6 +35,8 @@ fun Resources.getErrorString(error: Throwable): String = when (error) {
|
||||
is ServiceUnavailableException -> R.string.error_service_unavailable
|
||||
is FeatureDisabledException -> R.string.error_feature_disabled
|
||||
is FeatureNotAvailableException -> R.string.error_feature_not_available
|
||||
is BadCredentialsException -> R.string.error_password_invalid
|
||||
is AccountInactiveException -> R.string.error_account_inactive
|
||||
is VulcanException -> R.string.error_unknown_uonet
|
||||
is ScrapperException -> R.string.error_unknown_app
|
||||
is CloudflareVerificationException -> R.string.error_cloudflare_captcha
|
||||
|
@ -5,17 +5,21 @@ import java.net.CookiePolicy
|
||||
import java.net.CookieStore
|
||||
import java.net.HttpCookie
|
||||
import java.net.URI
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import android.webkit.CookieManager as WebkitCookieManager
|
||||
import java.net.CookieManager as JavaCookieManager
|
||||
|
||||
class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
|
||||
@Singleton
|
||||
class WebkitCookieManagerProxy @Inject constructor() :
|
||||
JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
|
||||
|
||||
private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager()
|
||||
val webkitCookieManager: WebkitCookieManager? = getCookieManager()
|
||||
|
||||
/**
|
||||
* @see [https://stackoverflow.com/a/70354583/6695449]
|
||||
*/
|
||||
private fun getWebkitCookieManager(): WebkitCookieManager? {
|
||||
private fun getCookieManager(): WebkitCookieManager? {
|
||||
return try {
|
||||
WebkitCookieManager.getInstance()
|
||||
} catch (e: AndroidRuntimeException) {
|
||||
|
@ -1,7 +1,11 @@
|
||||
Wersja 2.4.2
|
||||
Wersja 2.5.0
|
||||
|
||||
- naprawiliśmy crash przy przełączaniu uczniów, motywów i języków
|
||||
- naprawiliśmy crash przy dodawaniu dodatkowych lekcji
|
||||
- naprawiliśmy obsługę błędów widżetach
|
||||
— dodaliśmy wyświetlanie ogłoszeń
|
||||
— dodaliśmy opcję przywracania wiadomości z kosza
|
||||
— dodaliśmy opcję wyciszania nadawców wiadomości
|
||||
— naprawiliśmy opcjonalne liczenie średniej arytmetycznej, kiedy brak ocen z wagą w drugim semestrze
|
||||
— usprawniliśmy ładowanie frekwencji i planu lekcji
|
||||
— naprawiliśmy usprawiedliwianie nieobecności i autoryzację u użytkowników eduOne
|
||||
— zmieniliśmy komunikat o zmienionym haśle
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
10
app/src/main/res/drawable/ic_circle_notification.xml
Normal file
10
app/src/main/res/drawable/ic_circle_notification.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid android:color="@color/colorPrimary" />
|
||||
|
||||
<size
|
||||
android:width="10dp"
|
||||
android:height="10dp" />
|
||||
</shape>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M14.12,10.47L12,12.59l-2.13,-2.12 -1.41,1.41L10.59,14l-2.12,2.12 1.41,1.41L12,15.41l2.12,2.12 1.41,-1.41L13.41,14l2.12,-2.12zM15.5,4l-1,-1h-5l-1,1H5v2h14V4zM6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM8,9h8v10H8V9z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_menu_message_restore.xml
Normal file
9
app/src/main/res/drawable/ic_menu_message_restore.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"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4zM6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8,14L8,9h8v10L8,19v-5zM10,18h4v-4h2l-4,-4 -4,4h2z" />
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_notifications_off.xml
Normal file
5
app/src/main/res/drawable/ic_notifications_off.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="17dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="17dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,18.69L7.84,6.14 5.27,3.49 4,4.76l2.8,2.8v0.01c-0.52,0.99 -0.8,2.16 -0.8,3.42v5l-2,2v1h13.73l2,2L21,19.72l-1,-1.03zM12,22c1.11,0 2,-0.89 2,-2h-4c0,1.11 0.89,2 2,2zM18,14.68L18,11c0,-3.08 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.15,0.03 -0.29,0.08 -0.42,0.12 -0.1,0.03 -0.2,0.07 -0.3,0.11h-0.01c-0.01,0 -0.01,0 -0.02,0.01 -0.23,0.09 -0.46,0.2 -0.68,0.31 0,0 -0.01,0 -0.01,0.01L18,14.68z"/>
|
||||
</vector>
|
@ -16,7 +16,7 @@
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/main_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
|
@ -85,7 +85,7 @@
|
||||
android:id="@+id/accountEditDetailsSave"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -93,6 +93,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_save"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@ -102,7 +103,7 @@
|
||||
android:id="@+id/accountEditDetailsCancel"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -110,6 +111,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountEditDetailsSave"
|
||||
|
@ -113,7 +113,7 @@
|
||||
android:id="@+id/additionalLessonDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
@ -122,6 +122,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/additionalLessonDialogAdd"
|
||||
@ -131,7 +132,7 @@
|
||||
android:id="@+id/additionalLessonDialogAdd"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -139,6 +140,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_add"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -150,7 +150,7 @@
|
||||
android:id="@+id/attendanceDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -158,6 +158,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -7,15 +7,18 @@
|
||||
tools:context=".ui.modules.captcha.CaptchaDialog">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captcha_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="@string/captcha_dialog_title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/captcha_close"
|
||||
app:layout_constraintEnd_toStartOf="@id/captcha_refresh"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/captcha_refresh"
|
||||
@ -41,11 +44,29 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/captcha_toolbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="captcha_title,captcha_close,captcha_refresh" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/captcha_webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/captcha_description"
|
||||
app:layout_constraintDimensionRatio="1"
|
||||
app:layout_constraintTop_toBottomOf="@id/captcha_toolbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captcha_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="@string/captcha_dialog_description"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/captcha_close" />
|
||||
app:layout_constraintTop_toBottomOf="@id/captcha_webview" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -181,7 +181,7 @@
|
||||
android:id="@+id/conferenceDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -189,6 +189,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -220,7 +220,7 @@
|
||||
android:id="@+id/examDialogAddToCalendar"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:contentDescription="@string/all_add_to_calendar"
|
||||
@ -228,6 +228,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_add"
|
||||
app:icon="@drawable/ic_calendar_all"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@ -237,7 +238,7 @@
|
||||
android:id="@+id/examDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -245,6 +246,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -212,7 +212,7 @@
|
||||
android:id="@+id/gradeDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/gradeDialogColorValue"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="24dp"
|
||||
@ -222,6 +222,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
@ -27,7 +27,7 @@
|
||||
android:id="@+id/homeworkDialogRead"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -35,6 +35,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/homework_mark_as_done"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/homeworkDialogClose" />
|
||||
@ -43,13 +44,14 @@
|
||||
android:id="@+id/homeworkDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
@ -94,7 +94,7 @@
|
||||
android:id="@+id/homeworkDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
@ -103,6 +103,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/homeworkDialogAdd"
|
||||
@ -112,13 +113,14 @@
|
||||
android:id="@+id/homeworkDialogAdd"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_add"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -212,7 +212,7 @@
|
||||
android:id="@+id/completedLessonDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -220,6 +220,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -18,10 +18,10 @@
|
||||
android:layout_marginTop="24dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/mobile_device_qr"
|
||||
tools:src="@tools:sample/avatars"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceDialogTokenTitle"
|
||||
@ -66,6 +66,7 @@
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/mobileDeviceDialogTokenValue" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mobileDeviceDialogSymbolValue"
|
||||
android:layout_width="wrap_content"
|
||||
@ -113,7 +114,7 @@
|
||||
android:id="@+id/mobileDeviceDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -121,6 +122,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@ -131,19 +133,19 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible"
|
||||
app:constraint_referenced_ids="mobileDeviceQr,mobileDeviceDialogTokenTitle,mobileDeviceDialogTokenValue,mobileDeviceDialogSymbolTitle,mobileDeviceDialogSymbolValue,mobileDeviceDialogPinTitle,mobileDeviceDialogPinValue,mobileDeviceDialogClose" />
|
||||
app:constraint_referenced_ids="mobileDeviceQr,mobileDeviceDialogTokenTitle,mobileDeviceDialogTokenValue,mobileDeviceDialogSymbolTitle,mobileDeviceDialogSymbolValue,mobileDeviceDialogPinTitle,mobileDeviceDialogPinValue,mobileDeviceDialogClose"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/mobileDeviceDialogProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
tools:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
tools:visibility="invisible" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
@ -180,7 +180,7 @@
|
||||
android:id="@+id/noteDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -188,6 +188,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -122,7 +122,7 @@
|
||||
android:id="@+id/announcementDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -130,6 +130,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -263,7 +263,7 @@
|
||||
android:id="@+id/timetableDialogClose"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
@ -271,6 +271,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -45,6 +45,9 @@
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/gradeHeaderPointsSum"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@id/gradeHeaderSubject"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
||||
tools:text="Average: 6,00" />
|
||||
@ -55,8 +58,12 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/gradeHeaderNumber"
|
||||
app:layout_constraintStart_toEndOf="@+id/gradeHeaderAverage"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gradeHeaderSubject"
|
||||
tools:text="Points: 123/200 (61,5%)" />
|
||||
@ -67,8 +74,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/gradeHeaderPointsSum"
|
||||
app:layout_constraintTop_toBottomOf="@id/gradeHeaderSubject"
|
||||
tools:text="12 grades" />
|
||||
@ -85,6 +97,9 @@
|
||||
android:paddingRight="5dp"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textSize="14sp"
|
||||
app:autoSizeMaxTextSize="16dp"
|
||||
app:autoSizeMinTextSize="10dp"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -81,9 +81,9 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageItemUnreadIndicator"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:src="@drawable/ic_circle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_circle_notification"
|
||||
app:layout_constraintBottom_toBottomOf="@id/messageItemDate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/messageItemDate"
|
||||
|
@ -11,27 +11,41 @@
|
||||
android:id="@+id/schoolAnnouncementItemDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/schoolAnnouncementItemAuthor"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/schoolAnnouncementItemAuthor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:gravity="end"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/schoolAnnouncementItemDate"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/schoolAnnouncementItemType"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="@id/schoolAnnouncementItemDate"
|
||||
app:layout_constraintStart_toStartOf="@id/schoolAnnouncementItemDate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/schoolAnnouncementItemDate"
|
||||
app:layout_goneMarginEnd="0dp"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
@ -40,6 +54,7 @@
|
||||
android:id="@+id/schoolAnnouncementItemContent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:ellipsize="end"
|
||||
@ -47,8 +62,8 @@
|
||||
android:maxLines="2"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/schoolAnnouncementItemType"
|
||||
app:layout_constraintStart_toStartOf="@id/schoolAnnouncementItemDate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/schoolAnnouncementItemType"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -1,7 +1,6 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/relativeLayout2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
@ -15,15 +14,17 @@
|
||||
android:id="@+id/timetableItemNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLength="2"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="32sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
tools:text="5" />
|
||||
|
||||
<TextView
|
||||
@ -38,7 +39,7 @@
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/timetableItemTimeBarrier"
|
||||
app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemTimeStart"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<TextView
|
||||
@ -49,8 +50,11 @@
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/timetableItemTimeFinish"
|
||||
app:layout_constraintStart_toEndOf="@id/timetableItemNumber"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemNumber"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="11:11" />
|
||||
|
||||
<TextView
|
||||
@ -58,11 +62,13 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/timetableItemNumber"
|
||||
app:layout_constraintTop_toBottomOf="@id/timetableItemTimeStart"
|
||||
tools:text="12:00" />
|
||||
|
||||
<TextView
|
||||
@ -70,11 +76,16 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
|
||||
app:layout_constraintEnd_toStartOf="@id/timetableItemGroup"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
|
||||
tools:text="22"
|
||||
tools:visibility="visible" />
|
||||
|
||||
@ -83,13 +94,14 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/timetableItemTeacher"
|
||||
app:layout_constraintStart_toEndOf="@+id/timetableItemRoom"
|
||||
app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
|
||||
tools:text="(2/2)"
|
||||
tools:visibility="visible" />
|
||||
|
||||
@ -98,13 +110,15 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/timetableItemGroup"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
|
||||
tools:text="Agata Kowalska - Błaszczyk"
|
||||
tools:visibility="visible" />
|
||||
|
||||
@ -118,8 +132,8 @@
|
||||
android:textColor="?colorTimetableChange"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/timetableItemTeacher"
|
||||
app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
|
||||
app:layout_constraintStart_toEndOf="@id/timetableItemTimeFinish"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemTimeFinish"
|
||||
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
|
||||
tools:visibility="gone" />
|
||||
|
||||
@ -168,7 +182,7 @@
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="?colorPrimary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/timetableItemTimeStart"
|
||||
tools:text="jeszcze 15 min"
|
||||
tools:visibility="visible" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView 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:id="@+id/dashboard_small_grade_subitem_value"
|
||||
android:layout_width="wrap_content"
|
||||
@ -10,7 +11,11 @@
|
||||
android:gravity="center"
|
||||
android:maxLength="5"
|
||||
android:minWidth="20dp"
|
||||
android:padding="1dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:autoSizeMaxTextSize="14dp"
|
||||
app:autoSizeMinTextSize="10dp"
|
||||
app:autoSizeTextType="uniform"
|
||||
tools:text="6" />
|
||||
|
@ -29,6 +29,13 @@
|
||||
android:title="@string/message_forward"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuRestore"
|
||||
android:icon="@drawable/ic_menu_message_restore"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_restore_from_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuDelete"
|
||||
android:icon="@drawable/ic_menu_message_delete"
|
||||
@ -36,4 +43,18 @@
|
||||
android:title="@string/message_move_to_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuDeleteForever"
|
||||
android:icon="@drawable/ic_menu_message_delete_forever"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_delete_forever"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuMute"
|
||||
android:icon="@drawable/ic_settings_notifications"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_mute"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
|
@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuRestore"
|
||||
android:icon="@drawable/ic_menu_message_restore"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_restore_from_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuDelete"
|
||||
android:icon="@drawable/ic_menu_message_delete"
|
||||
@ -8,6 +15,13 @@
|
||||
android:title="@string/message_move_to_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuDeleteForever"
|
||||
android:icon="@drawable/ic_menu_message_delete_forever"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_delete_forever"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuSelectAll"
|
||||
android:icon="@drawable/ic_message_select_all"
|
||||
|
@ -56,7 +56,7 @@
|
||||
<string name="login_invalid_email">Neplatný e-mail</string>
|
||||
<string name="login_invalid_login">Místo e-mailu použijte přiřazené přihlašovací údaje</string>
|
||||
<string name="login_invalid_custom_email">Použijte přiřazené přihlašovací nebo e-mail v @%1$s</string>
|
||||
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
|
||||
<string name="login_invalid_domain_suffix">Neplatná přípona domény</string>
|
||||
<string name="login_invalid_symbol">Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu</string>
|
||||
<string name="login_invalid_symbol_definitely">Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu</string>
|
||||
<string name="login_incorrect_symbol">Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+</string>
|
||||
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Přihlásit se</string>
|
||||
<string name="main_session_expired">Relace vypršela</string>
|
||||
<string name="main_session_relogin">Relace vypršela. Přihlaste se prosím znovu</string>
|
||||
<string name="main_expired_credentials_description">Heslo k vašemu účtu bylo změněno. Musíte se znovu přihlásit do Wulkanového</string>
|
||||
<string name="main_expired_credentials_title">Heslo bylo změněno</string>
|
||||
<string name="main_expired_credentials_title">Heslo vypršelo nebo bylo změněno</string>
|
||||
<string name="main_expired_credentials_description">Platnost hesla k vašemu účtu vypršela nebo bylo změněno. Budete se muset znovu přihlásit do Wulkanového</string>
|
||||
<string name="main_support_title">Podpora aplikace</string>
|
||||
<string name="main_support_description">Líbí se Vám tato aplikace? Podpořte její vývoj tím, že povolíte neinvazivní reklamy, které můžete kdykoliv vypnout</string>
|
||||
<string name="main_support_positive">Zapnout reklamy</string>
|
||||
@ -336,8 +336,10 @@
|
||||
<string name="message_forward">Poslat dále</string>
|
||||
<string name="message_select_all">Vybrat vše</string>
|
||||
<string name="message_unselect_all">Odznačit vše</string>
|
||||
<string name="message_restore_from_trash">Obnovit z koše</string>
|
||||
<string name="message_move_to_trash">Přesunout do koše</string>
|
||||
<string name="message_delete_forever">Odstranit natrvalo</string>
|
||||
<string name="message_restore_success">Zpráva úspěšně obnovena</string>
|
||||
<string name="message_delete_success">Zpráva byla úspěšně odstraněna</string>
|
||||
<string name="message_mailbox_type_student">žák</string>
|
||||
<string name="message_mailbox_type_parent">rodič</string>
|
||||
@ -383,6 +385,7 @@
|
||||
<item quantity="other">%1$d vybraných</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Zprávy odstraněné</string>
|
||||
<string name="message_messages_restored">Obnovené zprávy</string>
|
||||
<string name="message_mailbox_chooser_title">Vyberte poštovní schránku</string>
|
||||
<string name="message_incognito_mode_on">Anonymní režim je zapnutý</string>
|
||||
<string name="message_incognito_description">Díky anonymnímu režimu není odesílatel upozorněn, když si zprávu přečtete</string>
|
||||
@ -849,13 +852,16 @@
|
||||
<string name="auth_description">Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli</string>
|
||||
<string name="auth_button_skip">Zatím přeskočit</string>
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Probíhá ověřování. Počkejte…</string>
|
||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
|
||||
<string name="captcha_verified_message">Úspěšně ověřeno</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Žádné internetové připojení</string>
|
||||
<string name="error_invalid_device_datetime">Vyskytla se chyba. Zkontrolujte hodiny svého zařízení</string>
|
||||
<string name="error_account_inactive">Tento účet je neaktivní. Zkuste se znovu přihlásit</string>
|
||||
<string name="error_timeout">Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později</string>
|
||||
<string name="error_login_failed">Načítání dat se nezdařilo. Prosím zkuste to znovu později</string>
|
||||
<string name="error_password_invalid">Vaše heslo vypršelo nebo bylo změněno. Přihlaste se znovu</string>
|
||||
<string name="error_password_change_required">Je vyžadována změna hesla pro deník</string>
|
||||
<string name="error_service_unavailable">Probíhá údržba deníku UONET+. Zkuste to později znovu</string>
|
||||
<string name="error_unknown_uonet">Neznámá chyba deniku UONET+. Prosím zkuste to znovu později</string>
|
||||
@ -865,4 +871,9 @@
|
||||
<string name="error_feature_disabled">Funkce je deaktivována přes vaší školou</string>
|
||||
<string name="error_feature_not_available">Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API</string>
|
||||
<string name="error_field_required">Toto pole je povinné</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Ztlumit</string>
|
||||
<string name="message_unmute">Zrušit ztlumení</string>
|
||||
<string name="message_mute_success">Ztlumili jste tohoto uživatele</string>
|
||||
<string name="message_unmute_success">Zrušili jste ztlumení tohoto uživatele</string>
|
||||
</resources>
|
||||
|
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Anmelden</string>
|
||||
<string name="main_session_expired">Die Sitzung ist abgelaufen</string>
|
||||
<string name="main_session_relogin">Die Sitzung ist abgelaufen, bitte loggen Sie sich erneut ein</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_expired_credentials_title">Password has expired or been changed</string>
|
||||
<string name="main_expired_credentials_description">Your account password has expired or been changed. You will need to log in to Wulkanowy again</string>
|
||||
<string name="main_support_title">Anwendungsunterstützung</string>
|
||||
<string name="main_support_description">Gefällt Ihnen diese App? Unterstützen Sie ihre Entwicklung, indem Sie nicht-invasive Werbung aktivieren, die Sie jederzeit deaktivieren können</string>
|
||||
<string name="main_support_positive">Werbung aktivieren</string>
|
||||
@ -296,8 +296,10 @@
|
||||
<string name="message_forward">Weiterleiten</string>
|
||||
<string name="message_select_all">Alle auswählen</string>
|
||||
<string name="message_unselect_all">Alle abwählen</string>
|
||||
<string name="message_restore_from_trash">Restore from trash</string>
|
||||
<string name="message_move_to_trash">In Papierkorb verschieben</string>
|
||||
<string name="message_delete_forever">Dauerhaft löschen</string>
|
||||
<string name="message_restore_success">Message restored successfully</string>
|
||||
<string name="message_delete_success">Nachricht erfolgreich gelöscht</string>
|
||||
<string name="message_mailbox_type_student">schüler</string>
|
||||
<string name="message_mailbox_type_parent">Eltern</string>
|
||||
@ -335,6 +337,7 @@
|
||||
<item quantity="other">%1$d ausgewählt</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Nachrichten gelöscht</string>
|
||||
<string name="message_messages_restored">Messages restored</string>
|
||||
<string name="message_mailbox_chooser_title">Postfach auswählen</string>
|
||||
<string name="message_incognito_mode_on">Incognito mode is on</string>
|
||||
<string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
|
||||
@ -755,13 +758,16 @@
|
||||
<string name="auth_description">To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below</string>
|
||||
<string name="auth_button_skip">Skip for now</string>
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Verification is in progress. Wait…</string>
|
||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
|
||||
<string name="captcha_verified_message">Verified successfully</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Keine Internetverbindung</string>
|
||||
<string name="error_invalid_device_datetime">Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr</string>
|
||||
<string name="error_account_inactive">This account is inactive. Try logging in again</string>
|
||||
<string name="error_timeout">Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal</string>
|
||||
<string name="error_login_failed">Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal</string>
|
||||
<string name="error_password_invalid">Your password has expired or been changed. Please log in again</string>
|
||||
<string name="error_password_change_required">Passwortänderung für Registrierung erforderlich</string>
|
||||
<string name="error_service_unavailable">Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal</string>
|
||||
<string name="error_unknown_uonet">Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut</string>
|
||||
@ -771,4 +777,9 @@
|
||||
<string name="error_feature_disabled">Funktion, die von Ihrer Schule deaktiviert wurde</string>
|
||||
<string name="error_feature_not_available">Feature in diesem Modus nicht verfügbar</string>
|
||||
<string name="error_field_required">Dieses Feld ist erforderlich</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Mute</string>
|
||||
<string name="message_unmute">Unmute</string>
|
||||
<string name="message_mute_success">You have muted this user</string>
|
||||
<string name="message_unmute_success">You have unmuted this user</string>
|
||||
</resources>
|
||||
|
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Zaloguj się</string>
|
||||
<string name="main_session_expired">Sesja wygasła</string>
|
||||
<string name="main_session_relogin">Sesja wygasła, zaloguj się ponownie</string>
|
||||
<string name="main_expired_credentials_description">Hasło do Twojego konta zostało zmienione. Musisz zalogować się ponownie do Wulkanowego</string>
|
||||
<string name="main_expired_credentials_title">Hasło zostało zmienione</string>
|
||||
<string name="main_expired_credentials_title">Hasło wygasło lub zostało zmienione</string>
|
||||
<string name="main_expired_credentials_description">Hasło do twojego konta wygasło lub zostało zmienione. Musisz zalogować się ponownie do Wulkanowego</string>
|
||||
<string name="main_support_title">Wparcie aplikacji</string>
|
||||
<string name="main_support_description">Podoba Ci się ta aplikacja? Wspieraj jej rozwój poprzez włączenie nieinwazyjnych reklam, które możesz wyłączyć w dowolnym momencie</string>
|
||||
<string name="main_support_positive">Włącz reklamy</string>
|
||||
@ -336,8 +336,10 @@
|
||||
<string name="message_forward">Prześlij dalej</string>
|
||||
<string name="message_select_all">Zaznacz wszystkie</string>
|
||||
<string name="message_unselect_all">Odznacz wszystkie</string>
|
||||
<string name="message_restore_from_trash">Przywróć z kosza</string>
|
||||
<string name="message_move_to_trash">Przenieś do kosza</string>
|
||||
<string name="message_delete_forever">Usuń trwale</string>
|
||||
<string name="message_restore_success">Wiadomość przywrócona pomyślnie</string>
|
||||
<string name="message_delete_success">Wiadomość usunięta pomyślnie</string>
|
||||
<string name="message_mailbox_type_student">uczeń</string>
|
||||
<string name="message_mailbox_type_parent">rodzic</string>
|
||||
@ -383,6 +385,7 @@
|
||||
<item quantity="other">%1$d wybranych</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Wiadomości zostały usunięte</string>
|
||||
<string name="message_messages_restored">Wiadomości przywrócone</string>
|
||||
<string name="message_mailbox_chooser_title">Wybierz skrzynkę</string>
|
||||
<string name="message_incognito_mode_on">Tryb incognito jest włączony</string>
|
||||
<string name="message_incognito_description">Dzięki trybowi incognito nadawca nie zobaczy, że przeczytałeś tę wiadomość</string>
|
||||
@ -849,13 +852,16 @@
|
||||
<string name="auth_description">Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej</string>
|
||||
<string name="auth_button_skip">Na razie pomiń</string>
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Trwa weryfikacja. Czekaj…</string>
|
||||
<string name="captcha_dialog_title">Strona dziennika VULCAN wymaga weryfikacji</string>
|
||||
<string name="captcha_dialog_description"><b>Dlaczego to widzę?</b>\nStrona internetowa dziennika, z której Wulkanowy pobiera dane, wyświetla ten sam ekran jak powyżej, więc Wulkanowy musi również ją pokazać, aby móc pobrać dane z tej witryny. Nie da się tego obejść</string>
|
||||
<string name="captcha_verified_message">Pomyślnie zweryfikowano</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Brak połączenia z internetem</string>
|
||||
<string name="error_invalid_device_datetime">Wystąpił błąd. Sprawdź poprawność daty w urządzeniu</string>
|
||||
<string name="error_account_inactive">Konto jest nieaktywne. Spróbuj zalogować się ponownie</string>
|
||||
<string name="error_timeout">Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później</string>
|
||||
<string name="error_login_failed">Ładowanie danych nie powiodło się. Spróbuj ponownie później</string>
|
||||
<string name="error_password_invalid">Twoje hasło wygasło lub zostało zmienione. Zaloguj się ponownie</string>
|
||||
<string name="error_password_change_required">Wymagana zmiana hasła do dziennika</string>
|
||||
<string name="error_service_unavailable">Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później</string>
|
||||
<string name="error_unknown_uonet">Nieznany błąd dziennika UONET+. Spróbuj ponownie później</string>
|
||||
@ -865,4 +871,9 @@
|
||||
<string name="error_feature_disabled">Funkcja wyłączona przez szkołę</string>
|
||||
<string name="error_feature_not_available">Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API</string>
|
||||
<string name="error_field_required">To pole jest wymagane</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Wycisz</string>
|
||||
<string name="message_unmute">Wyłącz wyciszenie</string>
|
||||
<string name="message_mute_success">Wyciszyleś tego użytkownika</string>
|
||||
<string name="message_unmute_success">Wyłączyłeś wyciszenie tego użytkownika</string>
|
||||
</resources>
|
||||
|
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Войти</string>
|
||||
<string name="main_session_expired">Сеанс истёк</string>
|
||||
<string name="main_session_relogin">Сеанс истёк, авторизуйтесь снова</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_expired_credentials_title">Password has expired or been changed</string>
|
||||
<string name="main_expired_credentials_description">Your account password has expired or been changed. You will need to log in to Wulkanowy again</string>
|
||||
<string name="main_support_title">Поддержка приложения</string>
|
||||
<string name="main_support_description">Вам нравится это приложение? Поддержите его разработку, включив неинвазивную рекламу, которую можно отключить в любое время</string>
|
||||
<string name="main_support_positive">Включить рекламу</string>
|
||||
@ -336,8 +336,10 @@
|
||||
<string name="message_forward">Переслать</string>
|
||||
<string name="message_select_all">Выбрать все</string>
|
||||
<string name="message_unselect_all">Отменить выбор</string>
|
||||
<string name="message_restore_from_trash">Restore from trash</string>
|
||||
<string name="message_move_to_trash">Перенести в корзину</string>
|
||||
<string name="message_delete_forever">Удалить навсегда</string>
|
||||
<string name="message_restore_success">Message restored successfully</string>
|
||||
<string name="message_delete_success">Сообщение успешно удалено</string>
|
||||
<string name="message_mailbox_type_student">ученик</string>
|
||||
<string name="message_mailbox_type_parent">родитель</string>
|
||||
@ -383,6 +385,7 @@
|
||||
<item quantity="other">%1$d выбрано</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Сообщение удалено</string>
|
||||
<string name="message_messages_restored">Messages restored</string>
|
||||
<string name="message_mailbox_chooser_title">Выбрать почтовый ящик</string>
|
||||
<string name="message_incognito_mode_on">Incognito mode is on</string>
|
||||
<string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
|
||||
@ -849,13 +852,16 @@
|
||||
<string name="auth_description">Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже</string>
|
||||
<string name="auth_button_skip">Пропустить сейчас</string>
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Verification is in progress. Wait…</string>
|
||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
|
||||
<string name="captcha_verified_message">Verified successfully</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Интернет-соединение отсутствует</string>
|
||||
<string name="error_invalid_device_datetime">Произошла ошибка. Проверьте время на вашем устройстве</string>
|
||||
<string name="error_account_inactive">This account is inactive. Try logging in again</string>
|
||||
<string name="error_timeout">Не удалось подключиться к дневнику. Возможно, сервера перегружены, повторите попытку позже</string>
|
||||
<string name="error_login_failed">Не удалось загрузить данные, повторите попытку позже</string>
|
||||
<string name="error_password_invalid">Your password has expired or been changed. Please log in again</string>
|
||||
<string name="error_password_change_required">Необходимо изменить пароль дневника</string>
|
||||
<string name="error_service_unavailable">UONET+ проводит техническое обслуживание, повторите попытку позже</string>
|
||||
<string name="error_unknown_uonet">Неизвестная ошибка дневника UONET+, повторите попытку позже</string>
|
||||
@ -865,4 +871,9 @@
|
||||
<string name="error_feature_disabled">Функция отключена вашей школой</string>
|
||||
<string name="error_feature_not_available">Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом</string>
|
||||
<string name="error_field_required">Это поле обязательно</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Mute</string>
|
||||
<string name="message_unmute">Unmute</string>
|
||||
<string name="message_mute_success">You have muted this user</string>
|
||||
<string name="message_unmute_success">You have unmuted this user</string>
|
||||
</resources>
|
||||
|
@ -56,7 +56,7 @@
|
||||
<string name="login_invalid_email">Neplatný e-mail</string>
|
||||
<string name="login_invalid_login">Namiesto e-mailu použite priradené prihlasovacie údaje</string>
|
||||
<string name="login_invalid_custom_email">Použite priradené prihlasovacie alebo e-mail v @%1$s</string>
|
||||
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
|
||||
<string name="login_invalid_domain_suffix">Neplatná prípona domény</string>
|
||||
<string name="login_invalid_symbol">Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu</string>
|
||||
<string name="login_invalid_symbol_definitely">Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu</string>
|
||||
<string name="login_incorrect_symbol">Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+</string>
|
||||
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Prihlásiť sa</string>
|
||||
<string name="main_session_expired">Relácia vypršala</string>
|
||||
<string name="main_session_relogin">Relácia vypršala. Prihláste sa prosím znovu</string>
|
||||
<string name="main_expired_credentials_description">Heslo k vášmu účtu bolo zmenené. Musíte sa znovu prihlásiť do Wulkanového</string>
|
||||
<string name="main_expired_credentials_title">Heslo bolo zmenené</string>
|
||||
<string name="main_expired_credentials_title">Heslo vypršalo alebo bolo zmenené</string>
|
||||
<string name="main_expired_credentials_description">Platnosť hesla k vášmu účtu vypršala alebo bolo zmenené. Budete sa musieť znova prihlásiť do Wulkanového</string>
|
||||
<string name="main_support_title">Podpora aplikácie</string>
|
||||
<string name="main_support_description">Páči sa Vám táto aplikácia? Podporte jej vývoj tým, že povolíte neinvazívne reklamy, ktoré môžete kedykoľvek vypnúť</string>
|
||||
<string name="main_support_positive">Zapnúť reklamy</string>
|
||||
@ -336,8 +336,10 @@
|
||||
<string name="message_forward">Poslať ďalej</string>
|
||||
<string name="message_select_all">Vybrať všetko</string>
|
||||
<string name="message_unselect_all">Odznačiť všetko</string>
|
||||
<string name="message_restore_from_trash">Obnoviť z koša</string>
|
||||
<string name="message_move_to_trash">Presunúť do koša</string>
|
||||
<string name="message_delete_forever">Odstrániť natrvalo</string>
|
||||
<string name="message_restore_success">Správa úspešne obnovená</string>
|
||||
<string name="message_delete_success">Správa bola úspešne odstránená</string>
|
||||
<string name="message_mailbox_type_student">žiak</string>
|
||||
<string name="message_mailbox_type_parent">rodič</string>
|
||||
@ -383,6 +385,7 @@
|
||||
<item quantity="other">%1$d vybraných</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Správy odstránené</string>
|
||||
<string name="message_messages_restored">Obnovené správy</string>
|
||||
<string name="message_mailbox_chooser_title">Vyberte poštovú schránku</string>
|
||||
<string name="message_incognito_mode_on">Režim inkognito je zapnutý</string>
|
||||
<string name="message_incognito_description">Vďaka inkognito režimu nie je odosielateľ upozornený, keď si správu prečítate</string>
|
||||
@ -849,13 +852,16 @@
|
||||
<string name="auth_description">Na prevádzku aplikácie potrebujeme potvrdiť vašu identitu. Zadajte PESEL žiaka <b>%1$s</b> v nižšie uvedenom poli</string>
|
||||
<string name="auth_button_skip">Zatiaľ preskočiť</string>
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Overovanie prebieha. Počkajte…</string>
|
||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
|
||||
<string name="captcha_verified_message">Úspešne overené</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Žiadne internetové pripojenie</string>
|
||||
<string name="error_invalid_device_datetime">Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia</string>
|
||||
<string name="error_account_inactive">Tento účet je neaktívny. Skúste sa znova prihlásiť</string>
|
||||
<string name="error_timeout">Nedá sa pripojiť ku denníku. Servery môžu byť preťažené. Prosím skúste to znova neskôr</string>
|
||||
<string name="error_login_failed">Načítanie údajov zlyhalo. Skúste neskôr prosím</string>
|
||||
<string name="error_password_invalid">Vaše heslo vypršalo alebo bolo zmenené. Prihláste sa znova</string>
|
||||
<string name="error_password_change_required">Je vyžadovaná zmena hesla pre denník</string>
|
||||
<string name="error_service_unavailable">Prebieha údržba denníka UONET+. Skúste to neskôr znova</string>
|
||||
<string name="error_unknown_uonet">Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr</string>
|
||||
@ -865,4 +871,9 @@
|
||||
<string name="error_feature_disabled">Funkcia je deaktivovaná cez vašou školou</string>
|
||||
<string name="error_feature_not_available">Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API</string>
|
||||
<string name="error_field_required">Toto pole je povinné</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Stlmiť</string>
|
||||
<string name="message_unmute">Zrušiť stlmenie</string>
|
||||
<string name="message_mute_success">Stlmili ste tohto používateľa</string>
|
||||
<string name="message_unmute_success">Zrušili ste stlmenie tohto používateľa</string>
|
||||
</resources>
|
||||
|
@ -56,7 +56,7 @@
|
||||
<string name="login_invalid_email">Недійсна адреса e-mail</string>
|
||||
<string name="login_invalid_login">Використовуйте призначений логін замість адреси e-mail</string>
|
||||
<string name="login_invalid_custom_email">Використовуйте призначений логін або адресу e-mail в @%1$s</string>
|
||||
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
|
||||
<string name="login_invalid_domain_suffix">Невірний суфікс домену</string>
|
||||
<string name="login_invalid_symbol">Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою</string>
|
||||
<string name="login_invalid_symbol_definitely">Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою</string>
|
||||
<string name="login_incorrect_symbol">Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+</string>
|
||||
@ -98,8 +98,8 @@
|
||||
<string name="main_log_in">Увійти</string>
|
||||
<string name="main_session_expired">Минув термін дії сесії</string>
|
||||
<string name="main_session_relogin">Минув термін дії сесії, авторизуйтеся знову</string>
|
||||
<string name="main_expired_credentials_description">Пароль вашого облікового запису був змінений. Ви повинні увійти в Wulkanowy знову</string>
|
||||
<string name="main_expired_credentials_title">Пароль змінено</string>
|
||||
<string name="main_expired_credentials_title">Термін дії пароля закінчився або його було змінено</string>
|
||||
<string name="main_expired_credentials_description">Термін дії пароля для вашого облікового запису закінчився або було змінено. Необхідно зайти в Wulkanowy знову</string>
|
||||
<string name="main_support_title">Підтримка додатку</string>
|
||||
<string name="main_support_description">Вам подобається цей додаток? Підтримайте його розвиток, увімкнувши неінвазивну рекламу, яку ви можете відключити в будь-який час</string>
|
||||
<string name="main_support_positive">Увімкнути рекламу</string>
|
||||
@ -336,8 +336,10 @@
|
||||
<string name="message_forward">Переслати</string>
|
||||
<string name="message_select_all">Вибрати всі</string>
|
||||
<string name="message_unselect_all">Відмінити вибір</string>
|
||||
<string name="message_restore_from_trash">Відновити зі смітника</string>
|
||||
<string name="message_move_to_trash">Перемістити до кошика</string>
|
||||
<string name="message_delete_forever">Видалити назавжди</string>
|
||||
<string name="message_restore_success">Повідомлення успішно відновлено</string>
|
||||
<string name="message_delete_success">Лист було успішно видалено</string>
|
||||
<string name="message_mailbox_type_student">учень</string>
|
||||
<string name="message_mailbox_type_parent">родич</string>
|
||||
@ -383,6 +385,7 @@
|
||||
<item quantity="other">%1$d вибрано</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Листи видалено</string>
|
||||
<string name="message_messages_restored">Повідомлення відновлені</string>
|
||||
<string name="message_mailbox_chooser_title">Вибрати поштову скриньку</string>
|
||||
<string name="message_incognito_mode_on">Режим анонімності включено</string>
|
||||
<string name="message_incognito_description">Завдяки режиму анонімності, відправник не буде сповіщений коли ви прочитаєте повідомлення</string>
|
||||
@ -849,13 +852,16 @@
|
||||
<string name="auth_description">Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче</string>
|
||||
<string name="auth_button_skip">Поки що пропустити</string>
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Верифікація в процесі. Чекайте…</string>
|
||||
<string name="captcha_dialog_title">Веб-сайт VULCAN потребує підтвердження</string>
|
||||
<string name="captcha_dialog_description"><b>Чому я це бачу?</b>\nСайт реєстру, з якого Wulkanowy завантажує дані, відображає той самий екран, що й вище, тому Wulkanowy також повинен показувати його, щоб мати змогу завантажувати дані з цього сайту. Це неможливо обійти</string>
|
||||
<string name="captcha_verified_message">Верифікація завершена</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Немає з\'єднання з інтернетом</string>
|
||||
<string name="error_invalid_device_datetime">Сталася помилка. Перевірте годинник пристрою</string>
|
||||
<string name="error_account_inactive">Цей обліковий запис неактивний. Спробуйте увійти ще раз</string>
|
||||
<string name="error_timeout">Помилка підключення до щоденнику. Сервери можуть бути перевантажені, спробуйте пізніше</string>
|
||||
<string name="error_login_failed">Помилка завантаження даних, спробуйте пізніше</string>
|
||||
<string name="error_password_invalid">Термін дії вашого пароля минув або був змінений. Будь ласка увійдіть знову</string>
|
||||
<string name="error_password_change_required">Необхідна зміна пароля щоденника</string>
|
||||
<string name="error_service_unavailable">UONET+ проводить технічне осблуговування, спробуйте пізніше</string>
|
||||
<string name="error_unknown_uonet">Невідома помилка щоденника UONET+, спробуйте пізніше</string>
|
||||
@ -865,4 +871,9 @@
|
||||
<string name="error_feature_disabled">Функція вимкнена вашою школою</string>
|
||||
<string name="error_feature_not_available">Функція недоступна в режимі Mobile API. Увійдіть в інший режим</string>
|
||||
<string name="error_field_required">Це поле обовʼязкове</string>
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Вимкнути сповіщення</string>
|
||||
<string name="message_unmute">Ввімкнути сповіщення</string>
|
||||
<string name="message_mute_success">Ви ігноруєте цього користувача</string>
|
||||
<string name="message_unmute_success">Ви не ігноруєте цього користувача</string>
|
||||
</resources>
|
||||
|
@ -66,7 +66,7 @@
|
||||
<item>gminaulanmajorat</item>
|
||||
<item>gminaozorkow</item>
|
||||
<item>gminalopiennikgorny</item>
|
||||
<item>warszawa</item>
|
||||
<item>saas1</item>
|
||||
<item>powiatwulkanowy</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -109,8 +109,8 @@
|
||||
<string name="main_log_in">Log in</string>
|
||||
<string name="main_session_expired">Session expired</string>
|
||||
<string name="main_session_relogin">Session expired, log in again</string>
|
||||
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||
<string name="main_expired_credentials_title">Password changed</string>
|
||||
<string name="main_expired_credentials_title">Password has expired or been changed</string>
|
||||
<string name="main_expired_credentials_description">Your account password has expired or been changed. You will need to log in to Wulkanowy again</string>
|
||||
<string name="main_support_title">Application support</string>
|
||||
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
||||
<string name="main_support_positive">Enable ads</string>
|
||||
@ -325,8 +325,10 @@
|
||||
<string name="message_forward">Forward</string>
|
||||
<string name="message_select_all">Select all</string>
|
||||
<string name="message_unselect_all">Unselect all</string>
|
||||
<string name="message_restore_from_trash">Restore from trash</string>
|
||||
<string name="message_move_to_trash">Move to trash</string>
|
||||
<string name="message_delete_forever">Delete permanently</string>
|
||||
<string name="message_restore_success">Message restored 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>
|
||||
@ -364,6 +366,7 @@
|
||||
<item quantity="other">%1$d selected</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Messages deleted</string>
|
||||
<string name="message_messages_restored">Messages restored</string>
|
||||
<string name="message_mailbox_chooser_title">Choose mailbox</string>
|
||||
<string name="message_incognito_mode_on">Incognito mode is on</string>
|
||||
<string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
|
||||
@ -845,15 +848,18 @@
|
||||
|
||||
|
||||
<!--Captcha-->
|
||||
<string name="captcha_dialog_title">Verification is in progress. Wait…</string>
|
||||
<string name="captcha_dialog_title">VULCAN\'s website requires verification</string>
|
||||
<string name="captcha_dialog_description"><b>Why am I seeing this?</b>\nThe register website from which Wulkanowy downloads data displays the same screen as above, so Wulkanowy must also show it to be able to download data from this website. There\'s no way around it</string>
|
||||
<string name="captcha_verified_message">Verified successfully</string>
|
||||
|
||||
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">No internet connection</string>
|
||||
<string name="error_invalid_device_datetime">An error occurred. Check your device clock</string>
|
||||
<string name="error_account_inactive">This account is inactive. Try logging in again</string>
|
||||
<string name="error_timeout">Connection to register failed. Servers can be overloaded. Please try again later</string>
|
||||
<string name="error_login_failed">Loading data failed. Please try again later</string>
|
||||
<string name="error_password_invalid">Your password has expired or been changed. Please log in again</string>
|
||||
<string name="error_password_change_required">Register password change required</string>
|
||||
<string name="error_service_unavailable">Maintenance underway UONET + register. Try again later</string>
|
||||
<string name="error_unknown_uonet">Unknown UONET + register error. Try again later</string>
|
||||
@ -863,4 +869,10 @@
|
||||
<string name="error_feature_disabled">Feature disabled by your school</string>
|
||||
<string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string>
|
||||
<string name="error_field_required">This field is required</string>
|
||||
|
||||
<!-- Mute system -->
|
||||
<string name="message_mute">Mute</string>
|
||||
<string name="message_unmute">Unmute</string>
|
||||
<string name="message_mute_success">You have muted this user</string>
|
||||
<string name="message_unmute_success">You have unmuted this user</string>
|
||||
</resources>
|
||||
|
@ -10,11 +10,17 @@ import io.github.wulkanowy.getSemesterEntity
|
||||
import io.github.wulkanowy.getStudentEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.mockk.*
|
||||
import io.mockk.MockKAnnotations
|
||||
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.SpyK
|
||||
import io.mockk.just
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -61,26 +67,36 @@ class AttendanceRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh without difference`() {
|
||||
fun `force refresh without difference`() = runTest {
|
||||
// prepare
|
||||
coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList
|
||||
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester, emptyList())),
|
||||
flowOf(remoteList.mapToEntities(semester, emptyList()))
|
||||
)
|
||||
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { attendanceDb.deleteAll(any()) } just Runs
|
||||
coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = attendanceRepository.getAttendance(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(2, res.dataOrNull?.size)
|
||||
coVerify { sdk.getAttendance(startDate, endDate) }
|
||||
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) }
|
||||
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) }
|
||||
coVerify {
|
||||
attendanceDb.removeOldAndSaveNew(
|
||||
oldItems = match { it.isEmpty() },
|
||||
newItems = match { it.isEmpty() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -89,14 +105,23 @@ class AttendanceRepositoryTest {
|
||||
coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList
|
||||
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result
|
||||
flowOf(
|
||||
remoteList.dropLast(1).mapToEntities(semester, emptyList())
|
||||
), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester, emptyList()))
|
||||
)
|
||||
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { attendanceDb.deleteAll(any()) } just Runs
|
||||
coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = runBlocking {
|
||||
attendanceRepository.getAttendance(
|
||||
student,
|
||||
semester,
|
||||
startDate,
|
||||
endDate,
|
||||
true
|
||||
).toFirstResult()
|
||||
}
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
@ -104,11 +129,13 @@ class AttendanceRepositoryTest {
|
||||
coVerify { sdk.getAttendance(startDate, endDate) }
|
||||
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify {
|
||||
attendanceDb.insertAll(match {
|
||||
attendanceDb.removeOldAndSaveNew(
|
||||
oldItems = match { it.isEmpty() },
|
||||
newItems = match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -117,25 +144,39 @@ class AttendanceRepositoryTest {
|
||||
coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList.dropLast(1)
|
||||
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester, emptyList())),
|
||||
flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result
|
||||
flowOf(
|
||||
remoteList.mapToEntities(
|
||||
semester,
|
||||
emptyList()
|
||||
)
|
||||
), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList()))
|
||||
)
|
||||
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { attendanceDb.deleteAll(any()) } just Runs
|
||||
coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = runBlocking {
|
||||
attendanceRepository.getAttendance(
|
||||
student,
|
||||
semester,
|
||||
startDate,
|
||||
endDate,
|
||||
true
|
||||
).toFirstResult()
|
||||
}
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(1, res.dataOrNull?.size)
|
||||
coVerify { sdk.getAttendance(startDate, endDate) }
|
||||
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) }
|
||||
coVerify {
|
||||
attendanceDb.deleteAll(match {
|
||||
attendanceDb.removeOldAndSaveNew(
|
||||
oldItems = match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
|
||||
})
|
||||
},
|
||||
newItems = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,16 @@ import io.github.wulkanowy.getSemesterEntity
|
||||
import io.github.wulkanowy.getStudentEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.mockk.*
|
||||
import io.mockk.MockKAnnotations
|
||||
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.SpyK
|
||||
import io.mockk.just
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -52,46 +57,28 @@ class CompletedLessonsRepositoryTest {
|
||||
MockKAnnotations.init(this)
|
||||
every { refreshHelper.shouldBeRefreshed(any()) } returns false
|
||||
|
||||
completedLessonRepository = CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper)
|
||||
completedLessonRepository =
|
||||
CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh without difference`() {
|
||||
fun `force refresh without difference`() = runTest {
|
||||
// prepare
|
||||
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
|
||||
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { completedLessonDb.deleteAll(any()) } just Runs
|
||||
coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(2, res.dataOrNull?.size)
|
||||
coVerify { sdk.getCompletedLessons(startDate, endDate) }
|
||||
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) }
|
||||
coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh with more items in remote`() {
|
||||
// prepare
|
||||
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
|
||||
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { completedLessonDb.deleteAll(any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = completedLessonRepository.getCompletedLessons(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
@ -99,15 +86,52 @@ class CompletedLessonsRepositoryTest {
|
||||
coVerify { sdk.getCompletedLessons(startDate, endDate) }
|
||||
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify {
|
||||
completedLessonDb.insertAll(match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
|
||||
})
|
||||
completedLessonDb.removeOldAndSaveNew(
|
||||
oldItems = match { it.isEmpty() },
|
||||
newItems = match { it.isEmpty() },
|
||||
)
|
||||
}
|
||||
coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh with more items in local`() {
|
||||
fun `force refresh with more items in remote`() = runTest {
|
||||
// prepare
|
||||
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
|
||||
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(
|
||||
remoteList.dropLast(1).mapToEntities(semester)
|
||||
), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = completedLessonRepository.getCompletedLessons(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
forceRefresh = true
|
||||
).toFirstResult()
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(2, res.dataOrNull?.size)
|
||||
coVerify { sdk.getCompletedLessons(startDate, endDate) }
|
||||
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify {
|
||||
completedLessonDb.removeOldAndSaveNew(
|
||||
oldItems = match { it.isEmpty() },
|
||||
newItems = match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh with more items in local`() = runTest {
|
||||
// prepare
|
||||
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList.dropLast(1)
|
||||
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
|
||||
@ -115,22 +139,29 @@ class CompletedLessonsRepositoryTest {
|
||||
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester))
|
||||
)
|
||||
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { completedLessonDb.deleteAll(any()) } just Runs
|
||||
coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = completedLessonRepository.getCompletedLessons(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(1, res.dataOrNull?.size)
|
||||
coVerify { sdk.getCompletedLessons(startDate, endDate) }
|
||||
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
|
||||
coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) }
|
||||
coVerify {
|
||||
completedLessonDb.deleteAll(match {
|
||||
completedLessonDb.removeOldAndSaveNew(
|
||||
oldItems = match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
|
||||
})
|
||||
},
|
||||
newItems = match { it.isEmpty() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,17 @@ import io.github.wulkanowy.getSemesterEntity
|
||||
import io.github.wulkanowy.getStudentEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.mockk.*
|
||||
import io.mockk.MockKAnnotations
|
||||
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.SpyK
|
||||
import io.mockk.just
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -64,35 +70,42 @@ class ExamRemoteTest {
|
||||
flowOf(remoteList.mapToEntities(semester)),
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { examDb.deleteAll(any()) } just Runs
|
||||
coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = runBlocking {
|
||||
examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult()
|
||||
}
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(2, res.dataOrNull?.size)
|
||||
coVerify { sdk.getExams(startDate, realEndDate) }
|
||||
coVerify { examDb.loadAll(1, 1, startDate, realEndDate) }
|
||||
coVerify { examDb.insertAll(match { it.isEmpty() }) }
|
||||
coVerify { examDb.deleteAll(match { it.isEmpty() }) }
|
||||
coVerify { examDb.removeOldAndSaveNew(emptyList(), emptyList()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh with more items in remote`() {
|
||||
fun `force refresh with more items in remote`() = runTest {
|
||||
// prepare
|
||||
coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList
|
||||
coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf(
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(
|
||||
remoteList.dropLast(1).mapToEntities(semester)
|
||||
), // after fetch end before save result
|
||||
flowOf(remoteList.mapToEntities(semester))
|
||||
)
|
||||
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { examDb.deleteAll(any()) } just Runs
|
||||
coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = examRepository.getExams(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
@ -100,15 +113,17 @@ class ExamRemoteTest {
|
||||
coVerify { sdk.getExams(startDate, realEndDate) }
|
||||
coVerify { examDb.loadAll(1, 1, startDate, realEndDate) }
|
||||
coVerify {
|
||||
examDb.insertAll(match {
|
||||
examDb.removeOldAndSaveNew(
|
||||
oldItems = emptyList(),
|
||||
newItems = match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
coVerify { examDb.deleteAll(match { it.isEmpty() }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `force refresh with more items in local`() {
|
||||
fun `force refresh with more items in local`() = runTest {
|
||||
// prepare
|
||||
coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList.dropLast(1)
|
||||
coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf(
|
||||
@ -116,22 +131,27 @@ class ExamRemoteTest {
|
||||
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
|
||||
flowOf(remoteList.dropLast(1).mapToEntities(semester))
|
||||
)
|
||||
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3)
|
||||
coEvery { examDb.deleteAll(any()) } just Runs
|
||||
coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs
|
||||
|
||||
// execute
|
||||
val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() }
|
||||
val res = examRepository.getExams(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = startDate,
|
||||
end = endDate,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
|
||||
// verify
|
||||
assertEquals(null, res.errorOrNull)
|
||||
assertEquals(1, res.dataOrNull?.size)
|
||||
coVerify { sdk.getExams(startDate, realEndDate) }
|
||||
coVerify { examDb.loadAll(1, 1, startDate, realEndDate) }
|
||||
coVerify { examDb.insertAll(match { it.isEmpty() }) }
|
||||
coVerify {
|
||||
examDb.deleteAll(match {
|
||||
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
|
||||
})
|
||||
examDb.removeOldAndSaveNew(
|
||||
oldItems = match { it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] },
|
||||
newItems = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user