1
0

Compare commits

...

30 Commits

Author SHA1 Message Date
d8b1264024 Merge branch 'release/0.21.0' into master 2020-09-27 22:12:27 +02:00
cddd17650b Version 0.21.0 2020-09-27 22:11:55 +02:00
a0f9c70036 Migrate from gson to moshi (#969) 2020-09-27 20:59:27 +02:00
2e05416fb5 Show groups next to subjects in timetable (#953) 2020-09-27 16:33:36 +02:00
d32ebd66de Add subjects sorting in grades (#946) 2020-09-27 16:28:39 +02:00
c6a99f1000 Add remembering the full screen mode in homework (#956)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-27 14:49:19 +02:00
bafe52e310 Different notification for notes and praises (#952) 2020-09-25 15:46:08 +02:00
e08abc1fc2 Show subjects without grades in "Grades" (#947) 2020-09-25 15:45:07 +02:00
2a74b11cce Add app shortcuts (#939)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-25 15:37:19 +02:00
b0b3ccfd53 Upgrade gradle wrapper to 6.6.1 (#968) 2020-09-20 21:54:59 +02:00
6c68456f7a Merge tag '0.20.5' into develop
Version 0.20.5
2020-09-19 13:03:32 +02:00
3e8e9b4ecc Merge branch 'release/0.20.5' into master 2020-09-19 13:03:28 +02:00
d6ebc343d5 Version 0.20.5 2020-09-19 01:25:34 +02:00
73be416807 Fix crash in flowWithResourceIn() (#967) 2020-09-19 00:57:55 +02:00
0cb65a29ba Merge tag '0.20.4' into develop
Version 0.20.4
2020-09-13 19:00:45 +02:00
13198f2ab4 Merge branch 'release/0.20.4' into master 2020-09-13 19:00:39 +02:00
cd92f37435 Version 0.20.4 2020-09-13 19:00:32 +02:00
5d8fb376ab Expand exam sync date range to next month (#960) 2020-09-13 18:37:34 +02:00
47150364d8 Fix lifecycle of timer tasks in timetable lessons (#958)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-13 16:27:53 +02:00
792b123598 Bump coil from 1.0.0-rc1 to 1.0.0-rc2 (#961) 2020-09-13 14:07:30 +00:00
acf5c8e9ba Bump firebase-crashlytics-gradle from 2.2.1 to 2.3.0 (#964) 2020-09-13 14:04:41 +00:00
53561668fc Bump hilt_version from 2.28.3-alpha to 2.29.1-alpha (#962) 2020-09-13 14:03:09 +00:00
7cfe58d311 Bump material from 1.2.0 to 1.2.1 (#963) 2020-09-13 14:02:31 +00:00
cd51fac621 Add eduportal.koszalin.pl register (#959) 2020-09-13 13:46:45 +02:00
adde5541e2 Move timetable notifications scheduling to background thread (#954) 2020-09-11 13:02:16 +02:00
6e56d3ff06 Ignore empty semesters on refresh (#955) 2020-09-09 13:28:44 +02:00
ec761f6329 Fix bug in grade statistics (#951) 2020-09-08 20:13:17 +02:00
6363c90e37 Disable sound of upcoming lessons notification (fix) (#950) 2020-09-07 20:28:32 +02:00
c30f105be5 Fix crash on unknown attendance category type (#949) 2020-09-07 09:35:26 +02:00
9f85b2206a Merge tag '0.20.3' into develop
Version 0.20.3
2020-09-04 23:42:12 +02:00
82 changed files with 474 additions and 148 deletions

View File

@ -14,7 +14,7 @@ cache:
branches:
only:
- develop
- 0.20.3
- 0.21.0
android:
licenses:
@ -48,8 +48,8 @@ before_script:
script:
- ./gradlew dependencies --stacktrace --daemon
- fossa --no-ansi || true
- ./gradlew -Pcoverage testPlayDebugUnitTest --stacktrace --daemon
- ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon
- ./gradlew -Pcoverage testFdroidDebugUnitTest --stacktrace --daemon
- ./gradlew -Pcoverage connectedFdroidDebugAndroidTest --stacktrace --daemon
- ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon
- |
if [ $TRAVIS_TAG ]; then

View File

@ -18,8 +18,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17
targetSdkVersion 29
versionCode 67
versionName "0.20.3"
versionCode 70
versionName "0.21.0"
multiDexEnabled true
resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -119,6 +119,7 @@ ext {
room = "2.2.5"
chucker = "3.2.0"
mockk = "1.10.0"
moshi = "1.9.3"
}
configurations.all {
@ -126,7 +127,7 @@ configurations.all {
}
dependencies {
implementation "io.github.wulkanowy:sdk:0.20.3"
implementation "io.github.wulkanowy:sdk:0.21.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
@ -147,7 +148,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.2.0"
implementation "com.google.android.material:material:1.2.1"
implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -170,13 +171,15 @@ dependencies {
implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.2.2"
implementation "com.google.code.gson:gson:2.8.6"
implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "fr.bipi.treessence:treessence:0.3.2"
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:1.0.0-rc1"
implementation "io.coil-kt:coil:1.0.0-rc2"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'

View File

@ -35,13 +35,13 @@ task jacocoTestReport(type: JacocoReport) {
dir: "$buildDir/intermediates/classes/debug",
excludes: excludes
) + fileTree(
dir: "$buildDir/tmp/kotlin-classes/playDebug",
dir: "$buildDir/tmp/kotlin-classes/fdroidDebug",
excludes: excludes
))
sourceDirectories.setFrom(files([
"src/main/java",
"src/play/java"
"src/fdroid/java"
]))
executionData.setFrom(fileTree(
dir: project.projectDir,

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.data
data class Resource<out T>(val status: Status, val data: T?, val error: Throwable?) {
data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)

View File

@ -1,8 +1,8 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
@ -12,6 +12,16 @@ import java.util.Date
class Converters {
private val moshi by lazy { Moshi.Builder().build() }
private val integerListAdapter by lazy {
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
}
private val stringMapAdapter by lazy {
moshi.adapter<Map<String, String>>(Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java))
}
@TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run {
Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate()
@ -39,22 +49,22 @@ class Converters {
fun intToMonth(value: Int?) = value?.let { Month.of(it) }
@TypeConverter
fun intListToGson(list: List<Int>): String {
return Gson().toJson(list)
fun intListToJson(list: List<Int>): String {
return integerListAdapter.toJson(list)
}
@TypeConverter
fun gsonToIntList(value: String): List<Int> {
return Gson().fromJson(value, object : TypeToken<List<Int>>() {}.type)
fun jsonToIntList(value: String): List<Int> {
return integerListAdapter.fromJson(value).orEmpty()
}
@TypeConverter
fun stringPairListToGson(list: List<Pair<String, String>>): String {
return Gson().toJson(list)
fun stringPairListToJson(list: List<Pair<String, String>>): String {
return stringMapAdapter.toJson(list.toMap())
}
@TypeConverter
fun gsonToStringPairList(value: String): List<Pair<String, String>> {
return Gson().fromJson(value, object : TypeToken<List<Pair<String, String>>>() {}.type)
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
return stringMapAdapter.fromJson(value).orEmpty().toList()
}
}

View File

@ -12,7 +12,7 @@ interface MessagesDao : BaseDao<Message> {
@Transaction
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment>
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Flow<List<Message>>

View File

@ -36,12 +36,6 @@ data class Message(
var unread: Boolean,
@ColumnInfo(name = "unread_by")
val unreadBy: Int,
@ColumnInfo(name = "read_by")
val readBy: Int,
val removed: Boolean,
@ColumnInfo(name = "has_attachments")
@ -54,5 +48,11 @@ data class Message(
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = ""
}

View File

@ -1,3 +1,9 @@
package io.github.wulkanowy.data.pojos
class Contributor(val displayName: String, val githubUsername: String)
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Contributor(
val displayName: String,
val githubUsername: String
)

View File

@ -1,7 +1,8 @@
package io.github.wulkanowy.data.repositories.appcreator
import android.content.res.AssetManager
import com.google.gson.Gson
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.utils.DispatchersProvider
import kotlinx.coroutines.withContext
@ -15,9 +16,9 @@ class AppCreatorRepository @Inject constructor(
) {
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
Gson().fromJson(
assets.open("contributors.json").bufferedReader().use { it.readText() },
Array<Contributor>::class.java
).toList()
val moshi = Moshi.Builder().build()
val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
val adapter = moshi.adapter<List<Contributor>>(type)
adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
}
}

View File

@ -2,9 +2,9 @@ package io.github.wulkanowy.data.repositories.exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import java.time.LocalDate
import javax.inject.Inject
@ -18,8 +18,8 @@ class ExamRepository @Inject constructor(
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getExams(semester, start.monday, end.sunday) },
fetch = { remote.getExams(student, semester, start.monday, end.sunday) },
query = { local.getExams(semester, start.startExamsDay, start.endExamsDay) },
fetch = { remote.getExams(student, semester, start.startExamsDay, start.endExamsDay) },
saveFetchResult = { old, new ->
local.deleteExams(old uniqueSubtract new)
local.saveExams(new uniqueSubtract old)

View File

@ -6,7 +6,6 @@ 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.db.entities.Student
import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
@ -29,7 +28,7 @@ class MessageLocal @Inject constructor(
messagesDb.deleteAll(messages)
}
fun getMessageWithAttachment(student: Student, message: Message): Flow<MessageWithAttachment> {
fun getMessageWithAttachment(student: Student, message: Message): Flow<MessageWithAttachment?> {
return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId)
}

View File

@ -30,12 +30,12 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) {
date = it.date ?: now(),
folderId = it.folderId,
unread = it.unread ?: false,
unreadBy = it.unreadBy ?: 0,
readBy = it.readBy ?: 0,
removed = it.removed,
hasAttachments = it.hasAttachments
).apply {
content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
}
}
}

View File

@ -34,12 +34,14 @@ class MessageRepository @Inject constructor(
fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource(
shouldFetch = {
checkNotNull(it, { "This message no longer exist!" })
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
it.message.unread || it.message.content.isEmpty()
},
query = { local.getMessageWithAttachment(student, message) },
fetch = { remote.getMessagesContentDetails(student, it.message, markAsRead) },
fetch = { remote.getMessagesContentDetails(student, it!!.message, markAsRead) },
saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old, { "Fetched message no longer exist!" })
local.updateMessages(listOf(old.message.copy(unread = !markAsRead).apply {
id = old.message.id
content = content.ifBlank { downloadedMessage }

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import javax.inject.Inject
import javax.inject.Singleton
@ -74,12 +75,25 @@ class PreferencesRepository @Inject constructor(
val fillMessageContent: Boolean
get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content)
val showGroupsInPlan: Boolean
get() = getBoolean(R.string.pref_key_timetable_show_groups, R.bool.pref_default_timetable_show_groups)
val showWholeClassPlan: String
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue(getString(R.string.pref_key_grade_sorting_mode, R.string.pref_default_grade_sorting_mode))
val showTimetableTimers: Boolean
get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers)
var isHomeworkFullscreen: Boolean
get() = getBoolean(R.string.pref_key_homework_fullscreen, R.bool.pref_default_homework_fullscreen)
set(value) = sharedPref.edit().putBoolean("homework_fullscreen", value).apply()
val showSubjectsWithoutGrades: Boolean
get() = getBoolean(R.string.pref_key_subjects_without_grades, R.bool.pref_default_subjects_without_grades)
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -41,7 +42,7 @@ class SemesterRepository @Inject constructor(
private suspend fun refreshSemesters(student: Student) {
val new = remote.getSemesters(student)
if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!")
if (new.isEmpty()) return Timber.i("Empty semester list!")
val old = local.getSemesters(student)
local.deleteSemesters(old.uniqueSubtract(new))

View File

@ -26,7 +26,9 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.time.LocalDateTime
import java.time.LocalDateTime.now
@ -35,7 +37,8 @@ import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val alarmManager: AlarmManager,
private val preferencesRepository: PreferencesRepository
private val preferencesRepository: PreferencesRepository,
private val dispatchersProvider: DispatchersProvider,
) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
@ -44,13 +47,15 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
}
fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
withContext(dispatchersProvider.backgroundThread) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
}
}
}
@ -61,28 +66,30 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
lessons.groupBy { it.date }
.map { it.value.sortedBy { lesson -> lesson.start } }
.map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } }
.map { day ->
day.forEachIndexed { index, lesson ->
val intent = createIntent(student, lesson, day.getOrNull(index + 1))
withContext(dispatchersProvider.backgroundThread) {
lessons.groupBy { it.date }
.map { it.value.sortedBy { lesson -> lesson.start } }
.map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } }
.map { day ->
day.forEachIndexed { index, lesson ->
val intent = createIntent(student, lesson, day.getOrNull(index + 1))
if (lesson.start > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson))
}
if (lesson.start > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson))
}
if (lesson.end > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
if (day.lastIndex == index) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
if (lesson.end > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
if (day.lastIndex == index) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
}
}
}
}
}
}
}
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {

View File

@ -44,6 +44,7 @@ class SyncManager @Inject constructor(
if (SDK_INT >= O) {
channels.forEach { it.create() }
notificationManager.deleteNotificationChannel("lesson_channel")
notificationManager.deleteNotificationChannel("new_entries_channel")
}

View File

@ -17,7 +17,7 @@ class UpcomingLessonsChannel @Inject constructor(
) : Channel {
companion object {
const val CHANNEL_ID = "lesson_channel"
const val CHANNEL_ID = "upcoming_lesson_channel"
}
override fun create() {

View File

@ -3,8 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.exam.ExamRepository
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.waitForResult
import java.time.LocalDate.now
import javax.inject.Inject
@ -12,6 +10,6 @@ import javax.inject.Inject
class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
examRepository.getExams(student, semester, now().monday, now().sunday, true).waitForResult()
examRepository.getExams(student, semester, now(), now(), true).waitForResult()
}
}

View File

@ -14,6 +14,9 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.NEUTRAL
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.POSITIVE
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
@ -41,8 +44,20 @@ class NoteWork @Inject constructor(
private fun notify(notes: List<Note>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewNotesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size))
.setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size))
.setContentTitle(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_new_items, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_new_items, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size)
}
)
.setContentText(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_notify_new_items, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_notify_new_items, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size)
}
)
.setSmallIcon(R.drawable.ic_stat_note)
.setAutoCancel(true)
.setDefaults(DEFAULT_ALL)
@ -52,7 +67,13 @@ class NoteWork @Inject constructor(
PendingIntent.getActivity(context, MainView.Section.NOTE.id,
MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT))
.setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size))
setSummaryText(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_number_item, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_number_item, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)
}
)
notes.forEach { addLine("${it.teacher}: ${it.category}") }
this
})

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.ui.modules.grade
enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"),
DATE("date");
companion object {
fun getByValue(value: String) = values().firstOrNull { it.value == value } ?: ALPHABETIC
}
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.details
import android.annotation.SuppressLint
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -10,6 +11,8 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource
@ -184,10 +187,20 @@ class GradeDetailsPresenter @Inject constructor(
}
}
@SuppressLint("DefaultLocale")
private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> {
return items
.filter { it.grades.isNotEmpty() }
.sortedBy { it.subject }
.let { gradesWithAverages ->
if (!preferencesRepository.showSubjectsWithoutGrades) {
gradesWithAverages.filter { it.grades.isNotEmpty() }
} else gradesWithAverages
}
.let {
when (preferencesRepository.gradeSortingMode) {
DATE -> it.sortedByDescending { gradeDetailsWithAverage -> gradeDetailsWithAverage.grades.firstOrNull()?.date }
ALPHABETIC -> it.sortedBy { gradeDetailsWithAverage -> gradeDetailsWithAverage.subject.toLowerCase() }
}
}
.map { (subject, average, points, _, grades) ->
val subItems = grades
.sortedByDescending { it.date }

View File

@ -164,8 +164,8 @@ class GradeStatisticsPresenter @Inject constructor(
Status.SUCCESS -> {
Timber.i("Loading grade stats result: Success")
view?.run {
showEmpty(it.data!!.isEmpty())
showContent(it.data.isNotEmpty())
showEmpty(it.data!!.isEmpty() || it.data.first().partial.isEmpty())
showContent(it.data.isNotEmpty() && it.data.first().partial.isNotEmpty())
showErrorView(false)
updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)

View File

@ -29,6 +29,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
attachments = value?.attachments.orEmpty()
}
var isHomeworkFullscreen = false
var onAttachmentClickListener: (url: String) -> Unit = {}
var onFullScreenClickListener = {}
@ -67,6 +69,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
homeworkDialogSubject.text = homework?.subject
homeworkDialogTeacher.text = homework?.teacher
homeworkDialogContent.text = homework?.content
homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE
homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE
homeworkDialogFullScreen.setOnClickListener {
homeworkDialogFullScreen.visibility = GONE
homeworkDialogFullScreenExit.visibility = VISIBLE

View File

@ -62,12 +62,25 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
homeworkDialogClose.setOnClickListener { dismiss() }
}
if (presenter.isHomeworkFullscreen) {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
} else {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
}
with(binding.homeworkDialogRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = detailsAdapter.apply {
onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) }
onFullScreenClickListener = { dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) }
onFullScreenExitClickListener = { dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) }
onFullScreenClickListener = {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
presenter.isHomeworkFullscreen = true
}
onFullScreenExitClickListener = {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
presenter.isHomeworkFullscreen = false
}
isHomeworkFullscreen = presenter.isHomeworkFullscreen
homework = this@HomeworkDetailsDialog.homework
}
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.homework.details
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.repositories.homework.HomeworkRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
@ -16,9 +17,16 @@ class HomeworkDetailsPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val homeworkRepository: HomeworkRepository,
private val analytics: FirebaseAnalyticsHelper
private val analytics: FirebaseAnalyticsHelper,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<HomeworkDetailsView>(errorHandler, studentRepository) {
var isHomeworkFullscreen
get() = preferencesRepository.isHomeworkFullscreen
set(value) {
preferencesRepository.isHomeworkFullscreen = value
}
override fun onAttachView(view: HomeworkDetailsView) {
super.onAttachView(view)
view.initView()

View File

@ -1,14 +1,21 @@
package io.github.wulkanowy.ui.modules.main
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.view.ViewCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
@ -31,6 +38,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
@ -48,6 +56,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
@Inject
lateinit var analytics: FirebaseAnalyticsHelper
@Inject
lateinit var appInfo: AppInfo
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
private val navController = FragNavController(supportFragmentManager, R.id.mainFragmentContainer)
@ -59,7 +70,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
return Intent(context, MainActivity::class.java)
.apply {
if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
startMenu?.let { putExtra(EXTRA_START_MENU, it) }
startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
}
}
}
@ -83,18 +94,45 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance()
)
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.mainToolbar)
messageContainer = binding.mainFragmentContainer
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.Section)
presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
with(navController) {
initialize(startMenuIndex, savedInstanceState)
pushFragment(moreMenuFragments[startMenuMoreIndex])
}
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) initShortcuts()
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun initShortcuts() {
val shortcutsList = mutableListOf<ShortcutInfo>()
listOf(
Triple(getString(R.string.grade_title), R.drawable.ic_shortcut_grade, MainView.Section.GRADE),
Triple(getString(R.string.attendance_title), R.drawable.ic_shortcut_attendance, MainView.Section.ATTENDANCE),
Triple(getString(R.string.exam_title), R.drawable.ic_shortcut_exam, MainView.Section.EXAM),
Triple(getString(R.string.timetable_title), R.drawable.ic_shortcut_timetable, MainView.Section.TIMETABLE),
Triple(getString(R.string.message_title), R.drawable.ic_shortcut_message, MainView.Section.MESSAGE)
).forEach { (title, icon, enum) ->
shortcutsList.add(ShortcutInfo.Builder(applicationContext, title)
.setShortLabel(title)
.setLongLabel(title)
.setIcon(Icon.createWithResource(applicationContext, icon))
.setIntents(arrayOf(
Intent(applicationContext, MainActivity::class.java).setAction(Intent.ACTION_VIEW),
Intent(applicationContext, MainActivity::class.java).putExtra(EXTRA_START_MENU, enum.id)
.setAction(Intent.ACTION_VIEW).addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)))
.build())
}
getSystemService<ShortcutManager>()?.dynamicShortcuts = shortcutsList
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {

View File

@ -64,7 +64,8 @@ class MessagePreviewPresenter @Inject constructor(
when (it.status) {
Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started")
Status.SUCCESS -> {
Timber.i("Loading message ${it.data!!.message.messageId} preview result: Success ")
Timber.i("Loading message ${message.messageId} preview result: Success ")
checkNotNull(it.data, { "Can't find message in local db! Probably no longer exist in this folder" })
this@MessagePreviewPresenter.message = it.data.message
this@MessagePreviewPresenter.attachments = it.data.attachments
view?.apply {
@ -194,6 +195,7 @@ class MessagePreviewPresenter @Inject constructor(
view?.run {
lastError = error
setErrorDetails(message)
showContent(false)
showErrorView(true)
setErrorRetryCallback { retryCallback() }
}

View File

@ -6,6 +6,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
@ -40,12 +41,14 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var showWholeClassPlan: String = "no"
var showGroupsInPlan: Boolean = false
var showTimers: Boolean = false
private val timers = mutableMapOf<Int, Timer>()
private fun resetTimers() {
Timber.d("Timetable timers reset")
fun resetTimers() {
Timber.d("Timetable timers (${timers.size}) reset")
with(timers) {
forEach { (_, timer) -> timer.cancel() }
clear()
@ -69,11 +72,6 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
resetTimers()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val lesson = items[position]
@ -103,6 +101,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
with(binding) {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
timetableItemGroup.text = lesson.group
timetableItemRoom.text = lesson.room
timetableItemTeacher.text = lesson.teacher
timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
@ -112,8 +111,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
bindNormalDescription(binding, lesson)
bindNormalColors(binding, lesson)
if (lesson.isStudentPlan && showTimers) timers[position] = timer(period = 1000) {
root.post { updateTimeLeft(binding, lesson, position) }
if (lesson.isStudentPlan && showTimers) {
timers[position] = timer(period = 1000) {
if (ViewCompat.isAttachedToWindow(root)) {
root.post { updateTimeLeft(binding, lesson, position) }
}
}
} else {
// reset item on set changed
timetableItemTimeUntil.visibility = GONE
@ -218,6 +221,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
timetableItemDescription.text = lesson.info
timetableItemRoom.visibility = GONE
timetableItemGroup.visibility = GONE
timetableItemTeacher.visibility = GONE
timetableItemDescription.setTextColor(root.context.getThemeAttrColor(
@ -227,6 +231,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} else {
timetableItemDescription.visibility = GONE
timetableItemRoom.visibility = VISIBLE
timetableItemGroup.visibility = if (showGroupsInPlan && lesson.group.isNotBlank()) VISIBLE else GONE
timetableItemTeacher.visibility = VISIBLE
}
}

View File

@ -88,11 +88,12 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
else false
}
override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showTimetableTimers: Boolean) {
override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean) {
with(timetableAdapter) {
items = data.toMutableList()
showTimers = showTimetableTimers
showWholeClassPlan = showWholeClassPlanType
showGroupsInPlan = showGroupsInPlanType
notifyDataSetChanged()
}
}
@ -185,6 +186,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
}
override fun onDestroyView() {
timetableAdapter.resetTimers()
presenter.onDetachView()
super.onDestroyView()
}

View File

@ -143,6 +143,7 @@ class TimetablePresenter @Inject constructor(
view?.apply {
updateData(
showWholeClassPlanType = prefRepository.showWholeClassPlan,
showGroupsInPlanType = prefRepository.showGroupsInPlan,
showTimetableTimers = prefRepository.showTimetableTimers,
data = it.data!!
.filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true }

View File

@ -12,7 +12,7 @@ interface TimetableView : BaseView {
fun initView()
fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showTimetableTimers: Boolean)
fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean)
fun updateNavigationDay(date: String)

View File

@ -27,7 +27,7 @@ private fun calculatePercentage(presence: Double, absence: Double): Double {
}
inline val Attendance.description
get() = when (AttendanceCategory.valueOf(name)) {
get() = when (AttendanceCategory.getCategoryByName(name)) {
AttendanceCategory.PRESENCE -> R.string.attendance_present
AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused
AttendanceCategory.ABSENCE_EXCUSED -> R.string.attendance_absence_excused

View File

@ -80,17 +80,13 @@ fun <T> flowWithResource(block: suspend () -> T) = flow {
fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.loading())
try {
block()
.catch { emit(Resource.error(it)) }
.collect {
if (it.status != Status.LOADING) { // LOADING is already emitted
emit(it)
}
block()
.catch { emit(Resource.error(it)) }
.collect {
if (it.status != Status.LOADING) { // LOADING is already emitted
emit(it)
}
} catch (e: Throwable) {
emit(Resource.error(e))
}
}
}
fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach {

View File

@ -13,8 +13,7 @@ import java.time.Month
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter.ofPattern
import java.time.format.TextStyle.FULL_STANDALONE
import java.time.format.TextStyle.*
import java.time.format.TextStyle.FULL
import java.time.temporal.TemporalAdjusters.firstInMonth
import java.time.temporal.TemporalAdjusters.next
import java.time.temporal.TemporalAdjusters.previous
@ -78,6 +77,12 @@ inline val LocalDate.nextOrSameSchoolDay: LocalDate
}
}
inline val LocalDate.startExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday
inline val LocalDate.endExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday.plusWeeks(4).minusDays(1)
inline val LocalDate.previousOrSameSchoolDay: LocalDate
get() {
return when (dayOfWeek) {

View File

@ -1,4 +1,10 @@
Wersja 0.20.3
- naprawiliśmy opisy wpisów frekwencyjnych, które teraz będą już w całości po polsku
Wersja 0.21.0
- naprawiliśmy logowanie do tarnowskiego dziennika
- naprawiliśmy wyświetlanie podsumowania punktów klasy
- dodaliśmy skróty aplikacji
- dodaliśmy opcję pokazywania nazwy grupy w planie lekcji na liście
- dodaliśmy opcję pokazywania w ocenach przedmiotów bez ocen
- dodaliśmy dodatkowe opcje sortowania ocen
- dodaliśmy rozróżnianie powiadomień dla pochwał i uwag
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -20,17 +21,18 @@
android:id="@+id/creatorRecycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_weight="1"
tools:listitem="@layout/item_contributor" />
<com.google.android.material.button.MaterialButton
android:id="@+id/creatorSeeMore"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="8dp"
android:layout_marginRight="32dp"
android:layout_marginBottom="8dp"
android:text="@string/contributor_see_more" />
</LinearLayout>
</FrameLayout>

View File

@ -15,7 +15,8 @@
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/contributor_avatar_description" />
android:contentDescription="@string/contributor_avatar_description"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/creatorItemName"

View File

@ -74,7 +74,22 @@
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart"
tools:text="22"
tools:visibility="gone" />
tools:visibility="visible" />
<TextView
android:id="@+id/timetableItemGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="5dp"
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"
tools:text="(2/2)"
tools:visibility="visible" />
<TextView
android:id="@+id/timetableItemTeacher"
@ -87,9 +102,9 @@
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
app:layout_constraintStart_toEndOf="@id/timetableItemRoom"
app:layout_constraintStart_toEndOf="@id/timetableItemGroup"
tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="gone" />
tools:visibility="visible" />
<TextView
android:id="@+id/timetableItemDescription"
@ -101,7 +116,7 @@
android:textColor="?colorTimetableChange"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart"
app:layout_constraintStart_toEndOf="@+id/timetableItemTeacher"
app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
tools:visibility="visible" />

View File

@ -19,7 +19,7 @@
<string name="homework_title">Hausaufgaben</string>
<string name="account_title">Wählen Sie ein Konto</string>
<!--Subtitles-->
<string name="grade_subtitle">Semester %d, %d/%d</string>
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
<!--Login-->
<string name="login_header_default">Melden Sie sich mit dem Studenten- oder Elternkonto an</string>
<string name="login_header_symbol">Geben Sie das Symbol</string>

View File

@ -29,6 +29,10 @@
<item>0,5</item>
<item>0,75</item>
</string-array>
<string-array name="grade_sorting_mode_entries">
<item>Alfabetycznie</item>
<item>Według daty</item>
</string-array>
<string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item>
<item>Wulkanowy</item>

View File

@ -19,7 +19,7 @@
<string name="homework_title">Zadania domowe</string>
<string name="account_title">Wybierz konto</string>
<!--Subtitles-->
<string name="grade_subtitle">Semestr %d, %d/%d</string>
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
<!--Login-->
<string name="login_header_default">Zaloguj się za pomocą konta ucznia lub rodzica</string>
<string name="login_header_symbol">Podaj symbol</string>
@ -252,6 +252,44 @@
<item quantity="many">Masz %1$d nowych uwag</item>
<item quantity="other">Masz %1$d nowych uwag</item>
</plurals>
<!--Praise-->
<plurals name="praise_number_item">
<item quantity="one">%d pochwała</item>
<item quantity="few">%d pochwały</item>
<item quantity="many">%d pochwał</item>
<item quantity="other">%d pochwał</item>
</plurals>
<plurals name="praise_new_items">
<item quantity="one">Nowa pochwała</item>
<item quantity="few">Nowe pochwały</item>
<item quantity="many">Nowe pochwały</item>
<item quantity="other">Nowe pochwały</item>
</plurals>
<plurals name="praise_notify_new_items">
<item quantity="one">Masz %1$d nową pochwałę</item>
<item quantity="few">Masz %1$d nowe pochwały</item>
<item quantity="many">Masz %1$d nowych pochwał</item>
<item quantity="other">Masz %1$d nowych pochwał</item>
</plurals>
<!--Neutral notes-->
<plurals name="neutral_note_number_item">
<item quantity="one">%d neutralna uwaga</item>
<item quantity="few">%d neutralne uwagi</item>
<item quantity="many">%d neutralnych uwag</item>
<item quantity="other">%d neutralnych uwag</item>
</plurals>
<plurals name="neutral_note_new_items">
<item quantity="one">Nowa neutralna uwaga</item>
<item quantity="few">Nowe neutralne uwagi</item>
<item quantity="many">Nowe neutralne uwagi</item>
<item quantity="other">Nowe neutralne uwagi</item>
</plurals>
<plurals name="neutral_note_notify_new_items">
<item quantity="one">Masz %1$d nową neutralną uwagę</item>
<item quantity="few">Masz %1$d nowe neutralne uwagi</item>
<item quantity="many">Masz %1$d nowych neutralnych uwag</item>
<item quantity="other">Masz %1$d nowych neutralnych uwag</item>
</plurals>
<!--Homework-->
<string name="homework_no_items">Brak zadań domowych</string>
<string name="homework_mark_as_done">Wykonane</string>
@ -357,9 +395,12 @@
<string name="pref_view_app_theme">Motyw aplikacji</string>
<string name="pref_view_expand_grade">Rozwiń oceny</string>
<string name="pref_view_timetable_show_timers">Oznaczaj bieżącą lekcję na planie</string>
<string name="pref_view_timetable_show_groups">Pokazuj grupę obok przedmiotu na planie</string>
<string name="pref_view_grade_statistics_list">Pokazuj listę wykresów w ocenach klasy</string>
<string name="pref_view_timetable_show_whole_class">Pokazuj lekcje całej klasy</string>
<string name="pref_view_subjects_without_grades">Pokazuj przedmioty bez ocen w Oceny</string>
<string name="pref_view_grade_color_scheme">Schemat kolorów ocen</string>
<string name="pref_view_grade_sorting_mode">Sortowanie przedmiotów w "Oceny"</string>
<string name="pref_view_app_language">Język aplikacji</string>
<string name="pref_notify_header">Powiadomienia</string>
<string name="pref_notify_switch">Pokazuj powiadomienia</string>

View File

@ -19,7 +19,7 @@
<string name="homework_title">Домашние задания</string>
<string name="account_title">Выберите аккаунт</string>
<!--Subtitles-->
<string name="grade_subtitle">%d семестр, %d/%d</string>
<string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
<!--Login-->
<string name="login_header_default">Авторизируйтесь при помощи аккаунта ученика или родителя</string>
<string name="login_header_symbol">Впишите \"symbol\"</string>

View File

@ -19,7 +19,7 @@
<string name="homework_title">Домашні завдання</string>
<string name="account_title">Оберіть аккаунт</string>
<!--Subtitles-->
<string name="grade_subtitle">%d семестр, %d/%d</string>
<string name="grade_subtitle">%1$d семестр, %2$d/%3$d</string>
<!--Login-->
<string name="login_header_default">Авторизуйтеся за допомогою аккаунта учня або батька</string>
<string name="login_header_symbol">Впишіть \"symbol\"</string>

View File

@ -7,6 +7,7 @@
<item>Lubelski Portal Oświatowy</item>
<item>EduNet Miasta Tarnowa</item>
<item>ResMan Rzeszów</item>
<item>Platforma Edukacyjna Koszalina</item>
<item>Rawa Mazowiecka - Platforma vEdukacja</item>
<item>Zduńska Wola - e-Urząd</item>
<item>Sieradz - Portal oświatowy</item>
@ -27,6 +28,7 @@
<item>https://edu.lublin.eu</item>
<item>https://umt.tarnow.pl</item>
<item>https://resman.pl</item>
<item>https://eduportal.koszalin.pl</item>
<item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item>
<item>https://vulcan.net.pl/</item>
@ -47,6 +49,7 @@
<item>lublin</item>
<item>tarnow</item>
<item>rzeszow</item>
<item>koszalin</item>
<item>rawamazowiecka</item>
<item>zdunskawola</item>
<item>sieradz</item>

View File

@ -18,6 +18,10 @@
<string name="pref_default_grade_modifier_plus">0.33</string>
<string name="pref_default_grade_modifier_minus">0.33</string>
<bool name="pref_default_fill_message_content">true</bool>
<bool name="pref_default_timetable_show_groups">false</bool>
<string name="pref_default_timetable_show_whole_class">no</string>
<string name="pref_default_grade_sorting_mode">alphabetic</string>
<bool name="pref_default_timetable_show_timers">false</bool>
<bool name="pref_default_homework_fullscreen">false</bool>
<bool name="pref_default_subjects_without_grades">false</bool>
</resources>

View File

@ -20,6 +20,10 @@
<string name="pref_key_grade_modifier_plus">grade_modifier_plus</string>
<string name="pref_key_grade_modifier_minus">grade_modifier_minus</string>
<string name="pref_key_fill_message_content">fill_message_content</string>
<string name="pref_key_grade_sorting_mode">grade_sorting_mode</string>
<string name="pref_key_timetable_show_whole_class">show_whole_class_plan</string>
<string name="pref_key_timetable_show_groups">show_groups_in_plan</string>
<string name="pref_key_timetable_show_timers">timetable_show_timers</string>
<string name="pref_key_homework_fullscreen">homework_fullscreen</string>
<string name="pref_key_subjects_without_grades">subjects_without_grades</string>
</resources>

View File

@ -75,6 +75,15 @@
<item>0.75</item>
</string-array>
<string-array name="grade_sorting_mode_entries">
<item>Alphabetic</item>
<item>By date</item>
</string-array>
<string-array name="grade_sorting_mode_values" translatable="false">
<item>alphabetic</item>
<item>date</item>
</string-array>
<string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item>
<item>Wulkanowy</item>

View File

@ -21,7 +21,7 @@
<!--Subtitles-->
<string name="grade_subtitle">Semester %d, %d/%d</string>
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
<!--Login-->
@ -249,6 +249,34 @@
<item quantity="other">You received %1$d notes</item>
</plurals>
<!--Praise-->
<plurals name="praise_number_item">
<item quantity="one">%d praise</item>
<item quantity="other">%d praises</item>
</plurals>
<plurals name="praise_new_items">
<item quantity="one">New praise</item>
<item quantity="other">New praises</item>
</plurals>
<plurals name="praise_notify_new_items">
<item quantity="one">You received %1$d praise</item>
<item quantity="other">You received %1$d praises</item>
</plurals>
<!--Neutral notes-->
<plurals name="neutral_note_number_item">
<item quantity="one">%d neutral note</item>
<item quantity="other">%d neutral notes</item>
</plurals>
<plurals name="neutral_note_new_items">
<item quantity="one">New neutral note</item>
<item quantity="other">New neutral notes</item>
</plurals>
<plurals name="neutral_note_notify_new_items">
<item quantity="one">You received %1$d neutral note</item>
<item quantity="other">You received %1$d neutral notes</item>
</plurals>
<!--Homework-->
<string name="homework_no_items">No info about homework</string>
@ -383,9 +411,12 @@
<string name="pref_view_app_theme">Application theme</string>
<string name="pref_view_expand_grade">Expand grades</string>
<string name="pref_view_timetable_show_timers">Mark current lesson in timetable</string>
<string name="pref_view_timetable_show_groups">Show groups next to subjects in timetable</string>
<string name="pref_view_grade_statistics_list">Show chart list in class grades</string>
<string name="pref_view_timetable_show_whole_class">Show whole class lessons</string>
<string name="pref_view_subjects_without_grades">Show subjects without grades in Grades</string>
<string name="pref_view_grade_color_scheme">Grades color scheme</string>
<string name="pref_view_grade_sorting_mode">Subjects sorting in "Grades"</string>
<string name="pref_view_app_language">App language</string>
<string name="pref_notify_header">Notifications</string>

View File

@ -40,6 +40,18 @@
app:key="@string/pref_key_grade_statistics_list"
app:singleLineTitle="false"
app:title="@string/pref_view_grade_statistics_list" />
<SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_timetable_show_groups"
app:iconSpaceReserved="false"
app:key="@string/pref_key_timetable_show_groups"
app:singleLineTitle="false"
app:title="@string/pref_view_timetable_show_groups" />
<SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_subjects_without_grades"
app:iconSpaceReserved="false"
app:key="@string/pref_key_subjects_without_grades"
app:singleLineTitle="false"
app:title="@string/pref_view_subjects_without_grades" />
<ListPreference
app:defaultValue="@string/pref_default_timetable_show_whole_class"
app:entries="@array/timetable_show_whole_class_entries"
@ -56,6 +68,14 @@
app:key="@string/pref_key_grade_color_scheme"
app:title="@string/pref_view_grade_color_scheme"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/pref_default_grade_sorting_mode"
app:entries="@array/grade_sorting_mode_entries"
app:entryValues="@array/grade_sorting_mode_values"
app:iconSpaceReserved="false"
app:key="@string/pref_key_grade_sorting_mode"
app:title="@string/pref_view_grade_sorting_mode"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/pref_default_app_language"
app:entries="@array/app_language_entries"

View File

@ -90,10 +90,10 @@ fun getMessageEntity(
date = now(),
folderId = 1,
unread = unread,
unreadBy = 1,
readBy = 1,
removed = false,
hasAttachments = false
).apply {
this.content = content
unreadBy = 1
readBy = 1
}

View File

@ -171,4 +171,42 @@ class TimeExtensionTest {
assertEquals(of(2019, 5, 1), of(2019, 5, 1).getLastSchoolDayIfHoliday(2018))
assertEquals(of(2018, 5, 1), of(2019, 5, 1).getLastSchoolDayIfHoliday(2017))
}
@Test
fun getExamsCutOffDates() {
with(of(2020, 9, 13)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 14)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 15)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 16)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 17)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 18)) {
assertEquals(of(2020, 9, 14), startExamsDay)
assertEquals(of(2020, 10, 11), endExamsDay)
}
with(of(2020, 9, 19)) {
assertEquals(of(2020, 9, 21), startExamsDay)
assertEquals(of(2020, 10, 18), endExamsDay)
}
}
}

View File

@ -1,8 +1,8 @@
buildscript {
ext {
kotlin_version = '1.4.0'
kotlin_version = '1.4.10'
about_libraries = '8.3.0'
hilt_version = "2.28.3-alpha"
hilt_version = "2.29.1-alpha"
}
repositories {
mavenCentral()
@ -15,7 +15,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.1'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
classpath "com.github.triplet.gradle:play-publisher:2.7.5"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0"
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

22
gradlew.bat vendored
View File

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell