Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
7b2c839775 | |||
f455064b9d | |||
2bbc157d03 | |||
a0a0b8dea6 | |||
3bab883a56 | |||
b319bb03cd | |||
333306e7ba | |||
fb240938ed | |||
dc9af29a44 | |||
e9d64de0cb | |||
05bda598fc | |||
3564366a8f | |||
f2d26453ed | |||
ccba31f2e8 | |||
a7238e3f23 | |||
ea28fc783c | |||
c04752ed39 | |||
c198e6a2f7 | |||
2c1337bb51 | |||
7a4032dda4 | |||
1ab300d74f | |||
1b8c389984 | |||
74a20b2f65 | |||
d5c17285c1 | |||
e378b4c70a | |||
31854fc4b8 | |||
f52fe8306f | |||
3eae3a7667 | |||
b613b84469 | |||
2776d019b9 | |||
729e72cddb | |||
ec101c1f52 | |||
cfec79405f | |||
7d8be1b9fc | |||
c781159e75 | |||
90a5b9e20f | |||
3cf6c295b0 | |||
e757585bd3 | |||
736d16a7ab | |||
6f4a8d5534 | |||
b5e17c4ff7 | |||
cc01525f16 | |||
c2ec05662b |
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 146
|
||||
versionName "2.4.0"
|
||||
versionCode 149
|
||||
versionName "2.5.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
@ -164,7 +164,7 @@ play {
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.50d
|
||||
userFraction = 0.20d
|
||||
updatePriority = 1
|
||||
enabled.set(false)
|
||||
}
|
||||
@ -187,19 +187,19 @@ 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"
|
||||
coroutines = "1.7.3"
|
||||
mockk = "1.13.10"
|
||||
coroutines = "1.8.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.4.0'
|
||||
implementation 'io.github.wulkanowy:sdk:2.5.0'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
@ -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.1')
|
||||
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:'
|
||||
@ -262,7 +262,7 @@ dependencies {
|
||||
playImplementation "com.google.android.play:integrity:1.3.0"
|
||||
playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
|
||||
playImplementation 'com.google.android.play:review-ktx:2.0.1'
|
||||
playImplementation "com.google.android.ump:user-messaging-platform:2.2.0"
|
||||
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
|
||||
|
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,17 +38,20 @@ internal class DataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) =
|
||||
Sdk().apply {
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
setAdditionalCookieManager(WebkitCookieManagerProxy())
|
||||
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)
|
||||
|
||||
// for debug only
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
// for debug only
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -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
|
||||
|
@ -30,8 +30,15 @@ val <T> Resource<T>.dataOrNull: T?
|
||||
get() = when (this) {
|
||||
is Resource.Success -> this.data
|
||||
is Resource.Intermediate -> this.data
|
||||
is Resource.Loading -> null
|
||||
is Resource.Error -> null
|
||||
else -> null
|
||||
}
|
||||
|
||||
val <T> Resource<T>.dataOrThrow: T
|
||||
get() = when (this) {
|
||||
is Resource.Success -> this.data
|
||||
is Resource.Intermediate -> this.data
|
||||
is Resource.Loading -> throw IllegalStateException("Resource is in loading state")
|
||||
is Resource.Error -> throw this.error
|
||||
}
|
||||
|
||||
val <T> Resource<T>.errorOrNull: Throwable?
|
||||
|
@ -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(
|
||||
semester.diaryId, semester.studentId, start.monday, end.sunday
|
||||
)
|
||||
}
|
||||
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 ->
|
||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||
attendanceDb.insertAll(new uniqueSubtract old)
|
||||
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 {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
conferenceDb.deleteAll(old uniqueSubtract new)
|
||||
conferenceDb.insertAll(conferencesToSave)
|
||||
conferenceDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
|
@ -62,12 +62,12 @@ class ExamRepository @Inject constructor(
|
||||
.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
val examsToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
examDb.deleteAll(old uniqueSubtract new)
|
||||
examDb.insertAll(examsToSave)
|
||||
examDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
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 {
|
||||
if (notify) it.isNotified = false
|
||||
})
|
||||
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 {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
isRead = false
|
||||
if (notify) isNotified = false
|
||||
}
|
||||
})
|
||||
|
||||
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,31 +120,43 @@ 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 }
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
else -> true
|
||||
}
|
||||
summary.isFinalGradeNotified = when {
|
||||
summary.finalGrade.isEmpty() -> true
|
||||
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
||||
else -> true
|
||||
}
|
||||
gradeSummaryDb.removeOldAndSaveNew(
|
||||
oldItems = oldSummaries uniqueSubtract newSummary,
|
||||
newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
getGradeSummaryWithUpdatedNotificationState(
|
||||
summary = summary,
|
||||
oldSummary = oldSummaries.find { it.subject == summary.subject },
|
||||
notify = notify,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
summary.predictedGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
||||
else -> oldSummary.predictedGradeLastChange
|
||||
}
|
||||
summary.finalGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
|
||||
else -> oldSummary.finalGradeLastChange
|
||||
}
|
||||
})
|
||||
private fun getGradeSummaryWithUpdatedNotificationState(
|
||||
summary: GradeSummary,
|
||||
oldSummary: GradeSummary?,
|
||||
notify: Boolean,
|
||||
) {
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
else -> true
|
||||
}
|
||||
summary.isFinalGradeNotified = when {
|
||||
summary.finalGrade.isEmpty() -> true
|
||||
notify && oldSummary?.finalGrade != summary.finalGrade -> false
|
||||
else -> true
|
||||
}
|
||||
summary.predictedGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
|
||||
else -> oldSummary.predictedGradeLastChange
|
||||
}
|
||||
summary.finalGradeLastChange = when {
|
||||
oldSummary == null -> Instant.now()
|
||||
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)
|
||||
|
||||
getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = TRASHED,
|
||||
forceRefresh = true,
|
||||
).first()
|
||||
} else {
|
||||
messagesDb.deleteAll(messages)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
|
||||
deleteMessages(student, mailbox, listOf(message))
|
||||
private suspend fun refreshFolders(
|
||||
student: Student,
|
||||
mailbox: Mailbox?,
|
||||
folders: List<MessageFolder> = MessageFolder.entries
|
||||
) {
|
||||
folders.forEach {
|
||||
getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = it,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
|
||||
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
|
||||
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
|
||||
schoolAnnouncementDb.removeOldAndSaveNew(
|
||||
oldItems = old uniqueSubtract new,
|
||||
newItems = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
},
|
||||
)
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() }
|
||||
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
|
||||
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
|
||||
loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() }
|
||||
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
|
||||
loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() }
|
||||
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
|
||||
@ -188,6 +189,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDomainSuffixInvalid() {
|
||||
with(binding.loginFormDomainSuffixLayout) {
|
||||
error = getString(R.string.login_invalid_domain_suffix)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearUsernameError() {
|
||||
binding.loginFormUsernameLayout.error = null
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
@ -206,6 +213,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
}
|
||||
|
||||
override fun clearDomainSuffixError() {
|
||||
binding.loginFormDomainSuffixLayout.error = null
|
||||
}
|
||||
|
||||
override fun showSoftKeyboard() {
|
||||
activity?.showSoftInput()
|
||||
}
|
||||
|
@ -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
|
||||
@ -101,6 +102,12 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onDomainSuffixChanged() {
|
||||
view?.apply {
|
||||
clearDomainSuffixError()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCustomDomainSuffixVisibility() {
|
||||
view?.run {
|
||||
showDomainSuffixInput("customSuffix" in formHostValue)
|
||||
@ -159,7 +166,7 @@ class LoginFormPresenter @Inject constructor(
|
||||
fun onSignInClick() {
|
||||
val loginData = getLoginData()
|
||||
|
||||
if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return
|
||||
if (!validateCredentials(loginData)) return
|
||||
|
||||
resourceFlow {
|
||||
studentRepository.getUserSubjectsFromScrapper(
|
||||
@ -198,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(
|
||||
@ -229,24 +239,29 @@ class LoginFormPresenter @Inject constructor(
|
||||
view?.onRecoverClick()
|
||||
}
|
||||
|
||||
private fun validateCredentials(login: String, password: String, host: String): Boolean {
|
||||
private fun validateCredentials(loginData: LoginData): Boolean {
|
||||
var isCorrect = true
|
||||
|
||||
if (login.isEmpty()) {
|
||||
if (loginData.login.isEmpty()) {
|
||||
view?.setErrorUsernameRequired()
|
||||
isCorrect = false
|
||||
} else {
|
||||
if ("@" in login && "login" in host) {
|
||||
if ("@" in loginData.login && "login" in loginData.baseUrl) {
|
||||
view?.setErrorLoginRequired()
|
||||
isCorrect = false
|
||||
}
|
||||
if ("@" !in login && "email" in host) {
|
||||
if ("@" !in loginData.login && "email" in loginData.baseUrl) {
|
||||
view?.setErrorEmailRequired()
|
||||
isCorrect = false
|
||||
}
|
||||
if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
|
||||
val emailHost = login.substringAfter("@")
|
||||
val emailDomain = URL(host).host
|
||||
|
||||
val isEmailLogin = "@" in loginData.login
|
||||
val isEmailWithLogin = "||" !in loginData.login
|
||||
val isLoginNotRequired = "login" !in loginData.baseUrl
|
||||
val isEmailNotRequired = "email" !in loginData.baseUrl
|
||||
if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) {
|
||||
val emailHost = loginData.login.substringAfter("@")
|
||||
val emailDomain = URL(loginData.baseUrl).host
|
||||
if (!emailHost.equals(emailDomain, true)) {
|
||||
view?.setErrorEmailInvalid(domain = emailDomain)
|
||||
isCorrect = false
|
||||
@ -254,16 +269,21 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (password.isEmpty()) {
|
||||
if (loginData.password.isEmpty()) {
|
||||
view?.setErrorPassRequired(focus = isCorrect)
|
||||
isCorrect = false
|
||||
}
|
||||
|
||||
if (password.length < 6 && password.isNotEmpty()) {
|
||||
if (loginData.password.length < 6 && loginData.password.isNotEmpty()) {
|
||||
view?.setErrorPassInvalid(focus = isCorrect)
|
||||
isCorrect = false
|
||||
}
|
||||
|
||||
if (loginData.domainSuffix !in listOf("", "rc", "kurs")) {
|
||||
view?.setDomainSuffixInvalid()
|
||||
isCorrect = false
|
||||
}
|
||||
|
||||
return isCorrect
|
||||
}
|
||||
}
|
||||
|
@ -46,12 +46,16 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun setErrorEmailInvalid(domain: String)
|
||||
|
||||
fun setDomainSuffixInvalid()
|
||||
|
||||
fun clearUsernameError()
|
||||
|
||||
fun clearPassError()
|
||||
|
||||
fun clearHostError()
|
||||
|
||||
fun clearDomainSuffixError()
|
||||
|
||||
fun showSoftKeyboard()
|
||||
|
||||
fun hideSoftKeyboard()
|
||||
|
@ -96,10 +96,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
?.takeIf { it.symbol == loginData.userEnteredSymbol }
|
||||
|
||||
if (enteredSymbolDetails?.error is InvalidSymbolException) {
|
||||
view?.run {
|
||||
setErrorSymbolInvalid()
|
||||
showContact(true)
|
||||
}
|
||||
showInvalidSymbolError()
|
||||
} else {
|
||||
Timber.i("Login with symbol result: Success")
|
||||
view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
|
||||
@ -128,6 +125,9 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
loginErrorHandler.dispatch(user.error)
|
||||
lastError = user.error
|
||||
view?.showContact(true)
|
||||
if (user.error is InvalidSymbolException) {
|
||||
showInvalidSymbolError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onResourceNotLoading {
|
||||
@ -145,6 +145,13 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
return normalizedSymbol in definitelyInvalidSymbols
|
||||
}
|
||||
|
||||
private fun showInvalidSymbolError() {
|
||||
view?.run {
|
||||
setErrorSymbolInvalid()
|
||||
showContact(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun onFaqClick() {
|
||||
view?.openFaqPage()
|
||||
}
|
||||
|
@ -11,10 +11,8 @@ import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.dataOrThrow
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
@ -69,8 +67,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
appWidgetIds?.forEach { widgetId ->
|
||||
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
|
||||
val luckyNumberResource = getLuckyNumber(studentId, widgetId)
|
||||
val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString()
|
||||
val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString()
|
||||
val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber)
|
||||
.apply {
|
||||
setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-")
|
||||
@ -143,18 +140,18 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
|
||||
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (currentStudent != null) {
|
||||
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
|
||||
.toFirstResult()
|
||||
} else {
|
||||
Resource.Success<LuckyNumber?>(null)
|
||||
}
|
||||
.dataOrThrow
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "An error has occurred in lucky number provider")
|
||||
Resource.Error(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,13 +124,15 @@ 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 ->
|
||||
"${attachment.filename}: ${attachment.url}"
|
||||
})
|
||||
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,34 +175,69 @@ 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(deleteMessageSuccessString)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
view?.showProgress(false)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import android.view.View.VISIBLE
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.dataOrNull
|
||||
import io.github.wulkanowy.data.dataOrThrow
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Co
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.getErrorString
|
||||
import io.github.wulkanowy.utils.getPlural
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@ -67,25 +68,31 @@ class TimetableWidgetFactory(
|
||||
override fun onDestroy() {}
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId ->
|
||||
val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
|
||||
val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
|
||||
val appWidgetId = intent?.extras?.getInt(EXTRA_APPWIDGET_ID) ?: return
|
||||
val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
|
||||
val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
|
||||
|
||||
items = emptyList()
|
||||
|
||||
runBlocking {
|
||||
runCatching {
|
||||
runBlocking {
|
||||
val student = getStudent(studentId) ?: return@runBlocking
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
items = createItems(
|
||||
lessons = getLessons(student, semester, date),
|
||||
lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
|
||||
)
|
||||
val student = getStudent(studentId) ?: return@runBlocking
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val lessons = getLessons(student, semester, date)
|
||||
val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
|
||||
|
||||
createItems(lessons, lastSync)
|
||||
}
|
||||
.onFailure {
|
||||
items = listOf(TimetableWidgetItem.Error(it))
|
||||
Timber.e(it, "An error has occurred in timetable widget factory")
|
||||
}
|
||||
.onSuccess {
|
||||
items = it
|
||||
if (date == LocalDate.now()) {
|
||||
updateTodayLastLessonEnd(appWidgetId)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.e(it, "An error has occurred in timetable widget factory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +105,7 @@ class TimetableWidgetFactory(
|
||||
student: Student, semester: Semester, date: LocalDate
|
||||
): List<Timetable> {
|
||||
val timetable = timetableRepository.getTimetable(student, semester, date, date, false)
|
||||
val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty()
|
||||
val lessons = timetable.toFirstResult().dataOrThrow.lessons
|
||||
return lessons.sortedBy { it.number }
|
||||
}
|
||||
|
||||
@ -110,6 +117,7 @@ class TimetableWidgetFactory(
|
||||
BETWEEN_AND_BEFORE_LESSONS -> 0
|
||||
else -> null
|
||||
}
|
||||
|
||||
return buildList {
|
||||
lessons.forEach {
|
||||
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
|
||||
@ -133,15 +141,12 @@ class TimetableWidgetFactory(
|
||||
sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TIME_FORMAT_STYLE = "HH:mm"
|
||||
}
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews? {
|
||||
return when (val item = items.getOrNull(position) ?: return null) {
|
||||
is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item)
|
||||
is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item)
|
||||
is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item)
|
||||
is TimetableWidgetItem.Error -> getErrorItemRemoteView(item)
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,6 +218,18 @@ class TimetableWidgetFactory(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getErrorItemRemoteView(item: TimetableWidgetItem.Error): RemoteViews {
|
||||
return RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.item_widget_timetable_error
|
||||
).apply {
|
||||
setTextViewText(
|
||||
R.id.timetable_widget_item_error_message,
|
||||
context.resources.getErrorString(item.error)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTheme() {
|
||||
when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||
Configuration.UI_MODE_NIGHT_YES -> {
|
||||
@ -300,4 +317,8 @@ class TimetableWidgetFactory(
|
||||
synchronizationTime,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TIME_FORMAT_STYLE = "HH:mm"
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,15 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
|
||||
data class Synchronized(
|
||||
val timestamp: Instant,
|
||||
) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED)
|
||||
|
||||
data class Error(
|
||||
val error: Throwable
|
||||
) : TimetableWidgetItem(TimetableWidgetItemType.ERROR)
|
||||
}
|
||||
|
||||
enum class TimetableWidgetItemType {
|
||||
NORMAL,
|
||||
EMPTY,
|
||||
SYNCHRONIZED,
|
||||
ERROR,
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,15 +1,31 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import android.util.AndroidRuntimeException
|
||||
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 = WebkitCookieManager.getInstance()
|
||||
val webkitCookieManager: WebkitCookieManager? = getCookieManager()
|
||||
|
||||
/**
|
||||
* @see [https://stackoverflow.com/a/70354583/6695449]
|
||||
*/
|
||||
private fun getCookieManager(): WebkitCookieManager? {
|
||||
return try {
|
||||
WebkitCookieManager.getInstance()
|
||||
} catch (e: AndroidRuntimeException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun put(uri: URI?, responseHeaders: Map<String?, List<String?>>?) {
|
||||
if (uri == null || responseHeaders == null) return
|
||||
@ -23,7 +39,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
|
||||
|
||||
// process each of the headers
|
||||
for (headerValue in responseHeaders[headerKey].orEmpty()) {
|
||||
webkitCookieManager.setCookie(url, headerValue)
|
||||
webkitCookieManager?.setCookie(url, headerValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,7 +50,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
|
||||
): Map<String, List<String>> {
|
||||
require(!(uri == null || requestHeaders == null)) { "Argument is null" }
|
||||
val res = mutableMapOf<String, List<String>>()
|
||||
val cookie = webkitCookieManager.getCookie(uri.toString())
|
||||
val cookie = webkitCookieManager?.getCookie(uri.toString())
|
||||
if (cookie != null) res["Cookie"] = listOf(cookie)
|
||||
return res
|
||||
}
|
||||
@ -50,7 +66,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
|
||||
cookies.remove(uri, cookie)
|
||||
|
||||
override fun removeAll(): Boolean {
|
||||
webkitCookieManager.removeAllCookies(null)
|
||||
webkitCookieManager?.removeAllCookies(null) ?: return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
Wersja 2.4.0
|
||||
Wersja 2.5.0
|
||||
|
||||
— naprawiliśmy logowanie do aplikacji na odmianie standardowej
|
||||
— naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start
|
||||
— dodaliśmy oceny opisowe
|
||||
— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność
|
||||
— 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"
|
||||
|
@ -39,7 +39,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:editable="false"
|
||||
android:focusable="false"
|
||||
android:inputType="text"
|
||||
android:inputType="none"
|
||||
tools:ignore="Deprecated" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:editable="false"
|
||||
android:focusable="false"
|
||||
android:inputType="text"
|
||||
android:inputType="none"
|
||||
tools:ignore="Deprecated" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
@ -87,7 +87,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:editable="false"
|
||||
android:focusable="false"
|
||||
android:inputType="text"
|
||||
android:inputType="none"
|
||||
tools:ignore="Deprecated" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
@ -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"
|
||||
|
@ -261,6 +261,7 @@
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:hint="@string/login_domain_suffix_hint"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout"
|
||||
|
@ -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>
|
||||
|
12
app/src/main/res/layout/item_widget_timetable_error.xml
Normal file
12
app/src/main/res/layout/item_widget_timetable_error.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/timetable_widget_item_error_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingTop="48dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:textSize="16sp"
|
||||
tools:text="@string/error_unknown" />
|
@ -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" />
|
||||
|
@ -114,7 +114,5 @@
|
||||
android:text="@string/widget_timetable_no_items"
|
||||
android:textAppearance="?attr/textAppearanceBody1"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -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,6 +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">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>
|
||||
@ -97,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>
|
||||
@ -335,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>
|
||||
@ -382,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>
|
||||
@ -848,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>
|
||||
@ -864,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>
|
||||
|
@ -56,6 +56,7 @@
|
||||
<string name="login_invalid_email">Ungültige email</string>
|
||||
<string name="login_invalid_login">Den zugewiesenen Login anstelle von email verwenden</string>
|
||||
<string name="login_invalid_custom_email">Benutze den zugewiesenen Login oder E-Mail in @%1$s</string>
|
||||
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
|
||||
<string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string>
|
||||
<string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string>
|
||||
<string name="login_incorrect_symbol">Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers</string>
|
||||
@ -97,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>
|
||||
@ -295,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>
|
||||
@ -334,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>
|
||||
@ -754,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>
|
||||
@ -770,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>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user