Compare commits

...

46 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
55268f1c43 [4.0-beta.11] Update build.gradle, signing and changelog. 2020-03-06 23:23:33 +01:00
1bec6d281c [Grades] Implement Grades editor. 2020-03-06 21:24:01 +01:00
f17a02be54 [Grades] Implement new Grades module (UI & API changes). 2020-03-06 21:09:05 +01:00
4e8fdd2225 [API/Idziennik] Fix incorrect exam type. 2020-03-06 09:25:35 +01:00
59819b4a96 [Base] Update TemplateFragment. 2020-03-04 19:09:53 +01:00
673378d8d9 [UI/Home] Improve no data text font in home cards. 2020-03-04 19:02:50 +01:00
30044d6b21 [Timetable] Ignore last lessons if cancelled and jump to the next day. 2020-03-03 18:06:55 +01:00
ee43d40680 [API/Librus] Fix device not registered error in push config. 2020-03-03 10:35:48 +01:00
1354faf8c7 [Dialog/GenerateBlockTimetable] Make better dialog layout. 2020-02-29 00:43:38 +01:00
1bfb3781ab [UI/Lists] Add missing item dividers. Try to improve attendance & grades design. 2020-02-28 23:45:46 +01:00
d7d0c6f822 [UI/Events] Add button tooltips in dialogs. Add showing weekday in home card. Add 'go to timetable' button in details dialog. 2020-02-28 23:01:38 +01:00
2bea18dc3c [Home/Events] Add new card to home fragment. Disable debug card swapping. 2020-02-28 22:38:03 +01:00
f998f2d956 [Home/Timetable] Remove "?" lessons from timetable card. 2020-02-28 21:10:03 +01:00
faa77ee5fb [Widgets/Timetable] Show crossed out classroom in lesson change if no new classroom specified. 2020-02-28 21:10:03 +01:00
88ec463284 [Gradle] Update gradle. 2020-02-28 18:33:28 +01:00
b7df71d7d9 [API/Grades] Fix proposed/final grades added date in Mobidziennik, Idziennik. 2020-02-27 23:41:41 +01:00
6a28dbd2c4 [API/Idziennik] Add changing the selected student/register (web) to get grades in some cases. 2020-02-27 23:36:41 +01:00
010f7fa1fe [API/Idziennik] Add getting lucky number from website. Fix API lucky number date. 2020-02-27 23:01:47 +01:00
209f98594f [Widgets/Timetable] Show lessons date in unified timetable widget. 2020-02-27 22:32:02 +01:00
54121c99a3 [Login/Captcha] Update captcha to fit smaller screens. Fix Librus invalid login error with captcha. 2020-02-26 21:27:03 +01:00
f6f1370edf [Debug] Add new debug mode. Include hidden Chucker in release. 2020-02-26 20:37:55 +01:00
d5863485f9 [API/Edudziennik] Fix getting attendance for the second semester. 2020-02-25 19:28:53 +01:00
159 changed files with 5814 additions and 3747 deletions

View File

@ -166,8 +166,8 @@ dependencies {
//implementation project(":Navigation")
implementation project(":szkolny-font")
debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1"
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
//releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1"
//implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT'
//implementation 'com.github.kuba2k2.uonet-request-signer:android:master-63f094b14a-1'
@ -190,6 +190,8 @@ dependencies {
implementation 'com.github.jetradarmobile:android-snowfall:1.2.0'
implementation "io.coil-kt:coil:0.9.2"
implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76'
}
repositories {
mavenCentral()

View File

@ -1,19 +1,23 @@
<h3>Wersja 4.0-beta.10, 2020-02-24</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] = {
0xd6, 0x0d, 0xa0, 0xa7, 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

@ -45,7 +45,9 @@ import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
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
@ -60,10 +62,13 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
get() = profile.id
var devMode = false
var debugMode = false
}
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
@ -103,7 +108,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
.readTimeout(10, TimeUnit.SECONDS)
builder.installHttpsSupport(this)
if (devMode || BuildConfig.DEBUG) {
if (debugMode || BuildConfig.DEBUG) {
HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this))
@ -158,6 +163,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
Iconics.registerFont(SzkolnyFont)
App.db = AppDb(this)
Themes.themeInt = config.ui.theme
debugMode = config.debugMode
MHttp.instance().customOkHttpClient(http)
if (!profileLoadById(config.lastProfileId)) {
@ -174,6 +180,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
if (config.devModePassword != null)
checkDevModePassword()
debugMode = devMode || config.debugMode
if (config.sync.enabled)
SyncWorker.scheduleNext(this@App, false)

View File

@ -1080,3 +1080,15 @@ fun Throwable.toErrorCode() = when (this) {
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
else -> ERROR_API_EXCEPTION
}
inline fun <A, B, R> ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
if (a != null && b != null) {
return code(a, b)
}
return null
}
@kotlin.jvm.JvmName("averageOrNullOfInt")
fun Iterable<Int>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
@kotlin.jvm.JvmName("averageOrNullOfFloat")
fun Iterable<Float>.averageOrNull() = this.average().let { if (it.isNaN()) null else it }

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()
@ -75,6 +75,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false }
set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value }
private var mDebugMode: Boolean? = null
var debugMode: Boolean
get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
set(value) { set("debugMode", value); mDebugMode = value }
private var mDevModePassword: String? = null
var devModePassword: String?
get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }

View File

@ -6,17 +6,11 @@ package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
class ConfigGrades(private val config: Config) {
companion object {
const val ORDER_BY_DATE_DESC = 0
const val ORDER_BY_SUBJECT_ASC = 1
const val ORDER_BY_DATE_ASC = 2
const val ORDER_BY_SUBJECT_DESC = 3
}
private var mOrderBy: Int? = null
var orderBy: Int
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: ORDER_BY_DATE_DESC }
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: GradesManager.ORDER_BY_DATE_DESC }
set(value) { config.set("gradesOrderBy", value); mOrderBy = value }
}
}

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
@ -90,4 +96,4 @@ class ConfigSync(private val config: Config) {
var tokenVulcanList: List<Int>
get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() }
set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value }
}
}

View File

@ -7,7 +7,6 @@ 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.ui.modules.home.HomeCardModel
class ConfigUI(private val config: Config) {
private var mTheme: Int? = null
@ -45,11 +44,6 @@ class ConfigUI(private val config: Config) {
get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false }
set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value }
private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
set(value) { config.set("homeCards", value); mHomeCards = value }
private var mSnowfall: Boolean? = null
var snowfall: Boolean
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }

View File

@ -5,9 +5,10 @@
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.getFloat
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
class ProfileConfigGrades(private val config: ProfileConfig) {
private var mColorMode: Int? = null
@ -24,4 +25,28 @@ class ProfileConfigGrades(private val config: ProfileConfig) {
var countZeroToAvg: Boolean
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 }
set(value) { config.set("plusValue", value); mPlusValue = value }
private var mMinusValue: Float? = null
var minusValue: Float?
get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue }
set(value) { config.set("minusValue", value); mMinusValue = value }
private var mDontCountGrades: List<String>? = null
var dontCountGrades: List<String>
get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }
set(value) { config.set("dontCountGrades", value); mDontCountGrades = value }
}

View File

@ -7,10 +7,16 @@ package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel
class ProfileConfigUI(private val config: ProfileConfig) {
private var mAgendaViewType: Int? = null
var agendaViewType: Int
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
set(value) { config.set("homeCards", value); mHomeCards = value }
}

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()
@ -78,4 +93,4 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
private fun String?.fix(): String? {
return this?.replace("\"", "")?.let { if (it == "null") null else it }
}
}
}

View File

@ -94,10 +94,14 @@ fun HashMap<String, String?>.getLongList(key: String, default: List<Long>?): Lis
return this[key]?.let { gson.fromJson<List<Long>>(it, object: TypeToken<List<Long>>(){}.type) } ?: default
}
fun HashMap<String, String?>.getFloat(key: String): Float? {
return this[key]?.toFloatOrNull()
}
fun List<ConfigEntry>.toHashMap(profileId: Int, map: HashMap<String, String?>) {
map.clear()
forEach {
if (it.profileId == profileId)
map[it.key] = it.value
}
}
}

View File

@ -10,7 +10,9 @@ import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.config.ConfigGrades.Companion.ORDER_BY_DATE_DESC
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
@ -63,12 +66,31 @@ class ConfigMigration(app: App, config: Config) {
if (dataVersion < 10) {
ui.openDrawerOnBackPressed = false
ui.homeCards = listOf()
ui.snowfall = false
ui.bottomSheetOpened = false
sync.dontShowAppManagerDialog = false
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

@ -6,8 +6,8 @@ package pl.szczodrzynski.edziennik.config.utils
import pl.szczodrzynski.edziennik.config.ProfileConfig
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
class ProfileConfigMigration(config: ProfileConfig) {
init { config.apply {
@ -21,4 +21,4 @@ class ProfileConfigMigration(config: ProfileConfig) {
dataVersion = 1
}
}}
}
}

View File

@ -60,6 +60,7 @@ const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT
const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik"
const val IDZIENNIK_WEB_LOGIN = "login.aspx"
const val IDZIENNIK_WEB_SETTINGS = "mod_panelRodzica/Ustawienia.aspx"
const val IDZIENNIK_WEB_HOME = "mod_panelRodzica/StronaGlowna.aspx"
const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec"
const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia"
const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia"

View File

@ -123,6 +123,7 @@ const val ERROR_LIBRUS_API_MAINTENANCE = 181
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM = 183
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184
const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202

View File

@ -109,6 +109,13 @@ object Regexes {
val IDZIENNIK_MESSAGES_RECIPIENT_PARENT by lazy {
"""(.+?)\s\((.+)\)""".toRegex()
}
/*<span id="ctl00_spanSzczesliwyLos">Szczęśliwy los na dzisiaj to <b>19</b>. Los na jutro to <b>22</b></span>*/
val IDZIENNIK_WEB_LUCKY_NUMBER by lazy {
"""dzisiaj to <b>([0-9]+)</b>""".toRegex()
}
val IDZIENNIK_WEB_SELECTED_REGISTER by lazy {
"""selected="selected" value="([0-9]+)" data-id-ucznia""".toRegex()
}

View File

@ -27,8 +27,15 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
private const val TAG = "EdudziennikWebAttendance"
}
init { data.profile?.also { profile ->
webGet(TAG, data.studentEndpoint + "Presence", semester = -1) { text ->
private var requestSemester: Int? = null
init {
if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1
getAttendances()
}
private fun getAttendances() { data.profile?.also { profile ->
webGet(TAG, data.studentEndpoint + "Presence", semester = requestSemester) { text ->
val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map {
val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim())
@ -91,8 +98,13 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
}
}
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE)
if (profile.empty && requestSemester == 1 && data.currentSemester == 2) {
requestSemester = null
getAttendances()
} else {
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE)
}
}
} ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE) }
}

View File

@ -14,7 +14,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZI
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
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_SUM
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.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.get
@ -90,7 +95,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
val columnName = info.child(4).text().trim()
val comment = info.ownText()
val description = columnName + if (comment.isNotBlank()) " - $comment" else ""
val description = columnName + if (comment.isNotBlank()) " - $comment" else null
val teacherName = info.child(1).text()
val teacher = data.getTeacherByLastFirst(teacherName)
@ -109,20 +114,20 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
} ?: -1
val gradeObject = Grade(
profileId,
id,
fullName,
color,
description,
name,
value,
if (gradeCountToAverage) weight else 0f,
semester,
teacher.id,
subject.id
).apply {
type = gradeType
}
profileId = profileId,
id = id,
name = name,
type = gradeType,
value = value,
weight = if (gradeCountToAverage) weight else 0f,
color = color,
category = fullName,
description = description,
comment = null,
semester = semester,
teacherId = teacher.id,
subjectId = subject.id
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
@ -139,23 +144,23 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
if (proposed != null && proposed.isNotBlank()) {
val proposedGradeObject = Grade(
profileId,
(-1 * subject.id) - 1,
"",
-1,
"",
proposed,
proposed.toFloatOrNull() ?: 0f,
0f,
semester,
-1,
subject.id
).apply {
type = when (semester) {
1 -> TYPE_SEMESTER1_PROPOSED
else -> TYPE_SEMESTER2_PROPOSED
}
}
profileId = profileId,
id = (-1 * subject.id) - 1,
name = proposed,
type = when (semester) {
1 -> TYPE_SEMESTER1_PROPOSED
else -> TYPE_SEMESTER2_PROPOSED
},
value = proposed.toFloatOrNull() ?: 0f,
weight = 0f,
color = -1,
category = null,
description = null,
comment = null,
semester = semester,
teacherId = -1,
subjectId = subject.id
)
data.gradeList.add(proposedGradeObject)
data.metadataList.add(Metadata(
@ -172,23 +177,23 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
if (final != null && final.isNotBlank()) {
val finalGradeObject = Grade(
profileId,
(-1 * subject.id) - 2,
"",
-1,
"",
final,
final.toFloatOrNull() ?: 0f,
0f,
semester,
-1,
subject.id
).apply {
type = when (semester) {
1 -> TYPE_SEMESTER1_FINAL
else -> TYPE_SEMESTER2_FINAL
}
}
profileId = profileId,
id = (-1 * subject.id) - 2,
name = final,
type = when (semester) {
1 -> TYPE_SEMESTER1_FINAL
else -> TYPE_SEMESTER2_FINAL
},
value = final.toFloatOrNull() ?: 0f,
weight = 0f,
color = -1,
category = null,
description = null,
comment = null,
semester = semester,
teacherId = -1,
subjectId = subject.id
)
data.gradeList.add(finalGradeObject)
data.metadataList.add(Metadata(

View File

@ -81,6 +81,11 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth }
set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value }
private var mWebSelectedRegister: Int? = null
var webSelectedRegister: Int
get() { mWebSelectedRegister = mWebSelectedRegister ?: loginStore.getLoginData("webSelectedRegister", 0); return mWebSelectedRegister ?: 0 }
set(value) { loginStore.putLoginData("webSelectedRegister", value); mWebSelectedRegister = value }
/* _
/\ (_)
/ \ _ __ _

View File

@ -13,6 +13,7 @@ import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebSwitchRegister
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.File
@ -48,6 +49,17 @@ open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?)
return
}
if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GRADES) {
// special override for accounts where displaying grades
// for another student requires switching it manually
if (data.registerId != data.webSelectedRegister) {
IdziennikWebSwitchRegister(data, data.registerId) {
webApiGet(tag, endpoint, parameters, onSuccess)
}
return
}
}
when {
response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED
response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
@ -115,7 +127,7 @@ open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?)
.enqueue()
}
fun webGet(tag: String, endpoint: String, onSuccess: (text: String) -> Unit) {
fun webGet(tag: String, endpoint: String, parameters: Map<String, Any> = emptyMap(), onSuccess: (text: String) -> Unit) {
d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : TextCallbackHandler() {
@ -160,7 +172,14 @@ open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?)
Request.builder()
.url("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.get()
.apply {
if (parameters.isEmpty()) get()
else post()
parameters.map { (name, value) ->
addParameter(name, value)
}
}
.callback(callback)
.build()
.enqueue()

View File

@ -69,7 +69,7 @@ class IdziennikApiCurrentRegister(override val data: DataIdziennik,
val luckyNumberObject = LuckyNumber(
data.profileId,
Date.getToday(),
luckyNumberDate,
luckyNumber
)

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class IdziennikWebExams(override val data: DataIdziennik,
override val lastSync: Long?,
@ -70,9 +71,12 @@ class IdziennikWebExams(override val data: DataIdziennik,
val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
val eventType = when (exam.getString("rodzaj")) {
"sprawdzian/praca klasowa" -> Event.TYPE_EXAM
else -> Event.TYPE_SHORT_QUIZ
val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) {
"sprawdzian/praca klasowa",
"sprawdzian",
"praca klasowa" -> Event.TYPE_EXAM
"kartkówka" -> Event.TYPE_SHORT_QUIZ
else -> Event.TYPE_EXAM
}
val eventObject = Event(

View File

@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
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.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
@ -63,17 +64,19 @@ class IdziennikWebGrades(override val data: DataIdziennik,
}
val gradeObject = Grade(
profileId,
id,
category,
colorInt,
"",
name,
value,
weight,
semester,
teacher.id,
subject.id)
profileId = profileId,
id = id,
name = name,
type = TYPE_NORMAL,
value = value,
weight = weight,
color = colorInt,
category = category,
description = null,
comment = null,
semester = semester,
teacherId = teacher.id,
subjectId = subject.id)
when (grade.getInt("Typ")) {
0 -> {
@ -98,17 +101,19 @@ class IdziennikWebGrades(override val data: DataIdziennik,
}
val historyObject = Grade(
profileId,
gradeObject.id * -1,
historyItem.get("Kategoria").asString,
colorInt,
historyItem.get("Uzasadnienie").asString,
historyItem.get("Ocena").asString,
value,
if (value > 0f && countToTheAverage) weight * -1f else 0f,
historyItem.get("Semestr").asInt,
teacher.id,
subject.id)
profileId = profileId,
id = gradeObject.id * -1,
name = historyItem.getString("Ocena") ?: "",
type = TYPE_NORMAL,
value = value,
weight = if (value > 0f && countToTheAverage) weight * -1f else 0f,
color = colorInt,
category = historyItem.getString("Kategoria"),
description = historyItem.getString("Uzasadnienie"),
comment = null,
semester = historyItem.getInt("Semestr") ?: 1,
teacherId = teacher.id,
subjectId = subject.id)
historyObject.parentId = gradeObject.id
val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()

View File

@ -13,8 +13,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_YEAR_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonArray
@ -54,20 +54,20 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
if (semester1Proposed != "") {
val gradeObject = Grade(
profileId,
semester1Id,
"",
-1,
"",
semester1Value.toString(),
semester1Value.toFloat(),
0f,
1,
-1,
subjectObject.id
).apply {
type = TYPE_SEMESTER1_PROPOSED
}
profileId = profileId,
id = semester1Id,
name = semester1Value.toString(),
type = TYPE_SEMESTER1_PROPOSED,
value = semester1Value.toFloat(),
weight = 0f,
color = -1,
category = null,
description = null,
comment = null,
semester = 1,
teacherId = -1,
subjectId = subjectObject.id
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
@ -82,20 +82,25 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
if (semester2Proposed != "") {
val gradeObject = Grade(
profileId,
semester2Id,
"",
-1,
"",
semester2Value.toString(),
semester2Value.toFloat(),
0f,
2,
-1,
subjectObject.id
).apply {
type = TYPE_YEAR_PROPOSED
}
profileId = profileId,
id = semester2Id,
name = semester2Value.toString(),
type = TYPE_YEAR_PROPOSED,
value = semester2Value.toFloat(),
weight = 0f,
color = -1,
category = null,
description = null,
comment = null,
semester = 2,
teacherId = -1,
subjectId = subjectObject.id
)
val addedDate = if (data.profile.empty)
data.profile.dateSemester1Start.inMillis
else
System.currentTimeMillis()
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
@ -104,7 +109,7 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
gradeObject.id,
profile.empty,
profile.empty,
System.currentTimeMillis()
addedDate
))
}
}

View File

@ -0,0 +1,36 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOME
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getString
class IdziennikWebSwitchRegister(override val data: DataIdziennik,
val registerId: Int,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
private const val TAG = "IdziennikWebSwitchRegister"
}
init {
val hiddenFields = data.loginStore.getLoginData("hiddenFields", JsonObject())
// TODO error checking
webGet(TAG, IDZIENNIK_WEB_HOME, mapOf(
"__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""),
"__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""),
"__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""),
"ctl00\$dxComboUczniowie" to registerId
)) { text ->
Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let {
val registerId = it[1].toIntOrNull() ?: return@let
data.webSelectedRegister = registerId
}
onSuccess()
}
}
}

View File

@ -8,14 +8,14 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.MINUTE
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
@ -69,6 +69,40 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */
val hiddenFields = JsonObject()
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach {
hiddenFields[it[1]] = it[2]
}
data.loginStore.putLoginData("hiddenFields", hiddenFields)
Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let {
val registerId = it[1].toIntOrNull() ?: return@let
data.webSelectedRegister = registerId
}
data.profile?.let { profile ->
Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also {
val number = it[1].toIntOrNull() ?: return@also
val luckyNumberObject = LuckyNumber(
data.profileId,
Date.getToday(),
number
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profile.id,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
true,
profile.empty,
System.currentTimeMillis()
))
}
}
return@run null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)

View File

@ -199,7 +199,6 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID -> {
login()
}
// TODO PORTAL CAPTCHA
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> {
data.timetableNotPublic = true
data()
@ -208,6 +207,11 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> {
data()
}
ERROR_LIBRUS_API_DEVICE_REGISTERED -> {
data.app.config.sync.tokenLibrusList =
data.app.config.sync.tokenLibrusList + data.profileId
data()
}
else -> callback.onError(apiError)
}
}

View File

@ -44,9 +44,12 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
.withResponse(response))
return
}
/*
{"Status":"Error","Code":"DeviceRegistered","Message":"This device is alerdy registered.","Resources":{"..":{"Url":"https:\/\/api.librus.pl\/2.0\/Root"}},"Url":"https:\/\/api.librus.pl\/2.0\/ChangeRegister"}*/
val error = if (response?.code() == 200) null else
json.getString("Code") ?:
json.getString("Message") ?:
json.getString("Status") ?:
response?.parserErrorBody
error?.let { code ->
when (code) {
@ -64,6 +67,8 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
"InvalidRequest" -> ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS
"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) {
@ -115,6 +120,8 @@ open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) {
.allowErrorCode(HTTP_FORBIDDEN)
.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

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -26,23 +27,36 @@ 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 ->
if (data.startPointsSemester1 > 0) {
val semester1StartGradeObject = Grade(
profileId,
-101,
data.app.getString(R.string.grade_start_points),
0xffbdbdbd.toInt(),
data.app.getString(R.string.grade_start_points_format, 1),
nameFormat.format(data.startPointsSemester1),
data.startPointsSemester1.toFloat(),
-1f,
1,
-1,
1
).apply { type = Grade.TYPE_POINT_SUM }
profileId = profileId,
id = -101,
name = nameFormat.format(data.startPointsSemester1),
type = TYPE_POINT_SUM,
value = data.startPointsSemester1.toFloat(),
weight = 0f,
color = 0xffbdbdbd.toInt(),
category = data.app.getString(R.string.grade_start_points),
description = data.app.getString(R.string.grade_start_points_format, 1),
comment = null,
semester = 1,
teacherId = -1,
subjectId = 1
)
data.gradeList.add(semester1StartGradeObject)
data.metadataList.add(Metadata(
@ -57,18 +71,20 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
if (data.startPointsSemester2 > 0) {
val semester2StartGradeObject = Grade(
profileId,
-102,
data.app.getString(R.string.grade_start_points),
0xffbdbdbd.toInt(),
data.app.getString(R.string.grade_start_points_format, 2),
nameFormat.format(data.startPointsSemester2),
data.startPointsSemester2.toFloat(),
-1f,
2,
-1,
1
).apply { type = Grade.TYPE_POINT_SUM }
profileId = profileId,
id = -102,
name = nameFormat.format(data.startPointsSemester2),
type = TYPE_POINT_SUM,
value = data.startPointsSemester2.toFloat(),
weight = -1f,
color = 0xffbdbdbd.toInt(),
category = data.app.getString(R.string.grade_start_points),
description = data.app.getString(R.string.grade_start_points_format, 2),
comment = null,
semester = 2,
teacherId = -1,
subjectId = 1
)
data.gradeList.add(semester2StartGradeObject)
data.metadataList.add(Metadata(
@ -90,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
}
@ -110,32 +130,33 @@ 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
val gradeObject = Grade(
profileId,
id,
categoryName,
color,
description,
name,
valueFrom,
-1f,
semester,
teacherId,
1
profileId = profileId,
id = id,
name = name,
type = TYPE_POINT_SUM,
value = valueFrom,
weight = -1f,
color = color,
category = categoryName,
description = text ?: description.join(" - "),
comment = if (text != null) description.join(" - ") else null,
semester = semester,
teacherId = teacherId,
subjectId = 1
).apply {
type = Grade.TYPE_POINT_SUM
valueMax = valueTo
}

View File

@ -10,8 +10,8 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_DESCRIPTIVE_TEXT
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_TEXT
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE_TEXT
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_TEXT
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -53,20 +53,20 @@ class LibrusApiDescriptiveGrades(override val data: DataLibrus,
val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach)
val gradeObject = Grade(
profileId,
id,
category?.text ?: "",
category?.color ?: -1,
description,
" ",
0f,
0f,
semester,
teacherId,
subjectId
).apply {
this.type = type
}
profileId = profileId,
id = id,
name = " ",
type = type,
value = 0f,
weight = 0f,
color = category?.color ?: -1,
category = category?.text,
description = description,
comment = null,
semester = semester,
teacherId = teacherId,
subjectId = subjectId
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(

View File

@ -6,7 +6,13 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
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_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.GradeCategory
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -54,32 +60,28 @@ class LibrusApiGrades(override val data: DataLibrus,
} ?: ""
val gradeObject = Grade(
profileId,
id,
category?.text ?: "",
category?.color ?: -1,
description,
name,
value,
weight,
semester,
teacherId,
subjectId
profileId = profileId,
id = id,
name = name,
type = when {
grade.getBoolean("IsConstituent") ?: false -> TYPE_NORMAL
grade.getBoolean("IsSemester") ?: false -> if (semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL
grade.getBoolean("IsSemesterProposition") ?: false -> if (semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED
grade.getBoolean("IsFinal") ?: false -> TYPE_YEAR_FINAL
grade.getBoolean("IsFinalProposition") ?: false -> TYPE_YEAR_PROPOSED
else -> TYPE_NORMAL
},
value = value,
weight = weight,
color = category?.color ?: -1,
category = category?.text ?: "",
description = description,
comment = null,
semester = semester,
teacherId = teacherId,
subjectId = subjectId
)
when {
grade.getBoolean("IsConstituent") ?: false ->
gradeObject.type = TYPE_NORMAL
grade.getBoolean("IsSemester") ?: false -> // semester final
gradeObject.type = if (gradeObject.semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL
grade.getBoolean("IsSemesterProposition") ?: false -> // semester proposed
gradeObject.type = if (gradeObject.semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED
grade.getBoolean("IsFinal") ?: false -> // year final
gradeObject.type = TYPE_YEAR_FINAL
grade.getBoolean("IsFinalProposition") ?: false -> // year final
gradeObject.type = TYPE_YEAR_PROPOSED
}
grade.getJsonObject("Improvement")?.also {
val historicalId = it.getLong("Id")
data.gradeList.firstOrNull { grade -> grade.id == historicalId }?.also { grade ->

View File

@ -10,7 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_POINT_AVG
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -44,19 +44,20 @@ class LibrusApiPointGrades(override val data: DataLibrus,
val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach)
val gradeObject = Grade(
profileId,
id,
category?.text ?: "",
category?.color ?: -1,
"",
name,
value,
category?.weight ?: 0f,
semester,
teacherId,
subjectId
profileId = profileId,
id = id,
name = name,
type = TYPE_POINT_AVG,
value = value,
weight = category?.weight ?: 0f,
color = category?.color ?: -1,
category = category?.text ?: "",
description = null,
comment = null,
semester = semester,
teacherId = teacherId,
subjectId = subjectId
).apply {
type = TYPE_POINT_AVG
valueMax = category?.valueTo ?: 0f
}

View File

@ -10,7 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.TYPE_DESCRIPTIVE
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
@ -48,20 +48,20 @@ class LibrusApiTextGrades(override val data: DataLibrus,
val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach)
val gradeObject = Grade(
profileId,
id,
category?.text ?: "",
category?.color ?: -1,
description,
name,
0f,
0f,
semester,
teacherId,
subjectId
).apply {
type = TYPE_DESCRIPTIVE
}
profileId = profileId,
id = id,
name = name,
type = TYPE_DESCRIPTIVE,
value = 0f,
weight = 0f,
color = category?.color ?: -1,
category = category?.text ?: "",
description = description,
comment = grade.getString("Phrase") /* whatever it is */,
semester = semester,
teacherId = teacherId,
subjectId = subjectId
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(

View File

@ -144,18 +144,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
.withResponse(response))
return
}
if (json.getBoolean("captchaRequired") == true) {
data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL)
.withResponse(response)
.withApiResponse(json))
return
}
val error = if (response.code() == 200) null else
json.getJsonArray("errors")?.getString(0)
error?.let { code ->
when {
code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
// this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set
code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL
else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
@ -164,6 +160,12 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
return
}
}
if (json.getBoolean("captchaRequired") == true) {
data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL)
.withResponse(response)
.withApiResponse(json))
return
}
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
}

View File

@ -7,7 +7,13 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
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_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.Metadata
class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
@ -61,18 +67,23 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
}
val gradeObject = Grade(
data.profileId,
id,
category,
color,
description,
name,
value,
weight,
semester,
teacherId,
subjectId)
gradeObject.type = type
profileId = data.profileId,
id = id,
name = name,
type = type,
value = value,
weight = weight,
color = color,
category = category,
description = description,
comment = null,
semester = semester,
teacherId = teacherId,
subjectId = subjectId)
if (data.profile?.empty == true) {
addedDate = data.profile.dateSemester1Start.inMillis
}
data.gradeList.add(gradeObject)
data.metadataList.add(

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidzienn
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
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.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.fixWhiteSpaces
@ -112,17 +113,19 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
}
val gradeObject = Grade(
profileId,
gradeId,
gradeCategory,
gradeColor,
"NLDŚR, $gradeDescription",
gradeName,
gradeValue,
0f,
gradeSemester,
teacherId,
subjectId
profileId = profileId,
id = gradeId,
name = gradeName,
type = TYPE_NORMAL,
value = gradeValue,
weight = 0f,
color = gradeColor,
category = gradeCategory,
description = "NLDŚR, $gradeDescription",
comment = null,
semester = gradeSemester,
teacherId = teacherId,
subjectId = subjectId
)
gradeObject.classAverage = gradeClassAverage

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
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.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import java.text.DecimalFormat
@ -88,17 +89,19 @@ class VulcanApiGrades(override val data: DataVulcan,
}.toInt()
val gradeObject = Grade(
profileId,
id,
category,
color,
finalDescription,
name,
value ?: 0.0f,
weight,
data.studentSemesterNumber,
teacherId,
subjectId
profileId = profileId,
id = id,
name = name,
type = TYPE_NORMAL,
value = value ?: 0.0f,
weight = weight,
color = color,
category = category,
description = finalDescription,
comment = null,
semester = data.studentSemesterNumber,
teacherId = teacherId,
subjectId = subjectId
)
data.gradeList.add(gradeObject)

View File

@ -8,6 +8,10 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_GRADES_SUMMARY
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.Grade
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.Metadata
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getJsonObject
@ -53,23 +57,25 @@ class VulcanApiProposedGrades(override val data: DataVulcan,
val color = Utils.getVulcanGradeColor(name)
val gradeObject = Grade(
profileId,
id,
"",
color,
"",
name,
value,
0f,
data.studentSemesterNumber,
-1,
subjectId
profileId = profileId,
id = id,
name = name,
type = if (data.studentSemesterNumber == 1) {
if (isFinal) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER1_PROPOSED
} else {
if (isFinal) TYPE_SEMESTER2_FINAL else TYPE_SEMESTER2_PROPOSED
},
value = value,
weight = 0f,
color = color,
category = "",
description = null,
comment = null,
semester = data.studentSemesterNumber,
teacherId = -1,
subjectId = subjectId
)
if (data.studentSemesterNumber == 1) {
gradeObject.type = if (isFinal) Grade.TYPE_SEMESTER1_FINAL else Grade.TYPE_SEMESTER1_PROPOSED
} else {
gradeObject.type = if (isFinal) Grade.TYPE_SEMESTER2_FINAL else Grade.TYPE_SEMESTER2_PROPOSED
}
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDp+2J6OAn===.$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");
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return start > 0 && now >= start && now <= end;
}*/
fun shouldBeQuiet() = false
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)
}
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 = 77)
], 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 {
@ -160,7 +162,9 @@ abstract class AppDb : RoomDatabase() {
Migration74(),
Migration75(),
Migration76(),
Migration77()
Migration77(),
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

@ -61,7 +61,7 @@ public abstract class EventDao {
@RawQuery(observedEntities = {Event.class})
abstract LiveData<List<EventFull>> getAll(SupportSQLiteQuery query);
public LiveData<List<EventFull>> getAll(int profileId, String filter) {
public LiveData<List<EventFull>> getAll(int profileId, String filter, String limit) {
String query = "SELECT \n" +
"*, \n" +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName,\n" +
@ -75,24 +75,24 @@ public abstract class EventDao {
"LEFT JOIN metadata ON eventId = thingId AND (thingType = " + TYPE_EVENT + " OR thingType = " + TYPE_HOMEWORK + ") AND metadata.profileId = "+profileId+"\n" +
"WHERE events.profileId = "+profileId+" AND events.eventBlacklisted = 0 AND "+filter+"\n" +
"GROUP BY eventId\n" +
"ORDER BY eventDate, eventStartTime ASC";
"ORDER BY eventDate, eventStartTime ASC "+limit;
Log.d("DB", query);
return getAll(new SimpleSQLiteQuery(query));
}
public LiveData<List<EventFull>> getAll(int profileId) {
return getAll(profileId, "1");
return getAll(profileId, "1", "");
}
public List<EventFull> getAllNow(int profileId) {
return getAllNow(profileId, "1");
}
public LiveData<List<EventFull>> getAllWhere(int profileId, String filter) {
return getAll(profileId, filter);
return getAll(profileId, filter, "");
}
public LiveData<List<EventFull>> getAllByType(int profileId, long type, String filter) {
return getAll(profileId, "eventType = "+type+" AND "+filter);
return getAll(profileId, "eventType = "+type+" AND "+filter, "");
}
public LiveData<List<EventFull>> getAllByDate(int profileId, @NonNull Date date) {
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"'");
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"'", "");
}
public List<EventFull> getAllByDateNow(int profileId, @NonNull Date date) {
return getAllNow(profileId, "eventDate = '"+date.getStringY_m_d()+"'");
@ -100,7 +100,10 @@ public abstract class EventDao {
public LiveData<List<EventFull>> getAllByDateTime(int profileId, @NonNull Date date, Time time) {
if (time == null)
return getAllByDate(profileId, date);
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"' AND eventStartTime = '"+time.getStringValue()+"'");
return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"' AND eventStartTime = '"+time.getStringValue()+"'", "");
}
public LiveData<List<EventFull>> getAllNearest(int profileId, @NonNull Date today, int limit) {
return getAll(profileId, "eventDate >= '"+today.getStringY_m_d()+"'", "LIMIT "+limit);
}
@RawQuery

View File

@ -63,8 +63,8 @@ public abstract class MetadataDao {
@Transaction
public void setSeen(int profileId, Object o, boolean seen) {
if (o instanceof Grade) {
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).id, seen, false, 0)) == -1) {
updateSeen(profileId, TYPE_GRADE, ((Grade) o).id, seen);
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), seen, false, 0)) == -1) {
updateSeen(profileId, TYPE_GRADE, ((Grade) o).getId(), seen);
}
}
if (o instanceof Attendance) {
@ -102,8 +102,8 @@ public abstract class MetadataDao {
@Transaction
public void setNotified(int profileId, Object o, boolean notified) {
if (o instanceof Grade) {
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).id, false, notified, 0)) == -1) {
updateNotified(profileId, TYPE_GRADE, ((Grade) o).id, notified);
if (add(new Metadata(profileId, TYPE_GRADE, ((Grade) o).getId(), false, notified, 0)) == -1) {
updateNotified(profileId, TYPE_GRADE, ((Grade) o).getId(), notified);
}
}
if (o instanceof Attendance) {

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

@ -1,104 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.entity;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
@Entity(tableName = "grades",
primaryKeys = {"profileId", "gradeId"},
indices = {@Index(value = {"profileId"})})
public class Grade {
public int profileId;
@ColumnInfo(name = "gradeId")
public long id;
@ColumnInfo(name = "gradeCategory")
public String category;
@ColumnInfo(name = "gradeColor")
public int color;
@ColumnInfo(name = "gradeDescription")
public String description;
@ColumnInfo(name = "gradeComment")
public String comment;
@ColumnInfo(name = "gradeName")
public String name;
@ColumnInfo(name = "gradeValue")
public float value;
@ColumnInfo(name = "gradeValueMax")
public float valueMax;
@ColumnInfo(name = "gradeWeight")
public float weight;
@ColumnInfo(name = "gradeSemester")
public int semester;
@ColumnInfo(name = "gradeClassAverage")
public float classAverage = -1;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_SEMESTER1_PROPOSED = 1;
public static final int TYPE_SEMESTER1_FINAL = 2;
public static final int TYPE_SEMESTER2_PROPOSED = 3;
public static final int TYPE_SEMESTER2_FINAL = 4;
public static final int TYPE_YEAR_PROPOSED = 5;
public static final int TYPE_YEAR_FINAL = 6;
public static final int TYPE_POINT_AVG = 10;
public static final int TYPE_POINT_SUM = 20;
public static final int TYPE_DESCRIPTIVE = 30;
public static final int TYPE_DESCRIPTIVE_TEXT = 31;
public static final int TYPE_TEXT = 40;
@ColumnInfo(name = "gradeType")
public int type = TYPE_NORMAL;
@ColumnInfo(name = "gradePointGrade")
public boolean pointGrade = false;
/**
* Applies for historical grades. It's the new/replacement grade's ID.
*/
@ColumnInfo(name = "gradeParentId")
public long parentId = -1;
/**
* Applies for current grades. If the grade was worse and this is the improved one.
*/
@ColumnInfo(name = "gradeIsImprovement")
public boolean isImprovement = false;
public long teacherId;
public long subjectId;
@Ignore
public Grade() {}
public Grade(int profileId, long id, String category, int color, String description, String name, float value, float weight, int semester, long teacherId, long subjectId) {
this.profileId = profileId;
this.id = id;
this.category = category;
this.color = color;
this.description = description;
this.name = name;
this.value = value;
this.weight = weight;
this.semester = semester;
this.teacherId = teacherId;
this.subjectId = subjectId;
}
/*@Ignore
public Grade(int profileId, long id, String description, String name, float value, float weight, int semester, long teacherId, long categoryId, long subjectId) {
this.profileId = profileId;
this.id = id;
this.description = description;
this.name = name;
this.value = value;
this.weight = weight;
this.semester = semester;
this.teacherId = teacherId;
//this.categoryId = categoryId;
this.subjectId = subjectId;
}*/
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
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) {
this.profileId = profileId;
this.id = id;
this.category = category;
this.color = color;
this.description = description;
this.name = name;
this.value = value;
this.weight = weight;
this.semester = semester;
this.teacherId = teacherId;
this.subjectId = subjectId;
}*/
@Entity(tableName = "grades",
primaryKeys = ["profileId", "gradeId"],
indices = [Index(value = ["profileId"])])
open class Grade(
val profileId: Int,
@ColumnInfo(name = "gradeId")
val id: Long,
@ColumnInfo(name = "gradeName")
var name: String,
@ColumnInfo(name = "gradeType")
var type: Int,
@ColumnInfo(name = "gradeValue")
var value: Float,
@ColumnInfo(name = "gradeWeight")
var weight: Float,
@ColumnInfo(name = "gradeColor")
var color: Int,
@ColumnInfo(name = "gradeCategory")
var category: String?,
@ColumnInfo(name = "gradeDescription")
var description: String?,
@ColumnInfo(name = "gradeComment")
var comment: String?,
@ColumnInfo(name = "gradeSemester")
val semester: Int,
val teacherId: Long,
val subjectId: Long
) {
companion object {
const val TYPE_NORMAL = 0
const val TYPE_SEMESTER1_PROPOSED = 1
const val TYPE_SEMESTER1_FINAL = 2
const val TYPE_SEMESTER2_PROPOSED = 3
const val TYPE_SEMESTER2_FINAL = 4
const val TYPE_YEAR_PROPOSED = 5
const val TYPE_YEAR_FINAL = 6
const val TYPE_POINT_AVG = 10
const val TYPE_POINT_SUM = 20
const val TYPE_DESCRIPTIVE = 30
const val TYPE_DESCRIPTIVE_TEXT = 31
const val TYPE_TEXT = 40
}
@ColumnInfo(name = "gradeValueMax")
var valueMax: Float? = null
@ColumnInfo(name = "gradeClassAverage")
var classAverage: Float? = null
/**
* Applies for historical grades. It's the new/replacement grade's ID.
*/
@ColumnInfo(name = "gradeParentId")
var parentId: Long? = null
/**
* Applies for current grades. If the grade was worse and this is the improved one.
*/
@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

@ -43,10 +43,12 @@ class LoginStore(
fun getLoginData(key: String, defaultValue: Long) = data.getLong(key) ?: defaultValue
fun getLoginData(key: String, defaultValue: Float) = data.getFloat(key) ?: defaultValue
fun getLoginData(key: String, defaultValue: Char) = data.getChar(key) ?: defaultValue
fun getLoginData(key: String, defaultValue: JsonObject) = data.getJsonObject(key) ?: defaultValue
fun putLoginData(key: String, value: Boolean) { data[key] = value }
fun putLoginData(key: String, value: String?) { data[key] = value }
fun putLoginData(key: String, value: Number) { data[key] = value }
fun putLoginData(key: String, value: Char) { data[key] = value }
fun putLoginData(key: String, value: JsonObject) { data[key] = value }
fun removeLoginData(key: String) { data.remove(key) }
fun copyFrom(args: Bundle) {

View File

@ -53,15 +53,8 @@ open class Profile(
const val REGISTRATION_UNSPECIFIED = 0
const val REGISTRATION_DISABLED = 1
const val REGISTRATION_ENABLED = 2
const val COLOR_MODE_DEFAULT = 0
const val COLOR_MODE_WEIGHTED = 1
const val AGENDA_DEFAULT = 0
const val AGENDA_CALENDAR = 1
const val YEAR_1_AVG_2_AVG = 0
const val YEAR_1_SEM_2_AVG = 1
const val YEAR_1_AVG_2_SEM = 2
const val YEAR_1_SEM_2_SEM = 3
const val YEAR_ALL_GRADES = 4
}
override var image: String? = null

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

@ -1,23 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full;
import pl.szczodrzynski.edziennik.data.db.entity.Grade;
public class GradeFull extends Grade {
//public String category = "";
//public int color;
public String subjectLongName = "";
public String subjectShortName = "";
public String teacherFullName = "";
// metadata
public boolean seen;
public boolean notified;
public long addedDate;
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full
import pl.szczodrzynski.edziennik.data.db.entity.Grade
class GradeFull(
profileId: Int, id: Long, name: String, type: Int,
value: Float, weight: Float, color: Int,
category: String?, description: String?, comment: String?,
semester: Int, teacherId: Long, subjectId: Long
) : Grade(
profileId, id, name, type,
value, weight, color,
category, description, comment,
semester, teacherId, subjectId
) {
var subjectLongName: String? = null
var subjectShortName: String? = null
var teacherFullName: String? = null
// metadata
var seen = false
var notified = false
var addedDate: Long = 0
}

View File

@ -0,0 +1,44 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration78 : Migration(77, 78) {
override fun migrate(database: SupportSQLiteDatabase) {
// grades migration to kotlin
database.execSQL("ALTER TABLE grades RENAME TO _grades;")
database.execSQL("""CREATE TABLE grades (
profileId INTEGER NOT NULL,
gradeId INTEGER NOT NULL,
gradeName TEXT NOT NULL,
gradeType INTEGER NOT NULL,
gradeValue REAL NOT NULL,
gradeWeight REAL NOT NULL,
gradeColor INTEGER NOT NULL,
gradeCategory TEXT,
gradeDescription TEXT,
gradeComment TEXT,
gradeSemester INTEGER NOT NULL,
teacherId INTEGER NOT NULL,
subjectId INTEGER NOT NULL,
gradeValueMax REAL DEFAULT NULL,
gradeClassAverage REAL DEFAULT NULL,
gradeParentId INTEGER DEFAULT NULL,
gradeIsImprovement INTEGER NOT NULL,
PRIMARY KEY(profileId, gradeId)
);""")
database.execSQL("DROP INDEX IF EXISTS index_grades_profileId;")
database.execSQL("CREATE INDEX index_grades_profileId ON grades (profileId);")
database.execSQL("""INSERT INTO grades (profileId, gradeId, gradeName, gradeType, gradeValue, gradeWeight, gradeColor, gradeCategory, gradeDescription, gradeComment, gradeSemester, teacherId, subjectId, gradeValueMax, gradeClassAverage, gradeParentId, gradeIsImprovement)
SELECT profileId, gradeId, gradeName, gradeType, gradeValue, gradeWeight, gradeColor,
CASE gradeCategory WHEN '' THEN NULL WHEN ' ' THEN NULL ELSE gradeCategory END,
CASE gradeDescription WHEN '' THEN NULL WHEN ' ' THEN NULL ELSE gradeDescription END,
CASE gradeComment WHEN '' THEN NULL WHEN ' ' THEN NULL ELSE gradeComment END,
gradeSemester, teacherId, subjectId,
CASE gradeValueMax WHEN 0 THEN NULL ELSE gradeValueMax END,
CASE gradeClassAverage WHEN -1 THEN NULL ELSE gradeClassAverage END,
CASE gradeParentId WHEN -1 THEN NULL ELSE gradeParentId END,
gradeIsImprovement FROM _grades;""")
database.execSQL("DROP TABLE _grades;")
}
}

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

@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
@ -118,9 +119,43 @@ class EventDetailsDialog(
)
}
b.goToTimetableButton.setOnClickListener {
dialog.dismiss()
val dateStr = event.eventDate?.stringY_m_d ?: return@setOnClickListener
val intent =
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE)
Intent(TimetableFragment.ACTION_SCROLL_TO_DATE)
else if (activity is MainActivity)
Intent("android.intent.action.MAIN")
else
Intent(activity, MainActivity::class.java)
intent.apply {
putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
putExtra("timetableDate", dateStr)
}
if (activity is MainActivity)
activity.sendBroadcast(intent)
else
activity.startActivity(intent)
}
b.saveInCalendarButton.setOnClickListener {
openInCalendar()
}
b.goToTimetableButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_go_to_timetable, Toast.LENGTH_SHORT).show()
true
}
b.saveInCalendarButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_save_in_calendar, Toast.LENGTH_SHORT).show()
true
}
b.editButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true
}
}
private fun showRemoveEventDialog() {

View File

@ -8,14 +8,19 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.EventListItemBinding
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class EventListAdapter(
val context: Context,
val simpleMode: Boolean = false,
val showDate: Boolean = false,
val showWeekDay: Boolean = false,
val onItemClick: ((event: EventFull) -> Unit)? = null,
val onEventEditClick: ((event: EventFull) -> Unit)? = null
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>() {
@ -40,12 +45,16 @@ class EventListAdapter(
val bullet = ""
b.simpleMode = simpleMode
b.topic.text = event.topic
b.details.text = mutableListOf<CharSequence?>(
if (showWeekDay) Week.getFullDayName(event.eventDate.weekDay) else null,
if (showDate) event.eventDate.getRelativeString(context, 7) ?: event.eventDate.formattedStringShort else null,
event.typeName,
event.startTime?.stringHM ?: app.getString(R.string.event_all_day),
event.subjectLongName
if (simpleMode) null else event.startTime?.stringHM ?: app.getString(R.string.event_all_day),
if (simpleMode) null else event.subjectLongName
).concat(bullet)
b.addedBy.setText(
@ -65,11 +74,16 @@ class EventListAdapter(
b.typeColor.background?.setTintColor(event.getColor())
b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE
b.editButton.visibility = if (event.addedManually && !simpleMode) View.VISIBLE else View.GONE
b.editButton.onClick {
onEventEditClick?.invoke(event)
}
b.editButton.setOnLongClickListener {
Toast.makeText(context, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true
}
/*with(holder) {
b.eventListItemRoot.background.colorFilter = when (event.type) {
Event.TYPE_HOMEWORK -> PorterDuffColorFilter(0xffffffff.toInt(), PorterDuff.Mode.CLEAR)

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

@ -1,129 +0,0 @@
package pl.szczodrzynski.edziennik.ui.dialogs.grade;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.AsyncTask;
import android.view.View;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.afollestad.materialdialogs.MaterialDialog;
import java.text.DecimalFormat;
import java.util.List;
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.databinding.DialogGradeDetailsBinding;
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesListAdapter;
import pl.szczodrzynski.edziennik.utils.Colors;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.COLOR_MODE_DEFAULT;
public class GradeDetailsDialog {
private App app;
private Context context;
private int profileId;
public GradeDetailsDialog(Context context) {
this.context = context;
this.profileId = App.Companion.getProfileId();
}
public GradeDetailsDialog(Context context, int profileId) {
this.context = context;
this.profileId = profileId;
}
public MaterialDialog dialog;
private DialogGradeDetailsBinding b;
private DialogInterface.OnDismissListener dismissListener;
public boolean callDismissListener = true;
public GradeDetailsDialog withDismissListener(DialogInterface.OnDismissListener dismissListener) {
this.dismissListener = dismissListener;
return this;
}
public void performDismiss(DialogInterface dialogInterface) {
if (callDismissListener && dismissListener != null) {
dismissListener.onDismiss(dialogInterface);
}
callDismissListener = true;
}
public void show(App _app, GradeFull grade)
{
this.app = _app;
dialog = new MaterialDialog.Builder(context)
.customView(R.layout.dialog_grade_details, true)
.positiveText(R.string.close)
.autoDismiss(false)
.onPositive((dialog, which) -> dialog.dismiss())
.dismissListener(this::performDismiss)
.show();
View root = dialog.getCustomView();
assert root != null;
b = DialogGradeDetailsBinding.bind(root);
b.setGrade(grade);
int gradeColor;
if (App.Companion.getConfig().getFor(profileId).getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
gradeColor = grade.color;
}
else {
gradeColor = Colors.gradeToColor(grade);
}
DecimalFormat format = new DecimalFormat("#.##");
if (grade.weight < 0) {
grade.weight *= -1;
}
if (grade.type == Grade.TYPE_DESCRIPTIVE || grade.type == Grade.TYPE_DESCRIPTIVE_TEXT || grade.type == Grade.TYPE_TEXT || grade.type == Grade.TYPE_POINT_SUM) {
b.setWeightText(null);
grade.weight = 0;
}
else {
if (grade.type == Grade.TYPE_POINT_AVG) {
b.setWeightText(app.getString(R.string.grades_max_points_format, format.format(grade.valueMax)));
}
else if (grade.weight == 0) {
b.setWeightText(app.getString(R.string.grades_weight_not_counted));
}
else {
b.setWeightText(app.getString(R.string.grades_weight_format, format.format(grade.weight)));
}
}
b.setCommentVisible(false);
b.setDevMode(App.Companion.getDevMode());
b.gradeName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff);
b.gradeName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY));
AsyncTask.execute(() -> {
List<GradeFull> historyList = app.db.gradeDao().getAllWithParentIdNow(profileId, grade.id);
if (historyList.size() == 0) {
b.setHistoryVisible(false);
return;
}
b.setHistoryVisible(true);
b.gradeHistoryNest.post(() -> {
b.gradeHistoryNest.setNestedScrollingEnabled(false);
b.gradeHistoryList.setHasFixedSize(false);
b.gradeHistoryList.setNestedScrollingEnabled(false);
b.gradeHistoryList.setLayoutManager(new LinearLayoutManager(context));
b.gradeHistoryList.setAdapter(new GradesListAdapter(context, historyList));
});
});
}
}

View File

@ -0,0 +1,92 @@
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.*
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
class GradeDetailsDialog(
val activity: AppCompatActivity,
val grade: GradeFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "GradeDetailsDialog"
}
private lateinit var app: App
private lateinit var b: DialogGradeDetailsBinding
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogGradeDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
val manager = app.gradesManager
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) 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)
}
if (historyList.isEmpty()) {
b.historyVisible = false
return@launch
}
b.historyVisible = true
//b.gradeHistoryNest.isNestedScrollingEnabled = false
b.gradeHistoryList.adapter = GradesAdapter(activity, {
GradeDetailsDialog(activity, it)
}).also { it.items = historyList.toMutableList() }
b.gradeHistoryList.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
}}
}

View File

@ -6,14 +6,20 @@ package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.config.ConfigGrades
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import it.sephiroth.android.library.numberpicker.doOnStopTrackingTouch
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.databinding.DialogConfigGradesBinding
import pl.szczodrzynski.edziennik.setOnSelectedListener
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_DEFAULT
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_SUBJECT_ASC
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
class GradesConfigDialog(
val activity: AppCompatActivity,
@ -42,6 +48,7 @@ class GradesConfigDialog(
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
@ -52,43 +59,84 @@ class GradesConfigDialog(
}}
private fun loadConfig() {
b.customPlusCheckBox.isChecked = profileConfig.plusValue != null
b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked
b.customMinusCheckBox.isChecked = profileConfig.minusValue != null
b.customMinusValue.isVisible = b.customMinusCheckBox.isChecked
b.customPlusValue.progress = profileConfig.plusValue ?: 0.5f
b.customMinusValue.progress = profileConfig.minusValue ?: 0.25f
when (config.orderBy) {
ConfigGrades.ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio
ConfigGrades.ORDER_BY_SUBJECT_ASC -> b.sortGradesBySubjectRadio
ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio
ORDER_BY_SUBJECT_ASC -> b.sortGradesBySubjectRadio
else -> null
}?.isChecked = true
when (profileConfig.colorMode) {
Profile.COLOR_MODE_DEFAULT -> b.gradeColorFromERegister
Profile.COLOR_MODE_WEIGHTED -> b.gradeColorByValue
COLOR_MODE_DEFAULT -> b.gradeColorFromERegister
COLOR_MODE_WEIGHTED -> b.gradeColorByValue
else -> null
}?.isChecked = true
when (profileConfig.yearAverageMode) {
Profile.YEAR_ALL_GRADES -> b.gradeAverageMode4
Profile.YEAR_1_AVG_2_AVG -> b.gradeAverageMode0
Profile.YEAR_1_SEM_2_AVG -> b.gradeAverageMode1
Profile.YEAR_1_AVG_2_SEM -> b.gradeAverageMode2
Profile.YEAR_1_SEM_2_SEM -> b.gradeAverageMode3
YEAR_ALL_GRADES -> b.gradeAverageMode4
YEAR_1_AVG_2_AVG -> b.gradeAverageMode0
YEAR_1_SEM_2_AVG -> b.gradeAverageMode1
YEAR_1_AVG_2_SEM -> b.gradeAverageMode2
YEAR_1_SEM_2_SEM -> b.gradeAverageMode3
else -> null
}?.isChecked = true
b.dontCountZeroToAverage.isChecked = !profileConfig.countZeroToAvg
b.hideImproved.isChecked = profileConfig.hideImproved
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
}
private fun saveConfig() {
profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
}
private fun initView() {
b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ConfigGrades.ORDER_BY_DATE_DESC }
b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ConfigGrades.ORDER_BY_SUBJECT_ASC }
b.customPlusCheckBox.onChange { _, isChecked ->
b.customPlusValue.isVisible = isChecked
}
b.customMinusCheckBox.onChange { _, isChecked ->
b.customMinusValue.isVisible = isChecked
}
b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = Profile.COLOR_MODE_DEFAULT }
b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = Profile.COLOR_MODE_WEIGHTED }
// who the hell named those methods
// THIS SHIT DOES NOT EVEN WORK
b.customPlusValue.doOnStopTrackingTouch {
profileConfig.plusValue = it.progress
}
b.customMinusValue.doOnStopTrackingTouch {
profileConfig.minusValue = it.progress
}
b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_ALL_GRADES }
b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_AVG_2_AVG }
b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_SEM_2_AVG }
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = Profile.YEAR_1_SEM_2_SEM }
b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ORDER_BY_DATE_DESC }
b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ORDER_BY_SUBJECT_ASC }
b.dontCountZeroToAverage.setOnCheckedChangeListener { _, isChecked -> profileConfig.countZeroToAvg = !isChecked }
b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_DEFAULT }
b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_WEIGHTED }
b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_ALL_GRADES }
b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG }
b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG }
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM }
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

@ -75,10 +75,6 @@ class ProfileRemoveDialog(
app.db.teamDao().clear(profileId)
app.db.timetableDao().clear(profileId)
val homeCards = app.config.ui.homeCards.toMutableList()
homeCards.removeAll { it.profileId == profileId }
app.config.ui.homeCards = homeCards
val loginStoreId = profileObject.loginStoreId
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
if (profilesUsingLoginStore.size == 1) {

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()
@ -77,36 +88,31 @@ class GenerateBlockTimetableDialog(
b = DialogGenerateBlockTimetableBinding.inflate(activity.layoutInflater)
b.showProfileNameItem.onClick { b.showProfileNameCheckbox.trigger() }
b.withChangesCurrentWeekRadio.setText(R.string.timetable_generate_current_week_format, weekCurrentStart.formattedStringShort, weekCurrentEnd.formattedStringShort)
b.withChangesNextWeekRadio.setText(R.string.timetable_generate_next_week_format, weekNextStart.formattedStringShort, weekCurrentEnd.formattedStringShort)
b.showProfileNameCheckbox.setOnCheckedChangeListener { _, isChecked -> showProfileName = isChecked }
b.showTeachersNamesItem.onClick { b.showTeachersNamesCheckbox.trigger() }
b.showTeachersNamesCheckbox.setOnCheckedChangeListener { _, isChecked -> showTeachersNames = isChecked }
b.noColorsItem.onClick { b.noColorsCheckbox.trigger() }
b.noColorsCheckbox.setOnCheckedChangeListener { _, isChecked -> noColors = isChecked }
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.timetable_generate_range)
.setItems(arrayOf(
activity.getString(R.string.timetable_generate_current_week_format, weekCurrentStart.formattedStringShort, weekCurrentEnd.formattedStringShort)
.asColoredSpannable(android.R.attr.textColorPrimary.resolveAttr(activity)),
activity.getString(R.string.timetable_generate_next_week_format, weekNextStart.formattedStringShort, weekNextEnd.formattedStringShort)
.asColoredSpannable(android.R.attr.textColorPrimary.resolveAttr(activity)),
activity.getString(R.string.timetable_generate_selected_week)
.asColoredSpannable(android.R.attr.textColorPrimary.resolveAttr(activity))
)) { dialog, which ->
dialog.dismiss()
when (which) {
0 -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd)
1 -> generateBlockTimetable(weekNextStart, weekNextEnd)
2 -> selectDate()
}
}
.setView(b.root)
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener { onDismissListener?.invoke(TAG) }
.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()
}
}
}}
private fun selectDate() {
@ -124,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())
@ -166,174 +186,212 @@ 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
}
val diff = Time.diff(maxTime, minTime)
dialog.dismiss()
val imageWidth = WIDTH_CONSTANT + maxWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) - WIDTH_SPACING
val imageHeight = heightProfileName + HEIGHT_CONSTANT + diff.inMinutes * HEIGHT_MINUTE + HEIGHT_FOOTER
val bitmap = Bitmap.createBitmap(imageWidth + 20, imageHeight + 30, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val uri = withContext(Dispatchers.Default) {
if (noColors) canvas.drawARGB(255, 255, 255, 255)
else canvas.drawARGB(255, 225, 225, 225)
val diff = Time.diff(maxTime, minTime)
val paint = Paint().apply {
isAntiAlias = true
isFilterBitmap = true
isDither = true
}
val imageWidth = WIDTH_CONSTANT + maxWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) - WIDTH_SPACING
val imageHeight = heightProfileName + HEIGHT_CONSTANT + diff.inMinutes * HEIGHT_MINUTE + HEIGHT_FOOTER
val bitmap = Bitmap.createBitmap(imageWidth + 20, imageHeight + 30, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
lessons.forEach { lesson ->
val lessonLength = Time.diff(lesson.displayEndTime, lesson.displayStartTime)
val firstOffset = Time.diff(lesson.displayStartTime, minTime)
val lessonWeekDay = lesson.displayDate!!.weekDay
if (noColors) canvas.drawARGB(255, 255, 255, 255)
else canvas.drawARGB(255, 225, 225, 225)
val left = WIDTH_CONSTANT + lessonWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING)
val top = heightProfileName + HEIGHT_CONSTANT + firstOffset.inMinutes * HEIGHT_MINUTE
val blockWidth = WIDTH_WEEKDAY
val blockHeight = lessonLength.inMinutes * HEIGHT_MINUTE
val viewWidth = 380.dp
val viewHeight = lessonLength.inMinutes * 4.dp
val layout = activity.layoutInflater.inflate(R.layout.row_timetable_block_item, null) as LinearLayout
val item: LinearLayout = layout.findViewById(R.id.timetableItemLayout)
val card: CardView = layout.findViewById(R.id.timetableItemCard)
val subjectName: TextView = layout.findViewById(R.id.timetableItemSubjectName)
val classroomName: TextView = layout.findViewById(R.id.timetableItemClassroomName)
val teacherName: TextView = layout.findViewById(R.id.timetableItemTeacherName)
val teamName: TextView = layout.findViewById(R.id.timetableItemTeamName)
if (noColors) {
card.setCardBackgroundColor(Color.WHITE)
card.cardElevation = 0f
item.setBackgroundResource(R.drawable.bg_rounded_16dp_outline)
subjectName.setTextColor(Color.BLACK)
classroomName.setTextColor(0xffaaaaaa.toInt())
teacherName.setTextColor(0xffaaaaaa.toInt())
teamName.setTextColor(0xffaaaaaa.toInt())
val paint = Paint().apply {
isAntiAlias = true
isFilterBitmap = true
isDither = true
}
subjectName.text = lesson.displaySubjectName ?: ""
classroomName.text = lesson.displayClassroom ?: ""
teacherName.text = lesson.displayTeacherName ?: ""
teamName.text = lesson.displayTeamName ?: ""
lessons.forEach { lesson ->
val lessonLength = Time.diff(lesson.displayEndTime, lesson.displayStartTime)
val firstOffset = Time.diff(lesson.displayStartTime, minTime)
val lessonWeekDay = lesson.displayDate!!.weekDay
if (!showTeachersNames) teacherName.visibility = View.GONE
val left = WIDTH_CONSTANT + lessonWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING)
val top = heightProfileName + HEIGHT_CONSTANT + firstOffset.inMinutes * HEIGHT_MINUTE
when (lesson.type) {
Lesson.TYPE_NORMAL -> {}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
card.setCardBackgroundColor(Color.BLACK)
subjectName.setTextColor(Color.WHITE)
subjectName.text = lesson.displaySubjectName?.asStrikethroughSpannable() ?: ""
val blockWidth = WIDTH_WEEKDAY
val blockHeight = lessonLength.inMinutes * HEIGHT_MINUTE
val viewWidth = 380.dp
val viewHeight = lessonLength.inMinutes * 4.dp
val layout = activity.layoutInflater.inflate(R.layout.row_timetable_block_item, null) as LinearLayout
val item: LinearLayout = layout.findViewById(R.id.timetableItemLayout)
val card: CardView = layout.findViewById(R.id.timetableItemCard)
val subjectName: TextView = layout.findViewById(R.id.timetableItemSubjectName)
val classroomName: TextView = layout.findViewById(R.id.timetableItemClassroomName)
val teacherName: TextView = layout.findViewById(R.id.timetableItemTeacherName)
val teamName: TextView = layout.findViewById(R.id.timetableItemTeamName)
if (noColors) {
card.setCardBackgroundColor(Color.WHITE)
card.cardElevation = 0f
item.setBackgroundResource(R.drawable.bg_rounded_16dp_outline)
subjectName.setTextColor(Color.BLACK)
classroomName.setTextColor(0xffaaaaaa.toInt())
teacherName.setTextColor(0xffaaaaaa.toInt())
teamName.setTextColor(0xffaaaaaa.toInt())
}
else -> {
card.setCardBackgroundColor(0xff234158.toInt())
subjectName.setTextColor(Color.WHITE)
subjectName.setTypeface(null, Typeface.BOLD_ITALIC)
subjectName.text = lesson.displaySubjectName ?: ""
classroomName.text = lesson.displayClassroom ?: ""
teacherName.text = lesson.displayTeacherName ?: ""
teamName.text = lesson.displayTeamName ?: ""
if (!showTeachersNames) teacherName.visibility = View.GONE
when (lesson.type) {
Lesson.TYPE_NORMAL -> {
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
card.setCardBackgroundColor(Color.BLACK)
subjectName.setTextColor(Color.WHITE)
subjectName.text = lesson.displaySubjectName?.asStrikethroughSpannable()
?: ""
}
else -> {
card.setCardBackgroundColor(0xff234158.toInt())
subjectName.setTextColor(Color.WHITE)
subjectName.setTypeface(null, Typeface.BOLD_ITALIC)
}
}
layout.isDrawingCacheEnabled = true
layout.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY))
layout.layout(0, 0, layout.measuredWidth, layout.measuredHeight)
layout.buildDrawingCache(true)
val itemBitmap = layout.drawingCache
canvas.drawBitmap(itemBitmap, null, Rect(left, top, left + blockWidth, top + blockHeight), paint)
}
val textPaint = Paint().apply {
setARGB(255, 0, 0, 0)
textAlign = Paint.Align.CENTER
textSize = 30f
isAntiAlias = true
isFilterBitmap = true
isDither = true
}
for (w in 0..maxWeekDay) {
val x = WIDTH_CONSTANT + w * WIDTH_WEEKDAY + w * WIDTH_SPACING
canvas.drawText(Week.getFullDayName(w), x + (WIDTH_WEEKDAY / 2f), heightProfileName + HEIGHT_CONSTANT / 2 + 10f, textPaint)
}
if (showProfileName) {
textPaint.textSize = 50f
canvas.drawText("${app.profile.name} - plan lekcji, ${weekStart.formattedStringShort} - ${weekEnd.formattedStringShort}", (imageWidth + 20) / 2f, 80f, textPaint)
}
textPaint.apply {
setARGB(128, 0, 0, 0)
textAlign = Paint.Align.RIGHT
textSize = 26f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
}
val footerTextPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt()
canvas.drawText("Wygenerowano w aplikacji Szkolny.eu", imageWidth - 10f, imageHeight - footerTextPaintCenter - 10f, textPaint)
textPaint.apply {
setARGB(255, 127, 127, 127)
textAlign = Paint.Align.CENTER
textSize = 16f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
}
val textPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt()
val linePaint = Paint().apply {
setARGB(255, 100, 100, 100)
style = Paint.Style.STROKE
pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f)
isAntiAlias = true
isFilterBitmap = true
isDither = true
}
val minTimeInt = ((minTime!!.value / 10000) * 60) + ((minTime!!.value / 100) % 100)
lessonRanges.forEach { (startTime, endTime) ->
listOf(startTime, endTime).forEach { value ->
val hour = value / 10000
val minute = (value / 100) % 100
val time = Time(hour, minute, 0)
val firstOffset = time.inMinutes - minTimeInt // offset in minutes
val top = (heightProfileName + HEIGHT_CONSTANT + firstOffset * HEIGHT_MINUTE).toFloat()
canvas.drawText(time.stringHM, WIDTH_CONSTANT / 2f, top - textPaintCenter, textPaint)
canvas.drawLine(WIDTH_CONSTANT.toFloat(), top, imageWidth.toFloat(), top, linePaint)
}
}
layout.isDrawingCacheEnabled = true
layout.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY))
layout.layout(0, 0, layout.measuredWidth, layout.measuredHeight)
layout.buildDrawingCache(true)
val today = Date.getToday().stringY_m_d
val now = Time.getNow().stringH_M_S
val itemBitmap = layout.drawingCache
canvas.drawBitmap(itemBitmap, null, Rect(left, top, left + blockWidth, top + blockHeight), paint)
}
val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() }
val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png")
val textPaint = Paint().apply {
setARGB(255, 0, 0, 0)
textAlign = Paint.Align.CENTER
textSize = 30f
isAntiAlias = true
isFilterBitmap = true
isDither = true
}
for (w in 0..maxWeekDay) {
val x = WIDTH_CONSTANT + w * WIDTH_WEEKDAY + w * WIDTH_SPACING
canvas.drawText(Week.getFullDayName(w), x + (WIDTH_WEEKDAY / 2f), heightProfileName + HEIGHT_CONSTANT / 2 + 10f, textPaint)
}
if (showProfileName) {
textPaint.textSize = 50f
canvas.drawText("${app.profile.name} - plan lekcji, ${weekStart.formattedStringShort} - ${weekEnd.formattedStringShort}", (imageWidth + 20) / 2f, 80f, textPaint)
}
textPaint.apply {
setARGB(128, 0, 0, 0)
textAlign = Paint.Align.RIGHT
textSize = 26f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
}
val footerTextPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt()
canvas.drawText("Wygenerowano w aplikacji Szkolny.eu", imageWidth - 10f, imageHeight - footerTextPaintCenter - 10f, textPaint)
textPaint.apply {
setARGB(255, 127, 127, 127)
textAlign = Paint.Align.CENTER
textSize = 16f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
}
val textPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt()
val linePaint = Paint().apply {
setARGB(255, 100, 100, 100)
style = Paint.Style.STROKE
pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f)
isAntiAlias = true
isFilterBitmap = true
isDither = true
}
val minTimeInt = ((minTime!!.value / 10000) * 60) + ((minTime!!.value / 100) % 100)
lessonRanges.forEach { (startTime, endTime) ->
listOf(startTime, endTime).forEach { value ->
val hour = value / 10000
val minute = (value / 100) % 100
val time = Time(hour, minute, 0)
val firstOffset = time.inMinutes - minTimeInt // offset in minutes
val top = (heightProfileName + HEIGHT_CONSTANT + firstOffset * HEIGHT_MINUTE).toFloat()
canvas.drawText(time.stringHM, WIDTH_CONSTANT / 2f, top - textPaintCenter, textPaint)
canvas.drawLine(WIDTH_CONSTANT.toFloat(), top, imageWidth.toFloat(), top, linePaint)
try {
val fos = FileOutputStream(outputFile)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.close()
} catch (e: Exception) {
Log.e("SAVE_IMAGE", e.message, e)
return@withContext null
}
}
val today = Date.getToday().stringY_m_d
val now = Time.getNow().stringH_M_S
val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() }
val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png")
try {
val fos = FileOutputStream(outputFile)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.close()
} catch (e: Exception) {
Log.e("SAVE_IMAGE", e.message, e)
return@launch
}
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile)
} else {
Uri.parse("file://" + outputFile.absolutePath)
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile)
} else {
Uri.parse("file://" + outputFile.absolutePath)
}
uri
}
progressDialog.dismiss()

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

@ -36,6 +36,7 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.entity.Subject;
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull;
import pl.szczodrzynski.edziennik.databinding.FragmentAttendanceBinding;
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
@ -178,6 +179,7 @@ public class AttendanceFragment extends Fragment {
b.attendanceView.setHasFixedSize(true);
b.attendanceView.setLayoutManager(linearLayoutManager);
b.attendanceView.addItemDecoration(new SimpleDividerItemDecoration(getContext()));
App.db.attendanceDao().getAll(App.Companion.getProfileId()).observe(this, attendance -> {
if (app == null || activity == null || b == null || !isAdded())

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

@ -10,9 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentTemplateBinding
import pl.szczodrzynski.edziennik.utils.Themes
import kotlin.coroutines.CoroutineContext
class TemplateFragment : Fragment(), CoroutineScope {
@ -34,18 +32,13 @@ class TemplateFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
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 = FragmentTemplateBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
if (!isAdded)
return

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

@ -0,0 +1,117 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-1.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades
import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
import android.util.AttributeSet
import android.util.TypedValue.COMPLEX_UNIT_SP
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.graphics.ColorUtils
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Grade
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.dp
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
class GradeView : AppCompatTextView {
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
constructor(context: Context, grade: Grade, manager: GradesManager, periodGradesTextual: Boolean = false) : this(context, null) {
setGrade(grade, manager, false, periodGradesTextual)
}
@SuppressLint("RestrictedApi")
fun setGrade(grade: Grade?, manager: GradesManager, bigView: Boolean = false, periodGradesTextual: Boolean = false) {
if (grade == null) {
visibility = View.GONE
return
}
visibility = View.VISIBLE
val gradeName = grade.name
val gradeColor = manager.getGradeColor(grade)
text = if (periodGradesTextual)
when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> context.getString(
R.string.grade_semester_proposed_format,
gradeName
)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> context.getString(
R.string.grade_semester_final_format,
gradeName
)
TYPE_YEAR_PROPOSED -> context.getString(
R.string.grade_year_proposed_format,
gradeName
)
TYPE_YEAR_FINAL -> context.getString(
R.string.grade_year_final_format,
gradeName
)
else -> gradeName
}
else
gradeName
setTextColor(when (grade.type) {
TYPE_SEMESTER1_PROPOSED,
TYPE_SEMESTER2_PROPOSED,
TYPE_YEAR_PROPOSED -> android.R.attr.textColorPrimary.resolveAttr(context)
else -> if (ColorUtils.calculateLuminance(gradeColor) > 0.3)
0x99000000.toInt()
else
0x99ffffff.toInt()
})
//typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
setBackgroundResource(when (grade.type) {
TYPE_SEMESTER1_PROPOSED,
TYPE_SEMESTER2_PROPOSED,
TYPE_YEAR_PROPOSED -> if (bigView) R.drawable.bg_rounded_8dp_outline else R.drawable.bg_rounded_4dp_outline
else -> if (bigView) R.drawable.bg_rounded_8dp else R.drawable.bg_rounded_4dp
})
background.setTintColor(gradeColor)
gravity = Gravity.CENTER
if (bigView) {
setTextSize(COMPLEX_UNIT_SP, 24f)
setAutoSizeTextTypeUniformWithConfiguration(
14,
32,
1,
COMPLEX_UNIT_SP
)
setPadding(2.dp, 2.dp, 2.dp, 2.dp)
}
else {
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)
}
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
measure(WRAP_CONTENT, WRAP_CONTENT)
}
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades
import android.animation.ObjectAnimator
import android.view.LayoutInflater
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>(), CoroutineScope {
companion object {
private const val TAG = "GradesAdapter"
private const val ITEM_TYPE_SUBJECT = 0
private const val ITEM_TYPE_SEMESTER = 1
private const val ITEM_TYPE_EMPTY = 2
private const val ITEM_TYPE_GRADE = 3
private const val ITEM_TYPE_STATS = 4
const val STATE_CLOSED = 0
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 {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ITEM_TYPE_SUBJECT -> SubjectViewHolder(inflater, parent)
ITEM_TYPE_SEMESTER -> SemesterViewHolder(inflater, parent)
ITEM_TYPE_EMPTY -> EmptyViewHolder(inflater, parent)
ITEM_TYPE_GRADE -> GradeViewHolder(inflater, parent)
ITEM_TYPE_STATS -> StatsViewHolder(inflater, parent)
else -> throw IllegalArgumentException("Incorrect viewType")
}
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is GradesSubject -> ITEM_TYPE_SUBJECT
is GradesSemester -> ITEM_TYPE_SEMESTER
is GradesEmpty -> ITEM_TYPE_EMPTY
is Grade -> ITEM_TYPE_GRADE
is GradesStats -> ITEM_TYPE_STATS
else -> throw IllegalArgumentException("Incorrect viewType")
}
}
private val onClickListener = View.OnClickListener { view ->
val model = view.getTag(R.string.tag_key_model)
if (model is GradeFull) {
onGradeClick?.invoke(model)
return@OnClickListener
}
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
if (model is GradesSubject || model is GradesSemester) {
view?.findViewById<View>(R.id.dropdownIcon)?.let { dropdownIcon ->
ObjectAnimator.ofFloat(
dropdownIcon,
View.ROTATION,
if (model.state == STATE_CLOSED) 0f else 180f,
if (model.state == STATE_CLOSED) 180f else 0f
).setDuration(200).start();
}
}
if (model is GradesSubject) {
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 = when {
model is GradesSemester && model.grades.isEmpty() ->
listOf(GradesEmpty())
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())
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 = when {
semester.grades.isEmpty() ->
listOf(GradesEmpty())
manager.hideImproved ->
semester.grades.filter { !it.seen || !it.isImproved }
else -> semester.grades
}
semester.state = STATE_OPENED
items.addAll(position + 2 + semesterIndex, grades)
if (notifyAdapter) notifyItemRangeInserted(position + 2 + semesterIndex, grades.size)
}
}
}
else {
val start = position + 1
var end: Int = items.size
for (i in start until items.size) {
val model1 = items[i]
val level = if (model1 is GradesStats) 0 else (model1 as? ExpandableItemModel<*>)?.level ?: 3
if (level <= model.level) {
end = i
break
} else {
if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) {
model1.state = STATE_CLOSED
}
}
}
if (end != -1) {
items.subList(start, end).clear()
if (notifyAdapter) notifyItemRangeRemoved(start, end - start)
}
model.state = STATE_CLOSED
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
if (holder !is BindableViewHolder<*>)
return
val viewType = when (holder) {
is SubjectViewHolder -> ITEM_TYPE_SUBJECT
is SemesterViewHolder -> ITEM_TYPE_SEMESTER
is EmptyViewHolder -> ITEM_TYPE_EMPTY
is GradeViewHolder -> ITEM_TYPE_GRADE
is StatsViewHolder -> ITEM_TYPE_STATS
else -> throw IllegalArgumentException("Incorrect viewType")
}
holder.itemView.setTag(R.string.tag_key_view_type, viewType)
holder.itemView.setTag(R.string.tag_key_position, position)
holder.itemView.setTag(R.string.tag_key_model, item)
when {
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) {
holder.b.editButton.onClick {
val subject = items.firstOrNull { it is GradesSubject && it.subjectId == item.subjectId } as? GradesSubject ?: return@onClick
onGradesEditorClick?.invoke(subject, item)
}
}
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

@ -1,513 +0,0 @@
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.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import java.util.ArrayList;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.config.ProfileConfigGrades;
import pl.szczodrzynski.edziennik.data.db.entity.Grade;
import pl.szczodrzynski.edziennik.data.db.entity.Subject;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.databinding.FragmentGradesBinding;
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem;
import static pl.szczodrzynski.edziennik.config.ConfigGrades.ORDER_BY_DATE_ASC;
import static pl.szczodrzynski.edziennik.config.ConfigGrades.ORDER_BY_DATE_DESC;
import static pl.szczodrzynski.edziennik.config.ConfigGrades.ORDER_BY_SUBJECT_ASC;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_GRADE;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_AVG_2_AVG;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_AVG_2_SEM;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_SEM_2_AVG;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_SEM_2_SEM;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_ALL_GRADES;
public class GradesFragment extends Fragment {
private App app = null;
private MainActivity activity = null;
private FragmentGradesBinding b = null;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
if (getActivity() == null || getContext() == null)
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_grades, container, false);
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
b.refreshLayout.setNestedScrollingEnabled(true);
return b.getRoot();
}
ListView listView;
List<ItemGradesSubjectModel> subjectList;
private String getRegisterCardAverageModeSubText() {
switch (App.Companion.getConfig().forProfile().getGrades().getYearAverageMode()) {
default:
case YEAR_1_AVG_2_AVG:
return getString(R.string.settings_register_avg_mode_0_short);
case YEAR_1_SEM_2_AVG:
return getString(R.string.settings_register_avg_mode_1_short);
case YEAR_1_AVG_2_SEM:
return getString(R.string.settings_register_avg_mode_2_short);
case YEAR_1_SEM_2_SEM:
return getString(R.string.settings_register_avg_mode_3_short);
case YEAR_ALL_GRADES:
return getString(R.string.settings_register_avg_mode_4_short);
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || activity == null || b == null || !isAdded())
return;
/*activity.getBottomSheet().setToggleGroupEnabled(true);
activity.getBottomSheet().toggleGroupRemoveItems();
activity.getBottomSheet().setToggleGroupSelectionMode(NavBottomSheet.TOGGLE_GROUP_SORTING_ORDER);
activity.getBottomSheet().toggleGroupAddItem(0, getString(R.string.sort_by_date), (Drawable)null, SORT_MODE_DESCENDING);
activity.getBottomSheet().toggleGroupAddItem(1, getString(R.string.sort_by_subject), (Drawable)null, SORT_MODE_ASCENDING);
activity.getBottomSheet().setToggleGroupSortingOrderListener((id, sortMode) -> {
sortModeChanged = true;
if (id == 0 && sortMode == SORT_MODE_ASCENDING) {
app.appConfig.gradesOrderBy = ORDER_BY_DATE_ASC;
}
else if (id == 1 && sortMode == SORT_MODE_ASCENDING) {
app.appConfig.gradesOrderBy = ORDER_BY_SUBJECT_ASC;
}
else if (id == 0 && sortMode == SORT_MODE_DESCENDING) {
app.appConfig.gradesOrderBy = ORDER_BY_DATE_DESC;
}
else if (id == 1 && sortMode == SORT_MODE_DESCENDING) {
app.appConfig.gradesOrderBy = ORDER_BY_SUBJECT_DESC;
}
return null;
});
activity.getBottomSheet().setToggleGroupTitle("Sortowanie");
activity.getBottomSheet().toggleGroupCheck(0);
activity.getBottomSheet().setOnCloseListener(() -> {
if (sortModeChanged) {
sortModeChanged = false;
activity.reloadTarget();
}
return null;
});*/
activity.getBottomSheet().prependItems(
new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_grades_averages)
.withDescription(R.string.menu_grades_averages_desc)
.withIcon(CommunityMaterial.Icon.cmd_chart_line)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
showAverages();
}),
new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_grades_config)
.withIcon(CommunityMaterial.Icon2.cmd_settings_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
new GradesConfigDialog(activity, true, null, null);
}),
new BottomSheetSeparatorItem(true),
new BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_GRADE, true));
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
})
);
activity.gainAttention();
/*b.refreshLayout.setOnRefreshListener(() -> {
activity.syncCurrentFeature(MainActivity.DRAWER_ITEM_GRADES, b.refreshLayout);
});*/
listView = b.gradesRecyclerView;
//listView.setHasFixedSize(true);
//listView.setLayoutManager(new LinearLayoutManager(getContext()));
long expandSubjectId = -1;
if (getArguments() != null) {
expandSubjectId = getArguments().getLong("gradesSubjectId", -1);
}
/*b.gradesSwipeLayout.setOnRefreshListener(() -> {
Toast.makeText(activity, "Works!", Toast.LENGTH_LONG).show();
// To keep animation for 4 seconds
new Handler().postDelayed(() -> {
// Stop animation (This will be after 3 seconds)
b.gradesSwipeLayout.setRefreshing(false);
}, 3000);
});*/
long finalExpandSubjectId = expandSubjectId;
String orderBy;
if (app.getConfig().getGrades().getOrderBy() == ORDER_BY_SUBJECT_ASC) {
orderBy = "subjectLongName ASC, addedDate DESC";
}
else if (app.getConfig().getGrades().getOrderBy() == ORDER_BY_DATE_DESC) {
orderBy = "addedDate DESC";
}
else if (app.getConfig().getGrades().getOrderBy() == ORDER_BY_DATE_ASC) {
orderBy = "addedDate ASC";
}
else {
orderBy = "subjectLongName DESC, addedDate DESC";
}
App.db.gradeDao().getAllOrderBy(App.Companion.getProfileId(), orderBy).observe(this, grades -> {
if (app == null || activity == null || b == null || !isAdded())
return;
subjectList = new ArrayList<>();
ProfileConfigGrades config = app.getConfig().getFor(App.Companion.getProfileId()).getGrades();
// now we have all grades from the newest to the oldest
for (GradeFull grade: grades) {
ItemGradesSubjectModel model = ItemGradesSubjectModel.searchModelBySubjectId(subjectList, grade.subjectId);
if (model == null) {
model = new ItemGradesSubjectModel(app.getProfile(),
new Subject(App.Companion.getProfileId(), grade.subjectId, grade.subjectLongName, grade.subjectShortName),
new ArrayList<>(),
new ArrayList<>());
subjectList.add(model);
if (model.subject != null && model.subject.id == finalExpandSubjectId) {
model.expandView = true;
}
model.colorMode = App.Companion.getConfig().forProfile().getGrades().getColorMode();
model.yearAverageMode = App.Companion.getConfig().forProfile().getGrades().getYearAverageMode();
}
if (!grade.seen && grade.semester == 1) {
model.semester1Unread++;
}
if (!grade.seen && grade.semester == 2) {
model.semester2Unread++;
}
// COUNT POINT GRADES
if (grade.type == Grade.TYPE_POINT_AVG) {
model.isPointSubject = true;
if (grade.semester == 1) {
model.gradeSumOverall += grade.value;
model.gradeCountOverall += grade.valueMax;
model.gradeSumSemester1 += grade.value;
model.gradeCountSemester1 += grade.valueMax;
model.semester1Average = model.gradeSumSemester1 / model.gradeCountSemester1 * 100;
model.grades1.add(grade);
}
if (grade.semester == 2) {
model.gradeSumOverall += grade.value;
model.gradeCountOverall += grade.valueMax;
model.gradeSumSemester2 += grade.value;
model.gradeCountSemester2 += grade.valueMax;
model.semester2Average = model.gradeSumSemester2 / model.gradeCountSemester2 * 100;
model.grades2.add(grade);
}
}
else if (grade.type == Grade.TYPE_POINT_SUM) {
model.isBehaviourSubject = true;
if (grade.semester == 1) {
model.semester1Average += grade.value;
model.yearAverage += grade.value;
model.grades1.add(grade);
}
if (grade.semester == 2) {
model.semester2Average += grade.value;
model.yearAverage += grade.value;
model.grades2.add(grade);
}
}
else if (grade.type == Grade.TYPE_NORMAL) {
model.isNormalSubject = true;
float weight = grade.weight;
if (weight < 0) {
// do not show *normal* grades with negative weight - these are historical grades - Iuczniowie
continue;
}
if (!config.getCountZeroToAvg() && grade.name.equals("0")) {
weight = 0;
}
float valueWeighted = grade.value * weight;
if (grade.semester == 1) {
model.gradeSumOverall += valueWeighted;
model.gradeCountOverall += weight;
model.gradeSumSemester1 += valueWeighted;
model.gradeCountSemester1 += weight;
model.semester1Average = model.gradeSumSemester1 / model.gradeCountSemester1;
if (grade.parentId == -1) {
// show only "current" grades - these which are not historical
model.grades1.add(grade);
}
}
if (grade.semester == 2) {
model.gradeSumOverall += valueWeighted;
model.gradeCountOverall += weight;
model.gradeSumSemester2 += valueWeighted;
model.gradeCountSemester2 += weight;
model.semester2Average = model.gradeSumSemester2 / model.gradeCountSemester2;
if (grade.parentId == -1) {
// show only "current" grades - these which are not historical
model.grades2.add(grade);
}
}
}
else if (grade.type == Grade.TYPE_SEMESTER1_PROPOSED) {
model.semester1Proposed = grade;
}
else if (grade.type == Grade.TYPE_SEMESTER1_FINAL) {
model.semester1Final = grade;
}
else if (grade.type == Grade.TYPE_SEMESTER2_PROPOSED) {
model.semester2Proposed = grade;
}
else if (grade.type == Grade.TYPE_SEMESTER2_FINAL) {
model.semester2Final = grade;
}
else if (grade.type == Grade.TYPE_YEAR_PROPOSED) {
model.yearProposed = grade;
}
else if (grade.type == Grade.TYPE_YEAR_FINAL) {
model.yearFinal = grade;
}
else {
// descriptive grades, text grades
model.isDescriptiveSubject = true;
if (grade.semester == 1) {
model.grades1.add(grade);
}
if (grade.semester == 2) {
model.grades2.add(grade);
}
}
}
for (ItemGradesSubjectModel model: subjectList) {
if (model.isPointSubject) {
model.yearAverage = model.gradeSumOverall / model.gradeCountOverall * 100.0f; // map the point grade "average" % value from 0.0f-1.0f to 0%-100%
}
/*else if (model.isDescriptiveSubject && !model.isNormalSubject) {
// applies for only descriptive grades - do nothing. average is hidden
//model.isDescriptiveSubject = false;
//model.semester1Average = -1;
//model.semester2Average = -1;
//model.yearAverage = -1;
}*/
else if (!model.isBehaviourSubject && model.isNormalSubject) {
// applies for normal grades & normal+descriptive grades
// calculate the normal grade average based on the user's setting
switch (App.Companion.getConfig().forProfile().getGrades().getYearAverageMode()) {
case YEAR_1_AVG_2_AVG:
model.yearAverage = (model.semester1Average + model.semester2Average) / 2;
break;
case YEAR_1_SEM_2_AVG:
model.yearAverage = model.semester1Final != null ? (model.semester1Final.value + model.semester2Average) / 2 : 0.0f;
break;
case YEAR_1_AVG_2_SEM:
model.yearAverage = model.semester2Final != null ? (model.semester1Average + model.semester2Final.value) / 2 : 0.0f;
break;
case YEAR_1_SEM_2_SEM:
model.yearAverage = model.semester1Final != null && model.semester2Final != null ? (model.semester1Final.value + model.semester2Final.value) / 2 : 0.0f;
break;
default:
case YEAR_ALL_GRADES:
model.yearAverage = model.gradeSumOverall / model.gradeCountOverall;
break;
}
}
}
if (subjectList.size() > 0) {
GradesSubjectAdapter adapter;
if ((adapter = (GradesSubjectAdapter) listView.getAdapter()) != null) {
adapter.subjectList = subjectList;
adapter.notifyDataSetChanged();
return;
}
adapter = new GradesSubjectAdapter(subjectList, activity);
listView.setAdapter(adapter);
listView.setVisibility(View.VISIBLE);
b.gradesNoData.setVisibility(View.GONE);
if (finalExpandSubjectId != -1) {
int subjectIndex = subjectList.indexOf(ItemGradesSubjectModel.searchModelBySubjectId(subjectList, finalExpandSubjectId));
listView.setSelection(subjectIndex > 0 ? subjectIndex - 1 : subjectIndex);
}
}
else {
listView.setVisibility(View.GONE);
b.gradesNoData.setVisibility(View.VISIBLE);
}
});
}
private int gradeFromAverage(float value) {
int grade = (int)Math.floor(value);
if (value % 1.0f >= 0.75f)
grade++;
return grade;
}
public void showAverages() {
if (app == null || activity == null || b == null || !isAdded() || subjectList == null)
return;
float semester1Sum = 0;
float semester1Count = 0;
float semester1ProposedSum = 0;
float semester1ProposedCount = 0;
float semester1FinalSum = 0;
float semester1FinalCount = 0;
float semester2Sum = 0;
float semester2Count = 0;
float semester2ProposedSum = 0;
float semester2ProposedCount = 0;
float semester2FinalSum = 0;
float semester2FinalCount = 0;
float yearSum = 0;
float yearCount = 0;
float yearProposedSum = 0;
float yearProposedCount = 0;
float yearFinalSum = 0;
float yearFinalCount = 0;
for (ItemGradesSubjectModel subject: subjectList) {
// we cannot skip non-normal subjects because a point subject may also have a final grade
if (subject.isBehaviourSubject)
continue;
// SEMESTER 1 GRADES & AVERAGES
if (subject.semester1Final != null && subject.semester1Final.value > 0) { // if final available, add to final grades & expected grades
semester1FinalSum += subject.semester1Final.value;
semester1FinalCount++;
semester1Sum += subject.semester1Final.value;
semester1Count++;
}
else if (subject.semester1Proposed != null && subject.semester1Proposed.value > 0) { // if final not available, add proposed to expected grades
semester1Sum += subject.semester1Proposed.value;
semester1Count++;
}
else if (!Float.isNaN(subject.semester1Average)
&& subject.semester1Average > 0
&& !subject.isPointSubject) { // if final&proposed unavailable, calculate from avg
semester1Sum += gradeFromAverage(subject.semester1Average);
semester1Count++;
}
if (subject.semester1Proposed != null && subject.semester1Proposed.value > 0) { // add proposed to proposed grades even if final is available
semester1ProposedSum += subject.semester1Proposed.value;
semester1ProposedCount++;
}
// SEMESTER 2 GRADES & AVERAGES
if (subject.semester2Final != null && subject.semester2Final.value > 0) { // if final available, add to final grades & expected grades
semester2FinalSum += subject.semester2Final.value;
semester2FinalCount++;
semester2Sum += subject.semester2Final.value;
semester2Count++;
}
else if (subject.semester2Proposed != null && subject.semester2Proposed.value > 0) { // if final not available, add proposed to expected grades
semester2Sum += subject.semester2Proposed.value;
semester2Count++;
}
else if (!Float.isNaN(subject.semester2Average)
&& subject.semester2Average > 0
&& !subject.isPointSubject) { // if final&proposed unavailable, calculate from avg
semester2Sum += gradeFromAverage(subject.semester2Average);
semester2Count++;
}
if (subject.semester2Proposed != null && subject.semester2Proposed.value > 0) { // add proposed to proposed grades even if final is available
semester2ProposedSum += subject.semester2Proposed.value;
semester2ProposedCount++;
}
// YEAR GRADES & AVERAGES
if (subject.yearFinal != null && subject.yearFinal.value > 0) { // if final available, add to final grades & expected grades
yearFinalSum += subject.yearFinal.value;
yearFinalCount++;
yearSum += subject.yearFinal.value;
yearCount++;
}
else if (subject.yearProposed != null && subject.yearProposed.value > 0) { // if final not available, add proposed to expected grades
yearSum += subject.yearProposed.value;
yearCount++;
}
else if (!Float.isNaN(subject.yearAverage)
&& subject.yearAverage > 0
&& !subject.isPointSubject) { // if final&proposed unavailable, calculate from avg
yearSum += gradeFromAverage(subject.yearAverage);
yearCount++;
}
if (subject.yearProposed != null && subject.yearProposed.value > 0) { // add proposed to proposed grades even if final is available
yearProposedSum += subject.yearProposed.value;
yearProposedCount++;
}
}
String semester1ExpectedAverageStr = semester1Count > semester1ProposedCount || semester1Count > semester1FinalCount ? getString(R.string.dialog_averages_expected_format, 1, semester1Sum / semester1Count) : "";
String semester1ProposedAverageStr = semester1ProposedCount > 0 ? getString(R.string.dialog_averages_proposed_format, 1, semester1ProposedSum / semester1ProposedCount) : "";
String semester1FinalAverageStr = semester1FinalCount > 0 ? getString(R.string.dialog_averages_final_format, 1, semester1FinalSum / semester1FinalCount) : "";
String semester2ExpectedAverageStr = semester2Count > semester2ProposedCount || semester2Count > semester2FinalCount ? getString(R.string.dialog_averages_expected_format, 2, semester2Sum / semester2Count) : "";
String semester2ProposedAverageStr = semester2ProposedCount > 0 ? getString(R.string.dialog_averages_proposed_format, 2, semester2ProposedSum / semester2ProposedCount) : "";
String semester2FinalAverageStr = semester2FinalCount > 0 ? getString(R.string.dialog_averages_final_format, 2, semester2FinalSum / semester2FinalCount) : "";
String yearExpectedAverageStr = yearCount > yearProposedCount || yearCount > yearFinalCount ? getString(R.string.dialog_averages_expected_yearly_format, yearSum / yearCount) : "";
String yearProposedAverageStr = yearProposedCount > 0 ? getString(R.string.dialog_averages_proposed_yearly_format, yearProposedSum / yearProposedCount) : "";
String yearFinalAverageStr = yearFinalCount > 0 ? getString(R.string.dialog_averages_final_yearly_format, yearFinalSum / yearFinalCount) : "";
if (semester1ExpectedAverageStr.isEmpty() && semester1ProposedAverageStr.isEmpty() && semester1FinalAverageStr.isEmpty()) {
semester1ExpectedAverageStr = getString(R.string.dialog_averages_unavailable_format, 1);
}
if (semester2ExpectedAverageStr.isEmpty() && semester2ProposedAverageStr.isEmpty() && semester2FinalAverageStr.isEmpty()) {
semester2ExpectedAverageStr = getString(R.string.dialog_averages_unavailable_format, 2);
}
if (yearExpectedAverageStr.isEmpty() && yearProposedAverageStr.isEmpty() && yearFinalAverageStr.isEmpty()) {
yearExpectedAverageStr = getString(R.string.dialog_averages_unavailable_yearly);
}
new MaterialDialog.Builder(activity)
.title(R.string.dialog_averages_title)
.content(getString(
R.string.dialog_averages_format,
semester1ExpectedAverageStr,
semester1ProposedAverageStr,
semester1FinalAverageStr,
semester2ExpectedAverageStr,
semester2ProposedAverageStr,
semester2FinalAverageStr,
yearExpectedAverageStr,
yearProposedAverageStr,
yearFinalAverageStr))
.positiveText(R.string.ok)
.show();
}
}

View File

@ -0,0 +1,324 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-4.
*/
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.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_GRADES_EDITOR
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"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: GradesFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
private val adapter by lazy {
GradesAdapter(activity)
}
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
context ?: return null
app = activity.application as App
b = GradesFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded)
return
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
app.db.gradeDao()
.getAllOrderBy(App.profileId, app.gradesManager.getOrderByString())
.observe(this, Observer { grades ->
if (b.gradesRecyclerView.adapter == null) {
b.gradesRecyclerView.adapter = adapter
b.gradesRecyclerView.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
//addItemDecoration(SimpleDividerItemDecoration(context))
}
}
launch(Dispatchers.Default) {
processGrades(grades)
}
if (grades != null && grades.isNotEmpty()) {
b.gradesRecyclerView.visibility = View.VISIBLE
b.gradesNoData.visibility = View.GONE
} else {
b.gradesRecyclerView.visibility = View.GONE
b.gradesNoData.visibility = View.VISIBLE
}
})
adapter.onGradeClick = {
GradeDetailsDialog(activity, it)
}
adapter.onGradesEditorClick = { subject, semester ->
val otherSemester = subject.semesters.firstOrNull { it != semester }
var gradeSumOtherSemester = otherSemester?.averages?.normalWeightedSum
var gradeCountOtherSemester = otherSemester?.averages?.normalWeightedCount
if (gradeSumOtherSemester ?: 0f == 0f || gradeCountOtherSemester ?: 0f == 0f) {
gradeSumOtherSemester = otherSemester?.averages?.normalSum
gradeCountOtherSemester = otherSemester?.averages?.normalCount?.toFloat()
}
activity.loadTarget(TARGET_GRADES_EDITOR, Bundle(
"subjectId" to subject.subjectId,
"semester" to semester.number,
"averageMode" to manager.yearAverageMode,
"yearAverageBefore" to subject.averages.normalAvg,
"gradeSumOtherSemester" to gradeSumOtherSemester,
"gradeCountOtherSemester" to gradeCountOtherSemester,
"averageOtherSemester" to otherSemester?.averages?.normalAvg,
"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>()
var subjectId = -1L
var semesterNumber = 0
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
for (grade in grades) {
/*if (grade.parentId != null && grade.parentId != -1L)
continue // the grade is hidden as a new, improved one is available*/
if (grade.subjectId != subjectId) {
subjectId = grade.subjectId
semesterNumber = 0
subject = items.firstOrNull { it.subjectId == subjectId }
?: GradesSubject(grade.subjectId, grade.subjectLongName ?: "").also {
items += it
it.semester = 2
}
}
if (grade.semester != semesterNumber) {
semesterNumber = grade.semester
semester = subject.semesters.firstOrNull { it.number == semesterNumber }
?: 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
Grade.TYPE_SEMESTER1_FINAL,
Grade.TYPE_SEMESTER2_FINAL -> semester.finalGrade = grade
Grade.TYPE_YEAR_PROPOSED -> subject.proposedGrade = grade
Grade.TYPE_YEAR_FINAL -> subject.finalGrade = grade
else -> {
semester.grades += grade
countGrade(grade, subject.averages)
countGrade(grade, semester.averages)
}
}
subject.lastAddedDate = max(subject.lastAddedDate, grade.addedDate)
}
val stats = GradesStats()
val sem1Expected = mutableListOf<Float>()
val sem2Expected = mutableListOf<Float>()
val yearlyExpected = mutableListOf<Float>()
val sem1Proposed = mutableListOf<Float>()
val sem2Proposed = mutableListOf<Float>()
val yearlyProposed = mutableListOf<Float>()
val sem1Final = mutableListOf<Float>()
val sem2Final = mutableListOf<Float>()
val yearlyFinal = mutableListOf<Float>()
val sem1Point = mutableListOf<Float>()
val sem2Point = mutableListOf<Float>()
val yearlyPoint = mutableListOf<Float>()
for (item in items) {
item.semesters.forEach { sem ->
manager.calculateAverages(sem.averages)
if (sem.number == 1) {
sem.proposedGrade?.value?.let { sem1Proposed += it }
sem.finalGrade?.value?.let {
sem1Final += it
sem1Expected += it
} ?: run {
sem.averages.normalAvg?.let { sem1Expected += manager.getRoundedGrade(it).toFloat() }
}
sem.averages.pointAvgPercent?.let { sem1Point += it }
}
if (sem.number == 2) {
sem.proposedGrade?.value?.let { sem2Proposed += it }
sem.finalGrade?.value?.let {
sem2Final += it
sem2Expected += it
} ?: run {
sem.averages.normalAvg?.let { sem2Expected += manager.getRoundedGrade(it).toFloat() }
}
sem.averages.pointAvgPercent?.let { sem2Point += it }
}
}
manager.calculateAverages(item.averages, item.semesters)
item.proposedGrade?.value?.let { yearlyProposed += it }
item.finalGrade?.value?.let {
yearlyFinal += it
yearlyExpected += it
} ?: run {
item.averages.normalAvg?.let { yearlyExpected += manager.getRoundedGrade(it).toFloat() }
}
item.averages.pointAvgPercent?.let { yearlyPoint += it }
}
stats.normalSem1 = sem1Expected.averageOrNull()?.toFloat() ?: 0f
stats.normalSem1Proposed = sem1Proposed.averageOrNull()?.toFloat() ?: 0f
stats.normalSem1Final = sem1Final.averageOrNull()?.toFloat() ?: 0f
stats.sem1NotAllFinal = sem1Final.size < sem1Expected.size
stats.normalSem2 = sem2Expected.averageOrNull()?.toFloat() ?: 0f
stats.normalSem2Proposed = sem2Proposed.averageOrNull()?.toFloat() ?: 0f
stats.normalSem2Final = sem2Final.averageOrNull()?.toFloat() ?: 0f
stats.sem2NotAllFinal = sem2Final.size < sem2Expected.size
stats.normalYearly = yearlyExpected.averageOrNull()?.toFloat() ?: 0f
stats.normalYearlyProposed = yearlyProposed.averageOrNull()?.toFloat() ?: 0f
stats.normalYearlyFinal = yearlyFinal.averageOrNull()?.toFloat() ?: 0f
stats.yearlyNotAllFinal = yearlyFinal.size < yearlyExpected.size
stats.pointSem1 = sem1Point.averageOrNull()?.toFloat() ?: 0f
stats.pointSem2 = sem2Point.averageOrNull()?.toFloat() ?: 0f
stats.pointYearly = yearlyPoint.averageOrNull()?.toFloat() ?: 0f
when (manager.orderBy) {
GradesManager.ORDER_BY_DATE_DESC -> items.sortByDescending { it.lastAddedDate }
GradesManager.ORDER_BY_DATE_ASC -> items.sortBy { it.lastAddedDate }
}
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) {
val value = manager.getGradeValue(grade)
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
}
Grade.TYPE_POINT_AVG -> {
averages.pointAvgSum += grade.value
averages.pointAvgMax += grade.valueMax ?: value
}
Grade.TYPE_POINT_SUM -> {
averages.pointSum += grade.value
}
}
}
}

View File

@ -1,144 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.grades;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import java.text.DecimalFormat;
import java.util.List;
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.ui.dialogs.grade.GradeDetailsDialog;
import pl.szczodrzynski.edziennik.utils.Colors;
import pl.szczodrzynski.edziennik.utils.models.Date;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.COLOR_MODE_DEFAULT;
public class GradesListAdapter extends RecyclerView.Adapter<GradesListAdapter.ViewHolder> {
private Context mContext;
private List<GradeFull> gradeList;
//getting the context and product list with constructor
public GradesListAdapter(Context mCtx, List<GradeFull> gradeList) {
this.mContext = mCtx;
this.gradeList = gradeList;
}
@NonNull
@Override
public GradesListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//inflating and returning our view holder
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.row_grades_list_item, parent, false);
return new GradesListAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull GradesListAdapter.ViewHolder holder, int position) {
App app = (App) mContext.getApplicationContext();
GradeFull grade = gradeList.get(position);
holder.root.setOnClickListener((v -> {
new GradeDetailsDialog(v.getContext(), App.Companion.getProfileId()).show(app, grade);
}));
int gradeColor;
if (App.Companion.getConfig().forProfile().getGrades().getColorMode() == COLOR_MODE_DEFAULT) {
gradeColor = grade.color;
}
else {
gradeColor = Colors.gradeToColor(grade);
}
holder.gradesListName.setText(grade.name);
holder.gradesListName.setSelected(true);
holder.gradesListName.setTypeface(null, Typeface.BOLD);
holder.gradesListName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff);
holder.gradesListName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY));
if (grade.description.trim().isEmpty()) {
holder.gradesListDescription.setText(grade.category);
holder.gradesListCategory.setText(grade.isImprovement ? app.getString(R.string.grades_improvement_category_format, "") : "");
}
else {
holder.gradesListDescription.setText(grade.description);
holder.gradesListCategory.setText(grade.isImprovement ? app.getString(R.string.grades_improvement_category_format, grade.category) : grade.category);
}
DecimalFormat format = new DecimalFormat("#.##");
DecimalFormat formatWithZeroes = new DecimalFormat("#.00");
if (grade.weight < 0) {
grade.weight *= -1;
}
if (grade.type == Grade.TYPE_DESCRIPTIVE || grade.type == Grade.TYPE_DESCRIPTIVE_TEXT || grade.type == Grade.TYPE_TEXT || grade.type == Grade.TYPE_POINT_SUM) {
holder.gradesListWeight.setVisibility(View.GONE);
grade.weight = 0;
}
else {
holder.gradesListWeight.setVisibility(View.VISIBLE);
if (grade.type == Grade.TYPE_POINT_AVG) {
holder.gradesListWeight.setText(app.getString(R.string.grades_max_points_format, format.format(grade.valueMax)));
}
else if (grade.weight == 0) {
holder.gradesListWeight.setText(app.getString(R.string.grades_weight_not_counted));
}
else {
holder.gradesListWeight.setText(app.getString(R.string.grades_weight_format, format.format(grade.weight) + (grade.classAverage != -1 ? ", " + formatWithZeroes.format(grade.classAverage) : "")));
}
}
holder.gradesListTeacher.setText(grade.teacherFullName);
holder.gradesListAddedDate.setText(Date.fromMillis(grade.addedDate).getFormattedStringShort());
if (!grade.seen) {
holder.gradesListDescription.setBackground(mContext.getResources().getDrawable(R.drawable.bg_rounded_4dp));
holder.gradesListDescription.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
}
else {
holder.gradesListDescription.setBackground(null);
}
}
@Override
public int getItemCount() {
return gradeList.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
View root;
TextView gradesListName;
TextView gradesListDescription;
TextView gradesListCategory;
TextView gradesListWeight;
TextView gradesListTeacher;
TextView gradesListAddedDate;
ViewHolder(View itemView) {
super(itemView);
root = itemView.getRootView();
gradesListName = itemView.findViewById(R.id.gradesListName);
gradesListDescription = itemView.findViewById(R.id.gradesListCategoryColumn);
gradesListCategory = itemView.findViewById(R.id.gradesListCategoryDescription);
gradesListWeight = itemView.findViewById(R.id.gradesListWeight);
gradesListTeacher = itemView.findViewById(R.id.gradesListTeacher);
gradesListAddedDate = itemView.findViewById(R.id.gradesListAddedDate);
}
}
}

View File

@ -1,787 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.grades;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.ColorUtils;
import androidx.core.widget.NestedScrollView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.mikepenz.iconics.view.IconicsImageView;
import java.text.DecimalFormat;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.entity.Subject;
import pl.szczodrzynski.edziennik.data.db.full.GradeFull;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Colors;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static pl.szczodrzynski.edziennik.MainActivity.TARGET_GRADES_EDITOR;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.COLOR_MODE_DEFAULT;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_AVG_2_SEM;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_SEM_2_AVG;
import static pl.szczodrzynski.edziennik.data.db.entity.Profile.YEAR_1_SEM_2_SEM;
public class GradesSubjectAdapter extends ArrayAdapter<ItemGradesSubjectModel> implements View.OnClickListener {
private static final String TAG = "GradesSubjectAdapter";
private MainActivity activity;
public List<ItemGradesSubjectModel> subjectList;
private void updateBadges(Context context, ItemGradesSubjectModel model) {
// do not need this since we have an Observer for unread counters..
//((App)getContext().getApplicationContext()).saveRegister(); // I don't like this.
}
private boolean invalidAvg(float avg) {
return avg == 0.0f || Float.isNaN(avg);
}
private void updateSubjectSemesterBadges(ViewHolder holder, ItemGradesSubjectModel model) {
if (model.semester1Unread > 0 || model.semester2Unread > 0) {
holder.gradesSubjectTitle.setBackground(getContext().getResources().getDrawable(R.drawable.bg_rounded_4dp));
holder.gradesSubjectTitle.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
}
else {
holder.gradesSubjectTitle.setBackground(null);
}
if (model.semester1Unread > 0) {
holder.gradesSubjectSemester1Title.setBackground(getContext().getResources().getDrawable(R.drawable.bg_rounded_4dp));
holder.gradesSubjectSemester1Title.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
}
else {
holder.gradesSubjectSemester1Title.setBackground(null);
}
if (model.semester2Unread > 0) {
holder.gradesSubjectSemester2Title.setBackground(getContext().getResources().getDrawable(R.drawable.bg_rounded_4dp));
holder.gradesSubjectSemester2Title.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
}
else {
holder.gradesSubjectSemester2Title.setBackground(null);
}
}
private boolean gradesSetAsRead(ViewHolder holder, ItemGradesSubjectModel model, int semester) {
boolean somethingChanged = false;
AppDb db = App.Companion.getDb();
if (semester == 1) {
model.semester1Unread = 0;
for (GradeFull grade : model.grades1) {
if (!grade.seen) {
db.metadataDao().setSeen(App.Companion.getProfileId(), grade, somethingChanged = true);
}
}
if (model.semester1Proposed != null && !model.semester1Proposed.seen)
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester1Proposed, somethingChanged = true);
if (model.semester1Final != null && !model.semester1Final.seen)
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester1Final, somethingChanged = true);
}
else if (semester == 2) {
model.semester2Unread = 0;
for (GradeFull grade : model.grades2) {
if (!grade.seen) {
db.metadataDao().setSeen(App.Companion.getProfileId(), grade, somethingChanged = true);
}
}
if (model.semester2Proposed != null && !model.semester2Proposed.seen)
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester2Proposed, somethingChanged = true);
if (model.semester2Final != null && !model.semester2Final.seen)
db.metadataDao().setSeen(App.Companion.getProfileId(), model.semester2Final, somethingChanged = true);
if (model.yearProposed != null && !model.yearProposed.seen)
db.metadataDao().setSeen(App.Companion.getProfileId(), model.yearProposed, somethingChanged = true);
if (model.yearFinal != null && !model.yearFinal.seen)
db.metadataDao().setSeen(App.Companion.getProfileId(), model.yearFinal, somethingChanged = true);
}
if (somethingChanged) updateSubjectSemesterBadges(holder, model);
return somethingChanged;
}
private void expandSubject(ViewHolder holder, ItemGradesSubjectModel model) {
Anim.fadeOut(holder.gradesSubjectPreviewContainer, 200, new Animation.AnimationListener() {
@Override public void onAnimationStart(Animation animation) { }
@Override public void onAnimationRepeat(Animation animation) { }
@Override
public void onAnimationEnd(Animation animation) {
boolean somethingChanged = false;
if (holder.gradesSubjectSemester1Container.getVisibility() != View.GONE) {
somethingChanged = gradesSetAsRead(holder, model, 1);
}
if (holder.gradesSubjectSemester2Container.getVisibility() != View.GONE) {
somethingChanged = gradesSetAsRead(holder, model, 2);
}
if (somethingChanged) updateBadges(getContext(), model);
//holder.gradesSubjectPreviewContent.setVisibility(View.INVISIBLE);
Anim.expand(holder.gradesSubjectContent, 500, null);
}
});
}
private void collapseSubject(ViewHolder holder, ItemGradesSubjectModel model) {
Anim.collapse(holder.gradesSubjectContent, 500, new Animation.AnimationListener() {
@Override public void onAnimationStart(Animation animation) { }
@Override public void onAnimationRepeat(Animation animation) { }
@Override
public void onAnimationEnd(Animation animation) {
//holder.gradesSubjectPreviewContent.setVisibility(View.VISIBLE);
Anim.fadeIn(holder.gradesSubjectPreviewContainer, 200, null);
}
});
}
class BuildGradeViews extends AsyncTask<Void, Void, Void> {
boolean findViews;
ItemGradesSubjectModel model;
//ViewGroup parent;
//int position;
ViewHolder holder;
BuildGradeViews(ItemGradesSubjectModel model, ViewHolder holder, ViewGroup parent, int position, boolean findViews) {
this.model = model;
this.holder = holder;
this.findViews = findViews;
//this.parent = parent;
//this.position = position;
}
protected Void doInBackground(Void... params) {
if (this.findViews) {
findViews(holder, holder.gradesSubjectRoot);
}
return null;
}
protected void onPostExecute(Void aVoid) {
DecimalFormat df = new DecimalFormat("#.00");
if (this.findViews) {
// TODO NIE WIEM CO TO ROBI XD
//this.viewHolder.semestrTitle1.setText(C0193R.string.semestr1);
//this.viewHolder.semestrTitle2.setText(C0193R.string.semestr2);
/*if (GradesSubjectAdapter.this.columns == 0) {
DisplayMetrics metrics = GradeListAdapterBySubject.this.context.getResources().getDisplayMetrics();
if (GradeListAdapterBySubject.this.context.getResources().getBoolean(C0193R.bool.tablet)) {
GradeListAdapterBySubject.this.columns = ((int) ((((float) (this.parent.getWidth() / 2)) / metrics.density) - 30.0f)) / 58;
} else {
GradeListAdapterBySubject.this.columns = ((int) ((((float) this.parent.getWidth()) / metrics.density) - 30.0f)) / 58;
}
}
this.viewHolder.semestrGridView1.setColumnCount(GradeListAdapterBySubject.this.columns);
this.viewHolder.semestrGridView2.setColumnCount(GradeListAdapterBySubject.this.columns);*/
}
if (model != null && model.subject != null && model.subject.id != holder.lastSubject) {
holder.gradesSubjectRoot.setBackground(Colors.getAdaptiveBackgroundDrawable(model.subject.color, model.subject.color));
updateSubjectSemesterBadges(holder, model);
holder.gradesSubjectTitle.setText(model.subject.longName);
holder.gradesSubjectPreviewContent.removeAllViews();
if (model.expandView) {
// commented is without animations, do not use now (with unread badges)
//holder.gradesSubjectContent.setVisibility(View.VISIBLE);
//holder.gradesSubjectPreviewContent.setVisibility(View.INVISIBLE);
expandSubject(holder, model);
model.expandView = false;
}
int showSemester = model.profile.getCurrentSemester();
List<GradeFull> gradeList = (showSemester == 1 ? model.grades1 : model.grades2);
if (gradeList.size() == 0) {
showSemester = (showSemester == 1 ? 2 : 1);
gradeList = (showSemester == 1 ? model.grades1 : model.grades2);
}
App app = (App) getContext().getApplicationContext();
float scale = getContext().getResources().getDisplayMetrics().density;
int _5dp = (int) (5 * scale + 0.5f);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
layoutParams.setMargins(0, 0, _5dp, 0);
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
int maxWidthPx = displayMetrics.widthPixels - Utils.dpToPx((app.getConfig().getUi().getMiniMenuVisible() ? 72 : 0)/*miniDrawer size*/ + 8 + 8/*left and right offsets*/ + 24/*ellipsize width*/);
int totalWidthPx = 0;
boolean ellipsized = false;
if (showSemester != model.profile.getCurrentSemester()) {
// showing different semester, because of no grades in the selected one
holder.gradesSubjectPreviewSemester.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewSemester.setText(getContext().getString(R.string.grades_semester_header_format, showSemester));
// decrease the max preview width. DONE below
/*holder.gradesSubjectPreviewSemester.measure(WRAP_CONTENT, WRAP_CONTENT);
maxWidthPx -= holder.gradesSubjectPreviewSemester.getMeasuredWidth();
maxWidthPx -= _5dp;*/
}
else {
holder.gradesSubjectPreviewSemester.setVisibility(View.GONE);
}
if (model.grades1.size() > 0) {
holder.gradesSubjectSemester1Nest.setNestedScrollingEnabled(false);
holder.gradesSubjectSemester1Content.setHasFixedSize(false);
holder.gradesSubjectSemester1Content.setNestedScrollingEnabled(false);
holder.gradesSubjectSemester1Content.setLayoutManager(new LinearLayoutManager(activity));
holder.gradesSubjectSemester1Content.setAdapter(new GradesListAdapter(activity, model.grades1));
holder.gradesSubjectSemester1Header.setVisibility(View.VISIBLE);
if (showSemester == 1) {
holder.gradesSubjectSemester1Container.setVisibility(View.VISIBLE);
}
else {
holder.gradesSubjectSemester1Container.setVisibility(View.GONE);
}
if (model.isDescriptiveSubject && !model.isNormalSubject && !model.isPointSubject && !model.isBehaviourSubject) {
holder.gradesSubjectPreviewAverage.setVisibility(View.GONE);
holder.gradesSubjectSemester1Average.setVisibility(View.GONE);
}
else {
holder.gradesSubjectPreviewAverage.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester1Average.setVisibility(View.VISIBLE);
int formatSingle = (model.isPointSubject ? R.string.grades_average_single_percent_format : model.isBehaviourSubject ? R.string.grades_average_single_point_format : R.string.grades_average_single_format);
int format = (model.isPointSubject ? R.string.grades_semester_average_percent_format : model.isBehaviourSubject ? R.string.grades_semester_average_point_format : R.string.grades_semester_average_format);
// PREVIEW AVERAGE
if (showSemester == 1) {
holder.gradesSubjectPreviewAverage.setText(
getContext().getString(
formatSingle,
invalidAvg(model.semester1Average) ? "-" : df.format(model.semester1Average)
)
);
}
// AVERAGE value
holder.gradesSubjectSemester1Average.setText(
getContext().getString(
format,
1,
invalidAvg(model.semester1Average) ? "-" : df.format(model.semester1Average)
)
);
}
// PROPOSED grade
if (model.semester1Proposed != null) {
holder.gradesSubjectSemester1Proposed.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester1Proposed.setText(model.semester1Proposed.name);
holder.gradesSubjectSemester1Proposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Proposed.name), PorterDuff.Mode.MULTIPLY));
holder.gradesSubjectSemester1Proposed.setTextColor(Colors.gradeNameToColor(model.semester1Proposed.name));
if (showSemester == 1) {
holder.gradesSubjectPreviewProposed.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewProposed.setText(model.semester1Proposed.name);
holder.gradesSubjectPreviewProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Proposed.name), PorterDuff.Mode.MULTIPLY));
holder.gradesSubjectPreviewProposed.setTextColor(Colors.gradeNameToColor(model.semester1Proposed.name));
}
}
else {
holder.gradesSubjectSemester1Proposed.setVisibility(View.GONE);
if (showSemester == 1) {
holder.gradesSubjectPreviewProposed.setVisibility(View.GONE);
}
}
// FINAL grade
if (model.semester1Final != null) {
holder.gradesSubjectSemester1Final.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester1Final.setText(model.semester1Final.name);
holder.gradesSubjectSemester1Final.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Final.name), PorterDuff.Mode.MULTIPLY));
if (showSemester == 1) {
holder.gradesSubjectPreviewFinal.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewFinal.setText(model.semester1Final.name);
holder.gradesSubjectPreviewFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Final.name), PorterDuff.Mode.MULTIPLY));
}
}
else {
holder.gradesSubjectSemester1Final.setVisibility(View.GONE);
if (showSemester == 1) {
holder.gradesSubjectPreviewFinal.setVisibility(View.GONE);
}
}
}
else {
holder.gradesSubjectSemester1Header.setVisibility(View.GONE);
holder.gradesSubjectSemester1Container.setVisibility(View.GONE);
}
if (model.grades2.size() > 0) {
holder.gradesSubjectSemester2Nest.setNestedScrollingEnabled(false);
holder.gradesSubjectSemester2Content.setHasFixedSize(false);
holder.gradesSubjectSemester2Content.setNestedScrollingEnabled(false);
holder.gradesSubjectSemester2Content.setLayoutManager(new LinearLayoutManager(activity));
holder.gradesSubjectSemester2Content.setAdapter(new GradesListAdapter(activity, model.grades2));
holder.gradesSubjectSemester2Header.setVisibility(View.VISIBLE);
if (showSemester == 2) {
holder.gradesSubjectSemester2Container.setVisibility(View.VISIBLE);
}
else {
holder.gradesSubjectSemester2Container.setVisibility(View.GONE);
}
if (model.isDescriptiveSubject && !model.isNormalSubject && !model.isPointSubject && !model.isBehaviourSubject) {
holder.gradesSubjectPreviewAverage.setVisibility(View.GONE);
holder.gradesSubjectSemester2Average.setVisibility(View.GONE);
}
else {
holder.gradesSubjectPreviewAverage.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester2Average.setVisibility(View.VISIBLE);
// PREVIEW AVERAGE
int formatDouble = (model.isPointSubject ? R.string.grades_average_double_percent_format : model.isBehaviourSubject ? R.string.grades_average_double_point_format : R.string.grades_average_double_format);
int format = (model.isPointSubject ? R.string.grades_semester_average_percent_format : model.isBehaviourSubject ? R.string.grades_semester_average_point_format : R.string.grades_semester_average_format);
if (showSemester == 2) {
if (model.semester2Proposed != null || model.semester2Final != null) {
holder.gradesSubjectPreviewAverage.setText(invalidAvg(model.semester2Average) ? "-" : df.format(model.semester2Average));
holder.gradesSubjectPreviewYearAverage.setText(invalidAvg(model.yearAverage) ? "-" : df.format(model.yearAverage));
holder.gradesSubjectPreviewYearAverage.setVisibility(View.VISIBLE);
}
else {
holder.gradesSubjectPreviewAverage.setText(
getContext().getString(
formatDouble,
invalidAvg(model.semester2Average) ? "-" : df.format(model.semester2Average),
invalidAvg(model.yearAverage) ? "-" : df.format(model.yearAverage)
)
);
holder.gradesSubjectPreviewYearAverage.setVisibility(View.GONE);
}
}
// AVERAGE value
holder.gradesSubjectSemester2Average.setText(
getContext().getString(
format,
2,
invalidAvg(model.semester2Average) ? "-" : df.format(model.semester2Average)
)
);
}
// PROPOSED grade
if (model.semester2Proposed != null) {
holder.gradesSubjectSemester2Proposed.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester2Proposed.setText(model.semester2Proposed.name);
holder.gradesSubjectSemester2Proposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Proposed.name), PorterDuff.Mode.MULTIPLY));
holder.gradesSubjectSemester2Proposed.setTextColor(Colors.gradeNameToColor(model.semester2Proposed.name));
if (showSemester == 2) {
holder.gradesSubjectPreviewProposed.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewProposed.setText(model.semester2Proposed.name);
holder.gradesSubjectPreviewProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Proposed.name), PorterDuff.Mode.MULTIPLY));
holder.gradesSubjectPreviewProposed.setTextColor(Colors.gradeNameToColor(model.semester2Proposed.name));
}
}
else {
holder.gradesSubjectSemester2Proposed.setVisibility(View.GONE);
if (showSemester == 2) {
holder.gradesSubjectPreviewProposed.setVisibility(View.GONE);
}
}
// FINAL grade
if (model.semester2Final != null) {
holder.gradesSubjectSemester2Final.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester2Final.setText(model.semester2Final.name);
holder.gradesSubjectSemester2Final.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Final.name), PorterDuff.Mode.MULTIPLY));
if (showSemester == 2) {
holder.gradesSubjectPreviewFinal.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewFinal.setText(model.semester2Final.name);
holder.gradesSubjectPreviewFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Final.name), PorterDuff.Mode.MULTIPLY));
}
}
else {
holder.gradesSubjectSemester2Final.setVisibility(View.GONE);
if (showSemester == 2) {
holder.gradesSubjectPreviewFinal.setVisibility(View.GONE);
}
}
if (model.isDescriptiveSubject && !model.isNormalSubject && !model.isPointSubject && !model.isBehaviourSubject) {
holder.gradesSubjectYearAverage.setVisibility(View.GONE);
}
else {
holder.gradesSubjectYearAverage.setVisibility(View.VISIBLE);
// AVERAGE value
int format = (model.isPointSubject ? R.string.grades_year_average_percent_format : model.isBehaviourSubject ? R.string.grades_year_average_point_format : R.string.grades_year_average_format);
holder.gradesSubjectYearAverage.setText(
getContext().getString(
format,
invalidAvg(model.yearAverage) ? "-" : df.format(model.yearAverage)
)
);
}
// PROPOSED grade
if (model.yearProposed != null) {
holder.gradesSubjectYearProposed.setVisibility(View.VISIBLE);
holder.gradesSubjectYearProposed.setText(model.yearProposed.name);
holder.gradesSubjectYearProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearProposed.name), PorterDuff.Mode.MULTIPLY));
holder.gradesSubjectYearProposed.setTextColor(Colors.gradeNameToColor(model.yearProposed.name));
if (showSemester == 2) {
holder.gradesSubjectPreviewYearProposed.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewYearProposed.setText(model.yearProposed.name);
holder.gradesSubjectPreviewYearProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearProposed.name), PorterDuff.Mode.MULTIPLY));
holder.gradesSubjectPreviewYearProposed.setTextColor(Colors.gradeNameToColor(model.yearProposed.name));
}
}
else {
holder.gradesSubjectYearProposed.setVisibility(View.GONE);
if (showSemester == 2) {
holder.gradesSubjectPreviewYearProposed.setVisibility(View.GONE);
}
}
// FINAL grade
if (model.yearFinal != null) {
holder.gradesSubjectYearFinal.setVisibility(View.VISIBLE);
holder.gradesSubjectYearFinal.setText(model.yearFinal.name);
holder.gradesSubjectYearFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearFinal.name), PorterDuff.Mode.MULTIPLY));
if (showSemester == 2) {
holder.gradesSubjectPreviewYearFinal.setVisibility(View.VISIBLE);
holder.gradesSubjectPreviewYearFinal.setText(model.yearFinal.name);
holder.gradesSubjectPreviewYearFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearFinal.name), PorterDuff.Mode.MULTIPLY));
}
}
else {
holder.gradesSubjectYearFinal.setVisibility(View.GONE);
if (showSemester == 2) {
holder.gradesSubjectPreviewYearFinal.setVisibility(View.GONE);
}
}
}
else {
holder.gradesSubjectSemester2Header.setVisibility(View.GONE);
holder.gradesSubjectSemester2Container.setVisibility(View.GONE);
}
// decrease the width by average, proposed, final and semester TextViews
holder.gradesSubjectPreviewContainer.measure(WRAP_CONTENT, MATCH_PARENT);
//Log.d(TAG, "gradesSubjectPreviewContainer "+holder.gradesSubjectPreviewContainer.getMeasuredWidth());
/*holder.gradesSubjectPreviewAverage.measure(WRAP_CONTENT, WRAP_CONTENT);
maxWidthPx -= holder.gradesSubjectPreviewAverage.getMeasuredWidth();
maxWidthPx -= 2*_5dp;
if (holder.gradesSubjectPreviewProposed.getVisibility() == View.VISIBLE) {
holder.gradesSubjectPreviewProposed.measure(WRAP_CONTENT, WRAP_CONTENT);
maxWidthPx -= holder.gradesSubjectPreviewProposed.getMeasuredWidth();
maxWidthPx -= _5dp;
}
if (holder.gradesSubjectPreviewFinal.getVisibility() == View.VISIBLE) {
holder.gradesSubjectPreviewFinal.measure(WRAP_CONTENT, WRAP_CONTENT);
maxWidthPx -= holder.gradesSubjectPreviewFinal.getMeasuredWidth();
maxWidthPx -= _5dp;
}*/
maxWidthPx -= holder.gradesSubjectPreviewContainer.getMeasuredWidth();
maxWidthPx -= _5dp;
for (GradeFull grade: gradeList) {
if (grade.semester != showSemester)
continue;
int gradeColor;
if (model.colorMode == COLOR_MODE_DEFAULT) {
gradeColor = grade.color;
} else {
gradeColor = Colors.gradeToColor(grade);
}
TextView gradeName = new TextView(activity);
gradeName.setText(grade.name);
gradeName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff);
gradeName.setPadding(_5dp, 0, _5dp, 0);
gradeName.setBackgroundResource(R.drawable.bg_rounded_4dp);
gradeName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY));
gradeName.setTypeface(null, Typeface.BOLD);
gradeName.measure(WRAP_CONTENT, WRAP_CONTENT);
totalWidthPx += gradeName.getMeasuredWidth() + _5dp;
//Log.d(TAG, "totalWidthPx " + totalWidthPx);
if (totalWidthPx >= maxWidthPx) {
if (ellipsized)
continue;
ellipsized = true;
TextView ellipsisText = new TextView(activity);
ellipsisText.setText(R.string.ellipsis);
ellipsisText.setTextAppearance(activity, R.style.NavView_TextView);
ellipsisText.setTypeface(null, Typeface.BOLD);
ellipsisText.setPadding(0, 0, 0, 0);
holder.gradesSubjectPreviewContent.addView(ellipsisText, layoutParams);
}
else {
holder.gradesSubjectPreviewContent.addView(gradeName, layoutParams);
}
}
}
if (findViews) {
//Log.d("GradesSubjectAdapter", "runOnUiThread");
//this.viewHolder.gradesSubjectContent.setTag(View.VISIBLE);
//this.viewHolder.gradesSubjectPreviewContainer.setTag(View.VISIBLE);
activity.runOnUiThread(() -> {
holder.gradesSubjectRoot.setOnClickListener(v -> {
if (holder.gradesSubjectContent.getVisibility() == View.GONE) {
expandSubject(holder, model);
}
else {
collapseSubject(holder, model);
}
});
holder.gradesSubjectSemester1Header.setOnClickListener(v -> {
if (holder.gradesSubjectSemester1Container.getVisibility() == View.GONE) {
if (gradesSetAsRead(holder, model, 1)) updateBadges(getContext(), model);
Anim.expand(holder.gradesSubjectSemester1Container, 500, null);
if (holder.gradesSubjectSemester2Container.getVisibility() != View.GONE) {
Anim.collapse(holder.gradesSubjectSemester2Container, 500, null);
}
}
else {
Anim.collapse(holder.gradesSubjectSemester1Container, 500, null);
}
});
holder.gradesSubjectSemester2Header.setOnClickListener(v -> {
if (holder.gradesSubjectSemester2Container.getVisibility() == View.GONE) {
if (gradesSetAsRead(holder, model, 2)) updateBadges(getContext(), model);
Anim.expand(holder.gradesSubjectSemester2Container, 500, null);
if (holder.gradesSubjectSemester1Container.getVisibility() != View.GONE) {
Anim.collapse(holder.gradesSubjectSemester1Container, 500, null);
}
}
else {
Anim.collapse(holder.gradesSubjectSemester2Container, 500, null);
}
});
// hide the grade simulator when there are point, behaviour or descriptive grades
if (model.isPointSubject || model.isBehaviourSubject || (model.isDescriptiveSubject && !model.isNormalSubject)) {
holder.gradesSubjectSemester1EditButton.setVisibility(View.GONE);
holder.gradesSubjectSemester2EditButton.setVisibility(View.GONE);
}
else {
holder.gradesSubjectSemester1EditButton.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester1EditButton.setOnClickListener(v -> {
Bundle arguments = new Bundle();
if (model.subject != null) {
arguments.putLong("subjectId", model.subject.id);
}
arguments.putInt("semester", 1);
//d(TAG, "Model is " + model);
switch (model.yearAverageMode) {
case YEAR_1_SEM_2_AVG:
case YEAR_1_SEM_2_SEM:
arguments.putInt("averageMode", -1);
break;
default:
arguments.putInt("averageMode", model.semester2Final == null && model.yearAverageMode == YEAR_1_AVG_2_SEM ? -1 : model.yearAverageMode);
arguments.putFloat("yearAverageBefore", model.yearAverage);
arguments.putFloat("gradeSumOtherSemester", model.gradeSumSemester2);
arguments.putFloat("gradeCountOtherSemester", model.gradeCountSemester2);
arguments.putFloat("averageOtherSemester", model.semester2Average);
arguments.putFloat("finalOtherSemester", model.semester2Final == null ? -1 : model.semester2Final.value);
break;
}
activity.loadTarget(TARGET_GRADES_EDITOR, arguments);
});
holder.gradesSubjectSemester2EditButton.setVisibility(View.VISIBLE);
holder.gradesSubjectSemester2EditButton.setOnClickListener(v -> {
Bundle arguments = new Bundle();
if (model.subject != null) {
arguments.putLong("subjectId", model.subject.id);
}
arguments.putInt("semester", 2);
//d(TAG, "Model is " + model);
switch (model.yearAverageMode) {
case YEAR_1_AVG_2_SEM:
case YEAR_1_SEM_2_SEM:
arguments.putInt("averageMode", -1);
break;
default:
arguments.putInt("averageMode", model.semester1Final == null && model.yearAverageMode == YEAR_1_SEM_2_AVG ? -1 : model.yearAverageMode);
arguments.putFloat("yearAverageBefore", model.yearAverage);
arguments.putFloat("gradeSumOtherSemester", model.gradeSumSemester1);
arguments.putFloat("gradeCountOtherSemester", model.gradeCountSemester1);
arguments.putFloat("averageOtherSemester", model.semester1Average);
arguments.putFloat("finalOtherSemester", model.semester1Final == null ? -1 : model.semester1Final.value);
break;
}
activity.loadTarget(TARGET_GRADES_EDITOR, arguments);
});
}
});
}
if (model != null && model.subject != null) {
holder.lastSubject = model.subject.id;
}
super.onPostExecute(aVoid);
}
}
//getting the context and product list with constructor
public GradesSubjectAdapter(List<ItemGradesSubjectModel> data, MainActivity context) {
super(context, R.layout.row_grades_subject_item, data);
this.activity = context;
this.subjectList = data;
}
@Override
public void onClick(View v) {
int position = (Integer) v.getTag();
Object object = getItem(position);
ItemGradesSubjectModel dataModel = (ItemGradesSubjectModel)object;
}
private void findViews(ViewHolder holder, View root) {
//holder.gradesSubjectRoot = root.findViewById(R.id.gradesSubjectRoot);
holder.gradesSubjectTitle = root.findViewById(R.id.gradesSubjectTitle);
holder.gradesSubjectExpandIndicator = root.findViewById(R.id.gradesSubjectExpandIndicator);
holder.gradesSubjectPreviewContainer = root.findViewById(R.id.gradesSubjectPreviewContainer);
holder.gradesSubjectPreviewSemester = root.findViewById(R.id.gradesSubjectPreviewSemester);
holder.gradesSubjectPreviewContent = root.findViewById(R.id.gradesSubjectPreviewContent);
holder.gradesSubjectPreviewAverage = root.findViewById(R.id.gradesSubjectPreviewAverage);
holder.gradesSubjectPreviewProposed = root.findViewById(R.id.gradesSubjectPreviewProposed);
holder.gradesSubjectPreviewFinal = root.findViewById(R.id.gradesSubjectPreviewFinal);
holder.gradesSubjectPreviewYearAverage = root.findViewById(R.id.gradesSubjectPreviewYearAverage);
holder.gradesSubjectPreviewYearProposed = root.findViewById(R.id.gradesSubjectPreviewYearProposed);
holder.gradesSubjectPreviewYearFinal = root.findViewById(R.id.gradesSubjectPreviewYearFinal);
holder.gradesSubjectContent = root.findViewById(R.id.gradesSubjectContent);
holder.gradesSubjectSemester1Header = root.findViewById(R.id.gradesSubjectSemester1Header);
holder.gradesSubjectSemester1Title = root.findViewById(R.id.gradesSubjectSemester1Title);
holder.gradesSubjectSemester1ExpandIndicator = root.findViewById(R.id.gradesSubjectSemester1ExpandIndicator);
holder.gradesSubjectSemester1Average = root.findViewById(R.id.gradesSubjectSemester1Average);
holder.gradesSubjectSemester1Proposed = root.findViewById(R.id.gradesSubjectSemester1Proposed);
holder.gradesSubjectSemester1Final = root.findViewById(R.id.gradesSubjectSemester1Final);
holder.gradesSubjectSemester1EditButton = root.findViewById(R.id.gradesSubjectSemester1EditButton);
holder.gradesSubjectSemester1Container = root.findViewById(R.id.gradesSubjectSemester1Container);
holder.gradesSubjectSemester1Nest = root.findViewById(R.id.gradesSubjectSemester1Nest);
holder.gradesSubjectSemester1Content = root.findViewById(R.id.gradesSubjectSemester1Content);
holder.gradesSubjectSemester2Header = root.findViewById(R.id.gradesSubjectSemester2Header);
holder.gradesSubjectSemester2Title = root.findViewById(R.id.gradesSubjectSemester2Title);
holder.gradesSubjectSemester2ExpandIndicator = root.findViewById(R.id.gradesSubjectSemester2ExpandIndicator);
holder.gradesSubjectSemester2Average = root.findViewById(R.id.gradesSubjectSemester2Average);
holder.gradesSubjectSemester2Proposed = root.findViewById(R.id.gradesSubjectSemester2Proposed);
holder.gradesSubjectSemester2Final = root.findViewById(R.id.gradesSubjectSemester2Final);
holder.gradesSubjectSemester2EditButton = root.findViewById(R.id.gradesSubjectSemester2EditButton);
holder.gradesSubjectSemester2Container = root.findViewById(R.id.gradesSubjectSemester2Container);
holder.gradesSubjectSemester2Nest = root.findViewById(R.id.gradesSubjectSemester2Nest);
holder.gradesSubjectSemester2Content = root.findViewById(R.id.gradesSubjectSemester2Content);
holder.gradesSubjectYearAverage = root.findViewById(R.id.gradesSubjectYearAverage);
holder.gradesSubjectYearProposed = root.findViewById(R.id.gradesSubjectYearProposed);
holder.gradesSubjectYearFinal = root.findViewById(R.id.gradesSubjectYearFinal);
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
if (position >= subjectList.size()) {
return convertView;
}
ItemGradesSubjectModel model = subjectList.get(position);
if (model == null) {
//Toast.makeText(activity, "return convertView;", Toast.LENGTH_SHORT).show();
return convertView;
}
ViewHolder holder;
if (convertView == null) {
try {
convertView = LayoutInflater.from(activity).inflate(R.layout.row_grades_subject_item, parent, false);
holder = new ViewHolder();
holder.gradesSubjectRoot = convertView.findViewById(R.id.gradesSubjectRoot);
convertView.setTag(holder);
new BuildGradeViews(model, holder, parent, position, true).execute();
return convertView;
} catch (Exception e) {
return new View(getContext());
}
}
holder = (ViewHolder) convertView.getTag();
Subject subject = model.subject;
holder.gradesSubjectTitle.setText(subject != null ? subject.longName : "");
/*if (model.getNotSeen() > 0) {
viewHolder.notseen.setVisibility(0);
viewHolder.notseen.setText(model.getNotSeen() + "");
} else {
viewHolder.notseen.setVisibility(8);
}*/
new BuildGradeViews(model, holder, parent, position, false).execute();
return convertView;
}
public int getViewTypeCount() {
return getCount();
}
public int getItemViewType(int position) {
return position;
}
class ViewHolder {
long lastSubject;
TextView gradesSubjectTitle;
ConstraintLayout gradesSubjectRoot;
IconicsImageView gradesSubjectExpandIndicator;
LinearLayout gradesSubjectPreviewContainer;
TextView gradesSubjectPreviewSemester;
LinearLayout gradesSubjectPreviewContent;
TextView gradesSubjectPreviewAverage;
TextView gradesSubjectPreviewProposed;
TextView gradesSubjectPreviewFinal;
TextView gradesSubjectPreviewYearAverage;
TextView gradesSubjectPreviewYearProposed;
TextView gradesSubjectPreviewYearFinal;
LinearLayout gradesSubjectContent;
LinearLayout gradesSubjectSemester1Header;
TextView gradesSubjectSemester1Title;
IconicsImageView gradesSubjectSemester1ExpandIndicator;
TextView gradesSubjectSemester1Average;
TextView gradesSubjectSemester1Proposed;
TextView gradesSubjectSemester1Final;
IconicsImageView gradesSubjectSemester1EditButton;
LinearLayout gradesSubjectSemester1Container;
NestedScrollView gradesSubjectSemester1Nest;
RecyclerView gradesSubjectSemester1Content;
LinearLayout gradesSubjectSemester2Header;
TextView gradesSubjectSemester2Title;
IconicsImageView gradesSubjectSemester2ExpandIndicator;
TextView gradesSubjectSemester2Average;
TextView gradesSubjectSemester2Proposed;
TextView gradesSubjectSemester2Final;
IconicsImageView gradesSubjectSemester2EditButton;
LinearLayout gradesSubjectSemester2Container;
NestedScrollView gradesSubjectSemester2Nest;
RecyclerView gradesSubjectSemester2Content;
TextView gradesSubjectYearAverage;
TextView gradesSubjectYearProposed;
TextView gradesSubjectYearFinal;
}
}

View File

@ -14,13 +14,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_1_AVG_2_AVG
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_1_AVG_2_SEM
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_1_SEM_2_AVG
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
import pl.szczodrzynski.edziennik.databinding.FragmentGradesEditorBinding
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
import java.text.DecimalFormat
import java.util.*
import kotlin.math.floor

View File

@ -0,0 +1,12 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.models
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter.Companion.STATE_CLOSED
abstract class ExpandableItemModel<T>(val items: MutableList<T>) {
open var level: Int = 3
var state: Int = STATE_CLOSED
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-1.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.models
class GradesAverages {
var normalSum = 0f
var normalCount = 0
var normalWeightedSum = 0f
var normalWeightedCount = 0f
var pointSum = 0f
var pointAvgSum = 0f
var pointAvgMax = 0f
var normalAvg: Float? = null
var pointAvgPercent: Float? = null
}

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-1.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.models
class GradesEmpty

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.models
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
data class GradesSemester(
val subjectId: Long,
val number: Int,
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

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-3.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.models
class GradesStats {
var normalSem1 = 0f
var normalSem1Proposed = 0f
var normalSem1Final = 0f
var normalSem2 = 0f
var normalSem2Proposed = 0f
var normalSem2Final = 0f
var normalYearly = 0f
var normalYearlyProposed = 0f
var normalYearlyFinal = 0f
var sem1NotAllFinal = false
var sem2NotAllFinal = false
var yearlyNotAllFinal = false
var pointSem1 = 0f
var pointSem2 = 0f
var pointYearly = 0f
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.models
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
data class GradesSubject(
val subjectId: Long,
val subjectName: String,
val semesters: MutableList<GradesSemester> = mutableListOf()
) : ExpandableItemModel<GradesSemester>(semesters) {
override var level = 1
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

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
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, adapter: GradesAdapter)
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-1.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.view.LayoutInflater
import android.view.ViewGroup
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(
inflater: LayoutInflater,
parent: ViewGroup,
val b: GradesItemEmptyBinding = GradesItemEmptyBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesEmpty> {
companion object {
private const val TAG = "EmptyViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesEmpty, position: Int, adapter: GradesAdapter) {
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
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(
inflater: LayoutInflater,
parent: ViewGroup,
val b: GradesItemGradeBinding = GradesItemGradeBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradeFull> {
companion object {
private const val TAG = "GradeViewHolder"
}
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun onBind(activity: AppCompatActivity, app: App, grade: GradeFull, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager
b.gradeName.setGrade(grade, manager, bigView = true)
if (grade.description.isNullOrBlank()) {
b.gradeDescription.text = grade.category
b.gradeCategory.text =
if (grade.isImprovement)
app.getString(R.string.grades_improvement_category_format, "")
else
""
} else {
b.gradeDescription.text = grade.description
b.gradeCategory.text =
if (grade.isImprovement)
app.getString(R.string.grades_improvement_category_format, grade.category)
else
grade.category
}
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
}
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

@ -0,0 +1,68 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
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.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,
parent: ViewGroup,
val b: GradesItemSemesterBinding = GradesItemSemesterBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesSemester> {
companion object {
private const val TAG = "SemesterViewHolder"
}
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) {
GradesAdapter.STATE_CLOSED -> 0f
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

@ -0,0 +1,143 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-3.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
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
class StatsViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: GradesItemStatsBinding = GradesItemStatsBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesStats> {
companion object {
private const val TAG = "StatsViewHolder"
}
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>()
getSemesterString(app, item.normalSem1, item.normalSem1Proposed, item.normalSem1Final, item.sem1NotAllFinal).let { (average, notice) ->
b.normalSemester1Layout.isVisible = average != null
b.normalSemester1Notice.isVisible = notice != null
b.normalSemester1.text = average
b.normalSemester1Notice.text = notice
if (average != null)
showAverages += 1
}
getSemesterString(app, item.normalSem2, item.normalSem2Proposed, item.normalSem2Final, item.sem2NotAllFinal).let { (average, notice) ->
b.normalSemester2Layout.isVisible = average != null
b.normalSemester2Notice.isVisible = notice != null
b.normalSemester2.text = average
b.normalSemester2Notice.text = notice
if (average != null)
showAverages += 2
}
getSemesterString(app, item.normalYearly, item.normalYearlyProposed, item.normalYearlyFinal, item.yearlyNotAllFinal).let { (average, notice) ->
b.normalYearlyLayout.isVisible = average != null
b.normalYearlyNotice.isVisible = notice != null
b.normalYearly.text = average
b.normalYearlyNotice.text = notice
if (average != null)
showAverages += 3
}
b.normalTitle.isVisible = showAverages.size > 0
b.normalLayout.isVisible = showAverages.size > 0
b.normalDivider.isVisible = showAverages.size > 0
b.helpButton.isVisible = showAverages.size > 0
b.normalDiv1.isVisible = showAverages.contains(1) && showAverages.contains(2) || showAverages.contains(1) && showAverages.contains(3)
b.normalDiv2.isVisible = showAverages.contains(2) && showAverages.contains(3)
getSemesterString(app, 0f, 0f, item.pointSem1, false).let { (average, _) ->
b.pointSemester1Layout.isVisible = average != null
b.pointSemester1.text = average
if (average != null)
showPoint += 1
}
getSemesterString(app, 0f, 0f, item.pointSem2, false).let { (average, _) ->
b.pointSemester2Layout.isVisible = average != null
b.pointSemester2.text = average
if (average != null)
showPoint += 2
}
getSemesterString(app, 0f, 0f, item.pointYearly, false).let { (average, _) ->
b.pointYearlyLayout.isVisible = average != null
b.pointYearly.text = average
if (average != null)
showPoint += 3
}
b.pointTitle.isVisible = showPoint.size > 0
b.pointLayout.isVisible = showPoint.size > 0
b.pointDivider.isVisible = showPoint.size > 0
b.pointDiv1.isVisible = showPoint.contains(1) && showPoint.contains(2)
b.pointDiv2.isVisible = showPoint.contains(2) && showPoint.contains(3)
b.noData.isVisible = showAverages.isEmpty() && showPoint.isEmpty()
b.disclaimer.isVisible = !b.noData.isVisible
b.helpButton.onClick {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.grades_stats_help_title)
.setMessage(R.string.grades_stats_help_text)
.setPositiveButton(R.string.ok, null)
.show()
}
b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null
b.customValueLayout.isVisible = b.customValueDivider.isVisible
b.customValueButton.onClick {
GradesConfigDialog(activity, reloadOnDismiss = true)
}
}
private fun getSemesterString(context: Context, expected: Float, proposed: Float, final: Float, notAllFinal: Boolean) : Pair<String?, String?> {
val format = DecimalFormat("#.00")
val average = when {
final != 0f -> final
proposed != 0f -> proposed
expected != 0f -> expected
else -> null
}?.let {
format.format(it)
}
val notice = when {
final != 0f -> when {
notAllFinal -> if (expected != 0f)
context.getString(R.string.grades_stats_from_final, format.format(expected))
else
context.getString(R.string.grades_stats_from_final_no_expected)
proposed != 0f -> context.getString(R.string.grades_stats_proposed_avg, format.format(proposed))
else -> null
}
proposed != 0f -> if (expected != 0f)
context.getString(R.string.grades_stats_from_proposed, format.format(expected))
else
context.getString(R.string.grades_stats_from_proposed_no_expected)
expected != 0f -> context.getString(R.string.grades_stats_expected)
else -> null
}
return average to notice
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.grades.viewholder
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
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
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
class SubjectViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: GradesItemSubjectBinding = GradesItemSubjectBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<GradesSubject> {
companion object {
private const val TAG = "SubjectViewHolder"
}
override fun onBind(activity: AppCompatActivity, app: App, item: GradesSubject, position: Int, adapter: GradesAdapter) {
val manager = app.gradesManager
val contextWrapper = ContextThemeWrapper(activity, Themes.themeInt)
b.subjectName.text = item.subjectName
b.dropdownIcon.rotation = when (item.state) {
STATE_CLOSED -> 0f
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
val gradesContainer = b.previewContainer[0]
b.previewContainer.removeAllViews()
b.gradesContainer.removeAllViews()
b.previewContainer.addView(gradesContainer)
val firstSemester = item.semesters.firstOrNull() ?: return
b.yearSummary.text = manager.getYearSummaryString(app, item.semesters.map { it.grades.size }.sum(), item.averages)
if (firstSemester.number != item.semester) {
b.gradesContainer.addView(TextView(contextWrapper).apply {
setText(R.string.grades_preview_other_semester, firstSemester.number)
setPadding(0, 0, 5.dp, 0)
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
})
}
/*if (firstSemester.grades.isEmpty()) {
b.previewContainer.addView(TextView(app).apply {
setText(R.string.grades_no_grades_in_semester, firstSemester.number)
})
}*/
val hideImproved = manager.hideImproved
for (grade in firstSemester.grades) {
if (hideImproved && grade.isImproved)
continue
b.gradesContainer.addView(GradeView(
contextWrapper,
grade,
manager,
periodGradesTextual = false
))
}
b.previewContainer.addView(TextView(contextWrapper).apply {
text = manager.getAverageString(app, firstSemester.averages, nameSemester = true, showSemester = firstSemester.number)
//gravity = Gravity.END
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, 0, 8.dp, 0)
}
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
})
firstSemester.proposedGrade?.let {
b.previewContainer.addView(GradeView(
contextWrapper,
it,
manager
))
}
firstSemester.finalGrade?.let {
b.previewContainer.addView(GradeView(
contextWrapper,
it,
manager
))
}
if (firstSemester.number == item.semester) {
b.previewContainer.addView(TextView(contextWrapper).apply {
text = manager.getAverageString(app, item.averages, nameSemester = true)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, 0, 8.dp, 0)
}
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
})
item.proposedGrade?.let {
b.previewContainer.addView(GradeView(
contextWrapper,
it,
manager
))
}
item.finalGrade?.let {
b.previewContainer.addView(GradeView(
contextWrapper,
it,
manager
))
}
}
}
}

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"
}
@ -57,4 +57,4 @@ class HomeDummyCard(override val id: Int) : HomeCard, CoroutineScope {
timer?.cancel()
timer = null
}
}
}

View File

@ -26,10 +26,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeDebugCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeGradesCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.*
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
@ -40,16 +37,21 @@ class HomeFragment : Fragment(), CoroutineScope {
private const val TAG = "HomeFragment"
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter) {
val homeCards = App.config.ui.homeCards.toMutableList()
val fromPair = homeCards[fromPosition]
homeCards[fromPosition] = homeCards[toPosition]
homeCards[toPosition] = fromPair
App.config.ui.homeCards = homeCards
val fromCard = cardAdapter.items[fromPosition]
val toCard = cardAdapter.items[toPosition]
if (fromCard.id == 100 || toCard.id == 100) {
// debug card is not swappable
return
}
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
cardAdapter.items[toPosition] = fromCard
cardAdapter.notifyItemMoved(fromPosition, toPosition)
val homeCards = App.config.forProfile().ui.homeCards.toMutableList()
val fromPair = homeCards[fromPosition]
homeCards[fromPosition] = homeCards[toPosition]
homeCards[toPosition] = fromPair
App.config.forProfile().ui.homeCards = homeCards
}
}
@ -107,15 +109,15 @@ class HomeFragment : Fragment(), CoroutineScope {
val showUnified = false
val cards = app.config.ui.homeCards.filter { it.profileId == app.profile.id }.toMutableList()
val cards = app.config.forProfile().ui.homeCards.filter { it.profileId == app.profile.id }.toMutableList()
if (cards.isEmpty()) {
cards += listOf(
HomeCardModel(app.profile.id, HomeCard.CARD_LUCKY_NUMBER),
HomeCardModel(app.profile.id, HomeCard.CARD_TIMETABLE),
/*HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS),*/
HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS),
HomeCardModel(app.profile.id, HomeCard.CARD_GRADES)
)
app.config.ui.homeCards = app.config.ui.homeCards.toMutableList().also { it.addAll(cards) }
app.config.forProfile().ui.homeCards = app.config.forProfile().ui.homeCards.toMutableList().also { it.addAll(cards) }
}
val items = mutableListOf<HomeCard>()
@ -124,6 +126,7 @@ class HomeFragment : Fragment(), CoroutineScope {
HomeCard.CARD_LUCKY_NUMBER -> HomeLuckyNumberCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_TIMETABLE -> HomeTimetableCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_GRADES -> HomeGradesCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_EVENTS -> HomeEventsCard(it.cardId, app, activity, this, app.profile)
else -> null
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-28.
*/
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.CardHomeEventsBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.onClick
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.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class HomeEventsCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragment,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeEventsCard"
}
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var adapter: EventListAdapter
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
holder.root.removeAllViews()
val b = CardHomeEventsBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
adapter = EventListAdapter(
activity,
simpleMode = true,
showDate = true,
showWeekDay = true,
onItemClick = {
EventDetailsDialog(
activity,
it
)
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it
)
}
)
app.db.eventDao().getAllNearest(App.profileId, Date.getToday(), 4).observe(activity, Observer { events ->
adapter.items = events
if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter
b.eventsView.apply {
isNestedScrollingEnabled = false
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
adapter.notifyDataSetChanged()
if (events != null && events.isNotEmpty()) {
b.eventsView.visibility = View.VISIBLE
b.eventsNoData.visibility = View.GONE
} else {
b.eventsView.visibility = View.GONE
b.eventsNoData.visibility = View.VISIBLE
}
})
holder.root.onClick {
activity.loadTarget(MainActivity.DRAWER_ITEM_AGENDA)
}
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

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,16 +25,15 @@ 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.*
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.models.ItemGradesSubjectModel
import kotlin.coroutines.CoroutineContext
@ -121,32 +117,11 @@ class HomeGradesCard(
16 /*ellipsize width*/)) / 1.5f
subject.grades1.onEach { grade ->
val gradeColor = when (App.config.forProfile().grades.colorMode) {
Profile.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

@ -156,6 +156,7 @@ class HomeTimetableCard(
|| (it.displayDate == today
&& it.displayEndTime != null
&& it.displayEndTime!! >= now))
&& !it.isCancelled
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
@ -165,8 +166,8 @@ class HomeTimetableCard(
&& !(it.isCancelled && ignoreCancelled)
}
if (lessons.isEmpty() && timetableDate.weekDay <= 5)
break
//if (lessons.isEmpty() && timetableDate.weekDay <= 5)
// break
checkedDays++
}
@ -208,6 +209,8 @@ class HomeTimetableCard(
return@launch
}
lessons = lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }
b.timetableLayout.visibility = View.VISIBLE
b.noTimetableLayout.visibility = View.GONE
b.noLessonsLayout.visibility = View.GONE
@ -246,7 +249,7 @@ class HomeTimetableCard(
subjectSpannable = firstLesson.subjectSpannable
counterJob = startCoroutineTimer(repeatMillis = 1000) {
counterJob = startCoroutineTimer(repeatMillis = 500) {
count()
}
}

Some files were not shown because too many files have changed in this diff Show More