Compare commits

...

24 Commits

Author SHA1 Message Date
4aa31424d6 [4.0-beta.12] Update build.gradle, signing and changelog. 2020-03-10 23:50:24 +01:00
8a825227cb [Timetable] Disable setting metadata for normal type lessons. 2020-03-10 23:45:40 +01:00
cc1b581d7e [Grades] Show custom plus/minus value annotation in GradeDetailsDialog. 2020-03-10 23:44:02 +01:00
9936d90ae2 [Dropdown/Date] Clarify strings a bit (makes sense during weekends). 2020-03-10 22:04:13 +01:00
df1a241b2b [Timetable] Fix showing "no timetable" when all nearest lessons are cancelled. Fix a crash in timetable fragment. 2020-03-10 22:01:30 +01:00
ae89b33fb7 [Events/Manual] Implement syncing timetable when no lessons for the selected date. 2020-03-10 21:49:02 +01:00
e05b483f5c [Grades] Disable counting grade value when custom values not specified. 2020-03-10 21:45:38 +01:00
715f536b23 [MainActivity] Fix some critical errors. 2020-03-10 20:57:04 +01:00
930813fb8a [Agenda] Try to fix agenda fragment not attached crashes. 2020-03-10 20:25:14 +01:00
acd5e9b998 [Timetable] Implement lazy day loading. Introduce TimetableManager class. 2020-03-10 19:27:18 +01:00
06011bf4ae [Grades] Add grades config and mark as read menu items. 2020-03-10 18:38:28 +01:00
30e15b813c [HotFix/Timers] Change timers intervals from 1s to 500ms. 2020-03-09 22:03:44 +01:00
fcd7a7f349 [Grades] Make home card use GradeView. Update GradeDetailsDialog text color. Remove deprecated items. 2020-03-09 20:39:48 +01:00
42ef40439e [Grades] Implement showing unseen badges and marking as seen. Change default "hide improved" config value. 2020-03-09 20:18:11 +01:00
098beb14fe [Timetable/Generate] Add automatic timetable sync when no timetable for the selected week. 2020-03-09 14:57:14 +01:00
0b186a754a [API/Librus] Implement behaviour grades with types. Use optional "Phrase" in text grades. 2020-03-08 20:12:37 +01:00
d00963b53d [Grades] Implement getting correct grade colors. 2020-03-08 19:39:23 +01:00
e282af0e80 [Grades] Add option to hide improved grades. Make counting average without weight configurable. 2020-03-08 17:57:44 +01:00
630361849c [Notifications] Implement Quiet hours. Add missing timetable manual strings. 2020-03-08 17:22:14 +01:00
88a1de50ca [Changelog] Update the changelog a bit. 2020-03-07 20:14:05 +01:00
d8263d0b6a [Timetable/Manual] Add database migration to implement new model. 2020-03-07 20:09:22 +01:00
611ab0f100 [Events/Manual] Create custom views for dropdowns. Simplify dialog code. Fix wrong start time saving. 2020-03-07 20:03:47 +01:00
70c307b796 [UI/Grades] Change some fonts. 2020-03-07 11:54:47 +01:00
054a233ad6 [API/Librus] Handle some more maintenance cases. 2020-03-07 09:45:45 +01:00
75 changed files with 2188 additions and 1405 deletions

View File

@ -1,19 +1,23 @@
<h3>Wersja 4.0-beta.11, 2020-03-06</h3>
<h3>Wersja 4.0-beta.12, 2020-03-10</h3>
<ul>
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkosć oraz poprawność pobieranych danych.</li>
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych.</li>
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli &#x1F44F;</li>
<li>Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze</li>
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
<li>Nowe <b>Oceny</b> - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej</li>
<li>Opcja wyłączenia wybranych powiadomień z aplikacji</li>
<br>
<br>
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
<li>Nowe, przyjemniejsze powiadomienia</li>
<li>Łatwiejsze dodawanie własnych wydarzeń</li>
<li>Dużo poprawek w widoku <b>Wiadomości</b> oraz <b>Ogłoszeń</b></li>
<li>Częściowa <b>Obsługa dziennika EduDziennik</b></li>
<li>Librus: opcja logowania w dziennikach <b>Jednostek Samorządu Terytorialnego</b> oraz <b>Oświata w Radomiu</b></li>
<li>Librus: <b>poprawione obliczanie frekwencji</b></li>
<li>Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)</li>
<li>Lepsze <b>przekazywanie powiadomień na komputer</b> oraz łatwiejsze parowanie</li>
<li>Łatwiejsze dodawanie własnych wydarzeń</li>
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
@ -26,7 +30,6 @@
Staramy się usuwać takie przypadki, jednak na chwilę obecną mogą występować błędy w:
<ul>
<li>Wysyłanie wiadomości może nie działać w pełni prawidłowo - proszę o zgłaszanie wszystkich błędów na naszym serwerze Discord</li>
<li>Cisza nocna w powiadomieniach jeszcze nie działa.</li>
</ul>
<br>
<br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0x00, 0x37, 0x6a, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0xb8, 0x59, 0x75, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -47,6 +47,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
import pl.szczodrzynski.edziennik.utils.*
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager
import pl.szczodrzynski.edziennik.utils.managers.TimetableManager
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
@ -67,6 +68,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
val userActionManager by lazy { UserActionManager(this) }
val gradesManager by lazy { GradesManager(this) }
val timetableManager by lazy { TimetableManager(this) }
val db
get() = App.db

View File

@ -12,7 +12,6 @@ import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.Toast
@ -42,7 +41,6 @@ import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
@ -286,8 +284,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
setContentView(b.root)
Log.d(TAG, Signing.appPassword)
mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
@ -906,7 +902,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
bottomSheet.close()
bottomSheet.removeAllContextual()
bottomSheet.toggleGroupEnabled = false
bottomSheet.onCloseListener = null
drawer.close()
drawer.setSelection(target.id, fireOnClick = false)
navView.toolbar.setTitle(target.title ?: target.name)

View File

@ -22,7 +22,7 @@ import kotlin.coroutines.CoroutineContext
class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
companion object {
const val DATA_VERSION = 10
const val DATA_VERSION = 11
}
private val job = Job()

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.utils.models.Time
class ConfigSync(private val config: Config) {
private var mDontShowAppManagerDialog: Boolean? = null
@ -40,14 +41,19 @@ class ConfigSync(private val config: Config) {
| | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __|
| |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \
\___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/
private var mQuietHoursStart: Long? = null
var quietHoursStart: Long
get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", 0L); return mQuietHoursStart ?: 0L }
private var mQuietHoursEnabled: Boolean? = null
var quietHoursEnabled: Boolean
get() { mQuietHoursEnabled = mQuietHoursEnabled ?: config.values.get("quietHoursEnabled", false); return mQuietHoursEnabled ?: false }
set(value) { config.set("quietHoursEnabled", value); mQuietHoursEnabled = value }
private var mQuietHoursStart: Time? = null
var quietHoursStart: Time?
get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", null as Time?); return mQuietHoursStart }
set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value }
private var mQuietHoursEnd: Long? = null
var quietHoursEnd: Long
get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", 0L); return mQuietHoursEnd ?: 0L }
private var mQuietHoursEnd: Time? = null
var quietHoursEnd: Time?
get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", null as Time?); return mQuietHoursEnd }
set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value }
private var mQuietDuringLessons: Boolean? = null

View File

@ -26,6 +26,16 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
get() { mCountZeroToAvg = mCountZeroToAvg ?: config.values.get("countZeroToAvg", true); return mCountZeroToAvg ?: true }
set(value) { config.set("countZeroToAvg", value); mCountZeroToAvg = value }
private var mHideImproved: Boolean? = null
var hideImproved: Boolean
get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
set(value) { config.set("hideImproved", value); mHideImproved = value }
private var mAverageWithoutWeight: Boolean? = null
var averageWithoutWeight: Boolean
get() { mAverageWithoutWeight = mAverageWithoutWeight ?: config.values.get("averageWithoutWeight", true); return mAverageWithoutWeight ?: true }
set(value) { config.set("averageWithoutWeight", value); mAverageWithoutWeight = value }
private var mPlusValue: Float? = null
var plusValue: Float?
get() { mPlusValue = mPlusValue ?: config.values.getFloat("plusValue"); return mPlusValue }

View File

@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.math.abs
class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
init { config.apply {
@ -42,9 +43,7 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
devModePassword = p.getString("$s.devModePassword", null).fix()
sync.tokenApp = p.getString("$s.fcmToken", null).fix()
timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0
sync.quietHoursStart = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0
sync.quietHoursEnd = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false
ui.headerBackground = p.getString("$s.headerBackground", null).fix()
ui.appBackground = p.getString("$s.appBackground", null).fix()
@ -59,6 +58,22 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true
timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) }
val startMillis = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
val endMillis = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
if (startMillis > 0) {
try {
sync.quietHoursStart = Time.fromMillis(abs(startMillis))
sync.quietHoursEnd = Time.fromMillis(abs(endMillis))
sync.quietHoursEnabled = true
}
catch (_: Exception) {}
}
else {
sync.quietHoursEnabled = false
sync.quietHoursStart = null
sync.quietHoursEnd = null
}
sync.tokenMobidziennikList = listOf()
sync.tokenVulcanList = listOf()
sync.tokenLibrusList = listOf()

View File

@ -11,6 +11,8 @@ import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.math.abs
class ConfigMigration(app: App, config: Config) {
init { config.apply {
@ -43,8 +45,9 @@ class ConfigMigration(app: App, config: Config) {
sync.interval = 1*HOUR.toInt()
sync.notifyAboutUpdates = true
sync.onlyWifi = false
sync.quietHoursStart = 0
sync.quietHoursEnd = 0
sync.quietHoursEnabled = false
sync.quietHoursStart = null
sync.quietHoursEnd = null
sync.quietDuringLessons = false
sync.tokenApp = null
sync.tokenMobidziennik = null
@ -69,5 +72,25 @@ class ConfigMigration(app: App, config: Config) {
dataVersion = 10
}
if (dataVersion < 11) {
val startMillis = config.values.get("quietHoursStart", 0L)
val endMillis = config.values.get("quietHoursEnd", 0L)
if (startMillis > 0) {
try {
sync.quietHoursStart = Time.fromMillis(abs(startMillis))
sync.quietHoursEnd = Time.fromMillis(abs(endMillis))
sync.quietHoursEnabled = true
}
catch (_: Exception) {}
}
else {
sync.quietHoursEnabled = false
sync.quietHoursStart = null
sync.quietHoursEnd = null
}
dataVersion = 11
}
}}
}

View File

@ -49,6 +49,7 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
val error = if (response?.code() == 200) null else
json.getString("Code") ?:
json.getString("Message") ?:
json.getString("Status") ?:
response?.parserErrorBody
error?.let { code ->
when (code) {
@ -67,6 +68,7 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
"Nieprawidłowy węzeł." -> ERROR_LIBRUS_API_INCORRECT_ENDPOINT
"NoticeboardProblem" -> ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM
"DeviceRegistered" -> ERROR_LIBRUS_API_DEVICE_REGISTERED
"Maintenance" -> ERROR_LIBRUS_API_MAINTENANCE
else -> ERROR_LIBRUS_API_OTHER
}.let { errorCode ->
if (errorCode !in ignoreErrors) {
@ -119,6 +121,7 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.allowErrorCode(HTTP_NOT_FOUND)
.allowErrorCode(503)
.callback(callback)
.build()
.enqueue()

View File

@ -44,6 +44,7 @@ open class LibrusPortal(open val data: DataLibrus) {
"Access token is invalid" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED
"ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED
"Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND
"Unable to refresh the account" -> ERROR_LIBRUS_PORTAL_MAINTENANCE
else -> when (json.getString("hint")) {
"Error while decoding to JSON" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED
else -> ERROR_LIBRUS_PORTAL_OTHER
@ -97,6 +98,7 @@ open class LibrusPortal(open val data: DataLibrus) {
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
.allowErrorCode(HttpURLConnection.HTTP_GONE)
.allowErrorCode(424)
.callback(callback)
.build()
.enqueue()

View File

@ -27,6 +27,17 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
private val nameFormat by lazy { DecimalFormat("#.##") }
private val types by lazy {
mapOf(
1 to ("wz" to "wzorowe"),
2 to ("bdb" to "bardzo dobre"),
3 to ("db" to "dobre"),
4 to ("popr" to "poprawne"),
5 to ("ndp" to "nieodpowiednie"),
6 to ("ng" to "naganne")
)
}
init { data.profile?.also { profile ->
apiGet(TAG, "BehaviourGrades/Points") { json ->
@ -95,8 +106,12 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
val addedDate = grade.getString("AddDate")?.let { Date.fromIso(it) }
?: System.currentTimeMillis()
val text = grade.getString("Text")
val type = grade.getJsonObject("BehaviourGrade")?.getInt("Id")?.let { types[it] }
val name = when {
value != null -> (if (value >= 0) "+" else "") + nameFormat.format(value)
type != null -> type.first
value != null -> (if (value > 0) "+" else "") + nameFormat.format(value)
shortName != null -> shortName
else -> return@forEach
}
@ -115,14 +130,14 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
val categoryName = category?.text ?: ""
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
if (comments.isNotEmpty()) {
data.gradeCategories.singleOrNull {
it.type == GradeCategory.TYPE_BEHAVIOUR_COMMENT
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
}?.text
} else null
} ?: ""
val comments = grade.getJsonArray("Comments")
?.asJsonObjectList()
?.mapNotNull { comment ->
val cId = comment.getLong("Id") ?: return@mapNotNull null
data.gradeCategories[cId]?.text
} ?: listOf()
val description = listOfNotNull(type?.second) + comments
val valueFrom = value ?: category?.valueFrom ?: 0f
val valueTo = category?.valueTo ?: 0f
@ -136,8 +151,8 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
weight = -1f,
color = color,
category = categoryName,
description = description,
comment = null,
description = text ?: description.join(" - "),
comment = if (text != null) description.join(" - ") else null,
semester = semester,
teacherId = teacherId,
subjectId = 1

View File

@ -57,7 +57,7 @@ class LibrusApiTextGrades(override val data: DataLibrus,
color = category?.color ?: -1,
category = category?.text ?: "",
description = description,
comment = null,
comment = grade.getString("Phrase") /* whatever it is */,
semester = semester,
teacherId = teacherId,
subjectId = subjectId

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDAtDsP5Si===.$param2".sha256()
return "$param1.MTIzNDU2Nzg5MDwj/ezwig===.$param2".sha256()
}
}

View File

@ -10,6 +10,7 @@ import androidx.core.util.forEach
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.data.db.entity.Notification as AppNotification
class PostNotifications(val app: App, nList: List<AppNotification>) {
@ -17,22 +18,34 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
private const val TAG = "PostNotifications"
}
/*public boolean shouldBeQuiet() {
long now = Time.getNow().getInMillis();
long start = app.config.getSync().getQuietHoursStart();
long end = app.config.getSync().getQuietHoursEnd();
private val quiet by lazy { shouldBeQuiet() }
fun shouldBeQuiet(): Boolean {
if (!app.config.sync.quietHoursEnabled)
return false
val now = Time.getNow().value
val start = app.config.sync.quietHoursStart?.value ?: return false
var end = app.config.sync.quietHoursEnd?.value ?: return false
if (start > end) {
end += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Night passing");
// the range spans between two days
end += 240000
}
if (start > now) {
now += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Now is smaller");
return now in start..end || now+240000 in start..end
}
private fun NotificationCompat.Builder.addDefaults(): NotificationCompat.Builder {
return this.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(if (quiet) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MAX)
.also {
if (quiet) {
it.setSound(null)
it.setVibrate(longArrayOf())
}
else
it.setDefaults(NotificationCompat.DEFAULT_ALL)
}
.setGroup(if (quiet) app.notificationChannelsManager.dataQuiet.key else app.notificationChannelsManager.data.key)
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return start > 0 && now >= start && now <= end;
}*/
fun shouldBeQuiet() = false
private fun buildSummaryText(summaryCounts: SparseIntArray): CharSequence {
val summaryTexts = mutableListOf<String>()
@ -108,11 +121,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
it.addLine(line)
}
})
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notificationChannelsManager.data.key)
.addDefaults()
.setContentIntent(summaryIntent)
.setAutoCancel(true)
.build()
@ -131,11 +140,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setStyle(NotificationCompat.BigTextStyle()
.bigText(it.text))
.setWhen(it.addedDate)
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notificationChannelsManager.data.key)
.addDefaults()
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setContentIntent(it.getPendingIntent(app))
.setAutoCancel(true)
@ -155,11 +160,7 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notificationChannelsManager.data.key)
.addDefaults()
.setGroupSummary(true)
.setContentIntent(summaryIntent)
.setAutoCancel(true)

View File

@ -41,8 +41,9 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
Lesson::class,
ConfigEntry::class,
LibrusLesson::class,
TimetableManual::class,
Metadata::class
], version = 78)
], version = 79)
@TypeConverters(
ConverterTime::class,
ConverterDate::class,
@ -80,6 +81,7 @@ abstract class AppDb : RoomDatabase() {
abstract fun timetableDao(): TimetableDao
abstract fun configDao(): ConfigDao
abstract fun librusLessonDao(): LibrusLessonDao
abstract fun timetableManualDao(): TimetableManualDao
abstract fun metadataDao(): MetadataDao
companion object {
@ -161,7 +163,8 @@ abstract class AppDb : RoomDatabase() {
Migration75(),
Migration76(),
Migration77(),
Migration78()
Migration78(),
Migration79()
).allowMainThreadQueries().build()
}
}

View File

@ -8,7 +8,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
class ConverterDateInt {
@TypeConverter
fun toDate(value: Int): Date = Date.fromValue(value)
fun toDate(value: Int): Date? = if (value == 0) null else Date.fromValue(value)
@TypeConverter
fun toInt(date: Date?): Int = date?.value ?: 0

View File

@ -5,10 +5,9 @@
package pl.szczodrzynski.edziennik.data.db.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
@ -53,6 +52,9 @@ interface TimetableDao {
@Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))")
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
@RawQuery(observedEntities = [Lesson::class])
fun getRaw(query: SupportSQLiteQuery): LiveData<List<LessonFull>>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND type != -1 AND type != 0
@ -67,12 +69,11 @@ interface TimetableDao {
""")
fun getChangesForDateNow(profileId: Int, date: Date): List<LessonFull>
@Query("""
fun getForDate(profileId: Int, date: Date) = getRaw(SimpleSQLiteQuery("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
WHERE timetable.profileId = $profileId AND ((type != 3 AND date = "${date.stringY_m_d}") OR ((type = 3 OR type = 1) AND oldDate = "${date.stringY_m_d}"))
ORDER BY id, type
""")
fun getForDate(profileId: Int, date: Date): LiveData<List<LessonFull>>
"""))
@Query("""
$QUERY

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-22.
*/
package pl.szczodrzynski.edziennik.data.db.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import pl.szczodrzynski.edziennik.data.db.entity.TimetableManual
import pl.szczodrzynski.edziennik.utils.models.Date
@Dao
interface TimetableManualDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(timetableManual: TimetableManual)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(timetableManualList: List<TimetableManual>)
@Query("SELECT * FROM timetableManual WHERE profileId = :profileId")
fun getAll(profileId: Int): LiveData<List<TimetableManual>>
@Query("SELECT * FROM timetableManual WHERE profileId = :profileId AND date >= :dateFrom AND date <= :dateTo")
fun getAllByDateRange(profileId: Int, dateFrom: Date, dateTo: Date): LiveData<List<TimetableManual>>
@Query("SELECT * FROM timetableManual WHERE profileId = :profileId AND (date IS NULL OR date = 0 OR date >= :dateFrom)")
fun getAllToDisplay(profileId: Int, dateFrom: Date): LiveData<List<TimetableManual>>
@Delete
fun delete(timetableManual: TimetableManual)
@Query("DELETE FROM timetableManual WHERE profileId = :profileId")
fun clear(profileId: Int)
}

View File

@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
/*public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) {
@ -81,5 +82,11 @@ open class Grade(
*/
@ColumnInfo(name = "gradeIsImprovement")
var isImprovement = false
@Ignore
var showAsUnseen = false
val isImproved
get() = parentId ?: -1L != -1L
}

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -66,6 +67,9 @@ open class Lesson(val profileId: Int, var id: Long) {
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
@Ignore
var showAsUnseen = false
override fun toString(): String {
return "Lesson(profileId=$profileId, " +
"id=$id, " +

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-22.
*/
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@Entity(tableName = "timetableManual",
indices = [
Index(value = ["profileId", "date"]),
Index(value = ["profileId", "weekDay"])
])
class TimetableManual(
val profileId: Int,
var type: Int,
var repeatBy: Int,
@PrimaryKey(autoGenerate = true)
var id: Int = 0
) {
companion object {
const val TYPE_NORMAL = 0
const val TYPE_CANCELLED = 1
const val TYPE_CHANGE = 2
const val TYPE_SHIFTED_SOURCE = 3
const val TYPE_SHIFTED_TARGET = 4
const val TYPE_REMOVED = 5
const val TYPE_CLASSROOM = 6
const val REPEAT_WEEKLY = 0
const val REPEAT_ONCE = 1
const val REPEAT_BY_SUBJECT = 2
}
// `date` for one time lesson
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
var date: Date? = null
// `weekDay` for repeating lesson (every week)
var weekDay: Int? = null
var lessonNumber: Int? = null
var startTime: Time? = null
var endTime: Time? = null
var subjectId: Long? = null
var teacherId: Long? = null
var teamId: Long? = null
var classroom: String? = null
fun verifyParams(): Boolean {
return when (repeatBy) {
REPEAT_WEEKLY -> date == null && weekDay != null
REPEAT_ONCE -> date != null && weekDay == null
REPEAT_BY_SUBJECT -> date == null && weekDay == null && subjectId != null
else -> false
}
}
}

View File

@ -0,0 +1,27 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration79 : Migration(78, 79) {
override fun migrate(database: SupportSQLiteDatabase) {
// manual timetable implementation
database.execSQL("""CREATE TABLE timetableManual (
profileId INTEGER NOT NULL,
id INTEGER PRIMARY KEY NOT NULL,
type INTEGER NOT NULL,
repeatBy INTEGER NOT NULL DEFAULT 0,
date INTEGER DEFAULT NULL,
weekDay INTEGER DEFAULT NULL,
lessonNumber INTEGER DEFAULT NULL,
startTime TEXT DEFAULT NULL,
endTime TEXT DEFAULT NULL,
subjectId INTEGER DEFAULT NULL,
teacherId INTEGER DEFAULT NULL,
teamId INTEGER DEFAULT NULL,
classroom TEXT DEFAULT NULL
)""")
database.execSQL("CREATE INDEX index_timetableManual_profileId_date ON timetableManual (profileId, date)")
database.execSQL("CREATE INDEX index_timetableManual_profileId_weekDay ON timetableManual (profileId, weekDay)")
}
}

View File

@ -90,7 +90,7 @@ class BellSyncDialog(
}
launch {
counterJob = startCoroutineTimer(repeatMillis = 1000) {
counterJob = startCoroutineTimer(repeatMillis = 500) {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text = app.getString(R.string.bell_sync_howto, bellTime.stringHM, bellDiffText)

View File

@ -12,24 +12,31 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL
import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
class EventManualDialog(
@ -48,7 +55,7 @@ class EventManualDialog(
private const val TAG = "EventManualDialog"
}
private lateinit var job: Job
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@ -56,7 +63,6 @@ class EventManualDialog(
private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var customColor: Int? = null
private val editingShared = editingEvent?.sharedBy != null
private val editingOwn = editingEvent?.sharedBy == "self"
@ -67,11 +73,14 @@ class EventManualDialog(
SzkolnyApi(app)
}
private var enqueuedWeekDialog: AlertDialog? = null
private var enqueuedWeekStart = Date.getToday()
init { run {
if (activity.isFinishing)
return@run
job = Job()
onShowListener?.invoke(TAG)
EventBus.getDefault().register(this)
b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_event_manual_title)
@ -85,6 +94,7 @@ class EventManualDialog(
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
EventBus.getDefault().unregister(this@EventManualDialog)
}
.setCancelable(false)
.create()
@ -104,12 +114,6 @@ class EventManualDialog(
show()
}
event = editingEvent?.clone() ?: Event().also { event ->
event.profileId = profileId
defaultType?.let {
event.type = it
}
}
b.shareSwitch.isChecked = editingShared
b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn)
@ -144,41 +148,138 @@ class EventManualDialog(
else -> R.string.dialog_event_manual_share_first_notice
}
b.shareDetails.setText(text, event.sharedByName ?: "")
b.shareDetails.setText(text, editingEvent?.sharedByName ?: "")
}
private fun syncTimetable(date: Date) {
if (enqueuedWeekDialog != null) {
return
}
if (app.profile.getStudentData("timetableNotPublic", false)) {
return
}
val weekStart = date.weekStart
enqueuedWeekDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.timetable_syncing_text)
.setCancelable(false)
.show()
enqueuedWeekStart = weekStart
EdziennikTask.syncProfile(
profileId = App.profileId,
viewIds = listOf(
MainActivity.DRAWER_ITEM_TIMETABLE to 0
),
arguments = JsonObject(
"weekStart" to weekStart.stringY_m_d
)
).enqueue(activity)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
if (event.profileId == App.profileId) {
enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null
launch {
b.timeDropdown.loadItems()
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
dialog.dismiss()
enqueuedWeekDialog?.dismiss()
enqueuedWeekDialog = null
}
private fun loadLists() { launch {
with (b.dateDropdown) {
db = app.db
profileId = App.profileId
showWeekDays = false
showDays = true
showOtherDate = true
defaultLesson?.let {
nextLessonSubjectId = it.displaySubjectId
nextLessonSubjectName = it.displaySubjectName
nextLessonTeamId = it.displayTeamId
}
loadItems()
selectDefault(editingEvent?.eventDate)
selectDefault(defaultLesson?.displayDate ?: defaultDate)
onDateSelected = { date, lesson ->
b.timeDropdown.deselect()
b.timeDropdown.lessonsDate = date
this@EventManualDialog.launch {
if (!b.timeDropdown.loadItems())
syncTimetable(date)
lesson?.displayStartTime?.let { b.timeDropdown.selectTime(it) }
lesson?.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
lesson?.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect()
lesson?.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass()
}
}
}
with (b.timeDropdown) {
db = app.db
profileId = App.profileId
showAllDay = true
showCustomTime = true
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
displayMode = DISPLAY_LESSONS
if (!loadItems())
syncTimetable(lessonsDate ?: Date.getToday())
selectDefault(editingEvent?.startTime)
selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
onLessonSelected = { lesson ->
lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect()
lesson.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect()
lesson.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass()
}
}
with (b.teamDropdown) {
db = app.db
profileId = App.profileId
showNoTeam = true
loadItems()
selectTeamClass()
selectDefault(editingEvent?.teamId)
selectDefault(defaultLesson?.displayTeamId)
}
with (b.subjectDropdown) {
db = app.db
profileId = App.profileId
showNoSubject = true
showCustomSubject = false
loadItems()
selectDefault(editingEvent?.subjectId)
selectDefault(defaultLesson?.displaySubjectId)
}
with (b.teacherDropdown) {
db = app.db
profileId = App.profileId
showNoTeacher = true
loadItems()
selectDefault(editingEvent?.teacherId)
selectDefault(defaultLesson?.displayTeacherId)
}
val deferred = async(Dispatchers.Default) {
// get the team list
val teams = app.db.teamDao().getAllNow(profileId)
b.teamDropdown.clear()
b.teamDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_team),
""
)
b.teamDropdown += teams.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
// get the subject list
val subjects = app.db.subjectDao().getAllNow(profileId)
b.subjectDropdown.clear()
b.subjectDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_subject),
""
)
b.subjectDropdown += subjects.map { TextInputDropDown.Item(it.id, it.longName, tag = it) }
// get the teacher list
val teachers = app.db.teacherDao().getAllNow(profileId)
b.teacherDropdown.clear()
b.teacherDropdown += TextInputDropDown.Item(
-1,
activity.getString(R.string.dialog_event_manual_no_teacher),
""
)
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
// get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
b.typeDropdown.clear()
@ -186,13 +287,10 @@ class EventManualDialog(
}
deferred.await()
b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true
b.typeDropdown.isEnabled = true
defaultType?.let {
b.typeDropdown.select(it.toLong())
b.typeDropdown.select(it)
}
b.typeDropdown.selected?.let { item ->
@ -201,9 +299,6 @@ class EventManualDialog(
// copy IDs from event being edited
editingEvent?.let {
b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId)
b.topic.setText(it.topic)
b.typeDropdown.select(it.type.toLong())?.let { item ->
customColor = (item.tag as EventType).color
@ -215,8 +310,6 @@ class EventManualDialog(
// copy IDs from the LessonFull
defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
b.typeDropdown.setOnChangeListener {
@ -244,278 +337,8 @@ class EventManualDialog(
})
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
}
loadDates()
}}
private fun loadDates() { launch {
val date = Date.getToday()
val today = date.value
var weekDay = date.weekDay
val deferred = async(Dispatchers.Default) {
val dates = mutableListOf<TextInputDropDown.Item>()
// item choosing the next lesson of specific subject
b.subjectDropdown.selected?.let {
if (it.tag is Subject) {
dates += TextInputDropDown.Item(
-it.id,
activity.getString(R.string.dialog_event_manual_date_next_lesson, it.tag.longName)
)
}
}
// TODAY
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_today, date.formattedString),
tag = date.clone()
)
// TOMORROW
if (weekDay < 4) {
date.stepForward(0, 0, 1)
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString),
tag = date.clone()
)
}
// REMAINING SCHOOL DAYS OF THE CURRENT WEEK
while (weekDay < 4) {
date.stepForward(0, 0, 1) // step one day forward
weekDay++
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
}
// go to next week Monday
date.stepForward(0, 0, -weekDay + 7)
weekDay = 0
// ALL SCHOOL DAYS OF THE NEXT WEEK
while (weekDay < 4) {
dates += TextInputDropDown.Item(
date.value.toLong(),
activity.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
date.stepForward(0, 0, 1) // step one day forward
weekDay++
}
dates += TextInputDropDown.Item(
-1L,
activity.getString(R.string.dialog_event_manual_date_other)
)
dates
}
val dates = deferred.await()
b.dateDropdown.clear().append(dates)
defaultDate?.let {
event.eventDate = it
if (b.dateDropdown.select(it) == null)
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
editingEvent?.eventDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
defaultLesson?.displayDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
if (b.dateDropdown.selected == null) {
b.dateDropdown.select(today.toLong())
}
b.dateDropdown.isEnabled = true
b.dateDropdown.setOnChangeListener { item ->
when {
// next lesson with specified subject
item.id < -1 -> {
val teamId = defaultLesson?.teamId ?: -1
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
when (teamId) {
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
}.observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.select(TextInputDropDown.Item(
lessonDate.value.toLong(),
lessonDate.formattedString,
tag = lessonDate
))
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
defaultLoaded = false
loadHours(it.displayStartTime)
})
return@setOnChangeListener false
}
// custom date
item.id == -1L -> {
MaterialDatePicker.Builder
.datePicker()
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
b.dateDropdown.select(TextInputDropDown.Item(
dateSelected.value.toLong(),
dateSelected.formattedString,
tag = dateSelected
))
loadHours()
}
show(this@EventManualDialog.activity.supportFragmentManager, "MaterialDatePicker")
}
return@setOnChangeListener false
}
// a specific date
else -> {
b.dateDropdown.select(item)
loadHours()
}
}
return@setOnChangeListener true
}
loadHours()
}}
private fun loadHours(defaultHour: Time? = null) {
b.timeDropdown.isEnabled = false
// get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
// get all lessons for selected date
app.db.timetableDao().getForDate(profileId, date).observeOnce(activity, Observer { lessons ->
val hours = mutableListOf<TextInputDropDown.Item>()
// add All day time choice
hours += TextInputDropDown.Item(
0L,
activity.getString(R.string.dialog_event_manual_all_day)
)
lessons.forEach { lesson ->
if (lesson.type == Lesson.TYPE_NO_LESSONS) {
// indicate there are no lessons this day
hours += TextInputDropDown.Item(
-2L,
activity.getString(R.string.dialog_event_manual_no_lessons)
)
return@forEach
}
// create the lesson caption
val text = listOfNotEmpty(
lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let {
when {
lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it
}
} ?: ""
)
// add an item with LessonFull as the tag
hours += TextInputDropDown.Item(
lesson.displayStartTime?.value?.toLong() ?: -1,
text.concat(" "),
tag = lesson
)
}
b.timeDropdown.clear().append(hours)
if (defaultLoaded) {
b.timeDropdown.deselect()
// select the TEAM_CLASS if possible
b.teamDropdown.items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS
}?.let {
b.teamDropdown.select(it)
} ?: b.teamDropdown.deselect()
// clear subject, teacher selection
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
}
else {
val setTime: (Time) -> Unit = {
event.startTime = it
if (b.timeDropdown.select(it) == null)
b.timeDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.stringHM,
tag = it
))
}
defaultTime?.let(setTime)
editingEvent?.startTime?.let(setTime)
defaultLesson?.displayStartTime?.let(setTime)
defaultHour?.let(setTime)
}
defaultLoaded = true
b.timeDropdown.isEnabled = true
// attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item ->
when (item.id) {
// no lessons this day
-2L -> {
b.timeDropdown.deselect()
return@setOnChangeListener false
}
// custom start hour
-1L -> return@setOnChangeListener false
// selected a specific lesson
else -> {
if (item.tag is LessonFull) {
// update team, subject, teacher dropdowns,
// using the LessonFull from item tag
b.teamDropdown.deselect()
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
item.tag.displayTeamId?.let {
b.teamDropdown.select(it)
}
item.tag.displaySubjectId?.let {
b.subjectDropdown.select(it)
}
item.tag.displayTeacherId?.let {
b.teacherDropdown.select(it)
}
}
}
}
return@setOnChangeListener true
}
})
}
private fun showRemoveEventDialog() {
val shareNotice = when {
editingShared && editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self)
@ -541,22 +364,29 @@ class EventManualDialog(
}
private fun saveEvent() {
val date = b.dateDropdown.selected?.tag.instanceOfOrNull<Date>()
val startTime = b.timeDropdown.selected?.tag.instanceOfOrNull<Time>()
val teamId = b.teamDropdown.selected?.id
val date = b.dateDropdown.getSelected() as? Date
val startTimePair = b.timeDropdown.getSelected() as? Pair<*, *>
val startTime = startTimePair?.first as? Time
val teamId = b.teamDropdown.getSelected() as? Long
val type = b.typeDropdown.selected?.id
val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.selected?.id
val teacherId = b.teacherDropdown.selected?.id
val subjectId = b.subjectDropdown.getSelected() as? Long
val teacherId = b.teacherDropdown.getSelected() as? Long
val share = b.shareSwitch.isChecked
b.dateDropdown.error = null
b.teamDropdown.error = null
b.typeDropdown.error = null
b.topic.error = null
var isError = false
if (date == null) {
b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose)
isError = true
}
if (share && teamId == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
isError = true

View File

@ -3,6 +3,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.grade
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
@ -10,7 +11,9 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
@ -50,16 +53,22 @@ class GradeDetailsDialog(
.show()
val manager = app.gradesManager
val gradeColor = manager.getColor(grade)
val gradeColor = manager.getGradeColor(grade)
b.grade = grade
b.weightText = manager.getWeightString(app, grade)
b.commentVisible = false
b.devMode = App.debugMode
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) -0x1000000 else -0x1)
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0x99000000.toInt() else 0x99ffffff.toInt())
b.gradeName.background.setTintColor(gradeColor)
b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade)
b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null
b.customValueLayout.isVisible = b.customValueDivider.isVisible
b.customValueButton.onClick {
GradesConfigDialog(activity, reloadOnDismiss = true)
}
launch {
val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getAllWithParentIdNow(App.profileId, grade.id)

View File

@ -89,6 +89,8 @@ class GradesConfigDialog(
}?.isChecked = true
b.dontCountZeroToAverage.isChecked = !profileConfig.countZeroToAvg
b.hideImproved.isChecked = profileConfig.hideImproved
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
}
private fun saveConfig() {
@ -125,6 +127,16 @@ class GradesConfigDialog(
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM }
b.dontCountZeroToAverage.setOnCheckedChangeListener { _, isChecked -> profileConfig.countZeroToAvg = !isChecked }
b.dontCountZeroToAverage.onChange { _, isChecked -> profileConfig.countZeroToAvg = !isChecked }
b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked }
b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked }
b.averageWithoutWeightHelp.onClick {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.grades_config_average_without_weight)
.setMessage(R.string.grades_config_average_without_weight_message)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}

View File

@ -21,7 +21,14 @@ import androidx.core.content.FileProvider
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogGenerateBlockTimetableBinding
@ -53,7 +60,7 @@ class GenerateBlockTimetableDialog(
private val app by lazy { activity.application as App }
private lateinit var job: Job
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@ -64,11 +71,15 @@ class GenerateBlockTimetableDialog(
private var showTeachersNames: Boolean = true
private var noColors: Boolean = false
private var enqueuedWeekDialog: AlertDialog? = null
private var enqueuedWeekStart = Date.getToday()
private var enqueuedWeekEnd = Date.getToday()
init { run {
if (activity.isFinishing)
return@run
job = Job()
onShowListener?.invoke(TAG)
EventBus.getDefault().register(this)
val weekCurrentStart = Week.getWeekStart()
val weekCurrentEnd = Week.getWeekEnd()
@ -88,16 +99,20 @@ class GenerateBlockTimetableDialog(
.setTitle(R.string.timetable_generate_range)
.setView(b.root)
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setPositiveButton(R.string.save) { dialog, _ ->
dialog.dismiss()
.setPositiveButton(R.string.save, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
EventBus.getDefault().unregister(this@GenerateBlockTimetableDialog)
}
.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick {
when (b.weekSelectionRadioGroup.checkedRadioButtonId) {
R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd)
R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd)
R.id.forSelectedWeekRadio -> selectDate()
}
}
.setOnDismissListener { onDismissListener?.invoke(TAG) }
.show()
}}
private fun selectDate() {
@ -115,12 +130,26 @@ class GenerateBlockTimetableDialog(
.show(activity.supportFragmentManager, "MaterialDatePicker")
}
private fun generateBlockTimetable(weekStart: Date, weekEnd: Date) { launch {
val progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.timetable_generate_progress_title)
.setMessage(R.string.timetable_generate_progress_text)
.show()
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
if (event.profileId == App.profileId) {
enqueuedWeekDialog?.dismiss()
generateBlockTimetable(enqueuedWeekStart, enqueuedWeekEnd)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
enqueuedWeekDialog?.dismiss()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
dialog.dismiss()
enqueuedWeekDialog?.dismiss()
}
private fun generateBlockTimetable(weekStart: Date, weekEnd: Date) { launch {
val weekDays = mutableListOf<MutableList<Lesson>>()
for (i in weekStart.weekDay..weekEnd.weekDay) {
weekDays.add(mutableListOf())
@ -157,12 +186,46 @@ class GenerateBlockTimetableDialog(
return@mapNotNull lesson
}
if (lessons.isEmpty()) {
if (enqueuedWeekDialog != null) {
return@launch
}
enqueuedWeekDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.timetable_syncing_text)
.setCancelable(false)
.show()
enqueuedWeekStart = weekStart
enqueuedWeekEnd = weekEnd
EdziennikTask.syncProfile(
profileId = App.profileId,
viewIds = listOf(
MainActivity.DRAWER_ITEM_TIMETABLE to 0
),
arguments = JsonObject(
"weekStart" to weekStart.stringY_m_d
)
).enqueue(activity)
return@launch
}
val progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.timetable_generate_progress_title)
.setMessage(R.string.timetable_generate_progress_text)
.show()
if (minTime == null) {
progressDialog.dismiss()
// TODO: Toast
return@launch
}
dialog.dismiss()
val uri = withContext(Dispatchers.Default) {
val diff = Time.diff(maxTime, minTime)
val imageWidth = WIDTH_CONSTANT + maxWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) - WIDTH_SPACING
@ -220,11 +283,13 @@ class GenerateBlockTimetableDialog(
if (!showTeachersNames) teacherName.visibility = View.GONE
when (lesson.type) {
Lesson.TYPE_NORMAL -> {}
Lesson.TYPE_NORMAL -> {
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
card.setCardBackgroundColor(Color.BLACK)
subjectName.setTextColor(Color.WHITE)
subjectName.text = lesson.displaySubjectName?.asStrikethroughSpannable() ?: ""
subjectName.text = lesson.displaySubjectName?.asStrikethroughSpannable()
?: ""
}
else -> {
card.setCardBackgroundColor(0xff234158.toInt())
@ -318,7 +383,7 @@ class GenerateBlockTimetableDialog(
fos.close()
} catch (e: Exception) {
Log.e("SAVE_IMAGE", e.message, e)
return@launch
return@withContext null
}
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -326,6 +391,8 @@ class GenerateBlockTimetableDialog(
} else {
Uri.parse("file://" + outputFile.absolutePath)
}
uri
}
progressDialog.dismiss()
MaterialAlertDialogBuilder(activity)

View File

@ -25,7 +25,6 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableUtils
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
@ -50,7 +49,7 @@ class LessonDetailsDialog(
get() = job + Dispatchers.Main
private lateinit var adapter: EventListAdapter
private val utils by lazy { TimetableUtils() }
private val manager by lazy { app.timetableManager }
init { run {
if (activity.isFinishing)
@ -91,7 +90,7 @@ class LessonDetailsDialog(
val lessonTime = lesson.displayStartTime ?: return
b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString
b.annotationVisible = utils.getAnnotation(activity, lesson, b.annotation)
b.annotationVisible = manager.getAnnotation(activity, lesson, b.annotation)
if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) {
b.shiftedLayout.visibility = View.VISIBLE

View File

@ -126,6 +126,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
}
private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch {
if (!isAdded)
return@launch
delay(500)
val eventList = mutableListOf<CalendarEvent>()
@ -139,6 +141,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
/**
* LESSON CHANGES
*/
if (!isAdded)
return@launch
val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getAllChangesNow(app.profileId) }
val lessonChangeCounters = mutableListOf<LessonChangeCounter>()
@ -170,6 +174,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
/**
* TEACHER ABSENCES
*/
if (!isAdded)
return@launch
val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true)
@ -208,6 +214,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
/**
* EVENTS
*/
if (!isAdded)
return@launch
val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) }
val unreadEventDates = mutableSetOf<Int>()

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-22.
*/
package pl.szczodrzynski.edziennik.ui.modules.base
import androidx.fragment.app.Fragment
abstract class PagerFragment : Fragment() {
private var isPageCreated = false
/**
* Called when the page is first shown, or if previous
* [onPageCreated] returned false
*
* @return true if the view is set up
* @return false if the setup failed. The method may be then called
* again, when page becomes visible.
*/
abstract fun onPageCreated(): Boolean
override fun onResume() {
if (!isPageCreated) {
isPageCreated = onPageCreated()
}
super.onResume()
}
}

View File

@ -48,6 +48,7 @@ class ErrorSnackbar(val activity: AppCompatActivity) : CoroutineScope {
fun addError(apiError: ApiError): ErrorSnackbar {
errors.add(apiError)
snackbar?.setText(apiError.getStringReason(activity))
snackbar?.duration = 15000
return this
}

View File

@ -6,7 +6,6 @@ package pl.szczodrzynski.edziennik.ui.modules.grades
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Typeface
import android.text.TextUtils
import android.util.AttributeSet
import android.util.TypedValue.COMPLEX_UNIT_SP
@ -48,7 +47,7 @@ class GradeView : AppCompatTextView {
val gradeName = grade.name
val gradeColor = manager.getColor(grade)
val gradeColor = manager.getGradeColor(grade)
text = if (periodGradesTextual)
when (grade.type) {
@ -83,7 +82,7 @@ class GradeView : AppCompatTextView {
0x99ffffff.toInt()
})
typeface = Typeface.create("serif-monospace", Typeface.BOLD)
//typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
setBackgroundResource(when (grade.type) {
TYPE_SEMESTER1_PROPOSED,
TYPE_SEMESTER2_PROPOSED,
@ -104,7 +103,7 @@ class GradeView : AppCompatTextView {
setPadding(2.dp, 2.dp, 2.dp, 2.dp)
}
else {
setTextSize(COMPLEX_UNIT_SP, 16f)
setTextSize(COMPLEX_UNIT_SP, 14f)
setPadding(5.dp, 0, 5.dp, 0)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, 0, 5.dp, 0)

View File

@ -10,19 +10,24 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.grades.models.*
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.*
import kotlin.coroutines.CoroutineContext
class GradesAdapter(
val activity: AppCompatActivity,
var onGradeClick: ((item: GradeFull) -> Unit)? = null,
var onGradesEditorClick: ((subject: GradesSubject, semester: GradesSemester) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope {
companion object {
private const val TAG = "GradesAdapter"
private const val ITEM_TYPE_SUBJECT = 0
@ -34,6 +39,13 @@ class GradesAdapter(
const val STATE_OPENED = 1
}
private val app = activity.applicationContext as App
private val manager = app.gradesManager
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
var items = mutableListOf<Any>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -67,13 +79,17 @@ class GradesAdapter(
}
if (model !is ExpandableItemModel<*>)
return@OnClickListener
expandModel(model, view)
}
fun expandModel(model: ExpandableItemModel<*>?, view: View?, notifyAdapter: Boolean = true) {
model ?: return
val position = items.indexOf(model)
if (position == -1)
return@OnClickListener
//val position = it.getTag(R.string.tag_key_position) as? Int ?: return@OnClickListener
return
if (model is GradesSubject || model is GradesSemester) {
view.findViewById<View>(R.id.dropdownIcon)?.let { dropdownIcon ->
view?.findViewById<View>(R.id.dropdownIcon)?.let { dropdownIcon ->
ObjectAnimator.ofFloat(
dropdownIcon,
View.ROTATION,
@ -83,40 +99,43 @@ class GradesAdapter(
}
}
if (model is GradesSubject) {
val preview = view.findViewById<View>(R.id.previewContainer)
val summary = view.findViewById<View>(R.id.yearSummary)
val preview = view?.findViewById<View>(R.id.previewContainer)
val summary = view?.findViewById<View>(R.id.yearSummary)
preview?.visibility = if (model.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE
summary?.visibility = if (model.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE
}
if (model.state == STATE_CLOSED) {
val subItems = if (model is GradesSemester && model.grades.isEmpty())
val subItems = when {
model is GradesSemester && model.grades.isEmpty() ->
listOf(GradesEmpty())
else
model.items
model is GradesSemester && manager.hideImproved ->
model.items.filter { !it.seen || !it.isImproved }
else -> model.items
}
model.state = STATE_OPENED
items.addAll(position + 1, subItems.filterNotNull())
notifyItemRangeInserted(position + 1, subItems.size)
/*notifyItemRangeChanged(
position + subItems.size,
items.size - (position + subItems.size)
)*/
//notifyItemRangeChanged(position, items.size - position)
if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size)
if (model is GradesSubject) {
// auto expand first semester
if (model.semesters.isNotEmpty()) {
val semester = model.semesters.firstOrNull { it.grades.isNotEmpty() } ?: model.semesters.first()
val semesterIndex = model.semesters.indexOf(semester)
val grades = if (semester.grades.isEmpty())
val grades = when {
semester.grades.isEmpty() ->
listOf(GradesEmpty())
else
semester.grades
manager.hideImproved ->
semester.grades.filter { !it.seen || !it.isImproved }
else -> semester.grades
}
semester.state = STATE_OPENED
items.addAll(position + 2 + semesterIndex, grades)
notifyItemRangeInserted(position + 2 + semesterIndex, grades.size)
if (notifyAdapter) notifyItemRangeInserted(position + 2 + semesterIndex, grades.size)
}
}
}
@ -138,9 +157,7 @@ class GradesAdapter(
if (end != -1) {
items.subList(start, end).clear()
notifyItemRangeRemoved(start, end - start)
//notifyItemRangeChanged(start, end - start)
//notifyItemRangeChanged(position, items.size - position)
if (notifyAdapter) notifyItemRangeRemoved(start, end - start)
}
model.state = STATE_CLOSED
@ -152,8 +169,6 @@ class GradesAdapter(
if (holder !is BindableViewHolder<*>)
return
val app = activity.applicationContext as App
val viewType = when (holder) {
is SubjectViewHolder -> ITEM_TYPE_SUBJECT
is SemesterViewHolder -> ITEM_TYPE_SEMESTER
@ -167,11 +182,11 @@ class GradesAdapter(
holder.itemView.setTag(R.string.tag_key_model, item)
when {
holder is SubjectViewHolder && item is GradesSubject -> holder.onBind(activity, app, item, position)
holder is SemesterViewHolder && item is GradesSemester -> holder.onBind(activity, app, item, position)
holder is EmptyViewHolder && item is GradesEmpty -> holder.onBind(activity, app, item, position)
holder is GradeViewHolder && item is GradeFull -> holder.onBind(activity, app, item, position)
holder is StatsViewHolder && item is GradesStats -> holder.onBind(activity, app, item, position)
holder is SubjectViewHolder && item is GradesSubject -> holder.onBind(activity, app, item, position, this)
holder is SemesterViewHolder && item is GradesSemester -> holder.onBind(activity, app, item, position, this)
holder is EmptyViewHolder && item is GradesEmpty -> holder.onBind(activity, app, item, position, this)
holder is GradeViewHolder && item is GradeFull -> holder.onBind(activity, app, item, position, this)
holder is StatsViewHolder && item is GradesStats -> holder.onBind(activity, app, item, position, this)
}
if (holder is SemesterViewHolder && item is GradesSemester) {
@ -184,5 +199,23 @@ class GradesAdapter(
holder.itemView.setOnClickListener(onClickListener)
}
fun notifyItemChanged(model: Any) {
startCoroutineTimer(1000L, 0L) {
val index = items.indexOf(model)
if (index != -1)
notifyItemChanged(index)
}
}
fun removeItem(model: Any) {
startCoroutineTimer(2000L, 0L) {
val index = items.indexOf(model)
if (index != -1) {
items.removeAt(index)
notifyItemRemoved(index)
}
}
}
override fun getItemCount() = items.size
}

View File

@ -4,31 +4,37 @@
package pl.szczodrzynski.edziennik.ui.modules.grades
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.Bundle
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_GRADES_EDITOR
import pl.szczodrzynski.edziennik.averageOrNull
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_GRADE
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradeDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesAverages
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesStats
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext
import kotlin.math.max
class GradesFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "GradesFragment"
@ -48,6 +54,7 @@ class GradesFragment : Fragment(), CoroutineScope {
}
private val manager by lazy { app.gradesManager }
private val dontCountGrades by lazy { manager.dontCountGrades }
private var expandSubjectId = 0L
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null
@ -62,6 +69,8 @@ class GradesFragment : Fragment(), CoroutineScope {
if (!isAdded)
return
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
app.db.gradeDao()
.getAllOrderBy(App.profileId, app.gradesManager.getOrderByString())
.observe(this, Observer { grades ->
@ -111,8 +120,29 @@ class GradesFragment : Fragment(), CoroutineScope {
"finalOtherSemester" to otherSemester?.finalGrade?.value
))
}
activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_grades_config)
.withIcon(Icon2.cmd_settings_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
GradesConfigDialog(activity, true, null, null)
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
AsyncTask.execute { App.db.metadataDao().setAllSeen(App.profileId, TYPE_GRADE, true) }
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
})
)
activity.gainAttention()
}
@Suppress("SuspendFunctionOnCoroutineScope")
private suspend fun processGrades(grades: List<GradeFull>) {
val items = mutableListOf<GradesSubject>()
@ -121,6 +151,8 @@ class GradesFragment : Fragment(), CoroutineScope {
var subject = GradesSubject(subjectId, "")
var semester = GradesSemester(0, 1)
val hideImproved = manager.hideImproved
// grades returned by the query are ordered
// by the subject ID, so it's easier and probably
// a bit faster to build all the models
@ -144,6 +176,11 @@ class GradesFragment : Fragment(), CoroutineScope {
?: GradesSemester(subject.subjectId, grade.semester).also { subject.semesters += it }
}
grade.showAsUnseen = !grade.seen
if (!grade.seen) {
semester.hasUnseen = true
}
when (grade.type) {
Grade.TYPE_SEMESTER1_PROPOSED,
Grade.TYPE_SEMESTER2_PROPOSED -> semester.proposedGrade = grade
@ -237,9 +274,27 @@ class GradesFragment : Fragment(), CoroutineScope {
adapter.items = items.toMutableList()
adapter.items.add(stats)
var expandSubjectModel: GradesSubject? = null
if (expandSubjectId != 0L) {
expandSubjectModel = items.firstOrNull { it.subjectId == expandSubjectId }
adapter.expandModel(
model = expandSubjectModel,
view = null,
notifyAdapter = false
)
}
withContext(Dispatchers.Main) {
adapter.notifyDataSetChanged()
}
startCoroutineTimer(500L, 0L) {
if (expandSubjectModel != null) {
b.gradesRecyclerView.smoothScrollToPosition(
items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0)
)
}
}
}
private fun countGrade(grade: Grade, averages: GradesAverages) {
@ -247,8 +302,13 @@ class GradesFragment : Fragment(), CoroutineScope {
val weight = manager.getGradeWeight(dontCountGrades, grade)
when (grade.type) {
Grade.TYPE_NORMAL -> {
if (grade.value > 0f) {
// count to the arithmetic average
// only if value more than 0
// to exclude "+", "-", "np" etc.
averages.normalSum += value
averages.normalCount++
}
averages.normalWeightedSum += value * weight
averages.normalWeightedCount += weight
}

View File

@ -4,16 +4,17 @@
package pl.szczodrzynski.edziennik.ui.modules.grades.models
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
data class GradesSemester(
val subjectId: Long,
val number: Int,
val grades: MutableList<Grade> = mutableListOf()
) : ExpandableItemModel<Grade>(grades) {
val grades: MutableList<GradeFull> = mutableListOf()
) : ExpandableItemModel<GradeFull>(grades) {
override var level = 2
var hasUnseen = false
val averages = GradesAverages()
var proposedGrade: GradeFull? = null
var finalGrade: GradeFull? = null

View File

@ -16,6 +16,9 @@ data class GradesSubject(
var lastAddedDate = 0L
var semester: Int = 1
val hasUnseen
get() = semesters.any { it.hasUnseen }
val averages = GradesAverages()
var proposedGrade: GradeFull? = null
var finalGrade: GradeFull? = null

View File

@ -6,7 +6,8 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import androidx.appcompat.app.AppCompatActivity
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
interface BindableViewHolder<T> {
fun onBind(activity: AppCompatActivity, app: App, item: T, position: Int)
fun onBind(activity: AppCompatActivity, app: App, item: T, position: Int, adapter: GradesAdapter)
}

View File

@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.GradesItemEmptyBinding
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesEmpty
class EmptyViewHolder(
@ -21,7 +22,7 @@ class EmptyViewHolder(
private const val TAG = "EmptyViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesEmpty, position: Int) {
override fun onBind(activity: AppCompatActivity, app: App, item: GradesEmpty, position: Int, adapter: GradesAdapter) {
}
}

View File

@ -7,11 +7,14 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.models.Date
class GradeViewHolder(
@ -24,7 +27,7 @@ class GradeViewHolder(
}
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun onBind(activity: AppCompatActivity, app: App, grade: GradeFull, position: Int) {
override fun onBind(activity: AppCompatActivity, app: App, grade: GradeFull, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager
b.gradeName.setGrade(grade, manager, bigView = true)
@ -45,19 +48,35 @@ class GradeViewHolder(
grade.category
}
b.gradeWeight.text = manager.getWeightString(activity, grade, showClassAverage = true)
val weightText = manager.getWeightString(activity, grade, showClassAverage = true)
b.gradeWeight.text = weightText
b.gradeWeight.isVisible = weightText != null
b.gradeTeacherName.text = grade.teacherFullName
b.gradeAddedDate.text = Date.fromMillis(grade.addedDate).let {
it.getRelativeString(app, 5) ?: it.formattedStringShort
}
/*if (!grade.seen) {
b.gradeDescription.setBackground(mContext.getResources().getDrawable(R.drawable.bg_rounded_4dp))
b.gradeDescription.getBackground()
.setColorFilter(PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY))
} else {
b.gradeDescription.setBackground(null)
}*/
b.unread.isVisible = grade.showAsUnseen
if (!grade.seen) {
manager.markAsSeen(grade)
val subject = adapter.items.firstOrNull {
it is GradesSubject && it.subjectId == grade.subjectId
} as? GradesSubject ?: return
val semester = subject.semesters.firstOrNull { it.number == grade.semester } ?: return
semester.hasUnseen = semester.grades.any { !it.seen }
// check if the unseen status has changed
if (!semester.hasUnseen) {
adapter.notifyItemChanged(semester)
}
if (!subject.hasUnseen) {
adapter.notifyItemChanged(subject)
}
if (manager.hideImproved && grade.isImproved) {
adapter.removeItem(grade)
}
}
}
}

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
@ -14,6 +15,7 @@ import pl.szczodrzynski.edziennik.databinding.GradesItemSemesterBinding
import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
class SemesterViewHolder(
inflater: LayoutInflater,
@ -24,7 +26,7 @@ class SemesterViewHolder(
private const val TAG = "SemesterViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSemester, position: Int) {
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSemester, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager
b.semesterName.setText(R.string.grades_semester_format, item.number)
b.dropdownIcon.rotation = when (item.state) {
@ -32,6 +34,33 @@ class SemesterViewHolder(
else -> 180f
}
b.unread.isVisible = item.hasUnseen
var unseenChanged = false
if (item.proposedGrade?.seen == false) {
manager.markAsSeen(item.proposedGrade!!)
unseenChanged = true
}
if (item.finalGrade?.seen == false) {
manager.markAsSeen(item.finalGrade!!)
unseenChanged = true
}
if (unseenChanged) {
val subject = adapter.items.firstOrNull {
it is GradesSubject && it.subjectId == item.subjectId
} as? GradesSubject ?: return
item.hasUnseen = item.grades.any { !it.seen }
// check if the unseen status has changed
if (!item.hasUnseen) {
adapter.notifyItemChanged(item)
}
if (!subject.hasUnseen) {
adapter.notifyItemChanged(subject)
}
}
b.average.text = manager.getAverageString(app, item.averages)
b.proposedGrade.setGrade(item.proposedGrade, manager)
b.finalGrade.setGrade(item.finalGrade, manager)

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.GradesItemStatsBinding
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesStats
import java.text.DecimalFormat
@ -28,7 +29,7 @@ class StatsViewHolder(
private const val TAG = "StatsViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesStats, position: Int) {
override fun onBind(activity: AppCompatActivity, app: App, item: GradesStats, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager
val showAverages = mutableListOf<Int>()
val showPoint = mutableListOf<Int>()
@ -109,7 +110,7 @@ class StatsViewHolder(
}
private fun getSemesterString(context: Context, expected: Float, proposed: Float, final: Float, notAllFinal: Boolean) : Pair<String?, String?> {
val format = DecimalFormat("#.##")
val format = DecimalFormat("#.00")
val average = when {
final != 0f -> final

View File

@ -14,6 +14,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
@ -21,6 +22,7 @@ import pl.szczodrzynski.edziennik.databinding.GradesItemSubjectBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.modules.grades.GradeView
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.Themes
@ -34,7 +36,7 @@ class SubjectViewHolder(
private const val TAG = "SubjectViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int) {
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager
val contextWrapper = ContextThemeWrapper(activity, Themes.themeInt)
@ -44,6 +46,8 @@ class SubjectViewHolder(
else -> 180f
}
b.unread.isVisible = item.hasUnseen
b.previewContainer.visibility = if (item.state == STATE_CLOSED) View.VISIBLE else View.INVISIBLE
b.yearSummary.visibility = if (item.state == STATE_CLOSED) View.INVISIBLE else View.VISIBLE
@ -71,7 +75,10 @@ class SubjectViewHolder(
})
}*/
val hideImproved = manager.hideImproved
for (grade in firstSemester.grades) {
if (hideImproved && grade.isImproved)
continue
b.gradesContainer.addView(GradeView(
contextWrapper,
grade,

View File

@ -71,7 +71,7 @@ class CounterActivity : AppCompatActivity(), CoroutineScope {
bellSyncDiffMillis *= app.config.timetable.bellSyncMultiplier.toLong()
}
counterJob = startCoroutineTimer(repeatMillis = 1000) {
counterJob = startCoroutineTimer(repeatMillis = 500) {
update()
}
}}

View File

@ -39,7 +39,7 @@ class HomeDummyCard(override val id: Int) : HomeCard, CoroutineScope {
}
holder.root += text
timer = startCoroutineTimer(repeatMillis = 1000) {
timer = startCoroutineTimer(repeatMillis = 500) {
time++
text.text = "Coroutine timer at #$id! $time seconds"
}

View File

@ -4,8 +4,6 @@
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.os.Build
import android.text.TextUtils
@ -18,7 +16,6 @@ import android.widget.LinearLayout
import android.widget.LinearLayout.HORIZONTAL
import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
import android.widget.TextView
import androidx.core.graphics.ColorUtils
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.lifecycle.Observer
@ -28,23 +25,16 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.CardHomeGradesBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.grades.GradeView
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_DEFAULT
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel
import kotlin.coroutines.CoroutineContext
@ -127,32 +117,11 @@ class HomeGradesCard(
16 /*ellipsize width*/)) / 1.5f
subject.grades1.onEach { grade ->
val gradeColor = when (App.config.forProfile().grades.colorMode) {
COLOR_MODE_DEFAULT -> grade.color
else -> Colors.gradeToColor(grade)
}
val gradeName = TextView(gradeItem.context).apply {
text = when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format, grade.name)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format, grade.name)
TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format, grade.name)
TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format, grade.name)
else -> grade.name
}
setTextColor(when (ColorUtils.calculateLuminance(gradeColor) > 0.25) {
true -> 0xff000000
else -> 0xffffffff
}.toInt())
setTypeface(null, Typeface.BOLD)
setBackgroundResource(R.drawable.bg_rounded_4dp)
background.colorFilter = PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)
setPadding(5.dp, 0, 5.dp, 0)
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
val gradeName = GradeView(
gradeItem.context,
grade,
app.gradesManager
)
totalWidth += gradeName.measuredWidth + 5.dp

View File

@ -166,8 +166,8 @@ class HomeTimetableCard(
&& !(it.isCancelled && ignoreCancelled)
}
if (lessons.isEmpty() && timetableDate.weekDay <= 5)
break
//if (lessons.isEmpty() && timetableDate.weekDay <= 5)
// break
checkedDays++
}
@ -249,7 +249,7 @@ class HomeTimetableCard(
subjectSpannable = firstLesson.subjectSpannable
counterJob = startCoroutineTimer(repeatMillis = 1000) {
counterJob = startCoroutineTimer(repeatMillis = 500) {
count()
}
}

View File

@ -523,10 +523,12 @@ public class SettingsNewFragment extends MaterialAboutFragment {
);
}
private String getSyncCardQuietHoursSubText() {
if (app.getConfig().getSync().getQuietHoursStart() == null || app.getConfig().getSync().getQuietHoursEnd() == null)
return "";
return getString(
app.getConfig().getSync().getQuietHoursStart() >= app.getConfig().getSync().getQuietHoursEnd() ? R.string.settings_sync_quiet_hours_subtext_next_day_format : R.string.settings_sync_quiet_hours_subtext_format,
Time.fromMillis(Math.abs(app.getConfig().getSync().getQuietHoursStart())).getStringHM(),
Time.fromMillis(app.getConfig().getSync().getQuietHoursEnd()).getStringHM()
app.getConfig().getSync().getQuietHoursStart().getValue() >= app.getConfig().getSync().getQuietHoursEnd().getValue() ? R.string.settings_sync_quiet_hours_subtext_next_day_format : R.string.settings_sync_quiet_hours_subtext_format,
app.getConfig().getSync().getQuietHoursStart().getStringHM(),
app.getConfig().getSync().getQuietHoursEnd().getStringHM()
);
}
private MaterialAboutItem getSyncCardWifiItem() {
@ -650,7 +652,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.size(IconicsSize.dp(iconSizeDp))
.color(IconicsColor.colorInt(iconColor))
);
syncCardQuietHoursItem.setChecked(app.getConfig().getSync().getQuietHoursStart() > 0);
syncCardQuietHoursItem.setChecked(app.getConfig().getSync().getQuietHoursEnabled());
syncCardQuietHoursItem.setSubTextChecked(getSyncCardQuietHoursSubText());
syncCardQuietHoursItem.setOnClickAction(() -> {
new MaterialDialog.Builder(activity)
@ -662,10 +664,12 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.itemsCallback((dialog, itemView, position, text) -> {
if (position == 0) {
// set beginning
Time time = Time.fromMillis(Math.abs(app.getConfig().getSync().getQuietHoursStart()));
Time time = app.getConfig().getSync().getQuietHoursStart();
if (time == null)
time = new Time(22, 30, 0);
TimePickerDialog.newInstance((v2, hourOfDay, minute, second) -> {
// if it's disabled, it'll be enabled automatically
app.getConfig().getSync().setQuietHoursStart(new Time(hourOfDay, minute, second).getInMillis());
app.getConfig().getSync().setQuietHoursEnabled(true);
app.getConfig().getSync().setQuietHoursStart(new Time(hourOfDay, minute, second));
syncCardQuietHoursItem.setChecked(true);
syncCardQuietHoursItem.setSubTextChecked(getSyncCardQuietHoursSubText());
refreshMaterialAboutList();
@ -673,13 +677,12 @@ public class SettingsNewFragment extends MaterialAboutFragment {
}
else {
// set end
Time time = Time.fromMillis(app.getConfig().getSync().getQuietHoursEnd());
Time time = app.getConfig().getSync().getQuietHoursEnd();
if (time == null)
time = new Time(5, 30, 0);
TimePickerDialog.newInstance((v2, hourOfDay, minute, second) -> {
if (app.getConfig().getSync().getQuietHoursStart() < 0) {
// if it's disabled, enable
app.getConfig().getSync().setQuietHoursStart(-1 * app.getConfig().getSync().getQuietHoursStart());
}
app.getConfig().getSync().setQuietHoursEnd(new Time(hourOfDay, minute, second).getInMillis());
app.getConfig().getSync().setQuietHoursEnabled(true);
app.getConfig().getSync().setQuietHoursEnd(new Time(hourOfDay, minute, second));
syncCardQuietHoursItem.setChecked(true);
syncCardQuietHoursItem.setSubTextChecked(getSyncCardQuietHoursSubText());
refreshMaterialAboutList();
@ -689,15 +692,10 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.show();
});
syncCardQuietHoursItem.setOnChangeAction((isChecked, tag) -> {
if (isChecked && app.getConfig().getSync().getQuietHoursStart() < 0) {
app.getConfig().getSync().setQuietHoursStart(app.getConfig().getSync().getQuietHoursStart() * -1);
}
else if (!isChecked && app.getConfig().getSync().getQuietHoursStart() > 0) {
app.getConfig().getSync().setQuietHoursStart(app.getConfig().getSync().getQuietHoursStart() * -1);
}
else if (isChecked && app.getConfig().getSync().getQuietHoursStart() == 0) {
app.getConfig().getSync().setQuietHoursStart(new Time(22, 30, 0).getInMillis());
app.getConfig().getSync().setQuietHoursEnd(new Time(5, 30, 0).getInMillis());
app.getConfig().getSync().setQuietHoursEnabled(isChecked);
if (isChecked && app.getConfig().getSync().getQuietHoursStart() == null) {
app.getConfig().getSync().setQuietHoursStart(new Time(22, 30, 0));
app.getConfig().getSync().setQuietHoursEnd(new Time(5, 30, 0));
syncCardQuietHoursItem.setSubTextChecked(getSyncCardQuietHoursSubText());
refreshMaterialAboutList();
}

View File

@ -5,15 +5,15 @@
package pl.szczodrzynski.edziennik.ui.modules.timetable
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig
@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.base.PagerFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
@ -35,7 +36,7 @@ import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.min
class TimetableDayFragment : Fragment(), CoroutineScope {
class TimetableDayFragment : PagerFragment(), CoroutineScope {
companion object {
private const val TAG = "TimetableDayFragment"
}
@ -44,7 +45,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
private lateinit var activity: MainActivity
private lateinit var inflater: AsyncLayoutInflater
private lateinit var job: Job
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@ -53,7 +54,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24 * 60
private val utils by lazy { TimetableUtils() }
private val manager by lazy { app.timetableManager }
// find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
@ -88,25 +89,23 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null
if (context == null)
return null
context ?: return null
app = activity.application as App
job = Job()
this.inflater = AsyncLayoutInflater(context!!)
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
return FrameLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
addView(ProgressBar(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
})
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
return
Log.d(TAG, "onViewCreated, date=$date")
override fun onPageCreated(): Boolean {
if (!isAdded)
return false
// observe lesson database
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons ->
@ -117,6 +116,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
processLessonList(lessons, events)
}
})
return true
}
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
@ -174,6 +175,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
}
dayView.setHourLabelViews(hourLabelViews)
lessons.forEach { it.showAsUnseen = !it.seen }
buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events)
}
@ -209,7 +212,6 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
eventView.tag = lesson
eventView.setOnClickListener {
Log.d(TAG, "Clicked ${it.tag}")
if (isAdded && it.tag is LessonFull)
LessonDetailsDialog(activity, it.tag as LessonFull)
}
@ -275,10 +277,13 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
lb.unread = lesson.type != Lesson.TYPE_NORMAL && !lesson.seen
lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen
if (!lesson.seen) {
manager.markAsSeen(lesson)
}
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
lb.annotationVisible = utils.getAnnotation(activity, lesson, lb.annotation)
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
// The day view needs the event time ranges in the start minute/end minute format,
// so calculate those here

View File

@ -15,7 +15,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.viewpager.widget.ViewPager
import com.google.android.material.datepicker.MaterialDatePicker
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
@ -26,12 +25,9 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
import pl.szczodrzynski.edziennik.observeOnce
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.GenerateBlockTimetableDialog
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
@ -50,7 +46,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2Binding
private lateinit var job: Job
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@ -61,11 +57,6 @@ class TimetableFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
job = Job()
context!!.theme.applyStyle(Themes.appTheme, true)
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false)
// activity, context and profile is valid
b = FragmentTimetableV2Binding.inflate(inflater)
// TODO: 2020-01-05 resolve issues with page scrolling (and scrolling up) with viewpager and swipe to refresh
//b.refreshLayout.setParent(activity.swipeRefreshLayout)
@ -91,8 +82,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
if (!isAdded)
return@launch
if (app.profile.getStudentData("timetableNotPublic", false)) {
@ -134,6 +124,8 @@ class TimetableFragment : Fragment(), CoroutineScope {
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
}
deferred.await()
if (!isAdded)
return@launch
val pagerAdapter = TimetablePagerAdapter(
fragmentManager ?: return@launch,
@ -141,7 +133,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
startHour,
endHour
)
b.viewPager.offscreenPageLimit = 2
b.viewPager.offscreenPageLimit = 1
b.viewPager.adapter = pagerAdapter
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
@ -162,7 +154,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
activity.gainAttentionFAB()
fabShown = true
}
markLessonsAsSeen()
//markLessonsAsSeen()
}
})
@ -224,7 +216,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
})
}}
private fun markLessonsAsSeen() = pageSelection?.let { date ->
/*private fun markLessonsAsSeen() = pageSelection?.let { date ->
app.db.timetableDao().getForDate(App.profileId, date).observeOnce(this@TimetableFragment, Observer { lessons ->
lessons.forEach { lesson ->
if (lesson.type != Lesson.TYPE_NORMAL && lesson.type != Lesson.TYPE_NO_LESSONS
@ -233,5 +225,5 @@ class TimetableFragment : Fragment(), CoroutineScope {
}
}
})
}
}*/
}

View File

@ -33,11 +33,6 @@ class TimetablePagerAdapter(
putInt("endHour", endHour)
}
}
/*return TimetableDayFragment().apply {
arguments = Bundle().also {
it.putLong("date", items[position].value.toLong())
}
}*/
}
override fun getCount(): Int {

View File

@ -0,0 +1,241 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.observeOnce
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class DateDropdown : TextInputDropDown {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb
var profileId: Int = 0
var showWeekDays = false
var showDays = true
var showOtherDate = true
var nextLessonSubjectId: Long? = null
var nextLessonSubjectName: String? = null
var nextLessonTeamId: Long? = null
var onDateSelected: ((date: Date, lesson: LessonFull?) -> Unit)? = null
var onWeekDaySelected: ((weekDay: Int) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val date = Date.getToday()
val today = date.value
var weekDay = date.weekDay
val dates = withContext(Dispatchers.Default) {
val dates = mutableListOf<Item>()
nextLessonSubjectId?.let {
// item choosing the next lesson of specific subject - relative to selected date
dates += Item(
-it,
context.getString(R.string.dialog_event_manual_date_next_lesson, nextLessonSubjectName),
tag = nextLessonSubjectName
)
}
if (showWeekDays) {
for (i in Week.MONDAY..Week.SUNDAY) {
dates += Item(
i.toLong(),
Week.getFullDayName(i),
tag = i
)
}
}
if (showDays) {
// TODAY
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_today, date.formattedString),
tag = date.clone()
)
// TOMORROW
if (weekDay < 4) {
date.stepForward(0, 0, 1)
weekDay++
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_tomorrow, date.formattedString),
tag = date.clone()
)
}
// REMAINING SCHOOL DAYS OF THE CURRENT WEEK
while (weekDay < 4) {
date.stepForward(0, 0, 1) // step one day forward
weekDay++
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_this_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
}
// go to next week Monday
date.stepForward(0, 0, -weekDay + 7)
weekDay = 0
// ALL SCHOOL DAYS OF THE NEXT WEEK
while (weekDay < 4) {
dates += Item(
date.value.toLong(),
context.getString(R.string.dialog_event_manual_date_next_week, Week.getFullDayName(weekDay), date.formattedString),
tag = date.clone()
)
date.stepForward(0, 0, 1) // step one day forward
weekDay++
}
}
if (showOtherDate) {
dates += Item(
-1L,
context.getString(R.string.dialog_event_manual_date_other),
tag = -1L
)
}
dates
}
clear().append(dates)
isEnabled = true
setOnChangeListener { item ->
when (item.tag) {
-1L -> {
pickerDialog()
false
}
is Date -> {
onDateSelected?.invoke(item.tag, null)
true
}
is Int -> {
onWeekDaySelected?.invoke(item.tag)
true
}
is String -> {
/* next lesson of subject */
activity ?: return@setOnChangeListener false
val subjectId = -item.id
val startDate = getSelected() as? Date ?: Date.getToday()
when (nextLessonTeamId) {
null -> db.timetableDao().getNextWithSubject(profileId, startDate, subjectId)
else -> db.timetableDao().getNextWithSubjectAndTeam(profileId, startDate, subjectId, nextLessonTeamId ?: -1)
}.observeOnce(activity!!, Observer {
if (it == null) {
Toast.makeText(context, R.string.dropdown_date_no_more_lessons, Toast.LENGTH_LONG).show()
return@Observer
}
val lessonDate = it.displayDate ?: return@Observer
selectDate(lessonDate)
onDateSelected?.invoke(lessonDate, it)
})
false
}
else -> false
}
}
}
fun pickerDialog() {
MaterialDatePicker.Builder
.datePicker()
.setSelection(
if (selected?.tag is Date)
(selected?.tag as Date).inMillis
else
Date.getToday().inMillis
)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
selectDate(dateSelected)
onDateSelected?.invoke(dateSelected, null)
}
this@DateDropdown.activity ?: return@apply
show(this@DateDropdown.activity!!.supportFragmentManager, "MaterialDatePicker")
}
}
fun selectDate(date: Date) {
if (select(date) == null)
select(Item(
date.value.toLong(),
date.formattedString,
tag = date
))
}
fun selectWeekDay(weekDay: Int) {
if (select(tag = weekDay) == null)
select(Item(
weekDay.toLong(),
Week.getFullDayName(weekDay),
tag = weekDay
))
}
fun selectDefault(date: Date?) {
if (date == null || selected != null)
return
selectDate(date)
}
fun selectDefault(weekDay: Int?) {
if (weekDay == null || selected != null)
return
selectWeekDay(weekDay)
}
/**
* Get the currently selected date.
* ### Returns:
* - null if no valid date is selected
* - [Date] - the selected date, if [showDays] or [showOtherDate] == true
* - [Int] - the selected week day, if [showWeekDays] == true
*/
fun getSelected(): Any? {
return when (val tag = selected?.tag) {
is Date -> tag
is Int -> tag
else -> null
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.text.InputType
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class SubjectDropdown : TextInputDropDown {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb
var profileId: Int = 0
var showNoSubject = true
var showCustomSubject = false
var customSubjectName = ""
var onSubjectSelected: ((subjectId: Long?) -> Unit)? = null
var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val subjects = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
if (showNoSubject) {
list += Item(
-1L,
context.getString(R.string.dialog_event_manual_no_subject),
tag = -1L
)
}
if (showCustomSubject) {
list += Item(
-2L,
context.getString(R.string.dropdown_subject_custom),
tag = -2L
)
}
val subjects = db.subjectDao().getAllNow(profileId)
list += subjects.map { Item(
it.id,
it.longName,
tag = it.id
) }
list
}
clear().append(subjects)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-2L -> {
// custom subject
customNameDialog()
false
}
-1L -> {
// no subject
onSubjectSelected?.invoke(null)
true
}
is Long -> {
// selected a subject
onSubjectSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
fun customNameDialog() {
activity ?: return
MaterialDialog.Builder(activity!!)
.title("Własny przedmiot")
.inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)
.input("Nazwa", "") { _: MaterialDialog?, input: CharSequence ->
customSubjectName = input.toString()
select(Item(
-1L * customSubjectName.crc16(),
customSubjectName,
tag = customSubjectName
))
onCustomSubjectSelected?.invoke(customSubjectName)
}
.show()
}
fun selectSubject(subjectId: Long) {
if (select(subjectId) == null)
select(Item(
subjectId,
"nieznany przedmiot ($subjectId)",
tag = subjectId
))
}
fun selectDefault(subjectId: Long?) {
if (subjectId == null || selected != null)
return
selectSubject(subjectId)
}
/**
* Get the currently selected subject.
* ### Returns:
* - null if no valid subject is selected
* - [Long] - the selected subject's ID
* - [String] - a custom subject name entered, if [showCustomSubject] == true
*/
fun getSelected(): Any? {
return when (selected?.tag) {
-1L -> null
is Long -> selected?.tag as Long
is String -> selected?.tag as String
else -> null
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class TeacherDropdown : TextInputDropDown {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb
var profileId: Int = 0
var showNoTeacher = true
var onTeacherSelected: ((teacherId: Long?) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val teachers = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
if (showNoTeacher) {
list += Item(
-1L,
context.getString(R.string.dialog_event_manual_no_teacher),
tag = -1L
)
}
val teachers = db.teacherDao().getAllNow(profileId)
list += teachers.map { Item(
it.id,
it.fullName,
tag = it.id
) }
list
}
clear().append(teachers)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-1L -> {
// no teacher
onTeacherSelected?.invoke(null)
true
}
is Long -> {
// selected a teacher
onTeacherSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
fun selectTeacher(teacherId: Long) {
if (select(teacherId) == null)
select(Item(
teacherId,
"nieznany nauczyciel ($teacherId)",
tag = teacherId
))
}
fun selectDefault(teacherId: Long?) {
if (teacherId == null || selected != null)
return
selectTeacher(teacherId)
}
/**
* Get the currently selected teacher.
* ### Returns:
* - null if no valid teacher is selected
* - [Long] - the selected teacher's ID
*/
fun getSelected(): Long? {
return when (selected?.tag) {
-1L -> null
is Long -> selected?.tag as Long
else -> null
}
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-7.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class TeamDropdown : TextInputDropDown {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb
var profileId: Int = 0
var showNoTeam = true
var onTeamSelected: ((teamId: Long?) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val teams = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
if (showNoTeam) {
list += Item(
-1L,
context.getString(R.string.dialog_event_manual_no_team),
tag = -1L
)
}
val teams = db.teamDao().getAllNow(profileId)
list += teams.map { Item(
it.id,
it.name,
tag = it.id
) }
list
}
clear().append(teams)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-1L -> {
// no team
onTeamSelected?.invoke(null)
true
}
is Long -> {
// selected a team
onTeamSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
fun selectTeam(teamId: Long) {
if (select(teamId) == null)
select(Item(
teamId,
"nieznana grupa ($teamId)",
tag = teamId
))
}
fun selectDefault(teamId: Long?) {
if (teamId == null || selected != null)
return
selectTeam(teamId)
}
fun selectTeamClass() {
select(items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS
})
}
/**
* Get the currently selected team.
* ### Returns:
* - null if no valid team is selected
* - [Long] - the team's ID
*/
fun getSelected(): Any? {
return when (selected?.tag) {
-1L -> null
is Long -> selected?.tag as Long
else -> null
}
}
}

View File

@ -0,0 +1,227 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-23.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.LessonRange
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class TimeDropdown : TextInputDropDown {
companion object {
const val DISPLAY_LESSON_RANGES = 0
const val DISPLAY_LESSONS = 1
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb
var profileId: Int = 0
var showAllDay = false
var showCustomTime = true
var displayMode = DISPLAY_LESSON_RANGES
var lessonsDate: Date? = null
var onTimeSelected: ((startTime: Time?, endTime: Time?, lessonNumber: Int?) -> Unit)? = null
var onLessonSelected: ((lesson: LessonFull) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems(): Boolean {
var noTimetable = false
val hours = withContext(Dispatchers.Default) {
val hours = mutableListOf<Item>()
if (showAllDay) {
hours += Item(
0L,
context.getString(R.string.dialog_event_manual_all_day),
tag = 0L
)
}
if (showCustomTime) {
hours += Item(
-1,
context.getString(R.string.dialog_event_manual_custom_time),
tag = -1L
)
}
if (displayMode == DISPLAY_LESSON_RANGES) {
val lessonRanges = db.lessonRangeDao().getAllNow(profileId)
hours += lessonRanges.map { Item(
it.startTime.value.toLong(),
context.getString(R.string.timetable_manual_dialog_time_format, it.startTime.stringHM, it.lessonNumber),
tag = it
) }
}
else if (displayMode == DISPLAY_LESSONS && lessonsDate != null) {
val lessons = db.timetableDao().getForDateNow(profileId, lessonsDate!!)
if (lessons.isEmpty()) {
hours += Item(
-2L,
context.getString(R.string.dialog_event_manual_no_timetable),
tag = -2L
)
noTimetable = true
return@withContext hours
}
hours += lessons.map { lesson ->
if (lesson.type == Lesson.TYPE_NO_LESSONS) {
// indicate there are no lessons this day
return@map Item(
-2L,
context.getString(R.string.dialog_event_manual_no_lessons),
tag = -2L
)
}
// create the lesson caption
val text = listOfNotEmpty(
lesson.displayStartTime?.stringHM ?: "",
if (lesson.displaySubjectName != null) "-" else "",
lesson.displaySubjectName?.let {
when {
lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it
}
} ?: ""
)
// add an item with LessonFull as the tag
return@map Item(
lesson.displayStartTime?.value?.toLong() ?: -1,
text.concat(" "),
tag = lesson
)
}
}
hours
}
clear().append(hours)
isEnabled = true
setOnChangeListener {
when (it.tag) {
-2L -> {
// no lessons this day
deselect()
false
}
-1L -> {
// custom start hour
pickerDialog()
false
}
0L -> {
// selected all day
onTimeSelected?.invoke(null, null, null)
true
}
is LessonFull -> {
// selected a specific lesson
onLessonSelected?.invoke(it.tag)
true
}
is LessonRange -> {
// selected a lesson range
onTimeSelected?.invoke(it.tag.startTime, it.tag.endTime, it.tag.lessonNumber)
true
}
is Time -> {
// selected a time
onTimeSelected?.invoke(it.tag, null, null)
true
}
else -> false
}
}
return !noTimetable
}
fun pickerDialog() {
/*MaterialDatePicker.Builder
.datePicker()
.setSelection((selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build()
.apply {
addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it)
selectDate(dateSelected)
}
this@DateDropdown.activity ?: return@apply
show(this@DateDropdown.activity!!.supportFragmentManager, "MaterialDatePicker")
}*/
}
fun selectTime(time: Time) {
if (select(time.value.toLong()) == null)
select(Item(
time.value.toLong(),
time.stringHM,
tag = time
))
}
fun selectDefault(time: Time?) {
if (time == null || selected != null)
return
selectTime(time)
}
/**
* Get the currently selected time.
* ### Returns:
* - null if no valid time is selected
* - a [Pair] of [Time] and [Time]? - the selected time object, if [displayMode] == [DISPLAY_LESSONS] or [showCustomTime]
* - [LessonRange] - the selected lesson range object, if [displayMode] == [DISPLAY_LESSON_RANGES]
*/
fun getSelected(): Any? {
return when (val tag = selected?.tag) {
0L -> null
is LessonFull ->
if (tag.displayStartTime != null)
tag.displayStartTime!! to tag.displayEndTime
else
null
is LessonRange -> tag
is Time -> tag to null
else -> null
}
}
}

View File

@ -229,8 +229,8 @@ class WidgetTimetableProvider : AppWidgetProvider() {
&& !(it.isCancelled && ignoreCancelled)
}
if (lessons.isEmpty() && timetableDate.weekDay <= 5)
break
//if (lessons.isEmpty() && timetableDate.weekDay <= 5)
// break
checkedDays++
}

View File

@ -7,7 +7,7 @@ import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.textfield.TextInputEditText
import pl.szczodrzynski.edziennik.R
class TextInputDropDown : TextInputEditText {
open class TextInputDropDown : TextInputEditText {
constructor(context: Context) : super(context) {
create(context)
}
@ -31,7 +31,7 @@ class TextInputDropDown : TextInputEditText {
setText(selected?.displayText ?: selected?.text)
}
fun create(context: Context) {
open fun create(context: Context) {
val drawable = context.resources.getDrawable(R.drawable.dropdown_arrow)
val wrappedDrawable = DrawableCompat.wrap(drawable)
DrawableCompat.setTint(wrappedDrawable, Themes.getPrimaryTextColor(context))

View File

@ -5,17 +5,23 @@
package pl.szczodrzynski.edziennik.utils.managers
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesAverages
import pl.szczodrzynski.edziennik.ui.modules.grades.models.GradesSemester
import pl.szczodrzynski.edziennik.utils.Colors
import java.text.DecimalFormat
import kotlin.coroutines.CoroutineContext
import kotlin.math.floor
class GradesManager(val app: App) {
class GradesManager(val app: App) : CoroutineScope {
companion object {
const val ORDER_BY_DATE_DESC = 0
const val ORDER_BY_SUBJECT_ASC = 1
@ -30,7 +36,11 @@ class GradesManager(val app: App) {
const val COLOR_MODE_WEIGHTED = 1
}
private val gradeRegex by lazy { """([0-6])([+-])?""".toRegex() }
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
private val gradeRegex by lazy { """([+-])?([0-6])([+-])?""".toRegex() }
private val format = DecimalFormat("#.##")
val orderBy
@ -45,6 +55,10 @@ class GradesManager(val app: App) {
get() = app.config.forProfile().grades.minusValue
val dontCountGrades
get() = app.config.forProfile().grades.dontCountGrades
val hideImproved
get() = app.config.forProfile().grades.hideImproved
val averageWithoutWeight
get() = app.config.forProfile().grades.averageWithoutWeight
fun getOrderByString() = when (orderBy) {
@ -66,51 +80,21 @@ class GradesManager(val app: App) {
else -> null
}
private fun gradeNameToColorStr(grade: String): String? {
when (grade.toLowerCase()) {
"+", "++", "+++" ->
return "4caf50"
"-", "-,", "-,-,", "np", "np.", "npnp", "np,", "np,np,", "bs", "nk" ->
return "ff7043"
"1-", "1", "f" ->
return "ff0000"
"1+", "ef" ->
return "ff3d00"
"2-", "2", "e" ->
return "ff9100"
"2+", "de" ->
return "ffab00"
"3-", "3", "d" ->
return "ffff00"
"3+", "cd" ->
return "c6ff00"
"4-", "4", "c" ->
return "76ff03"
"4+", "bc" ->
return "64dd17"
"5-", "5", "b" ->
return "00c853"
"5+", "ab" ->
return "00bfa5"
"6-", "6", "a" ->
return "2196f3"
"6+", "a+" ->
return "0091ea"
}
return "bdbdbd"
}
fun getRoundedGrade(value: Float): Int {
return floor(value.toDouble()).toInt() + if (value % 1.0f >= 0.75) 1 else 0
}
fun getGradeValue(grade: Grade): Float {
gradeRegex.find(grade.name)?.let {
var value = it[1].toFloatOrNull() ?: return grade.value
if (it[2] == "+")
value += plusValue ?: return grade.value
if (it[2] == "-")
value -= minusValue ?: return grade.value
if (plusValue == null && minusValue == null)
return grade.value
gradeRegex.find(grade.name)?.let { it ->
var value = it[2].toFloatOrNull() ?: return grade.value
when (it[1].notEmptyOrNull() ?: it[3]) {
"+" -> value += plusValue ?: return grade.value
"-" -> value -= minusValue ?: return grade.value
else -> return grade.value
}
return value
}
return grade.value
@ -122,8 +106,62 @@ class GradesManager(val app: App) {
return grade.weight
}
fun getColor(grade: Grade): Int {
return Colors.gradeToColor(grade)
fun getGradeColor(grade: Grade): Int {
val type = grade.type
val defColor = colorMode == COLOR_MODE_DEFAULT
val valueMax = grade.valueMax ?: 0f
val color = when {
type == TYPE_POINT_SUM && !defColor -> {
when {
grade.id < 0 -> grade.color and 0xffffff /* starting points */
grade.value < 0 -> 0xf44336
grade.value > 0 -> 0x4caf50
else -> 0xbdbdbd
}
}
type == TYPE_POINT_AVG && !defColor ->
when (valueMax) {
0f -> 0xbdbdbd
else -> when (grade.value / valueMax * 100f) {
in 0f..29f -> 0xf50000 // 1
in 30f..49f -> 0xff5722 // 2
in 50f..74f -> 0xff9100 // 3
in 75f..89f -> 0xffd600 // 4
in 90f..97f -> 0x00c853 // 5
else -> 0x0091ea // 6
}
}
type == TYPE_NORMAL && defColor -> grade.color and 0xffffff
type in TYPE_NORMAL..TYPE_YEAR_FINAL -> {
when (grade.name.toLowerCase()) {
"+", "++", "+++" -> 0x4caf50
"0", "-", "-,", "-,-,", "np", "np.", "npnp", "np,", "np,np,", "bs", "nk", "bz" -> 0xff7043
"1-", "1", "f", "ng" -> 0xff0000
"1+", "ef" -> 0xff3d00
"2-", "2", "e", "ndp" -> 0xff9100
"2+", "de" -> 0xffab00
"3-", "3", "d", "popr" -> 0xffff00
"3+", "cd" -> 0xc6ff00
"4-", "4", "c", "db" -> 0x76ff03
"4+", "bc" -> 0x64dd17
"5-", "5", "b", "bdb" -> 0x00c853
"5+", "ab" -> 0x00bfa5
"6-", "6", "a", "wz" -> 0x2196f3
"6+", "a+" -> 0x0091ea
else -> grade.color and 0xffffff
}
}
else -> grade.color and 0xffffff
}
return color or 0xff000000.toInt()
}
fun markAsSeen(grade: GradeFull) {
grade.seen = true
startCoroutineTimer(500L, 0L) {
app.db.metadataDao().setSeen(grade.profileId, grade, true)
}
}
fun calculateAverages(averages: GradesAverages, semesters: List<GradesSemester>? = null) {
@ -142,7 +180,7 @@ class GradesManager(val app: App) {
averages.normalWeightedCount > 0f -> {
averages.normalWeightedSum / averages.normalWeightedCount
}
averages.normalSum > 0f && averages.normalCount > 0f -> {
averageWithoutWeight && averages.normalSum > 0f && averages.normalCount > 0f -> {
averages.normalSum / averages.normalCount
}
else -> null

View File

@ -1,22 +1,37 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-16.
* Copyright (c) Kuba Szczodrzyński 2020-3-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.timetable
package pl.szczodrzynski.edziennik.utils.managers
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.widget.TextView
import pl.szczodrzynski.edziennik.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.navlib.getColorFromAttr
import kotlin.coroutines.CoroutineContext
class TimetableManager(val app: App) : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
fun markAsSeen(lesson: LessonFull) {
lesson.seen = true
if (lesson.type <= Lesson.TYPE_NORMAL)
return
startCoroutineTimer(500L, 0L) {
app.db.metadataDao().setSeen(lesson.profileId, lesson, true)
}
}
class TimetableUtils {
fun getAnnotation(context: Context, lesson: LessonFull, annotation: TextView): Boolean {
var annotationVisible = false
when (lesson.type) {

View File

@ -4,7 +4,8 @@
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -13,12 +14,21 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
android:paddingStart="24dp"
android:paddingEnd="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="10dp"
style="@style/TextAppearance.AppCompat.Small"
android:text="@string/grades_config_title"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginBottom="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
@ -28,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="0dp"
android:text="Własna wartość plusa" />
android:text="@string/grades_config_plus_value" />
<it.sephiroth.android.library.numberpicker.NumberPicker
android:id="@+id/customPlusValue"
@ -44,7 +54,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginBottom="6dp"
android:gravity="center_vertical"
android:orientation="horizontal">
@ -54,7 +64,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="0dp"
android:text="Własna wartość minusa" />
android:text="@string/grades_config_minus_value" />
<it.sephiroth.android.library.numberpicker.NumberPicker
android:id="@+id/customMinusValue"
@ -67,9 +77,55 @@
app:picker_orientation="horizontal" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="6dp"
android:background="@drawable/divider"/>
<CheckBox
android:id="@+id/dontCountZeroToAverage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="@string/settings_register_dont_count_zero_text"/>
<CheckBox
android:id="@+id/hideImproved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="32dp"
android:text="@string/grades_config_dont_show_improved"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/averageWithoutWeight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="32dp"
android:text="@string/grades_config_average_without_weight"/>
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/averageWithoutWeightHelp"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="?selectableItemBackgroundBorderless"
android:scaleType="centerInside"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-help-circle-outline"
app:iiv_size="16dp"
tools:src="@android:drawable/ic_menu_help" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
style="@style/TextAppearance.AppCompat.Small"
android:text="@string/menu_grades_sort_mode"/>
@ -167,21 +223,6 @@
android:minHeight="0dp"
android:text="@string/settings_register_avg_mode_3"/>
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="10dp"
style="@style/TextAppearance.AppCompat.Small"
android:text="@string/other"/>
<CheckBox
android:id="@+id/dontCountZeroToAverage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="@string/settings_register_dont_count_zero_text"/>
</LinearLayout>
</ScrollView>
</layout>

View File

@ -24,7 +24,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_event_manual_date">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.DateDropdown
android:id="@+id/dateDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_time">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown
android:id="@+id/timeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -53,7 +53,7 @@
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_team">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.TeamDropdown
android:id="@+id/teamDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -158,7 +158,7 @@
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.SubjectDropdown
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -173,7 +173,7 @@
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
<pl.szczodrzynski.edziennik.ui.modules.views.TeacherDropdown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -9,7 +9,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="20dp">
android:paddingHorizontal="20dp"
android:paddingTop="16dp">
<RadioGroup
android:id="@+id/weekSelectionRadioGroup"
@ -36,10 +37,17 @@
android:text="@string/timetable_generate_selected_week" />
</RadioGroup>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="8dp"
android:background="@drawable/divider"/>
<CheckBox
android:id="@+id/showProfileNameCheckbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="@string/timetable_generate_show_profile_name" />
<CheckBox
@ -47,12 +55,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:minHeight="0dp"
android:text="@string/timetable_generate_show_teachers_names" />
<CheckBox
android:id="@+id/noColorsCheckbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="@string/timetable_generate_no_colors" />
</LinearLayout>
</layout>

View File

@ -55,13 +55,12 @@
android:layout_width="72dp"
android:layout_height="72dp"
android:background="@drawable/bg_rounded_16dp"
android:fontFamily="serif-monospace"
android:gravity="center"
android:padding="8dp"
android:text="@{grade.name}"
android:textIsSelectable="true"
android:textSize="36sp"
android:textStyle="bold"
app:autoSizeMinTextSize="18sp"
app:autoSizeMaxTextSize="56sp"
app:autoSizeTextType="uniform"
tools:background="#ff4caf50"
@ -270,10 +269,45 @@
android:id="@+id/gradeHistoryList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/row_grades_list_item" />
tools:listitem="@layout/grades_item_grade"
tools:itemCount="2"/>
</androidx.core.widget.NestedScrollView>
<View
android:id="@+id/customValueDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@drawable/divider" />
<LinearLayout
android:id="@+id/customValueLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginVertical="8dp"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:text="@string/grades_stats_custom_value_notice"
android:textAppearance="@style/NavView.TextView.Helper"
android:textSize="12sp"
android:textStyle="italic" />
<Button
android:id="@+id/customValueButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="@string/configure" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</layout>

View File

@ -294,6 +294,12 @@
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@drawable/divider"/>
<LinearLayout
android:id="@+id/eventsNoData"
android:layout_width="match_parent"
@ -331,7 +337,6 @@
android:id="@+id/eventsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
tools:visibility="visible"
tools:listitem="@layout/event_list_item" />

View File

@ -20,6 +20,10 @@
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
tools:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="#4caf50"
tools:textSize="24sp"
tools:gravity="center"
tools:text="5+" />
<LinearLayout
@ -30,6 +34,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
@ -38,12 +43,22 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
tools:text="kraje hehe no jak zwykle jedynka z geografii. to jest baaardzo długi tekst ale szkoda że się nie scrolluje." />
tools:text="kraje" />
<View
android:id="@+id/unread"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:visibility="gone"
android:background="@drawable/unread_red_circle"
tools:visibility="visible"/>
<TextView
android:id="@+id/gradeAddedDate"
@ -81,7 +96,8 @@
android:ellipsize="end"
android:maxWidth="200dp"
android:maxLines="1"
tools:text="Kartkówki - K1 123456789 12345678" />
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Kartkówki - K1" />
<TextView
android:id="@+id/gradeTeacherName"
@ -94,7 +110,7 @@
android:gravity="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Anna Jakaśtam-Cośtam" />
tools:text="Jan Kowalski" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -37,6 +37,17 @@
android:textSize="18sp"
tools:text="Semestr 1" />
<View
android:id="@+id/unread"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="gone"
android:background="@drawable/unread_red_circle"
tools:visibility="visible"/>
<TextView
android:id="@+id/average"
android:layout_width="wrap_content"

View File

@ -20,7 +20,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/subjectName"
@ -29,11 +30,22 @@
android:layout_margin="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:fontFamily="sans-serif"
android:maxLines="2"
android:textColor="?android:textColorPrimary"
android:textSize="20sp"
tools:text="systemy operacyjne\n1234" />
tools:text="systemy operacyjne" />
<View
android:id="@+id/unread"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="gone"
android:background="@drawable/unread_red_circle"
tools:visibility="visible"/>
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/dropdownIcon"
@ -85,7 +97,7 @@
tools:text1="Cały rok: 3 oceny • suma: 320 pkt"
tools:text2="Cały rok: 15 ocen • średnia: 2,62"
tools:text="Cały rok: 6 ocen • punkty: 34.20/40 (87.5%)"
tools:visibility="gone"/>
tools:visibility="visible"/>
</FrameLayout>
</LinearLayout>
</layout>

View File

@ -50,7 +50,8 @@
android:background="@drawable/bg_rounded_8dp"
android:gravity="center"
android:textSize="24sp"
app:autoSizeMaxTextSize="24sp"
app:autoSizeMinTextSize="14sp"
app:autoSizeMaxTextSize="32sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/gradesListName"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_rounded_8dp"
android:fontFamily="serif-monospace"
android:gravity="center"
android:padding="2dp"
android:textSize="24sp"
android:textStyle="bold"
app:autoSizeMaxTextSize="32sp"
app:autoSizeTextType="uniform"
tools:background="#4caf50"
tools:text="NB" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/gradesListCategoryColumn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
tools:text="kraje hehe no jak zwykle jedynka z geografii. to jest baaardzo długi tekst ale szkoda że się nie scrolluje." />
<TextView
android:id="@+id/gradesListAddedDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="14.10.2015" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/gradesListWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:textStyle="bold"
tools:text="waga 30"
tools:visibility="visible" />
<TextView
android:id="@+id/gradesListCategoryDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:ellipsize="end"
android:maxWidth="200dp"
android:maxLines="1"
tools:text="Kartkówki - K1 123456789 12345678" />
<TextView
android:id="@+id/gradesListTeacher"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="end"
android:maxLines="1"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="Anna Jakaśtam-Cośtam" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -1,459 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<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/gradesSubjectRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/gradesSubjectTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/loading"
android:textAppearance="@style/NavView.TextView.Title"
app:layout_constraintEnd_toStartOf="@+id/gradesSubjectExpandIndicator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="{SUBJECT_NAME}" />
<LinearLayout
android:id="@+id/gradesSubjectPreviewContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:orientation="horizontal"
tools:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/gradesSubjectTitle">
<TextView
android:id="@+id/gradesSubjectPreviewSemester"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:textStyle="italic"
android:visibility="gone"
tools:text="Semestr 1"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/gradesSubjectPreviewContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
</LinearLayout>
<TextView
android:id="@+id/gradesSubjectPreviewAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
tools:text="4.70" />
<TextView
android:id="@+id/gradesSubjectPreviewProposed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp_outline"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:visibility="gone"
tools:text="4"
tools:visibility="visible" />
<TextView
android:id="@+id/gradesSubjectPreviewFinal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
android:visibility="gone"
tools:text="5"
tools:visibility="visible" />
<TextView
android:id="@+id/gradesSubjectPreviewYearAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:visibility="gone"
tools:visibility="visible"
tools:text="4.70"
android:layout_marginEnd="5dp" />
<TextView
android:id="@+id/gradesSubjectPreviewYearProposed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp_outline"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:visibility="gone"
tools:text="4"
tools:visibility="visible" />
<TextView
android:id="@+id/gradesSubjectPreviewYearFinal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
android:visibility="gone"
tools:text="5"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:id="@+id/gradesSubjectContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/gradesSubjectTitle">
<LinearLayout
android:id="@+id/gradesSubjectSemester2Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/gradesSubjectSemester2ExpandIndicator"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:padding="8dp"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-menu-down"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/gradesSubjectSemester2Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:ellipsize="start"
android:singleLine="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:text="@string/grades_semester2_header" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="end"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/gradesSubjectYearAverage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
tools:text="koniec roku: 4.70" />
<TextView
android:id="@+id/gradesSubjectYearProposed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp_outline"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
tools:text="4" />
<TextView
android:id="@+id/gradesSubjectYearFinal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="5" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="5dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/gradesSubjectSemester2Average"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
tools:text="semestr 2: 5.00" />
<TextView
android:id="@+id/gradesSubjectSemester2Proposed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp_outline"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
tools:text="5" />
<TextView
android:id="@+id/gradesSubjectSemester2Final"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="5" />
</LinearLayout>
</LinearLayout>
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/gradesSubjectSemester2EditButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center_vertical"
android:background="?selectableItemBackground"
android:padding="8dp"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-pencil"
tools:srcCompat="@tools:sample/avatars" />
</LinearLayout>
<LinearLayout
android:id="@+id/gradesSubjectSemester2Container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="visible">
<androidx.core.widget.NestedScrollView
android:id="@+id/gradesSubjectSemester2Nest"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gradesSubjectSemester2Content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:itemCount="3"
tools:listitem="@layout/row_grades_list_item" />
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<LinearLayout
android:id="@+id/gradesSubjectSemester1Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/gradesSubjectSemester1ExpandIndicator"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:padding="8dp"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-menu-down"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/gradesSubjectSemester1Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:ellipsize="start"
android:singleLine="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:text="@string/grades_semester1_header"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="end"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/gradesSubjectSemester1Average"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
tools:text="semestr 1: 0.63" />
<TextView
android:id="@+id/gradesSubjectSemester1Proposed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp_outline"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@android:color/white"
android:textStyle="bold"
tools:text="1" />
<TextView
android:id="@+id/gradesSubjectSemester1Final"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="2" />
</LinearLayout>
</LinearLayout>
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/gradesSubjectSemester1EditButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center_vertical"
android:background="?selectableItemBackground"
android:padding="8dp"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-pencil"
tools:srcCompat="@tools:sample/avatars" />
</LinearLayout>
<LinearLayout
android:id="@+id/gradesSubjectSemester1Container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<androidx.core.widget.NestedScrollView
android:id="@+id/gradesSubjectSemester1Nest"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gradesSubjectSemester1Content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:itemCount="5"
tools:listitem="@layout/row_grades_list_item" />
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</LinearLayout>
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/gradesSubjectExpandIndicator"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:padding="8dp"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-menu-down"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars[0]" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -74,8 +74,8 @@
tools:text="pracownia urządzeń techniki komputerowej" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"

View File

@ -188,7 +188,7 @@
<string name="error_56_reason">Błąd serwera: odpowiedź niedostępna</string>
<string name="error_57_reason">Błąd serwera: niespełnione zależności</string>
<string name="error_58_reason">Wewnętrzny błąd serwera</string>
<string name="error_59_reason">Usługa tymczasowo niedostępna</string>
<string name="error_59_reason">Dziennik jest tymczasowo niedostępny</string>
<string name="error_60_reason">Brak internetu: nie znaleziono adresu serwera</string>
<string name="error_61_reason">Brak internetu: przekroczono czas oczekiwania</string>
<string name="error_62_reason">Brak internetu</string>

View File

@ -1043,7 +1043,7 @@
<string name="dialog_event_manual_date_this_week" translatable="false">%s (%s)</string>
<string name="dialog_event_manual_date_other">-- inna data --</string>
<string name="dialog_event_manual_date_today">dzisiaj (%s)</string>
<string name="dialog_event_manual_date_next_week">następny %s (%s)</string>
<string name="dialog_event_manual_date_next_week">przyszły %s (%s)</string>
<string name="dialog_event_manual_no_lessons">Nie ma lekcji tego dnia</string>
<string name="dialog_profile_remove_success">Profil został usunięty.</string>
<string name="snackbar_error_text">Wystąpił błąd</string>
@ -1233,4 +1233,28 @@
<string name="grades_stats_help_text">Ocena przewidywana z danego przedmiotu jest obliczana na podstawie aktualnej średniej ważonej.\n\nOcena jest liczbą całkowitą, jaką wystawił by nauczyciel bazując na średniej. Liczba zaokrąglona jest w górę jeśli część po przecinku przekroczy ,75.\nPrzykładowo: średnie 3,75 jak również 4,74 dają w wyniku ocenę dobrą (4).\n\nŚrednia przewidywana ze wszystkich przedmiotów obejmuje obliczone w ten sposób oceny końcowe.</string>
<string name="grades_stats_custom_value_notice">Została ustawiona własna wartość plusa/minusa. Jeśli uważasz, że średnia się nie zgadza, kliknij Konfiguruj.</string>
<string name="configure">Konfiguruj</string>
<string name="menu_timetable_manual">Edytor planu lekcji</string>
<string name="dropdown_subject_custom">Własny przedmiot</string>
<string name="dialog_event_manual_date_choose">Wybierz datę</string>
<string name="dropdown_date_no_more_lessons">Nie ma więcej lekcji tego przedmiotu. Pobierz plan lekcji i spróbuj ponownie.</string>
<string name="timetable_manual_dialog_date">Data</string>
<string name="timetable_manual_dialog_date_choose">Wybierz datę</string>
<string name="timetable_manual_dialog_subject_choose">Wybierz przedmiot</string>
<string name="timetable_manual_dialog_time">Godzina</string>
<string name="timetable_manual_dialog_time_choose">Wybierz godzinę</string>
<string name="timetable_manual_dialog_time_format" formatted="false">%s (lekcja %d)</string>
<string name="timetable_manual_item_empty">Nie dodano własnych lekcji na ten dzień.</string>
<string name="timetable_manual_repeat_by_subject_notice">Na każdej lekcji przedmiotu %s</string>
<string name="timetable_manual_repeating_notice">Obejmuje plan lekcji na każdy tydzień</string>
<string name="timetable_manual_type_by_subject">Wg. przedmiotu</string>
<string name="timetable_manual_type_one_time">Jednorazowo</string>
<string name="timetable_manual_type_repeating">Cyklicznie</string>
<string name="grades_config_title">Konfiguracja ocen</string>
<string name="grades_config_dont_show_improved">Ukrywaj oceny poprawione z listy</string>
<string name="grades_config_average_without_weight">Licz średnią jeśli wszystkie wagi to 0</string>
<string name="grades_config_average_without_weight_message">Pozwala na liczenie średniej arytmetycznej z przedmiotów, w których wszystkie wystawione oceny mają wagę 0 (nie są liczone do średniej).\n\nJeśli taki przedmiot celowo nie powinien być liczony, odznacz okienko przy tym ustawieniu.</string>
<string name="grades_config_minus_value">Własna wartość minusa</string>
<string name="grades_config_plus_value">Własna wartość plusa</string>
<string name="timetable_syncing_text">Pobieranie planu lekcji na wybrany tydzień...</string>
<string name="dialog_event_manual_no_timetable">Nie pobrano planu lekcji...</string>
</resources>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.3.61'
release = [
versionName: "4.0-beta.11",
versionCode: 4000011
versionName: "4.0-beta.12",
versionCode: 4000012
]
setup = [