Compare commits

...

24 commits

Author SHA1 Message Date
Kuba Szczodrzyński
da48c059ec [4.0-rc.4] Update build.gradle, signing and changelog. 2020-03-30 23:29:34 +02:00
Kuba Szczodrzyński
ee5566d1ef [Events] Add toast hint to mark as done button. 2020-03-30 23:28:50 +02:00
Kuba Szczodrzyński
b794b30346 [UI] Fix disabling pull to refresh when changing page using tab layout. 2020-03-30 23:16:35 +02:00
Kuba Szczodrzyński
0db6393bb0 [Events] Add showing green check when event is done. Hide done events from homework current list. 2020-03-30 23:02:19 +02:00
Kuba Szczodrzyński
fcc3c55110 [Events] Fix preserving isDone value. Improve DataRemoveModel method of keeping items. 2020-03-30 22:37:48 +02:00
Kuba Szczodrzyński
328c07eaf4 [Messages] Fix Librus attachment downloading. Add option to force (re)download an attachment. 2020-03-30 19:48:56 +02:00
Kuba Szczodrzyński
b004ec048e [UI] Refactor Grades, Notifications, Homework fragments to better match unified templates. 2020-03-30 18:55:28 +02:00
Kacper Ziubryniewicz
b9f83875a0 [Event] Add isDone attribute and marking events as done. 2020-03-30 18:19:19 +02:00
Kuba Szczodrzyński
8c869d082b [UI] Add pager fragment templates. Move all templates to 'template' module. Fix swipe to refresh with pager fragments. 2020-03-30 12:50:21 +02:00
Kuba Szczodrzyński
043f8210ba [UI] Add lazy loading to fragments with view pager. 2020-03-29 23:11:17 +02:00
Kuba Szczodrzyński
41a79caf83 [API/Mobidziennik] Change data remove model to include only possible types. 2020-03-29 21:06:39 +02:00
Kuba Szczodrzyński
0427fa6087 [Events] Add support for selective updates and upserting. 2020-03-29 18:05:56 +02:00
Kuba Szczodrzyński
2f3c912dbe [Config] Disable teacher absence notifications by default. Add missing migration values. 2020-03-29 16:27:05 +02:00
Kuba Szczodrzyński
219a7443c0 [4.0-rc.3] Update build.gradle, signing and changelog. 2020-03-29 15:31:49 +02:00
Kuba Szczodrzyński
6deb408d80 [API/Librus] Fix attachment downloading, once again. 2020-03-29 15:30:30 +02:00
Kuba Szczodrzyński
c6e1ff2164 [Events] Fix event sorting. Fix showing event teacher name. 2020-03-29 15:26:48 +02:00
Kuba Szczodrzyński
bc0918a115 [API/Librus] Fix attachment downloading. 2020-03-29 15:16:35 +02:00
Kacper Ziubryniewicz
55ff9173be [API/Liburs] Fix unseen teacher absence metadata and add notifications for new teacher absences. 2020-03-28 17:08:36 +01:00
Kuba Szczodrzyński
d4d548846f [Refactor] Refactor EventDao class. 2020-03-28 11:17:39 +01:00
Kuba Szczodrzyński
ef4527f140 [Refactor] Rewrite events to Kotlin. 2020-03-27 18:51:56 +01:00
Kuba Szczodrzyński
0b1e7242bb [API/Mobidziennik] Fix some errors. 2020-03-27 14:05:03 +01:00
Kuba Szczodrzyński
30b6ac2a06 [4.0-rc.2] Update build.gradle, singing and changelog. 2020-03-26 20:46:03 +01:00
Kuba Szczodrzyński
a7fa7cb5e4 [API/Librus] Fix a typo. 2020-03-26 20:45:46 +01:00
Kuba Szczodrzyński
f3e87f9016 [API/Librus] Fix missing login data error. 2020-03-26 20:42:58 +01:00
119 changed files with 2718 additions and 1352 deletions

1
annotation/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

29
annotation/build.gradle Normal file
View file

@ -0,0 +1,29 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
*/
apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
sourceCompatibility = "7"
targetCompatibility = "7"
repositories {
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
*/
package pl.szczodrzynski.edziennik.annotation
import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class SelectiveDao(
val db: KClass<*>
)

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
*/
package pl.szczodrzynski.edziennik.annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class UpdateSelective(
val primaryKeys: Array<String>,
val skippedColumns: Array<String> = []
)

View file

@ -193,6 +193,9 @@ dependencies {
implementation "io.coil-kt:coil:0.9.2" implementation "io.coil-kt:coil:0.9.2"
implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76' implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76'
implementation project(":annotation")
kapt project(":codegen")
} }
repositories { repositories {
mavenCentral() mavenCentral()

View file

@ -1,4 +1,10 @@
<h3>Wersja 4.0-rc.1, 2020-03-26</h3> <h3>Wersja 4.0-rc.4, 2020-03-30</h3>
<ul>
<li>Poprawione pobieranie załączników w Librusie.</li>
<li>Nowy widok zadań domowych</li>
<li>Możliwość oznaczania zadań domowych i wydarzeń jako wykonane.</li>
</ul>
<!--<h3>Wersja 4.0-rc.3, 2020-03-29</h3>
<ul> <ul>
<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><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><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość 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>
@ -22,7 +28,7 @@
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li> <li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</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> <li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
</ul> </ul>-->
<br> <br>
<br> <br>
Dzięki za korzystanie ze Szkolnego!<br> Dzięki za korzystanie ze Szkolnego!<br>

View file

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/ /*secret password - removed for source code publication*/
static toys AES_IV[16] = { static toys AES_IV[16] = {
0x53, 0xfb, 0x18, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 0x1c, 0x15, 0x0f, 0x1c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View file

@ -40,6 +40,9 @@ import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.viewpager.widget.ViewPager
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
@ -141,6 +144,10 @@ fun CharSequence?.isNotNullNorEmpty(): Boolean {
return this != null && this.isNotEmpty() return this != null && this.isNotEmpty()
} }
fun <T> Collection<T>?.isNotNullNorEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
fun CharSequence?.isNotNullNorBlank(): Boolean { fun CharSequence?.isNotNullNorBlank(): Boolean {
return this != null && this.isNotBlank() return this != null && this.isNotBlank()
} }
@ -722,6 +729,13 @@ inline fun <T : View> T.onClick(crossinline onClickListener: (v: T) -> Unit) {
} }
} }
@Suppress("UNCHECKED_CAST")
inline fun <T : View> T.onLongClick(crossinline onLongClickListener: (v: T) -> Boolean) {
setOnLongClickListener { v: View ->
onLongClickListener(v as T)
}
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
inline fun <T : CompoundButton> T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) { inline fun <T : CompoundButton> T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
setOnCheckedChangeListener { buttonView, isChecked -> setOnCheckedChangeListener { buttonView, isChecked ->
@ -1008,6 +1022,7 @@ fun Context.getNotificationTitle(type: Int): String {
Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message
Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement
Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving
Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence
Notification.TYPE_GENERAL -> R.string.notification_type_general Notification.TYPE_GENERAL -> R.string.notification_type_general
else -> R.string.notification_type_general else -> R.string.notification_type_general
}) })
@ -1166,3 +1181,19 @@ fun TextView.getTextPosition(range: IntRange): Rect {
return parentTextViewRect return parentTextViewRect
} }
inline fun ViewPager.addOnPageSelectedListener(crossinline block: (position: Int) -> Unit) = addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { block(position) }
})
val SwipeRefreshLayout.onScrollListener: RecyclerView.OnScrollListener
get() = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (recyclerView.canScrollVertically(-1))
this@onScrollListener.isEnabled = false
if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE)
this@onScrollListener.isEnabled = true
}
}

View file

@ -61,7 +61,7 @@ import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment import pl.szczodrzynski.edziennik.ui.modules.grades.GradesListFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
@ -70,9 +70,10 @@ import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeFragment
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesListFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesListFragment
import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsListFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
import pl.szczodrzynski.edziennik.ui.modules.template.TemplateFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
@ -129,6 +130,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
const val TARGET_MESSAGES_DETAILS = 503 const val TARGET_MESSAGES_DETAILS = 503
const val TARGET_MESSAGES_COMPOSE = 504 const val TARGET_MESSAGES_COMPOSE = 504
const val TARGET_WEB_PUSH = 140 const val TARGET_WEB_PUSH = 140
const val TARGET_TEMPLATE = 1000
const val HOME_ID = DRAWER_ITEM_HOME const val HOME_ID = DRAWER_ITEM_HOME
@ -153,7 +155,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
.withBadgeTypeId(TYPE_EVENT) .withBadgeTypeId(TYPE_EVENT)
.isInDrawer(true) .isInDrawer(true)
list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class) list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesListFragment::class)
.withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline) .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline)
.withBadgeTypeId(TYPE_GRADE) .withBadgeTypeId(TYPE_GRADE)
.isInDrawer(true) .isInDrawer(true)
@ -185,7 +187,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// static drawer items // static drawer items
list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class) list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsListFragment::class)
.withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline) .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline)
.isInDrawer(true) .isInDrawer(true)
.isStatic(true) .isStatic(true)
@ -227,6 +229,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
list += NavTarget(TARGET_MESSAGES_COMPOSE, R.string.menu_message_compose, MessagesComposeFragment::class) list += NavTarget(TARGET_MESSAGES_COMPOSE, R.string.menu_message_compose, MessagesComposeFragment::class)
list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class) list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class)
list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
if (App.devMode) {
list += NavTarget(TARGET_TEMPLATE, R.string.menu_template, TemplateFragment::class)
.withIcon(CommunityMaterial.Icon2.cmd_test_tube_empty)
.isInDrawer(true)
.isBelowSeparator(true)
.isStatic(true)
}
list list
} }
@ -1068,6 +1077,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
.also { if (target.icon != null) it.withIcon(target.icon!!) } .also { if (target.icon != null) it.withIcon(target.icon!!) }
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) } .also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
.also { if (target.badgeTypeId != null) it.withBadgeStyle(drawer.badgeStyle)} .also { if (target.badgeTypeId != null) it.withBadgeStyle(drawer.badgeStyle)}
.withSelectedBackgroundAnimated(false)
if (target.badgeTypeId != null) if (target.badgeTypeId != null)
drawer.addUnreadCounterType(target.badgeTypeId!!, target.id) drawer.addUnreadCounterType(target.badgeTypeId!!, target.id)

View file

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

View file

@ -18,7 +18,7 @@ import kotlin.coroutines.CoroutineContext
class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEntry>) : CoroutineScope, AbstractConfig { class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEntry>) : CoroutineScope, AbstractConfig {
companion object { companion object {
const val DATA_VERSION = 1 const val DATA_VERSION = 2
} }
private val job = Job() private val job = Job()

View file

@ -64,11 +64,25 @@ class ConfigMigration(app: App, config: Config) {
dataVersion = 2 dataVersion = 2
} }
if (dataVersion < 3) {
update = null
privacyPolicyAccepted = false
debugMode = false
devModePassword = null
appInstalledTime = 0L
appRateSnackbarTime = 0L
dataVersion = 3
}
if (dataVersion < 10) { if (dataVersion < 10) {
ui.openDrawerOnBackPressed = false ui.openDrawerOnBackPressed = false
ui.snowfall = false ui.snowfall = false
ui.bottomSheetOpened = false ui.bottomSheetOpened = false
sync.dontShowAppManagerDialog = false sync.dontShowAppManagerDialog = false
sync.webPushEnabled = true
sync.lastAppSync = 0L
dataVersion = 10 dataVersion = 10
} }

View file

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.config.utils package pl.szczodrzynski.edziennik.config.utils
import pl.szczodrzynski.edziennik.config.ProfileConfig import pl.szczodrzynski.edziennik.config.ProfileConfig
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
@ -14,11 +15,23 @@ class ProfileConfigMigration(config: ProfileConfig) {
if (dataVersion < 1) { if (dataVersion < 1) {
grades.colorMode = COLOR_MODE_WEIGHTED grades.colorMode = COLOR_MODE_WEIGHTED
grades.dontCountEnabled = false
grades.yearAverageMode = YEAR_ALL_GRADES grades.yearAverageMode = YEAR_ALL_GRADES
grades.hideImproved = false
grades.averageWithoutWeight = true
grades.plusValue = null
grades.minusValue = null
grades.dontCountEnabled = false
grades.dontCountGrades = listOf()
ui.agendaViewType = AGENDA_DEFAULT ui.agendaViewType = AGENDA_DEFAULT
// no migration for ui.homeCards
dataVersion = 1 dataVersion = 1
} }
if (dataVersion < 2) {
sync.notificationFilter = sync.notificationFilter + Notification.TYPE_TEACHER_ABSENCE
dataVersion = 2
}
}} }}
} }

View file

@ -70,13 +70,13 @@ val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java) LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ -> .withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
}, },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java) LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ -> .withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
} }
) )

View file

@ -39,17 +39,16 @@ class EdudziennikWebEvents(override val data: DataEdudziennik,
?: return@forEach ?: return@forEach
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
date, date = date,
null, time = null,
title, topic = title,
-1, color = null,
Event.TYPE_CLASS_EVENT, type = Event.TYPE_CLASS_EVENT,
false, teacherId = -1,
-1, subjectId = -1,
-1, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -56,17 +56,16 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
val eventType = data.getEventType(eventTypeId, eventTypeName) val eventType = data.getEventType(eventTypeId, eventTypeName)
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
date, date = date,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
eventType.id, type = eventType.id,
false, teacherId = -1,
-1, subjectId = subject.id,
subject.id, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -52,17 +52,16 @@ class EdudziennikWebHomework(override val data: DataEdudziennik,
val topic = homeworkElement.child(4).text() val topic = homeworkElement.child(4).text()
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
date, date = date,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacher.id,
teacher.id, subjectId = subject.id,
subject.id, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -80,17 +80,16 @@ class IdziennikWebExams(override val data: DataIdziennik,
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
examDate, date = examDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
eventType, type = eventType,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -67,17 +67,16 @@ class IdziennikWebHomework(override val data: DataIdziennik,
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -252,10 +252,11 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
.enqueue() .enqueue()
} }
fun sandboxGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit, fun sandboxGetFile(tag: String, url: String, targetFile: File, onSuccess: (file: File) -> Unit,
method: Int = GET,
onProgress: (written: Long, total: Long) -> Unit) { onProgress: (written: Long, total: Long) -> Unit) {
d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action") d(tag, "Request: Librus/Messages - $url")
val callback = object : FileCallbackHandler(targetFile) { val callback = object : FileCallbackHandler(targetFile) {
override fun onSuccess(file: File?, response: Response?) { override fun onSuccess(file: File?, response: Response?) {
@ -291,9 +292,14 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
} }
Request.builder() Request.builder()
.url("$LIBRUS_SANDBOX_URL$action") .url(url)
.userAgent(SYNERGIA_USER_AGENT) .userAgent(SYNERGIA_USER_AGENT)
.post() .also {
when (method) {
POST -> it.post()
else -> it.get()
}
}
.callback(callback) .callback(callback)
.build() .build()
.enqueue() .enqueue()

View file

@ -47,17 +47,16 @@ class LibrusApiEvents(override val data: DataLibrus,
val addedDate = Date.fromIso(event.getString("AddDate")) val addedDate = Date.fromIso(event.getString("AddDate"))
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
type, type = type,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId
teamId
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -34,17 +34,16 @@ class LibrusApiHomework(override val data: DataLibrus,
val addedDate = Date.fromY_m_d(homework.getString("Date")) val addedDate = Date.fromY_m_d(homework.getString("Date"))
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
null, time = null,
topic, topic = topic,
-1, color = null,
-1, type = -1,
false, teacherId = teacherId,
teacherId, subjectId = -1,
-1, teamId = -1
-1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -39,17 +39,16 @@ class LibrusApiPtMeetings(override val data: DataLibrus,
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_PT_MEETING, type = Event.TYPE_PT_MEETING,
false, teacherId = teacherId,
teacherId, subjectId = -1,
-1, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -59,7 +59,7 @@ class LibrusApiTeacherFreeDays(override val data: DataLibrus,
profileId, profileId,
Metadata.TYPE_TEACHER_ABSENCE, Metadata.TYPE_TEACHER_ABSENCE,
id, id,
profile?.empty ?: false, true,
profile?.empty ?: false, profile?.empty ?: false,
System.currentTimeMillis() System.currentTimeMillis()
)) ))

View file

@ -6,9 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.ERROR_FILE_DOWNLOAD import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.EXCEPTION_LIBRUS_MESSAGES_REQUEST
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
@ -53,11 +51,10 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus,
val attachmentKey = keyMatcher[1] val attachmentKey = keyMatcher[1]
getAttachmentCheckKey(attachmentKey) { getAttachmentCheckKey(attachmentKey) {
downloadAttachment(attachmentKey) downloadAttachment("${LIBRUS_SANDBOX_URL}CSDownload&singleUseKey=$attachmentKey", method = POST)
} }
} else { } else {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) downloadAttachment("$downloadLink/get", method = GET)
.withApiResponse(doc.toString()))
} }
} }
} }
@ -91,10 +88,10 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus,
} }
} }
private fun downloadAttachment(attachmentKey: String) { private fun downloadAttachment(url: String, method: Int = GET) {
val targetFile = File(Utils.getStorageDir(), attachmentName) val targetFile = File(Utils.getStorageDir(), attachmentName)
sandboxGetFile(TAG, "CSDownload&singleUseKey=$attachmentKey", targetFile, { file -> sandboxGetFile(TAG, url, targetFile, { file ->
val event = AttachmentGetEvent( val event = AttachmentGetEvent(
profileId, profileId,

View file

@ -109,6 +109,11 @@ class LibrusMessagesGetList(override val data: DataLibrus,
id id
) )
element.select("isAnyFileAttached")?.text()?.let {
if (it == "1")
messageObject.overrideHasAttachments = true
}
data.messageIgnoreList.add(messageObject) data.messageIgnoreList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
data.setSeenMetadataList.add(Metadata( data.setSeenMetadataList.add(Metadata(

View file

@ -89,17 +89,16 @@ class LibrusSynergiaHomework(override val data: DataLibrus,
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
"$topic\n$description", topic = "$topic\n$description",
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -51,17 +51,16 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List<String>) {
val eventObject = Event( val eventObject = Event(
data.profileId, profileId = data.profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
type, type = type,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId)
teamId)
data.eventList.add(eventObject) data.eventList.add(eventObject)
data.metadataList.add( data.metadataList.add(
@ -76,6 +75,8 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List<String>) {
} }
} }
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_DEFAULT))
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_EXAM))
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_SHORT_QUIZ))
} }
} }

View file

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api
import android.text.Html
import androidx.core.util.contains import androidx.core.util.contains
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
@ -25,22 +26,21 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List<String>) {
val id = cols[0].toLong() val id = cols[0].toLong()
val teacherId = cols[7].toLong() val teacherId = cols[7].toLong()
val subjectId = cols[6].toLong() val subjectId = cols[6].toLong()
val topic = cols[1] val topic = Html.fromHtml(cols[1])?.toString() ?: ""
val eventDate = Date.fromYmd(cols[2]) val eventDate = Date.fromYmd(cols[2])
val startTime = Time.fromYmdHm(cols[3]) val startTime = Time.fromYmdHm(cols[3])
val eventObject = Event( val eventObject = Event(
data.profileId, profileId = data.profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
Event.TYPE_HOMEWORK, type = Event.TYPE_HOMEWORK,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId)
teamId)
data.eventList.add(eventObject) data.eventList.add(eventObject)
data.metadataList.add( data.metadataList.add(

View file

@ -44,7 +44,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List<String>?
val studentId = cols[1].toInt() val studentId = cols[1].toInt()
val teamId = cols[2].toLong() val teamId = cols[2].toLong()
val studentNumber = cols[4].toInt() val studentNumber = cols[4].toIntOrNull() ?: -1
if (studentId != data.studentId) if (studentId != data.studentId)
continue continue

View file

@ -61,26 +61,25 @@ class MobidziennikWebCalendar(override val data: DataMobidziennik,
val title = event.getString("title") val title = event.getString("title")
val comment = event.getString("comment") val comment = event.getString("comment")
var topic = title var topic = title ?: ""
if (title != comment) { if (title != comment) {
topic += "\n" + comment topic += "\n" + comment
} }
if (id == -1L) { if (id == -1L) {
id = crc16(topic?.toByteArray()).toLong() id = crc16(topic.toByteArray()).toLong()
} }
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, null, date = eventDate, time = null,
topic, topic = topic,
-1, color = null,
eventType, type = eventType,
false, teacherId = -1,
-1, subjectId = -1,
-1, teamId = data.teamClass?.id ?: -1
data.teamClass?.id ?: -1
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -72,17 +72,16 @@ class VulcanApiEvents(override val data: DataVulcan,
val teamId = event.getLong("IdOddzial") ?: data.teamClass?.id ?: -1 val teamId = event.getLong("IdOddzial") ?: data.teamClass?.id ?: -1
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
id, id = id,
eventDate, date = eventDate,
startTime, time = startTime,
topic, topic = topic,
-1, color = null,
type, type = type,
false, teacherId = teacherId,
teacherId, subjectId = subjectId,
subjectId, teamId = teamId
teamId
) )
data.eventList.add(eventObject) data.eventList.add(eventObject)

View file

@ -284,7 +284,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
db.gradeDao().addAll(gradeList) db.gradeDao().addAll(gradeList)
} }
if (eventList.isNotEmpty()) { if (eventList.isNotEmpty()) {
db.eventDao().addAll(eventList) db.eventDao().upsertAll(eventList, removeNotKept = true)
} }
if (noticeList.isNotEmpty()) { if (noticeList.isNotEmpty()) {
db.noticeDao().clear(profile.id) db.noticeDao().clear(profile.id)

View file

@ -56,9 +56,9 @@ open class DataRemoveModel {
} }
fun commit(profileId: Int, dao: EventDao) { fun commit(profileId: Int, dao: EventDao) {
type?.let { dao.removeFutureWithType(profileId, Date.getToday(), it) } type?.let { dao.dontKeepFutureWithType(profileId, Date.getToday(), it) }
exceptType?.let { dao.removeFutureExceptType(profileId, Date.getToday(), it) } exceptType?.let { dao.dontKeepFutureExceptType(profileId, Date.getToday(), it) }
exceptTypes?.let { dao.removeFutureExceptTypes(profileId, Date.getToday(), it) } exceptTypes?.let { dao.dontKeepFutureExceptTypes(profileId, Date.getToday(), it) }
} }
} }

View file

@ -196,6 +196,11 @@ class SzkolnyApi(val app: App) : CoroutineScope {
// skip blacklisted events // skip blacklisted events
if (event.id in blacklistedIds) if (event.id in blacklistedIds)
return@forEach return@forEach
// force nullable non-negative colors
if (event.color == -1)
event.color = null
// create the event for every matching team and profile // create the event for every matching team and profile
teams.filter { it.code == event.teamCode }.onEach { team -> teams.filter { it.code == event.teamCode }.onEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach

View file

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

View file

@ -12,7 +12,8 @@ data class EventShareRequest (
val action: String = "event", val action: String = "event",
val sharedByName: String, /* If null, the server shows an error */
val sharedByName: String?,
val shareTeamCode: String? = null, val shareTeamCode: String? = null,
val unshareTeamCode: String? = null, val unshareTeamCode: String? = null,
val requesterName: String? = null, val requesterName: String? = null,

View file

@ -34,7 +34,7 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
if (events.isNotEmpty()) { if (events.isNotEmpty()) {
val today = Date.getToday() val today = Date.getToday()
app.db.metadataDao().addAllIgnore(events.map { event -> app.db.metadataDao().addAllIgnore(events.map { event ->
val isPast = event.eventDate < today val isPast = event.date < today
Metadata( Metadata(
event.profileId, event.profileId,
Metadata.TYPE_EVENT, Metadata.TYPE_EVENT,
@ -44,7 +44,7 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
event.addedDate event.addedDate
) )
}) })
return app.db.eventDao().addAll(events).size return app.db.eventDao().upsertAll(events).size
} }
return 0; return 0;
} }

View file

@ -34,6 +34,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
announcementNotifications() announcementNotifications()
messageNotifications() messageNotifications()
luckyNumberNotifications() luckyNumberNotifications()
teacherAbsenceNotifications()
} }
private fun timetableNotifications() { private fun timetableNotifications() {
@ -58,7 +59,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
} }
private fun eventNotifications() { private fun eventNotifications() {
for (event in app.db.eventDao().notNotifiedNow.filter { it.eventDate >= today }) { for (event in app.db.eventDao().getNotNotifiedNow().filter { it.date >= today }) {
val text = if (event.type == Event.TYPE_HOMEWORK) val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString( app.getString(
if (event.subjectLongName.isNullOrEmpty()) if (event.subjectLongName.isNullOrEmpty())
@ -66,7 +67,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
else else
R.string.notification_homework_format, R.string.notification_homework_format,
event.subjectLongName, event.subjectLongName,
event.eventDate.formattedString event.date.formattedString
) )
else else
app.getString( app.getString(
@ -74,8 +75,8 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
R.string.notification_event_no_subject_format R.string.notification_event_no_subject_format
else else
R.string.notification_event_format, R.string.notification_event_format,
event.typeName, event.typeName ?: "wydarzenie",
event.eventDate.formattedString, event.date.formattedString,
event.subjectLongName event.subjectLongName
) )
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
@ -88,17 +89,17 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
} }
} }
fun sharedEventNotifications() { fun sharedEventNotifications() {
for (event in app.db.eventDao().notNotifiedNow.filter { it.eventDate >= today && it.sharedBy != null }) { for (event in app.db.eventDao().getNotNotifiedNow().filter { it.date >= today && it.sharedBy != null }) {
val text = app.getString( val text = app.getString(
R.string.notification_shared_event_format, R.string.notification_shared_event_format,
event.sharedByName, event.sharedByName,
event.typeName ?: "wydarzenie", event.typeName ?: "wydarzenie",
event.eventDate.formattedString, event.date.formattedString,
event.topic event.topic
) )
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
@ -111,7 +112,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
} }
} }
@ -274,4 +275,23 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
) )
} }
} }
private fun teacherAbsenceNotifications() {
for (teacherAbsence in app.db.teacherAbsenceDao().getNotNotifiedNow()) {
val message = app.getString(
R.string.notification_teacher_absence_new_format,
teacherAbsence.teacherFullName
)
notifications += Notification(
id = Notification.buildId(teacherAbsence.profileId, Notification.TYPE_TEACHER_ABSENCE, teacherAbsence.id),
title = app.getNotificationTitle(Notification.TYPE_TEACHER_ABSENCE),
text = message,
type = Notification.TYPE_TEACHER_ABSENCE,
profileId = teacherAbsence.profileId,
profileName = profiles.singleOrNull { it.id == teacherAbsence.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_AGENDA,
addedDate = teacherAbsence.addedDate
).addExtra("eventDate", teacherAbsence.dateFrom.value.toLong())
}
}
} }

View file

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
LibrusLesson::class, LibrusLesson::class,
TimetableManual::class, TimetableManual::class,
Metadata::class Metadata::class
], version = 79) ], version = 83)
@TypeConverters( @TypeConverters(
ConverterTime::class, ConverterTime::class,
ConverterDate::class, ConverterDate::class,
@ -164,7 +164,11 @@ abstract class AppDb : RoomDatabase() {
Migration76(), Migration76(),
Migration77(), Migration77(),
Migration78(), Migration78(),
Migration79() Migration79(),
Migration80(),
Migration81(),
Migration82(),
Migration83()
).allowMainThreadQueries().build() ).allowMainThreadQueries().build()
} }
} }

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
*/
package pl.szczodrzynski.edziennik.data.db.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import pl.szczodrzynski.edziennik.data.db.entity.Keepable
@Dao
interface BaseDao<T : Keepable, F : T> {
@RawQuery
fun getRaw(query: SupportSQLiteQuery): LiveData<List<F>>
fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query))
@RawQuery
fun getRawNow(query: SupportSQLiteQuery): List<F>
fun getRawNow(query: String) = getRawNow(SimpleSQLiteQuery(query))
@RawQuery
fun getOneNow(query: SupportSQLiteQuery): F?
fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query))
@Query("DELETE FROM events WHERE keep = 0")
fun removeNotKept()
/**
* INSERT an [item] into the database,
* ignoring any conflicts.
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun add(item: T): Long
/**
* INSERT [items] into the database,
* ignoring any conflicts.
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun addAll(items: List<T>): LongArray
/**
* REPLACE an [item] in the database,
* removing any conflicting rows.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun replace(item: T)
/**
* REPLACE [items] in the database,
* removing any conflicting rows.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun replaceAll(items: List<T>)
/**
* Selective UPDATE an [item] in the database.
* Do nothing if a matching item does not exist.
*/
fun update(item: T): Long
/**
* Selective UPDATE [items] in the database.
* Do nothing for those items which do not exist.
*/
fun updateAll(items: List<T>): LongArray
/**
* Remove all items from the database,
* that match the given [profileId].
*/
fun clear(profileId: Int)
/**
* INSERT an [item] into the database,
* doing a selective [update] on conflicts.
* @return the newly inserted item's ID or -1L if the item was updated instead
*/
@Transaction
fun upsert(item: T): Long {
val id = add(item)
if (id == -1L) update(item)
return id
}
/**
* INSERT [items] into the database,
* doing a selective [update] on conflicts.
* @return a [LongArray] of IDs of newly inserted items or -1L if the item existed before
*/
@Transaction
fun upsertAll(items: List<T>, removeNotKept: Boolean = false): LongArray {
val insertResult = addAll(items)
val updateList = mutableListOf<T>()
insertResult.forEachIndexed { index, result ->
if (result == -1L) updateList.add(items[index])
}
if (updateList.isNotEmpty()) updateAll(items)
if (removeNotKept) removeNotKept()
return insertResult
}
}

View file

@ -1,186 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.dao;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Transaction;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Event;
import pl.szczodrzynski.edziennik.data.db.full.EventFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_EVENT;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LESSON_CHANGE;
@Dao
public abstract class EventDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long add(Event event);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long[] addAll(List<Event> eventList);
@Query("DELETE FROM events WHERE profileId = :profileId")
public abstract void clear(int profileId);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventId = :id")
public abstract void remove(int profileId, long id);
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
public abstract void removeMetadata(int profileId, int thingType, long thingId);
@Transaction
public void remove(int profileId, long type, long id) {
remove(profileId, id);
removeMetadata(profileId, type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, id);
}
@Transaction
public void remove(Event event) {
remove(event.profileId, event.type, event.id);
}
@Transaction
public void remove(int profileId, Event event) {
remove(profileId, event.type, event.id);
}
@Query("DELETE FROM events WHERE teamId = :teamId AND eventId = :id")
public abstract void removeByTeamId(long teamId, long id);
@RawQuery(observedEntities = {Event.class})
abstract LiveData<List<EventFull>> getAll(SupportSQLiteQuery query);
public LiveData<List<EventFull>> getAll(int profileId, String filter, String limit) {
String query = "SELECT \n" +
"*, \n" +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName,\n" +
"eventTypes.eventTypeName AS typeName,\n" +
"eventTypes.eventTypeColor AS typeColor\n" +
"FROM events\n" +
"LEFT JOIN subjects USING(profileId, subjectId)\n" +
"LEFT JOIN teachers USING(profileId, teacherId)\n" +
"LEFT JOIN teams USING(profileId, teamId)\n" +
"LEFT JOIN eventTypes USING(profileId, eventType)\n" +
"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 "+limit;
Log.d("DB", query);
return getAll(new SimpleSQLiteQuery(query));
}
public LiveData<List<EventFull>> getAll(int profileId) {
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, "");
}
public LiveData<List<EventFull>> getAllByType(int profileId, long type, String 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()+"'", "");
}
public List<EventFull> getAllByDateNow(int profileId, @NonNull Date date) {
return getAllNow(profileId, "eventDate = '"+date.getStringY_m_d()+"'");
}
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()+"'", "");
}
public LiveData<List<EventFull>> getAllNearest(int profileId, @NonNull Date today, int limit) {
return getAll(profileId, "eventDate >= '"+today.getStringY_m_d()+"'", "LIMIT "+limit);
}
@RawQuery
abstract List<EventFull> getAllNow(SupportSQLiteQuery query);
public List<EventFull> getAllNow(int profileId, String filter) {
return getAllNow(new SimpleSQLiteQuery("SELECT \n" +
"*, \n" +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName,\n" +
"eventTypes.eventTypeName AS typeName,\n" +
"eventTypes.eventTypeColor AS typeColor\n" +
"FROM events \n" +
"LEFT JOIN subjects USING(profileId, subjectId)\n" +
"LEFT JOIN teachers USING(profileId, teacherId)\n" +
"LEFT JOIN teams USING(profileId, teamId)\n" +
"LEFT JOIN eventTypes USING(profileId, eventType)\n" +
"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 eventStartTime, addedDate ASC"));
}
public List<EventFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds(int profileId);
@Query("SELECT eventId FROM events WHERE eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds();
@Query("SELECT " +
"*, " +
"eventTypes.eventTypeName AS typeName, " +
"eventTypes.eventTypeColor AS typeColor " +
"FROM events " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN eventTypes USING(profileId, eventType) " +
"LEFT JOIN metadata ON eventId = thingId AND (thingType = " + TYPE_EVENT + " OR thingType = " + TYPE_HOMEWORK + ") AND metadata.profileId = events.profileId " +
"WHERE events.eventBlacklisted = 0 AND notified = 0 " +
"GROUP BY eventId " +
"ORDER BY addedDate ASC")
public abstract List<EventFull> getNotNotifiedNow();
public EventFull getByIdNow(int profileId, long eventId) {
List<EventFull> eventList = getAllNow(profileId, "eventId = "+eventId);
return eventList.size() == 0 ? null : eventList.get(0);
}
@Query("UPDATE events SET eventAddedManually = 1 WHERE profileId = :profileId AND eventDate < :date")
public abstract void convertOlderToManual(int profileId, Date date);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0")
public abstract void removeNotManual(int profileId);
@RawQuery
abstract long removeFuture(SupportSQLiteQuery query);
@Transaction
public void removeFuture(int profileId, Date todayDate, String filter) {
removeFuture(new SimpleSQLiteQuery("DELETE FROM events WHERE profileId = " + profileId
+ " AND eventAddedManually = 0 AND eventDate >= '" + todayDate.getStringY_m_d() + "'" +
" AND " + filter));
}
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
public abstract void removeFutureWithType(int profileId, Date todayDate, long type);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
public abstract void removeFutureExceptType(int profileId, Date todayDate, long exceptType);
@Transaction
public void removeFutureExceptTypes(int profileId, Date todayDate, List<Long> exceptTypes) {
removeFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')'));
}
@Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = "+TYPE_EVENT+" OR thingType = "+TYPE_LESSON_CHANGE+" OR thingType = "+TYPE_HOMEWORK+") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)")
public abstract void setSeenByDate(int profileId, Date date, boolean seen);
@Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId")
public abstract void setBlacklisted(int profileId, long eventId, boolean blacklisted);
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.annotation.SelectiveDao
import pl.szczodrzynski.edziennik.annotation.UpdateSelective
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@Dao
@SelectiveDao(db = AppDb::class)
abstract class EventDao : BaseDao<Event, EventFull> {
companion object {
private const val QUERY = """
SELECT
*,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
eventTypes.eventTypeName AS typeName,
eventTypes.eventTypeColor AS typeColor
FROM events
LEFT JOIN subjects USING(profileId, subjectId)
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teams USING(profileId, teamId)
LEFT JOIN eventTypes USING(profileId, eventType)
LEFT JOIN metadata ON eventId = thingId AND (thingType = ${Metadata.TYPE_EVENT} OR thingType = ${Metadata.TYPE_HOMEWORK}) AND metadata.profileId = events.profileId
"""
private const val ORDER_BY = """GROUP BY eventId ORDER BY eventDate, eventTime, addedDate ASC"""
private const val NOT_BLACKLISTED = """events.eventBlacklisted = 0"""
private const val NOT_DONE = """events.eventIsDone = 0"""
}
private val selective by lazy { EventDaoSelective(App.db) }
@RawQuery(observedEntities = [Event::class])
abstract override fun getRaw(query: SupportSQLiteQuery): LiveData<List<EventFull>>
// SELECTIVE UPDATE
@UpdateSelective(primaryKeys = ["profileId", "eventId"], skippedColumns = ["eventIsDone", "eventBlacklisted", "homeworkBody", "attachmentIds", "attachmentNames"])
override fun update(item: Event) = selective.update(item)
override fun updateAll(items: List<Event>) = selective.updateAll(items)
// CLEAR
@Query("DELETE FROM events WHERE profileId = :profileId")
abstract override fun clear(profileId: Int)
// GET ALL - LIVE DATA
fun getAll(profileId: Int) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
fun getAllByType(profileId: Int, type: Long, filter: String = "1") =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventType = $type AND $filter $ORDER_BY")
fun getAllByDate(profileId: Int, date: Date) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
fun getAllByDateTime(profileId: Int, date: Date, time: Time) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' AND eventTime = '${time.stringValue}'")
fun getNearestNotDone(profileId: Int, today: Date, limit: Int) =
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND $NOT_DONE AND events.profileId = $profileId AND eventDate >= '${today.stringY_m_d}' $ORDER_BY LIMIT $limit")
// GET ALL - NOW
fun getAllNow(profileId: Int) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
fun getNotNotifiedNow() =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND notified = 0 $ORDER_BY")
fun getNotNotifiedNow(profileId: Int) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND notified = 0 $ORDER_BY")
fun getAllByDateNow(profileId: Int, date: Date) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
// GET ONE - NOW
fun getByIdNow(profileId: Int, id: Long) =
getOneNow("$QUERY WHERE events.profileId = $profileId AND eventId = $id")
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
abstract fun getBlacklistedIds(profileId: Int): List<Long>
@get:Query("SELECT eventId FROM events WHERE eventBlacklisted = 1")
abstract val blacklistedIds: List<Long>
/*@Query("UPDATE events SET eventAddedManually = 1 WHERE profileId = :profileId AND eventDate < :date")
abstract fun convertOlderToManual(profileId: Int, date: Date?)
@Query("DELETE FROM events WHERE teamId = :teamId AND eventId = :id")
abstract fun removeByTeamId(teamId: Long, id: Long)
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0")
abstract fun removeNotManual(profileId: Int)*/
@RawQuery
abstract fun dontKeepFuture(query: SupportSQLiteQuery?): Long
@Transaction
open fun dontKeepFuture(profileId: Int, todayDate: Date, filter: String) {
dontKeepFuture(SimpleSQLiteQuery("UPDATE events SET keep = 0 WHERE profileId = " + profileId
+ " AND eventAddedManually = 0 AND eventDate >= '" + todayDate.stringY_m_d + "'" +
" AND " + filter))
}
@Query("UPDATE events SET keep = 0 WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
abstract fun dontKeepFutureWithType(profileId: Int, todayDate: Date, type: Long)
@Query("UPDATE events SET keep = 0 WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
abstract fun dontKeepFutureExceptType(profileId: Int, todayDate: Date, exceptType: Long)
@Transaction
open fun dontKeepFutureExceptTypes(profileId: Int, todayDate: Date, exceptTypes: List<Long>) {
dontKeepFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')'))
}
@Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = " + Metadata.TYPE_EVENT + " OR thingType = " + Metadata.TYPE_LESSON_CHANGE + " OR thingType = " + Metadata.TYPE_HOMEWORK + ") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)")
abstract fun setSeenByDate(profileId: Int, date: Date, seen: Boolean)
@Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId")
abstract fun setBlacklisted(profileId: Int, eventId: Long, blacklisted: Boolean)
@Query("DELETE FROM events WHERE profileId = :profileId AND eventId = :id")
abstract fun remove(profileId: Int, id: Long)
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
abstract fun removeMetadata(profileId: Int, thingType: Int, thingId: Long)
@Transaction
open fun remove(profileId: Int, type: Long, id: Long) {
remove(profileId, id)
removeMetadata(profileId, if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id)
}
@Transaction
open fun remove(event: Event) {
remove(event.profileId, event.type, event.id)
}
@Transaction
open fun remove(profileId: Int, event: Event) {
remove(profileId, event.type, event.id)
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
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 java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.EventType;
@Dao
public interface EventTypeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void add(EventType gradeCategory);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addAll(List<EventType> gradeCategoryList);
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
void clear(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
EventType getByIdNow(int profileId, long typeId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
LiveData<List<EventType>> getAll(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
List<EventType> getAllNow(int profileId);
@Query("SELECT * FROM eventTypes")
List<EventType> getAllNow();
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.dao
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
import pl.szczodrzynski.edziennik.data.db.entity.EventType
@Dao
abstract class EventTypeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun add(eventType: EventType)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun addAll(eventTypeList: List<EventType>)
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
abstract fun clear(profileId: Int)
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
abstract fun getByIdNow(profileId: Int, typeId: Long): EventType?
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
abstract fun getAll(profileId: Int): LiveData<List<EventType>>
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
abstract fun getAllNow(profileId: Int): List<EventType>
@get:Query("SELECT * FROM eventTypes")
abstract val allNow: List<EventType>
fun addDefaultTypes(context: Context, profileId: Int): List<EventType> {
val typeList = listOf(
EventType(profileId, TYPE_HOMEWORK, context.getString(R.string.event_type_homework), COLOR_HOMEWORK),
EventType(profileId, TYPE_DEFAULT, context.getString(R.string.event_other), COLOR_DEFAULT),
EventType(profileId, TYPE_EXAM, context.getString(R.string.event_exam), COLOR_EXAM),
EventType(profileId, TYPE_SHORT_QUIZ, context.getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ),
EventType(profileId, TYPE_ESSAY, context.getString(R.string.event_essay), COLOR_ESSAY),
EventType(profileId, TYPE_PROJECT, context.getString(R.string.event_project), COLOR_PROJECT),
EventType(profileId, TYPE_PT_MEETING, context.getString(R.string.event_pt_meeting), COLOR_PT_MEETING),
EventType(profileId, TYPE_EXCURSION, context.getString(R.string.event_excursion), COLOR_EXCURSION),
EventType(profileId, TYPE_READING, context.getString(R.string.event_reading), COLOR_READING),
EventType(profileId, TYPE_CLASS_EVENT, context.getString(R.string.event_class_event), COLOR_CLASS_EVENT),
EventType(profileId, TYPE_INFORMATION, context.getString(R.string.event_information), COLOR_INFORMATION)
)
addAll(typeList)
return typeList
}
}

View file

@ -78,8 +78,8 @@ public abstract class MetadataDao {
} }
} }
if (o instanceof Event) { if (o instanceof Event) {
if (add(new Metadata(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, seen, false, 0)) == -1) { if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen, false, 0)) == -1) {
updateSeen(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, seen); updateSeen(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen);
} }
} }
if (o instanceof LessonFull) { if (o instanceof LessonFull) {
@ -117,8 +117,8 @@ public abstract class MetadataDao {
} }
} }
if (o instanceof Event) { if (o instanceof Event) {
if (add(new Metadata(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, false, notified, 0)) == -1) { if (add(new Metadata(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), false, notified, 0)) == -1) {
updateNotified(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, notified); updateNotified(profileId, ((Event) o).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), notified);
} }
} }
if (o instanceof LessonFull) { if (o instanceof LessonFull) {
@ -141,9 +141,9 @@ public abstract class MetadataDao {
@Transaction @Transaction
public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) { public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) {
if (o != null) { if (o != null) {
if (add(new Metadata(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, seen, notified, addedDate)) == -1) { if (add(new Metadata(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified, addedDate)) == -1) {
updateSeen(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, seen); updateSeen(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen);
updateNotified(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, notified); updateNotified(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), notified);
} }
} }
} }

View file

@ -49,6 +49,17 @@ interface TeacherAbsenceDao {
"AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo")
fun getAllByDateNow(profileId: Int, date: Date): List<TeacherAbsenceFull> fun getAllByDateNow(profileId: Int, date: Date): List<TeacherAbsenceFull>
@Query("""
SELECT *,
teachers.teacherName || ' ' || teachers.teacherSurname as teacherFullName
FROM teacherAbsence
LEFT JOIN teachers USING (profileId, teacherId)
LEFT JOIN metadata ON teacherAbsenceId = thingId AND metadata.thingType = ${Metadata.TYPE_TEACHER_ABSENCE}
AND teachers.profileId = metadata.profileId WHERE metadata.notified = 0
ORDER BY addedDate DESC
""")
fun getNotNotifiedNow(): List<TeacherAbsenceFull>
@Query("DELETE FROM teacherAbsence WHERE profileId = :profileId") @Query("DELETE FROM teacherAbsence WHERE profileId = :profileId")
fun clear(profileId: Int) fun clear(profileId: Int)
} }

View file

@ -1,161 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.entity;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import java.util.Calendar;
import pl.szczodrzynski.edziennik.data.db.full.EventFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
@Entity(tableName = "events",
primaryKeys = {"profileId", "eventId"},
indices = {@Index(value = {"profileId", "eventDate", "eventStartTime"}), @Index(value = {"profileId", "eventType"})})
public class Event {
public int profileId;
@ColumnInfo(name = "eventId")
public long id;
@ColumnInfo(name = "eventDate")
public Date eventDate;
@ColumnInfo(name = "eventStartTime")
@Nullable
public Time startTime; // null for allDay
@ColumnInfo(name = "eventTopic")
public String topic;
@ColumnInfo(name = "eventColor")
public int color = -1;
public static final long TYPE_UNDEFINED = -2;
public static final long TYPE_HOMEWORK = -1;
public static final long TYPE_DEFAULT = 0;
public static final long TYPE_EXAM = 1;
public static final long TYPE_SHORT_QUIZ = 2;
public static final long TYPE_ESSAY = 3;
public static final long TYPE_PROJECT = 4;
public static final long TYPE_PT_MEETING = 5;
public static final long TYPE_EXCURSION = 6;
public static final long TYPE_READING = 7;
public static final long TYPE_CLASS_EVENT = 8;
public static final long TYPE_INFORMATION = 9;
public static final long TYPE_TEACHER_ABSENCE = 10;
public static final int COLOR_HOMEWORK = 0xff795548;
public static final int COLOR_DEFAULT = 0xffffc107;
public static final int COLOR_EXAM = 0xfff44336;
public static final int COLOR_SHORT_QUIZ = 0xff76ff03;
public static final int COLOR_ESSAY = 0xFF4050B5;
public static final int COLOR_PROJECT = 0xFF673AB7;
public static final int COLOR_PT_MEETING = 0xff90caf9;
public static final int COLOR_EXCURSION = 0xFF4CAF50;
public static final int COLOR_READING = 0xFFFFEB3B;
public static final int COLOR_CLASS_EVENT = 0xff388e3c;
public static final int COLOR_INFORMATION = 0xff039be5;
public static final int COLOR_TEACHER_ABSENCE = 0xff039be5;
@ColumnInfo(name = "eventType")
public long type = TYPE_DEFAULT;
@ColumnInfo(name = "eventAddedManually")
public boolean addedManually;
@ColumnInfo(name = "eventSharedBy")
public String sharedBy = null;
@ColumnInfo(name = "eventSharedByName")
public String sharedByName = null;
@ColumnInfo(name = "eventBlacklisted")
public boolean blacklisted = false;
public long teacherId;
public long subjectId;
public long teamId;
@Ignore
public Event() {}
public Event(int profileId, long id, Date eventDate, @Nullable Time startTime, String topic, int color, long type, boolean addedManually, long teacherId, long subjectId, long teamId)
{
this.profileId = profileId;
this.id = id;
this.eventDate = eventDate;
this.startTime = startTime;
this.topic = topic;
this.color = color;
this.type = type;
this.addedManually = addedManually;
this.teacherId = teacherId;
this.subjectId = subjectId;
this.teamId = teamId;
}
@Ignore
public EventFull withMetadata(Metadata metadata) {
return new EventFull(this, metadata);
}
@Ignore
public Calendar getStartTimeCalendar() {
Calendar c = Calendar.getInstance();
c.set(
eventDate.year,
eventDate.month - 1,
eventDate.day,
(startTime == null) ? 0 : startTime.hour,
(startTime == null) ? 0 : startTime.minute,
(startTime == null) ? 0 : startTime.second
);
return c;
}
@Ignore
public Calendar getEndTimeCalendar() {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(getStartTimeCalendar().getTimeInMillis() + (45 * 60 * 1000));
return c;
}
@Override
public Event clone() {
Event event = new Event(
profileId,
id,
eventDate.clone(),
startTime == null ? null : startTime.clone(),
topic,
color,
type,
addedManually,
subjectId,
teacherId,
teamId
);
event.sharedBy = sharedBy;
event.sharedByName = sharedByName;
event.blacklisted = blacklisted;
return event;
}
@Override
public String toString() {
return "Event{" +
"profileId=" + profileId +
", id=" + id +
", eventDate=" + eventDate +
", startTime=" + startTime +
", topic='" + topic + '\'' +
", color=" + color +
", type=" + type +
", addedManually=" + addedManually +
", sharedBy='" + sharedBy + '\'' +
", sharedByName='" + sharedByName + '\'' +
", teacherId=" + teacherId +
", subjectId=" + subjectId +
", teamId=" + teamId +
'}';
}
}

View file

@ -0,0 +1,109 @@
/*
* 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
import com.google.gson.annotations.SerializedName
import pl.szczodrzynski.edziennik.MINUTE
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.*
@Entity(tableName = "events",
primaryKeys = ["profileId", "eventId"],
indices = [
Index(value = ["profileId", "eventDate", "eventTime"]),
Index(value = ["profileId", "eventType"])
])
open class Event(
/* This needs to be mutable: see SzkolnyApi.getEvents() */
var profileId: Int,
@ColumnInfo(name = "eventId")
var id: Long,
@ColumnInfo(name = "eventDate")
@SerializedName("eventDate")
var date: Date,
@ColumnInfo(name = "eventTime")
@SerializedName("startTime")
var time: Time?,
@ColumnInfo(name = "eventTopic")
var topic: String,
@ColumnInfo(name = "eventColor")
var color: Int?,
@ColumnInfo(name = "eventType")
var type: Long,
var teacherId: Long,
var subjectId: Long,
var teamId: Long
) : Keepable() {
companion object {
const val TYPE_UNDEFINED = -2L
const val TYPE_HOMEWORK = -1L
const val TYPE_DEFAULT = 0L
const val TYPE_EXAM = 1L
const val TYPE_SHORT_QUIZ = 2L
const val TYPE_ESSAY = 3L
const val TYPE_PROJECT = 4L
const val TYPE_PT_MEETING = 5L
const val TYPE_EXCURSION = 6L
const val TYPE_READING = 7L
const val TYPE_CLASS_EVENT = 8L
const val TYPE_INFORMATION = 9L
const val TYPE_TEACHER_ABSENCE = 10L
const val COLOR_HOMEWORK = 0xff795548.toInt()
const val COLOR_DEFAULT = 0xffffc107.toInt()
const val COLOR_EXAM = 0xfff44336.toInt()
const val COLOR_SHORT_QUIZ = 0xff76ff03.toInt()
const val COLOR_ESSAY = 0xFF4050B5.toInt()
const val COLOR_PROJECT = 0xFF673AB7.toInt()
const val COLOR_PT_MEETING = 0xff90caf9.toInt()
const val COLOR_EXCURSION = 0xFF4CAF50.toInt()
const val COLOR_READING = 0xFFFFEB3B.toInt()
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
const val COLOR_INFORMATION = 0xff039be5.toInt()
const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt()
}
@ColumnInfo(name = "eventAddedManually")
var addedManually: Boolean = false
@ColumnInfo(name = "eventSharedBy")
var sharedBy: String? = null
@ColumnInfo(name = "eventSharedByName")
var sharedByName: String? = null
@ColumnInfo(name = "eventBlacklisted")
var blacklisted: Boolean = false
@ColumnInfo(name = "eventIsDone")
var isDone: Boolean = false
var homeworkBody: String? = null
var attachmentIds: List<Long>? = null
var attachmentNames: List<String>? = null
@Ignore
var showAsUnseen = false
val startTimeCalendar: Calendar
get() = Calendar.getInstance().also { it.set(
date.year,
date.month - 1,
date.day,
time?.hour ?: 0,
time?.minute ?: 0,
time?.second ?: 0
) }
val endTimeCalendar: Calendar
get() = startTimeCalendar.also {
it.timeInMillis += 45 * MINUTE * 1000
}
@Ignore
fun withMetadata(metadata: Metadata) = EventFull(this, metadata)
}

View file

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.data.db.entity
abstract class Keepable {
var keep: Boolean = true
}

View file

@ -52,6 +52,7 @@ data class Notification(
const val TYPE_NEW_ANNOUNCEMENT = 15 const val TYPE_NEW_ANNOUNCEMENT = 15
const val TYPE_FEEDBACK_MESSAGE = 16 const val TYPE_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17 const val TYPE_AUTO_ARCHIVING = 17
const val TYPE_TEACHER_ABSENCE = 19
fun buildId(profileId: Int, type: Int, itemId: Long): Long { fun buildId(profileId: Int, type: Int, itemId: Long): Long {
return 1000000000000 + profileId*10000000000 + type*100000000 + itemId; return 1000000000000 + profileId*10000000000 + type*100000000 + itemId;

View file

@ -1,117 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full;
import pl.szczodrzynski.edziennik.data.db.entity.Event;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
public class EventFull extends Event {
public String typeName = "";
public int typeColor = -1;
public String teacherFullName = "";
public String subjectLongName = "";
public String subjectShortName = "";
public String teamName = "";
public String teamCode = null;
// metadata
public boolean seen;
public boolean notified;
public long addedDate;
public EventFull() {}
public EventFull(Event event) {
super(
event.profileId,
event.id,
event.eventDate.clone(),
event.startTime == null ? null : event.startTime.clone(),
event.topic,
event.color,
event.type,
event.addedManually,
event.teacherId,
event.subjectId,
event.teamId
);
this.sharedBy = event.sharedBy;
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
}
public EventFull(EventFull event) {
super(
event.profileId,
event.id,
event.eventDate.clone(),
event.startTime == null ? null : event.startTime.clone(),
event.topic,
event.color,
event.type,
event.addedManually,
event.teacherId,
event.subjectId,
event.teamId
);
this.sharedBy = event.sharedBy;
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
this.typeName = event.typeName;
this.typeColor = event.typeColor;
this.teacherFullName = event.teacherFullName;
this.subjectLongName = event.subjectLongName;
this.subjectShortName = event.subjectShortName;
this.teamName = event.teamName;
this.teamCode = event.teamCode;
this.seen = event.seen;
this.notified = event.notified;
this.addedDate = event.addedDate;
}
public EventFull(Event event, Metadata metadata) {
this(event);
this.seen = metadata.seen;
this.notified = metadata.notified;
this.addedDate = metadata.addedDate;
}
public int getColor() {
return color == -1 ? typeColor : color;
}
@Override
public String toString() {
return "EventFull{" +
"profileId=" + profileId +
", id=" + id +
", eventDate=" + eventDate +
", startTime=" + startTime +
", topic='" + topic + '\'' +
", color=" + color +
", type=" + type +
", addedManually=" + addedManually +
", sharedBy='" + sharedBy + '\'' +
", sharedByName='" + sharedByName + '\'' +
", blacklisted=" + blacklisted +
", teacherId=" + teacherId +
", subjectId=" + subjectId +
", teamId=" + teamId +
", typeName='" + typeName + '\'' +
", teacherFullName='" + teacherFullName + '\'' +
", subjectLongName='" + subjectLongName + '\'' +
", subjectShortName='" + subjectShortName + '\'' +
", teamName='" + teamName + '\'' +
", seen=" + seen +
", notified=" + notified +
", addedDate=" + addedDate +
'}';
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.full
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class EventFull(
profileId: Int, id: Long, date: Date, time: Time?,
topic: String, color: Int?, type: Long,
teacherId: Long, subjectId: Long, teamId: Long
) : Event(
profileId, id, date, time,
topic, color, type,
teacherId, subjectId, teamId
) {
constructor(event: Event, metadata: Metadata? = null) : this(
event.profileId, event.id, event.date, event.time,
event.topic, event.color, event.type,
event.teacherId, event.subjectId, event.teamId) {
event.let {
addedManually = it.addedManually
sharedBy = it.sharedBy
sharedByName = it.sharedByName
blacklisted = it.blacklisted
homeworkBody = it.homeworkBody
attachmentIds = it.attachmentIds
attachmentNames = it.attachmentNames
}
metadata?.let {
seen = it.seen
notified = it.notified
addedDate = it.addedDate
}
}
var typeName: String? = null
var typeColor: Int? = null
var teacherName: String? = null
var subjectLongName: String? = null
var subjectShortName: String? = null
var teamName: String? = null
var teamCode: String? = null
// metadata
var seen = false
var notified = false
var addedDate: Long = 0
val eventColor
get() = color ?: typeColor ?: 0xff2196f3.toInt()
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-27.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration80 : Migration(79, 80) {
override fun migrate(database: SupportSQLiteDatabase) {
// The Homework Update
database.execSQL("ALTER TABLE events RENAME TO _events;")
database.execSQL("""CREATE TABLE events (
profileId INTEGER NOT NULL,
eventId INTEGER NOT NULL,
eventDate TEXT NOT NULL,
eventTime TEXT,
eventTopic TEXT NOT NULL,
eventColor INTEGER,
eventType INTEGER NOT NULL,
teacherId INTEGER NOT NULL,
subjectId INTEGER NOT NULL,
teamId INTEGER NOT NULL,
eventAddedManually INTEGER NOT NULL DEFAULT 0,
eventSharedBy TEXT DEFAULT NULL,
eventSharedByName TEXT DEFAULT NULL,
eventBlacklisted INTEGER NOT NULL DEFAULT 0,
homeworkBody TEXT DEFAULT NULL,
attachmentIds TEXT DEFAULT NULL,
attachmentNames TEXT DEFAULT NULL,
PRIMARY KEY(profileId, eventId)
)""")
database.execSQL("DROP INDEX IF EXISTS index_events_profileId_eventDate_eventStartTime")
database.execSQL("DROP INDEX IF EXISTS index_events_profileId_eventType")
database.execSQL("CREATE INDEX index_events_profileId_eventDate_eventTime ON events (profileId, eventDate, eventTime)")
database.execSQL("CREATE INDEX index_events_profileId_eventType ON events (profileId, eventType)")
database.execSQL("""
INSERT INTO events (profileId, eventId, eventDate, eventTime, eventTopic, eventColor, eventType, teacherId, subjectId, teamId, eventAddedManually, eventSharedBy, eventSharedByName, eventBlacklisted)
SELECT profileId, eventId, eventDate, eventStartTime, eventTopic,
CASE eventColor WHEN -1 THEN NULL ELSE eventColor END,
eventType, teacherId, subjectId, teamId,
eventAddedManually, eventSharedBy, eventSharedByName, eventBlacklisted
FROM _events
""")
database.execSQL("DROP TABLE _events")
}
}

View file

@ -0,0 +1,11 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
class Migration81 : Migration(80, 81) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE metadata SET seen = 1, notified = 1 WHERE thingType = ${Metadata.TYPE_TEACHER_ABSENCE}")
}
}

View file

@ -0,0 +1,10 @@
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration82 : Migration(81, 82) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE events ADD COLUMN eventIsDone INTEGER DEFAULT 0 NOT NULL")
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration83 : Migration(82, 83) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE events ADD COLUMN keep INTEGER NOT NULL DEFAULT 1")
}
}

View file

@ -105,18 +105,19 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
if (profile.registration != Profile.REGISTRATION_ENABLED) if (profile.registration != Profile.REGISTRATION_ENABLED)
return@forEach return@forEach
val event = Event( val event = Event(
team.profileId, profileId = team.profileId,
json.getLong("id") ?: return, id = json.getLong("id") ?: return,
json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return, date = json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return,
json.getInt("startTime")?.let { Time.fromValue(it) }, time = json.getInt("startTime")?.let { Time.fromValue(it) },
json.getString("topic") ?: "", topic = json.getString("topic") ?: "",
json.getInt("color") ?: -1, color = json.getInt("color"),
json.getLong("type") ?: 0, type = json.getLong("type") ?: 0,
true, teacherId = json.getLong("teacherId") ?: -1,
json.getLong("teacherId") ?: -1, subjectId = json.getLong("subjectId") ?: -1,
json.getLong("subjectId") ?: -1, teamId = team.id
team.id
) )
if (event.color == -1)
event.color = null
event.sharedBy = json.getString("sharedBy") event.sharedBy = json.getString("sharedBy")
event.sharedByName = json.getString("sharedByName") event.sharedByName = json.getString("sharedByName")
@ -144,14 +145,14 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
profileName = profile.name, profileName = profile.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate addedDate = metadata.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
notificationList += notification notificationList += notification
} }
events += event events += event
metadataList += metadata metadataList += metadata
} }
app.db.eventDao().addAll(events) app.db.eventDao().upsertAll(events)
app.db.metadataDao().addAllReplace(metadataList) app.db.metadataDao().addAllReplace(metadataList)
if (notificationList.isNotEmpty()) { if (notificationList.isNotEmpty()) {
app.db.notificationDao().addAll(notificationList) app.db.notificationDao().addAll(notificationList)

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-19.
*/
package pl.szczodrzynski.edziennik.ui
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.TemplateListItemBinding
import pl.szczodrzynski.edziennik.onClick
class TemplateAdapter(
val context: Context,
val onItemClick: ((item: TemplateItem) -> Unit)? = null,
val onItemButtonClick: ((item: TemplateItem) -> Unit)? = null
) : RecyclerView.Adapter<TemplateAdapter.ViewHolder>() {
private val app by lazy { context.applicationContext as App }
var items = listOf<TemplateItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = TemplateListItemBinding.inflate(inflater, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
val b = holder.b
onItemClick?.let { listener ->
b.root.onClick { listener(item) }
}
/*b.someButton.visibility = if (buttonVisible) View.VISIBLE else View.GONE
onItemButtonClick?.let { listener ->
b.someButton.onClick { listener(item) }
}*/
}
override fun getItemCount() = items.size
class ViewHolder(val b: TemplateListItemBinding) : RecyclerView.ViewHolder(b.root)
data class TemplateItem(val text: String)
}

View file

@ -146,6 +146,11 @@ class DayDialog(
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
onItemClick = { onItemClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,

View file

@ -87,11 +87,11 @@ class EventDetailsDialog(
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
try { try {
b.monthName = app.resources.getStringArray(R.array.months_day_of_array)[event.eventDate.month - 1] b.monthName = app.resources.getStringArray(R.array.months_day_of_array)[event.date.month - 1]
} }
catch (_: Exception) {} catch (_: Exception) {}
b.typeColor.background?.setTintColor(event.getColor()) b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf( b.details = mutableListOf(
event.subjectLongName, event.subjectLongName,
@ -102,14 +102,14 @@ class EventDetailsDialog(
when (event.sharedBy) { when (event.sharedBy) {
null -> when { null -> when {
event.addedManually -> R.string.event_details_added_by_self_format event.addedManually -> R.string.event_details_added_by_self_format
event.teacherFullName == null -> R.string.event_details_added_by_unknown_format event.teacherName == null -> R.string.event_details_added_by_unknown_format
else -> R.string.event_details_added_by_format else -> R.string.event_details_added_by_format
} }
"self" -> R.string.event_details_shared_by_self_format "self" -> R.string.event_details_shared_by_self_format
else -> R.string.event_details_shared_by_format else -> R.string.event_details_shared_by_format
}, },
Date.fromMillis(event.addedDate).formattedString, Date.fromMillis(event.addedDate).formattedString,
event.sharedByName ?: event.teacherFullName ?: "" event.sharedByName ?: event.teacherName ?: ""
) )
b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE
@ -125,7 +125,7 @@ class EventDetailsDialog(
b.goToTimetableButton.setOnClickListener { b.goToTimetableButton.setOnClickListener {
dialog.dismiss() dialog.dismiss()
val dateStr = event.eventDate?.stringY_m_d ?: return@setOnClickListener val dateStr = event.date.stringY_m_d
val intent = val intent =
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE) if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE)
@ -148,6 +148,10 @@ class EventDetailsDialog(
openInCalendar() openInCalendar()
} }
b.checkDoneButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_mark_as_done, Toast.LENGTH_SHORT).show()
true
}
b.goToTimetableButton.setOnLongClickListener { b.goToTimetableButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_go_to_timetable, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.hint_go_to_timetable, Toast.LENGTH_SHORT).show()
true true
@ -161,6 +165,14 @@ class EventDetailsDialog(
true true
} }
b.checkDoneButton.isChecked = event.isDone
b.checkDoneButton.addOnCheckedChangeListener { _, isChecked ->
event.isDone = isChecked
launch(Dispatchers.Default) {
app.db.eventDao().replace(event)
}
}
b.topic.text = event.topic b.topic.text = event.topic
BetterLink.attach(b.topic) { BetterLink.attach(b.topic) {
dialog.dismiss() dialog.dismiss()
@ -245,7 +257,7 @@ class EventDetailsDialog(
} }
private fun openInCalendar() { launch { private fun openInCalendar() { launch {
val title = (event.typeName ?: "") + val title = event.typeName ?: "" +
(if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") + (if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") +
(event.subjectLongName ?: "") (event.subjectLongName ?: "")
@ -254,12 +266,12 @@ class EventDetailsDialog(
putExtra(Events.TITLE, title) putExtra(Events.TITLE, title)
putExtra(Events.DESCRIPTION, event.topic) putExtra(Events.DESCRIPTION, event.topic)
if (event.startTime == null) { if (event.time == null) {
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true) putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.eventDate.inMillis) putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.date.inMillis)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.eventDate.inMillis) putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.date.inMillis)
} else { } else {
val startTime = event.eventDate.combineWith(event.startTime) val startTime = event.date.combineWith(event.time)
val endTime = startTime + 45 * 60 * 1000 /* 45 min */ val endTime = startTime + 45 * 60 * 1000 /* 45 min */
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, false) putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, false)

View file

@ -6,9 +6,9 @@ package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
@ -19,8 +19,11 @@ import pl.szczodrzynski.edziennik.utils.models.Week
class EventListAdapter( class EventListAdapter(
val context: Context, val context: Context,
val simpleMode: Boolean = false, val simpleMode: Boolean = false,
val showDate: Boolean = false,
val showWeekDay: Boolean = false, val showWeekDay: Boolean = false,
val showDate: Boolean = false,
val showType: Boolean = true,
val showTime: Boolean = true,
val showSubject: Boolean = true,
val onItemClick: ((event: EventFull) -> Unit)? = null, val onItemClick: ((event: EventFull) -> Unit)? = null,
val onEventEditClick: ((event: EventFull) -> Unit)? = null val onEventEditClick: ((event: EventFull) -> Unit)? = null
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>() { ) : RecyclerView.Adapter<EventListAdapter.ViewHolder>() {
@ -48,37 +51,41 @@ class EventListAdapter(
b.simpleMode = simpleMode b.simpleMode = simpleMode
b.topic.text = event.topic b.topic.text = event.topic
b.topic.maxLines = if (simpleMode) 2 else 3
b.details.text = mutableListOf<CharSequence?>( b.details.text = mutableListOf<CharSequence?>(
if (showWeekDay) Week.getFullDayName(event.eventDate.weekDay) else null, if (showWeekDay) Week.getFullDayName(event.date.weekDay) else null,
if (showDate) event.eventDate.getRelativeString(context, 7) ?: event.eventDate.formattedStringShort else null, if (showDate) event.date.getRelativeString(context, 7) ?: event.date.formattedStringShort else null,
event.typeName, if (showType) event.typeName else null,
if (simpleMode) null else event.startTime?.stringHM ?: app.getString(R.string.event_all_day), if (showTime) event.time?.stringHM ?: app.getString(R.string.event_all_day) else null,
if (simpleMode) null else event.subjectLongName if (showSubject) event.subjectLongName else null
).concat(bullet) ).concat(bullet)
b.addedBy.setText( b.addedBy.setText(
when (event.sharedBy) { when (event.sharedBy) {
null -> when { null -> when {
event.addedManually -> R.string.event_list_added_by_self_format event.addedManually -> R.string.event_list_added_by_self_format
event.teacherFullName == null -> R.string.event_list_added_by_unknown_format event.teacherName == null -> R.string.event_list_added_by_unknown_format
else -> R.string.event_list_added_by_format else -> R.string.event_list_added_by_format
} }
"self" -> R.string.event_list_shared_by_self_format "self" -> R.string.event_list_shared_by_self_format
else -> R.string.event_list_shared_by_format else -> R.string.event_list_shared_by_format
}, },
Date.fromMillis(event.addedDate).formattedString, Date.fromMillis(event.addedDate).formattedString,
event.sharedByName ?: event.teacherFullName ?: "", event.sharedByName ?: event.teacherName ?: "",
event.teamName?.let { bullet+it } ?: "" event.teamName?.let { bullet+it } ?: ""
) )
b.typeColor.background?.setTintColor(event.getColor()) b.typeColor.background?.setTintColor(event.eventColor)
b.typeColor.isVisible = showType
b.editButton.visibility = if (event.addedManually && !simpleMode) View.VISIBLE else View.GONE b.editButton.isVisible = !simpleMode && event.addedManually && !event.isDone
b.editButton.onClick { b.editButton.onClick {
onEventEditClick?.invoke(event) onEventEditClick?.invoke(event)
} }
b.isDone.isVisible = event.isDone
b.editButton.setOnLongClickListener { b.editButton.setOnLongClickListener {
Toast.makeText(context, R.string.hint_edit_event, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true true

View file

@ -216,7 +216,7 @@ class EventManualDialog(
progressDialog?.dismiss() progressDialog?.dismiss()
launch { launch {
b.timeDropdown.loadItems() b.timeDropdown.loadItems()
b.timeDropdown.selectDefault(editingEvent?.startTime) b.timeDropdown.selectDefault(editingEvent?.time)
b.timeDropdown.selectDefault(defaultLesson?.displayStartTime ?: defaultTime) b.timeDropdown.selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
} }
} }
@ -251,7 +251,7 @@ class EventManualDialog(
nextLessonTeamId = it.displayTeamId nextLessonTeamId = it.displayTeamId
} }
loadItems() loadItems()
selectDefault(editingEvent?.eventDate) selectDefault(editingEvent?.date)
selectDefault(defaultLesson?.displayDate ?: defaultDate) selectDefault(defaultLesson?.displayDate ?: defaultDate)
onDateSelected = { date, lesson -> onDateSelected = { date, lesson ->
b.timeDropdown.deselect() b.timeDropdown.deselect()
@ -276,8 +276,8 @@ class EventManualDialog(
displayMode = DISPLAY_LESSONS displayMode = DISPLAY_LESSONS
if (!loadItems()) if (!loadItems())
syncTimetable(lessonsDate ?: Date.getToday()) syncTimetable(lessonsDate ?: Date.getToday())
selectDefault(editingEvent?.startTime) selectDefault(editingEvent?.time)
if (editingEvent != null && editingEvent.startTime == null) if (editingEvent != null && editingEvent.time == null)
select(0L) select(0L)
selectDefault(defaultLesson?.displayStartTime ?: defaultTime) selectDefault(defaultLesson?.displayStartTime ?: defaultTime)
onLessonSelected = { lesson -> onLessonSelected = { lesson ->
@ -319,7 +319,12 @@ class EventManualDialog(
val deferred = async(Dispatchers.Default) { val deferred = async(Dispatchers.Default) {
// get the event type list // get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId) var eventTypes = app.db.eventTypeDao().getAllNow(profileId)
if (eventTypes.none { it.id in -1L..10L }) {
eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId)
}
b.typeDropdown.clear() b.typeDropdown.clear()
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) } b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
} }
@ -338,10 +343,10 @@ class EventManualDialog(
// copy IDs from event being edited // copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.topic.setText(it.topic) b.topic.setText(it.topic)
b.typeDropdown.select(it.type.toLong())?.let { item -> b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color customColor = (item.tag as EventType).color
} }
if (it.color != -1) if (it.color != null && it.color != -1)
customColor = it.color customColor = it.color
} }
@ -464,22 +469,25 @@ class EventManualDialog(
(timeSelected as? Pair<*, *>)?.first as? Time (timeSelected as? Pair<*, *>)?.first as? Time
if (isError) return if (isError) return
date ?: return
topic ?: return
val id = System.currentTimeMillis() val id = System.currentTimeMillis()
val eventObject = Event( val eventObject = Event(
profileId, profileId = profileId,
editingEvent?.id ?: id, id = editingEvent?.id ?: id,
date, date = date,
startTime, time = startTime,
topic, topic = topic,
customColor ?: -1, color = customColor,
type ?: Event.TYPE_DEFAULT, type = type ?: Event.TYPE_DEFAULT,
true, teacherId = teacherId ?: -1,
teacherId ?: -1, subjectId = subjectId ?: -1,
subjectId ?: -1, teamId = teamId ?: -1
teamId ?: -1 ).also {
) it.addedManually = true
}
val metadataObject = Metadata( val metadataObject = Metadata(
profileId, profileId,
@ -579,7 +587,7 @@ class EventManualDialog(
private fun finishAdding(eventObject: Event, metadataObject: Metadata) { private fun finishAdding(eventObject: Event, metadataObject: Metadata) {
launch { launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
app.db.eventDao().add(eventObject) app.db.eventDao().upsert(eventObject)
app.db.metadataDao().add(metadataObject) app.db.metadataDao().add(metadataObject)
} }
} }
@ -590,6 +598,7 @@ class EventManualDialog(
activity.reloadTarget() activity.reloadTarget()
} }
private fun finishRemoving() { private fun finishRemoving() {
editingEvent ?: return
launch { launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
app.db.eventDao().remove(editingEvent) app.db.eventDao().remove(editingEvent)

View file

@ -37,7 +37,8 @@ class NotificationFilterDialog(
Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement, Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement,
Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event, Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event,
Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework, Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework,
Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event,
Notification.TYPE_TEACHER_ABSENCE to R.string.notification_type_new_teacher_absence
) )
} }

View file

@ -170,6 +170,11 @@ class LessonDetailsDialog(
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
onItemClick = { onItemClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,

View file

@ -222,22 +222,22 @@ class AgendaFragment : Fragment(), CoroutineScope {
events.forEach { event -> events.forEach { event ->
eventList.add(BaseCalendarEvent( eventList.add(BaseCalendarEvent(
"${event.typeName} - ${event.topic}", "${event.typeName ?: "wydarzenie"} - ${event.topic}",
"", "",
(if (event.startTime == null) getString(R.string.agenda_event_all_day) else event.startTime!!.stringHM) + (if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) +
(event.subjectLongName?.let { ", $it" } ?: "") + (event.subjectLongName?.let { ", $it" } ?: "") +
(event.teacherFullName?.let { ", $it" } ?: "") + (event.teacherName?.let { ", $it" } ?: "") +
(event.teamName?.let { ", $it" } ?: ""), (event.teamName?.let { ", $it" } ?: ""),
event.getColor(), event.eventColor,
Colors.legibleTextColor(event.getColor()), Colors.legibleTextColor(event.eventColor),
event.startTimeCalendar, event.startTimeCalendar,
event.endTimeCalendar, event.endTimeCalendar,
event.startTime == null, event.time == null,
event.id, event.id,
!event.seen !event.seen
)) ))
if (!event.seen) unreadEventDates.add(event.eventDate.value) if (!event.seen) unreadEventDates.add(event.date.value)
} }
b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController { b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController {
@ -281,11 +281,11 @@ class AgendaFragment : Fragment(), CoroutineScope {
val eventIcon = IconicsDrawable(activity) val eventIcon = IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle) .icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle)
.size(IconicsSize.dp(10)) .size(IconicsSize.dp(10))
.color(IconicsColor.colorInt(event.getColor())) .color(IconicsColor.colorInt(event.eventColor))
dayList.add(EventDay(event.startTimeCalendar, eventIcon)) dayList.add(EventDay(event.startTimeCalendar, eventIcon))
if (!event.seen) unreadEventDates.add(event.eventDate.value) if (!event.seen) unreadEventDates.add(event.date.value)
} }
b.agendaCalendarView.setEvents(dayList) b.agendaCalendarView.setEvents(dayList)

View file

@ -1,28 +0,0 @@
/*
* 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

@ -0,0 +1,18 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import androidx.fragment.app.FragmentManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
class FragmentLazyPagerAdapter(
fragmentManager: FragmentManager,
swipeRefreshLayout: SwipeRefreshLayout,
private val fragments: List<Pair<LazyFragment, CharSequence>>
) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
override fun getPage(position: Int) = fragments[position].first
override fun getPageTitle(position: Int) = fragments[position].second
override fun getCount() = fragments.size
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
abstract class LazyFragment : Fragment() {
private var isPageCreated = false
internal var position = -1
internal var swipeRefreshLayoutCallback: ((position: Int, isEnabled: Boolean) -> Unit)? = null
/**
* 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
fun enableSwipeToRefresh() = swipeRefreshLayoutCallback?.invoke(position, true)
fun disableSwipeToRefresh() = swipeRefreshLayoutCallback?.invoke(position, false)
fun setSwipeToRefresh(enabled: Boolean) = swipeRefreshLayoutCallback?.invoke(position, enabled)
val onScrollListener: RecyclerView.OnScrollListener
get() = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (recyclerView.canScrollVertically(-1))
disableSwipeToRefresh()
if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE)
enableSwipeToRefresh()
}
}
internal fun createPage() {
if (!isPageCreated && isAdded) {
isPageCreated = onPageCreated()
}
}
override fun onDestroyView() {
isPageCreated = false
super.onDestroyView()
}
override fun onResume() {
createPage()
super.onResume()
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import android.util.SparseBooleanArray
import androidx.core.util.set
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
abstract class LazyPagerAdapter(fragmentManager: FragmentManager, val swipeRefreshLayout: SwipeRefreshLayout? = null) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
internal val enabledList = SparseBooleanArray()
private val refreshLayoutCallback: (position: Int, isEnabled: Boolean) -> Unit = { position, isEnabled ->
swipeRefreshLayout?.isEnabled = isEnabled
if (position > -1)
enabledList[position] = isEnabled
}
final override fun getItem(position: Int): LazyFragment {
return getPage(position).also {
it.position = position
it.swipeRefreshLayoutCallback = refreshLayoutCallback
}
}
abstract fun getPage(position: Int): LazyFragment
abstract override fun getPageTitle(position: Int): CharSequence
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import android.content.Context
import android.util.AttributeSet
import androidx.viewpager.widget.ViewPager
class LazyViewPager @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
private var pageSelection = -1
private var scrollState = 0
init {
addOnPageChangeListener(object : OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
scrollState = state
(adapter as? LazyPagerAdapter)?.let {
it.swipeRefreshLayout?.isEnabled = state == SCROLL_STATE_IDLE && it.enabledList[pageSelection, true]
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
pageSelection = position
(adapter as? LazyPagerAdapter)?.let {
it.swipeRefreshLayout?.isEnabled = scrollState == SCROLL_STATE_IDLE && it.enabledList[pageSelection, true]
val fragment = adapter?.instantiateItem(this@LazyViewPager, position)
val lazyFragment = fragment as? LazyFragment
lazyFragment?.createPage()
}
}
})
}
}

View file

@ -10,11 +10,10 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2 import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -23,7 +22,7 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.TARGET_GRADES_EDITOR
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_GRADE import pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_GRADE
import pl.szczodrzynski.edziennik.data.db.full.GradeFull import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesFragmentBinding import pl.szczodrzynski.edziennik.databinding.GradesListFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradeDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradeDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog 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.GradesAverages
@ -36,24 +35,20 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.max import kotlin.math.max
class GradesListFragment : Fragment(), CoroutineScope {
class GradesFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "GradesFragment" private const val TAG = "GradesFragment"
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: GradesFragmentBinding private lateinit var b: GradesListFragmentBinding
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val adapter by lazy {
GradesAdapter(activity)
}
private val manager by lazy { app.gradesManager } private val manager by lazy { app.gradesManager }
private val dontCountEnabled by lazy { manager.dontCountEnabled } private val dontCountEnabled by lazy { manager.dontCountEnabled }
private val dontCountGrades by lazy { manager.dontCountGrades } private val dontCountGrades by lazy { manager.dontCountGrades }
@ -63,51 +58,49 @@ class GradesFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
b = GradesFragmentBinding.inflate(inflater) b = GradesListFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout) b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { startCoroutineTimer(100L) {
if (!isAdded) if (!isAdded) return@startCoroutineTimer
return
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
app.db.gradeDao() val adapter = GradesAdapter(activity)
.getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()) var firstRun = true
.observe(this, Observer { grades ->
if (b.gradesRecyclerView.adapter == null) { app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(this, Observer { items -> launch {
b.gradesRecyclerView.adapter = adapter if (!isAdded) return@launch
b.gradesRecyclerView.apply {
// load & configure the adapter
adapter.items = withContext(Dispatchers.Default) { processGrades(items) }
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
b.list.adapter = adapter
b.list.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
//addItemDecoration(SimpleDividerItemDecoration(context)) addOnScrollListener(b.refreshLayout.onScrollListener)
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (recyclerView.canScrollVertically(-1)) {
b.refreshLayout.isEnabled = false
}
if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
b.refreshLayout.isEnabled = true
} }
} }
}) adapter.notifyDataSetChanged()
}
if (firstRun) {
expandSubject(adapter)
firstRun = false
} }
launch(Dispatchers.Default) { // show/hide relevant views
processGrades(grades) b.progressBar.isVisible = false
} if (items.isNullOrEmpty()) {
b.list.isVisible = false
if (grades != null && grades.isNotEmpty()) { b.noData.isVisible = true
b.gradesRecyclerView.visibility = View.VISIBLE
b.gradesNoData.visibility = View.GONE
} else { } else {
b.gradesRecyclerView.visibility = View.GONE b.list.isVisible = true
b.gradesNoData.visibility = View.VISIBLE b.noData.isVisible = false
} }
}) }})
adapter.onGradeClick = { adapter.onGradeClick = {
GradeDetailsDialog(activity, it) GradeDetailsDialog(activity, it)
@ -153,10 +146,30 @@ class GradesFragment : Fragment(), CoroutineScope {
}) })
) )
activity.gainAttention() activity.gainAttention()
}}
private fun expandSubject(adapter: GradesAdapter) {
var expandSubjectModel: GradesSubject? = null
if (expandSubjectId != 0L) {
expandSubjectModel = adapter.items.firstOrNull { it is GradesSubject && it.subjectId == expandSubjectId } as? GradesSubject
adapter.expandModel(
model = expandSubjectModel,
view = null,
notifyAdapter = false
)
}
startCoroutineTimer(500L) {
if (expandSubjectModel != null) {
b.list.smoothScrollToPosition(
adapter.items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0)
)
}
}
} }
@Suppress("SuspendFunctionOnCoroutineScope") @Suppress("SuspendFunctionOnCoroutineScope")
private suspend fun processGrades(grades: List<GradeFull>) { private fun processGrades(grades: List<GradeFull>): MutableList<Any> {
val items = mutableListOf<GradesSubject>() val items = mutableListOf<GradesSubject>()
var subjectId = -1L var subjectId = -1L
@ -284,30 +297,7 @@ class GradesFragment : Fragment(), CoroutineScope {
GradesManager.ORDER_BY_DATE_ASC -> items.sortBy { it.lastAddedDate } GradesManager.ORDER_BY_DATE_ASC -> items.sortBy { it.lastAddedDate }
} }
adapter.items = items.toMutableList() return (items + stats).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) { private fun countGrade(grade: Grade, averages: GradesAverages) {

View file

@ -60,8 +60,11 @@ class HomeEventsCard(
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
simpleMode = true, simpleMode = true,
showDate = true,
showWeekDay = true, showWeekDay = true,
showDate = true,
showType = true,
showTime = false,
showSubject = false,
onItemClick = { onItemClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,
@ -77,7 +80,7 @@ class HomeEventsCard(
} }
) )
app.db.eventDao().getAllNearest(profile.id, Date.getToday(), 4).observe(activity, Observer { events -> app.db.eventDao().getNearestNotDone(profile.id, Date.getToday(), 4).observe(activity, Observer { events ->
adapter.items = events adapter.items = events
if (b.eventsView.adapter == null) { if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter b.eventsView.adapter = adapter

View file

@ -50,17 +50,17 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
EventFull homework = homeworkList.get(position); EventFull homework = homeworkList.get(position);
int diffDays = Date.diffDays(homework.eventDate, Date.getToday()); int diffDays = Date.diffDays(homework.getDate(), Date.getToday());
holder.homeworkItemHomeworkDate.setText(app.getString(R.string.date_relative_format, homework.eventDate.getFormattedString(), Date.dayDiffString(context, diffDays))); holder.homeworkItemHomeworkDate.setText(app.getString(R.string.date_relative_format, homework.getDate().getFormattedString(), Date.dayDiffString(context, diffDays)));
holder.homeworkItemTopic.setText(homework.topic); holder.homeworkItemTopic.setText(homework.getTopic());
holder.homeworkItemSubjectTeacher.setText(context.getString(R.string.homework_subject_teacher_format, bs(homework.subjectLongName), bs(homework.teacherFullName))); holder.homeworkItemSubjectTeacher.setText(context.getString(R.string.homework_subject_teacher_format, bs(homework.getSubjectLongName()), bs(homework.getTeacherName())));
holder.homeworkItemTeamDate.setText(context.getString(R.string.homework_team_date_format, bs(homework.teamName), Date.fromMillis(homework.addedDate).getFormattedStringShort())); holder.homeworkItemTeamDate.setText(context.getString(R.string.homework_team_date_format, bs(homework.getTeamName()), Date.fromMillis(homework.getAddedDate()).getFormattedStringShort()));
if (!homework.seen) { if (!homework.getSeen()) {
holder.homeworkItemTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); holder.homeworkItemTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp));
holder.homeworkItemTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); holder.homeworkItemTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
homework.seen = true; homework.setSeen(true);
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
App.db.metadataDao().setSeen(App.Companion.getProfileId(), homework, true); App.db.metadataDao().setSeen(App.Companion.getProfileId(), homework, true);
}); });
@ -69,11 +69,11 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
holder.homeworkItemTopic.setBackground(null); holder.homeworkItemTopic.setBackground(null);
} }
holder.homeworkItemEdit.setVisibility((homework.addedManually ? View.VISIBLE : View.GONE)); holder.homeworkItemEdit.setVisibility((homework.getAddedManually() ? View.VISIBLE : View.GONE));
holder.homeworkItemEdit.setOnClickListener(v -> { holder.homeworkItemEdit.setOnClickListener(v -> {
new EventManualDialog( new EventManualDialog(
(MainActivity) context, (MainActivity) context,
homework.profileId, homework.getProfileId(),
null, null,
null, null,
null, null,
@ -83,11 +83,11 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
null); null);
}); });
if (homework.sharedBy == null) { if (homework.getSharedBy() == null) {
holder.homeworkItemSharedBy.setVisibility(View.GONE); holder.homeworkItemSharedBy.setVisibility(View.GONE);
} }
else if (homework.sharedByName != null) { else if (homework.getSharedByName() != null) {
holder.homeworkItemSharedBy.setText(app.getString(R.string.event_shared_by_format, (homework.sharedBy.equals("self") ? app.getString(R.string.event_shared_by_self) : homework.sharedByName))); holder.homeworkItemSharedBy.setText(app.getString(R.string.event_shared_by_format, (homework.getSharedBy().equals("self") ? app.getString(R.string.event_shared_by_self) : homework.getSharedByName())));
} }
} }

View file

@ -1,3 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.homework package pl.szczodrzynski.edziennik.ui.modules.homework
import android.os.AsyncTask import android.os.AsyncTask
@ -7,46 +11,48 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import pl.szczodrzynski.edziennik.App import kotlinx.coroutines.CoroutineScope
import pl.szczodrzynski.edziennik.MainActivity import kotlinx.coroutines.Dispatchers
import pl.szczodrzynski.edziennik.R import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding import pl.szczodrzynski.edziennik.databinding.HomeworkFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext
class HomeworkFragment : Fragment() { class HomeworkFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "HomeworkFragment"
var pageSelection = 0 var pageSelection = 0
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentHomeworkBinding private lateinit var b: HomeworkFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
if (context == null) context ?: return null
return null
app = activity.application as App app = activity.application as App
context!!.theme.applyStyle(Themes.appTheme, true) b = HomeworkFragmentBinding.inflate(inflater)
// activity, context and profile is valid
b = FragmentHomeworkBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout) b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null if (!isAdded) return
if (app.profile == null || !isAdded)
return
activity.bottomSheet.prependItems( activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
@ -67,34 +73,29 @@ class HomeworkFragment : Fragment() {
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
})) }))
b.viewPager.adapter = MessagesFragment.Adapter(childFragmentManager).also { adapter -> val pagerAdapter = FragmentLazyPagerAdapter(
adapter.addFragment(HomeworkListFragment().also { fragment -> fragmentManager ?: return,
fragment.arguments = Bundle().also { args -> b.refreshLayout,
args.putInt("homeworkDate", HomeworkDate.CURRENT) listOf(
} HomeworkListFragment().apply {
}, getString(R.string.homework_tab_current)) arguments = Bundle("homeworkDate" to HomeworkDate.CURRENT)
} to getString(R.string.homework_tab_current),
adapter.addFragment(HomeworkListFragment().also { fragment -> HomeworkListFragment().apply {
fragment.arguments = Bundle().also { args -> arguments = Bundle("homeworkDate" to HomeworkDate.PAST)
args.putInt("homeworkDate", HomeworkDate.PAST) } to getString(R.string.homework_tab_past)
)
)
b.viewPager.apply {
offscreenPageLimit = 1
adapter = pagerAdapter
currentItem = pageSelection
addOnPageSelectedListener {
pageSelection = it
} }
}, getString(R.string.homework_tab_past)) b.tabLayout.setupWithViewPager(this)
} }
b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {
pageSelection = position
}
})
b.tabLayout.setupWithViewPager(b.viewPager)
activity.navView.apply { activity.navView.apply {
bottomBar.apply { bottomBar.apply {
fabEnable = true fabEnable = true

View file

@ -4,67 +4,106 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import pl.szczodrzynski.edziennik.App import kotlinx.coroutines.CoroutineScope
import pl.szczodrzynski.edziennik.MainActivity import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.databinding.HomeworkListBinding import pl.szczodrzynski.edziennik.databinding.HomeworkListFragmentBinding
import pl.szczodrzynski.edziennik.getInt 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.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class HomeworkListFragment : Fragment() { class HomeworkListFragment : LazyFragment(), CoroutineScope {
companion object {
private const val TAG = "HomeworkListFragment"
}
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: HomeworkListBinding private lateinit var b: HomeworkListFragmentBinding
private var homeworkDate = HomeworkDate.CURRENT private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
b = HomeworkListBinding.inflate(inflater) b = HomeworkListFragmentBinding.inflate(inflater)
return b.root return b.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onPageCreated(): Boolean { startCoroutineTimer(100L) {
// TODO check if app, activity, b can be null val homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT)
if (!isAdded)
return
if (arguments != null) {
homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT)
}
val layoutManager = LinearLayoutManager(context)
layoutManager.reverseLayout = homeworkDate == HomeworkDate.PAST
layoutManager.stackFromEnd = homeworkDate == HomeworkDate.PAST
b.homeworkView.setHasFixedSize(true)
b.homeworkView.layoutManager = layoutManager
val today = Date.getToday()
val filter = when(homeworkDate) { val filter = when(homeworkDate) {
HomeworkDate.CURRENT -> "eventDate >= '" + Date.getToday().stringY_m_d + "'" HomeworkDate.CURRENT -> "eventDate >= '${today.stringY_m_d}' AND eventIsDone = 0"
else -> "eventDate < '" + Date.getToday().stringY_m_d + "'" else -> "eventDate < '${today.stringY_m_d}' OR eventIsDone = 1"
} }
app.db.eventDao() val adapter = EventListAdapter(
.getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter) activity,
.observe(this, Observer { homeworkList -> showWeekDay = true,
showDate = true,
showType = false,
showTime = true,
showSubject = true,
onItemClick = {
EventDetailsDialog(
activity,
it
)
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it
)
}
)
app.db.eventDao().getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter).observe(this@HomeworkListFragment, Observer { items ->
if (!isAdded) return@Observer if (!isAdded) return@Observer
if (homeworkList != null && homeworkList.size > 0) { // load & configure the adapter
val adapter = HomeworkAdapter(context, homeworkList) adapter.items = items
b.homeworkView.adapter = adapter if (items.isNotNullNorEmpty() && b.list.adapter == null) {
b.homeworkView.visibility = View.VISIBLE b.list.adapter = adapter
b.homeworkNoData.visibility = View.GONE b.list.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context).apply {
reverseLayout = homeworkDate == HomeworkDate.PAST
stackFromEnd = homeworkDate == HomeworkDate.PAST
}
addItemDecoration(SimpleDividerItemDecoration(context))
addOnScrollListener(onScrollListener)
}
}
adapter.notifyDataSetChanged()
setSwipeToRefresh(false) // TODO
// show/hide relevant views
b.progressBar.isVisible = false
if (items.isNullOrEmpty()) {
b.list.isVisible = false
b.noData.isVisible = true
} else { } else {
b.homeworkView.visibility = View.GONE b.list.isVisible = true
b.homeworkNoData.visibility = View.VISIBLE b.noData.isVisible = false
} }
}) })
} }; return true }
} }

View file

@ -19,8 +19,6 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskProgressEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskProgressEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskStartedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskStartedEvent
import pl.szczodrzynski.edziennik.data.db.entity.Event.*
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_DISABLED import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_DISABLED
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncBinding import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncBinding
@ -62,20 +60,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
else else
REGISTRATION_DISABLED REGISTRATION_DISABLED
val typeList = listOf( app.db.eventTypeDao().addDefaultTypes(activity, it.id)
EventType(it.id, TYPE_HOMEWORK, getString(R.string.event_type_homework), COLOR_HOMEWORK),
EventType(it.id, TYPE_DEFAULT, getString(R.string.event_other), COLOR_DEFAULT),
EventType(it.id, TYPE_EXAM, getString(R.string.event_exam), COLOR_EXAM),
EventType(it.id, TYPE_SHORT_QUIZ, getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ),
EventType(it.id, TYPE_ESSAY, getString(R.string.event_essay), COLOR_SHORT_QUIZ),
EventType(it.id, TYPE_PROJECT, getString(R.string.event_project), COLOR_PROJECT),
EventType(it.id, TYPE_PT_MEETING, getString(R.string.event_pt_meeting), COLOR_PT_MEETING),
EventType(it.id, TYPE_EXCURSION, getString(R.string.event_excursion), COLOR_EXCURSION),
EventType(it.id, TYPE_READING, getString(R.string.event_reading), COLOR_READING),
EventType(it.id, TYPE_CLASS_EVENT, getString(R.string.event_class_event), COLOR_CLASS_EVENT),
EventType(it.id, TYPE_INFORMATION, getString(R.string.event_information), COLOR_INFORMATION)
)
app.db.eventTypeDao().addAll(typeList)
} }
app.db.profileDao().addAll(profiles) app.db.profileDao().addAll(profiles)

View file

@ -15,6 +15,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.mikepenz.iconics.IconicsColor import com.mikepenz.iconics.IconicsColor
@ -62,7 +63,7 @@ class MessageFragment : Fragment(), CoroutineScope {
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: MessageFragmentBinding private lateinit var b: MessageFragmentBinding
private lateinit var job: Job private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
@ -73,16 +74,12 @@ class MessageFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
context!!.theme.applyStyle(Themes.appTheme, true)
b = MessageFragmentBinding.inflate(inflater) b = MessageFragmentBinding.inflate(inflater)
job = Job()
return b.root return b.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null if (!isAdded) return
if (app.profile == null || !isAdded)
return
b.closeButton.setImageDrawable( b.closeButton.setImageDrawable(
IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_window_close) IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_window_close)
@ -260,16 +257,35 @@ class MessageFragment : Fragment(), CoroutineScope {
MessagesFragment.pageSelection = min(message.type, 1) MessagesFragment.pageSelection = min(message.type, 1)
} }
private val attachmentOnClick = { v: View ->
if (v.tag is Int) {
downloadAttachment(v.tag as Int)
}
}
private val attachmentOnLongClick = { v: View ->
(v.tag as? Int)?.let { tag ->
val popupMenu = PopupMenu(v.context, v)
popupMenu.menu.add(0, tag, 0, R.string.messages_attachment_download_again)
popupMenu.setOnMenuItemClickListener {
downloadAttachment(it.itemId, forceDownload = true)
true
}
popupMenu.show()
}
true
}
private fun showAttachments() { private fun showAttachments() {
if (message.attachmentIds != null) { if (message.attachmentIds != null) {
val insertPoint = b.attachments val insertPoint = b.attachments
insertPoint.removeAllViews() insertPoint.removeAllViews()
val chipLayoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) val chipLayoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
chipLayoutParams.setMargins(0, Utils.dpToPx(8), 0, Utils.dpToPx(8)) chipLayoutParams.setMargins(0, 8.dp, 0, 0)
val progressLayoutParams = FrameLayout.LayoutParams(Utils.dpToPx(18), Utils.dpToPx(18)) val progressLayoutParams = FrameLayout.LayoutParams(18.dp, 18.dp)
progressLayoutParams.setMargins(Utils.dpToPx(8), 0, Utils.dpToPx(8), 0) progressLayoutParams.setMargins(8.dp, 0, 8.dp, 0)
progressLayoutParams.gravity = END or CENTER_VERTICAL progressLayoutParams.gravity = END or CENTER_VERTICAL
// CREATE VIEWS AND AN OBJECT FOR EVERY ATTACHMENT // CREATE VIEWS AND AN OBJECT FOR EVERY ATTACHMENT
@ -280,12 +296,13 @@ class MessageFragment : Fragment(), CoroutineScope {
val size = message.attachmentSizes[index] val size = message.attachmentSizes[index]
// create the parent // create the parent
val attachmentLayout = FrameLayout(b.root.context) val attachmentLayout = FrameLayout(b.root.context)
attachmentLayout.setPadding(Utils.dpToPx(16), 0, Utils.dpToPx(16), 0) attachmentLayout.setPadding(16.dp, 0, 16.dp, 0)
val attachmentChip = Chip(attachmentLayout.context) val attachmentChip = Chip(attachmentLayout.context)
//attachmentChip.setChipBackgroundColorResource(ThemeUtils.getChipColorRes()); //attachmentChip.setChipBackgroundColorResource(ThemeUtils.getChipColorRes());
attachmentChip.layoutParams = chipLayoutParams attachmentChip.layoutParams = chipLayoutParams
attachmentChip.height = Utils.dpToPx(40) attachmentChip.chipMinHeight = 40.dp.toFloat()
//attachmentChip.height = Utils.dpToPx(40)
// show the file size or not // show the file size or not
if (size == -1L) if (size == -1L)
@ -312,11 +329,8 @@ class MessageFragment : Fragment(), CoroutineScope {
attachmentChip.isCloseIconVisible = false attachmentChip.isCloseIconVisible = false
// set the object's index in the attachmentList as the tag // set the object's index in the attachmentList as the tag
attachmentChip.tag = index attachmentChip.tag = index
attachmentChip.setOnClickListener { v -> attachmentChip.onClick(attachmentOnClick)
if (v.tag is Int) { attachmentChip.onLongClick(attachmentOnLongClick)
downloadAttachment(v.tag as Int)
}
}
attachmentLayout.addView(attachmentChip) attachmentLayout.addView(attachmentChip)
val attachmentProgress = ProgressBar(attachmentLayout.context) val attachmentProgress = ProgressBar(attachmentLayout.context)
@ -338,10 +352,10 @@ class MessageFragment : Fragment(), CoroutineScope {
} }
} }
private fun downloadAttachment(index: Int) { private fun downloadAttachment(index: Int, forceDownload: Boolean = false) {
val attachment = attachmentList[index] val attachment = attachmentList[index]
if (attachment.downloaded != null) { if (!forceDownload && attachment.downloaded != null) {
Utils.openFile(activity, File(attachment.downloaded)) Utils.openFile(activity, File(attachment.downloaded))
return return
} }

View file

@ -6,7 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
@ -14,8 +14,9 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.databinding.FragmentMessagesBinding import pl.szczodrzynski.edziennik.databinding.FragmentMessagesBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyPagerAdapter
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import java.util.*
class MessagesFragment : Fragment() { class MessagesFragment : Fragment() {
companion object { companion object {
@ -54,7 +55,7 @@ class MessagesFragment : Fragment() {
return return
} }
b.viewPager.adapter = Adapter(childFragmentManager).also { adapter -> b.viewPager.adapter = Adapter(childFragmentManager, b.refreshLayout).also { adapter ->
adapter.addFragment(MessagesListFragment().also { fragment -> adapter.addFragment(MessagesListFragment().also { fragment ->
fragment.arguments = Bundle().also { args -> fragment.arguments = Bundle().also { args ->
@ -71,13 +72,8 @@ class MessagesFragment : Fragment() {
} }
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {}
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position
@ -126,11 +122,11 @@ class MessagesFragment : Fragment() {
}*/ }*/
} }
internal class Adapter(manager: FragmentManager) : FragmentPagerAdapter(manager) { internal class Adapter(manager: FragmentManager, swipeRefreshLayout: SwipeRefreshLayout) : LazyPagerAdapter(manager, swipeRefreshLayout) {
private val mFragmentList = ArrayList<Fragment>() private val mFragmentList = mutableListOf<LazyFragment>()
private val mFragmentTitleList = ArrayList<String>() private val mFragmentTitleList = mutableListOf<String>()
override fun getItem(position: Int): Fragment { override fun getPage(position: Int): LazyFragment {
return mFragmentList[position] return mFragmentList[position]
} }
@ -138,12 +134,12 @@ class MessagesFragment : Fragment() {
return mFragmentList.size return mFragmentList.size
} }
fun addFragment(fragment: Fragment, title: String) { fun addFragment(fragment: LazyFragment, title: String) {
mFragmentList.add(fragment) mFragmentList.add(fragment)
mFragmentTitleList.add(title) mFragmentTitleList.add(title)
} }
override fun getPageTitle(position: Int): CharSequence? { override fun getPageTitle(position: Int): CharSequence {
return mFragmentTitleList[position] return mFragmentTitleList[position]
} }
} }

View file

@ -10,11 +10,10 @@ import android.view.ViewGroup;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -26,13 +25,15 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message;
import pl.szczodrzynski.edziennik.data.db.full.MessageFull; import pl.szczodrzynski.edziennik.data.db.full.MessageFull;
import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull; import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull;
import pl.szczodrzynski.edziennik.databinding.MessagesListBinding; import pl.szczodrzynski.edziennik.databinding.MessagesListBinding;
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment;
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration; import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration;
import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Themes;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
import static pl.szczodrzynski.edziennik.utils.Utils.d; import static pl.szczodrzynski.edziennik.utils.Utils.d;
public class MessagesListFragment extends Fragment { public class MessagesListFragment extends LazyFragment {
private App app = null; private App app = null;
private MainActivity activity = null; private MainActivity activity = null;
@ -65,9 +66,9 @@ public class MessagesListFragment extends Fragment {
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public boolean onPageCreated() {
if (app == null || activity == null || b == null || !isAdded()) if (app == null || activity == null || b == null || !isAdded())
return; return false;
long messageId = -1; long messageId = -1;
if (getArguments() != null) { if (getArguments() != null) {
@ -78,7 +79,7 @@ public class MessagesListFragment extends Fragment {
args.putLong("messageId", messageId); args.putLong("messageId", messageId);
getArguments().remove("messageId"); getArguments().remove("messageId");
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args); activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args);
return; return false;
} }
if (getArguments() != null) { if (getArguments() != null) {
@ -161,11 +162,22 @@ public class MessagesListFragment extends Fragment {
// TODO ANIMATION // TODO ANIMATION
//postponeEnterTransition(); //postponeEnterTransition();
viewParent = (ViewGroup) view.getParent(); viewParent = (ViewGroup) getView().getParent();
b.emailList.setLayoutManager(new LinearLayoutManager(view.getContext())); b.emailList.setLayoutManager(new LinearLayoutManager(getView().getContext()));
b.emailList.addItemDecoration(new SimpleDividerItemDecoration(view.getContext())); b.emailList.addItemDecoration(new SimpleDividerItemDecoration(getView().getContext()));
b.emailList.setAdapter(messagesAdapter); b.emailList.setAdapter(messagesAdapter);
b.emailList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (b.emailList.canScrollVertically(-1)) {
setSwipeToRefresh(false);
}
if (!b.emailList.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
setSwipeToRefresh(true);
}
}
});
if (messageType == Message.TYPE_RECEIVED) { if (messageType == Message.TYPE_RECEIVED) {
App.db.messageDao().getReceived(App.Companion.getProfileId()).observe(this, messageFulls -> { App.db.messageDao().getReceived(App.Companion.getProfileId()).observe(this, messageFulls -> {
@ -215,7 +227,7 @@ public class MessagesListFragment extends Fragment {
}); });
} }
return true;
} }
private void createMessageList(List<MessageFull> messageFulls) { private void createMessageList(List<MessageFull> messageFulls) {

View file

@ -1,71 +1,62 @@
package pl.szczodrzynski.edziennik.ui.modules.notifications package pl.szczodrzynski.edziennik.ui.modules.notifications
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import androidx.appcompat.app.AppCompatActivity
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.databinding.NotificationsListItemBinding
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class NotificationsAdapter( class NotificationsAdapter(
private val context: Context private val activity: AppCompatActivity,
) : RecyclerView.Adapter<NotificationsAdapter.ViewHolder>() { val onItemClick: ((item: Notification) -> Unit)? = null
) : RecyclerView.Adapter<NotificationsAdapter.ViewHolder>(), CoroutineScope {
companion object { companion object {
private const val TAG = "NotificationsAdapter" private const val TAG = "NotificationsAdapter"
} }
private val app = activity.applicationContext as App
// optional: place the manager here
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
var items = listOf<Notification>() var items = listOf<Notification>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(activity)
val view = inflater.inflate(R.layout.row_notifications_item, parent, false) val view = NotificationsListItemBinding.inflate(inflater, parent, false)
return ViewHolder(view) return ViewHolder(view)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val app = context.applicationContext as App val item = items[position]
val b = holder.b
val notification = items[position] val date = Date.fromMillis(item.addedDate).formattedString
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
val date = Date.fromMillis(notification.addedDate).formattedString b.title.text = item.text
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(context) b.profileDate.text = listOf(
item.profileName ?: "",
holder.title.text = notification.text
holder.profileDate.text = listOf(
notification.profileName ?: "",
"", "",
date.asColoredSpannable(colorSecondary) date
).concat() ).concat().asColoredSpannable(colorSecondary)
holder.type.text = context.getNotificationTitle(notification.type) b.type.text = activity.getNotificationTitle(item.type)
holder.root.onClick { onItemClick?.let { listener ->
val intent = Intent("android.intent.action.MAIN") b.root.onClick { listener(item) }
notification.fillIntent(intent)
d(TAG, "notification with item " + notification.viewId + " extras " + if (intent.extras == null) "null" else intent.extras!!.toString())
//Log.d(TAG, "Got date "+intent.getLongExtra("timetableDate", 0));
if (notification.profileId != null && notification.profileId != -1 && notification.profileId != app.profile.id && context is Activity) {
Toast.makeText(app, app.getString(R.string.toast_changing_profile), Toast.LENGTH_LONG).show()
}
app.sendBroadcast(intent)
} }
} }
override fun getItemCount() = items.size override fun getItemCount() = items.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class ViewHolder(val b: NotificationsListItemBinding) : RecyclerView.ViewHolder(b.root)
var root = itemView
var title: TextView = itemView.findViewById(R.id.title)
var profileDate: TextView = itemView.findViewById(R.id.profileDate)
var type: TextView = itemView.findViewById(R.id.type)
}
} }

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-22.
*/
package pl.szczodrzynski.edziennik.ui.modules.notifications
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 pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentNotificationsBinding
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
class NotificationsFragment : Fragment() {
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: FragmentNotificationsBinding
private val adapter by lazy {
NotificationsAdapter(activity)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
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 = FragmentNotificationsBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
return
activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_remove_notifications)
.withIcon(CommunityMaterial.Icon.cmd_delete_sweep_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
AsyncTask.execute { app.db.notificationDao().clearAll() }
Toast.makeText(activity, R.string.menu_remove_notifications_success, Toast.LENGTH_SHORT).show()
}))
app.db.notificationDao()
.getAll()
.observe(this, Observer { notifications ->
if (app.profile == null || !isAdded) return@Observer
adapter.items = notifications
if (b.notificationsView.adapter == null) {
b.notificationsView.adapter = adapter
b.notificationsView.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
adapter.notifyDataSetChanged()
if (notifications != null && notifications.isNotEmpty()) {
b.notificationsView.visibility = View.VISIBLE
b.notificationsNoData.visibility = View.GONE
} else {
b.notificationsView.visibility = View.GONE
b.notificationsNoData.visibility = View.VISIBLE
}
})
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-22.
*/
package pl.szczodrzynski.edziennik.ui.modules.notifications
import android.app.Activity
import android.content.Intent
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.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.databinding.NotificationsListFragmentBinding
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import kotlin.coroutines.CoroutineContext
class NotificationsListFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "NotificationsListFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: NotificationsListFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
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 = NotificationsListFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { startCoroutineTimer(100L) {
if (!isAdded) return@startCoroutineTimer
activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_remove_notifications)
.withIcon(CommunityMaterial.Icon.cmd_delete_sweep_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
AsyncTask.execute { app.db.notificationDao().clearAll() }
Toast.makeText(activity, R.string.menu_remove_notifications_success, Toast.LENGTH_SHORT).show()
}))
val adapter = NotificationsAdapter(activity) { notification ->
val intent = Intent("android.intent.action.MAIN")
notification.fillIntent(intent)
Utils.d(TAG, "notification with item " + notification.viewId + " extras " + if (intent.extras == null) "null" else intent.extras!!.toString())
if (notification.profileId != null && notification.profileId != -1 && notification.profileId != app.profile.id && context is Activity) {
Toast.makeText(app, app.getString(R.string.toast_changing_profile), Toast.LENGTH_LONG).show()
}
app.sendBroadcast(intent)
}
app.db.notificationDao().getAll().observe(this, Observer { items ->
if (!isAdded) return@Observer
// load & configure the adapter
adapter.items = items
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
b.list.adapter = adapter
b.list.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
adapter.notifyDataSetChanged()
// show/hide relevant views
b.progressBar.isVisible = false
if (items.isNullOrEmpty()) {
b.list.isVisible = false
b.noData.isVisible = true
} else {
b.list.isVisible = true
b.noData.isVisible = false
}
})
}}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.template
import android.view.LayoutInflater
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.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.databinding.TemplateListItemBinding
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class TemplateAdapter(
val activity: AppCompatActivity,
val onItemClick: ((item: Notification) -> Unit)? = null
) : RecyclerView.Adapter<TemplateAdapter.ViewHolder>(), CoroutineScope {
companion object {
private const val TAG = "TemplateAdapter"
}
private val app = activity.applicationContext as App
// optional: place the manager here
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
var items = listOf<Notification>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = TemplateListItemBinding.inflate(inflater, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
val b = holder.b
val date = Date.fromMillis(item.addedDate).formattedString
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
b.title.text = item.text
b.profileDate.text = listOf(
item.profileName ?: "",
"",
date
).concat().asColoredSpannable(colorSecondary)
b.type.text = activity.getNotificationTitle(item.type)
onItemClick?.let { listener ->
b.root.onClick { listener(item) }
}
}
override fun getItemCount() = items.size
class ViewHolder(val b: TemplateListItemBinding) : RecyclerView.ViewHolder(b.root)
}

View file

@ -1,8 +1,8 @@
/* /*
* Copyright (c) Kuba Szczodrzyński 2020-1-8. * Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/ */
package pl.szczodrzynski.edziennik.ui.dialogs package pl.szczodrzynski.edziennik.ui.modules.template
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.template
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.addOnPageSelectedListener
import pl.szczodrzynski.edziennik.databinding.TemplateFragmentBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
import kotlin.coroutines.CoroutineContext
class TemplateFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "TemplateFragment"
var pageSelection = 0
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: TemplateFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
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 = TemplateFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return
val pagerAdapter = FragmentLazyPagerAdapter(
fragmentManager ?: return,
b.refreshLayout,
listOf(
TemplatePageFragment() to "Pager 0",
TemplatePageFragment() to "Pager 1",
TemplatePageFragment() to "Pager 2",
TemplatePageFragment() to "Pager 3",
TemplateListPageFragment() to "Pager 4",
TemplateListPageFragment() to "Pager 5",
TemplateListPageFragment() to "Pager 6",
TemplateListPageFragment() to "Pager 7"
)
)
b.viewPager.apply {
offscreenPageLimit = 1
adapter = pagerAdapter
currentItem = pageSelection
addOnPageSelectedListener {
HomeworkFragment.pageSelection = it
}
b.tabLayout.setupWithViewPager(this)
}
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.template
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.databinding.TemplateListFragmentBinding
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
class TemplateListFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "TemplateListFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: TemplateListFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
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 = TemplateListFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { startCoroutineTimer(100L) {
if (!isAdded) return@startCoroutineTimer
val adapter = TemplateAdapter(activity)
app.db.notificationDao().getAll().observe(this, Observer { items ->
if (!isAdded) return@Observer
// load & configure the adapter
adapter.items = items
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
b.list.adapter = adapter
b.list.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
addOnScrollListener(b.refreshLayout.onScrollListener)
}
}
adapter.notifyDataSetChanged()
b.refreshLayout.isEnabled = false // TODO
// show/hide relevant views
b.progressBar.isVisible = false
if (items.isNullOrEmpty()) {
b.list.isVisible = false
b.noData.isVisible = true
} else {
b.list.isVisible = true
b.noData.isVisible = false
}
})
}}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.template
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.databinding.TemplateListPageFragmentBinding
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
class TemplateListPageFragment : LazyFragment(), CoroutineScope {
companion object {
private const val TAG = "TemplateListPagerFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: TemplateListPageFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
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 = TemplateListPageFragmentBinding.inflate(inflater)
return b.root
}
override fun onPageCreated(): Boolean { startCoroutineTimer(100L) {
val adapter = TemplateAdapter(activity)
app.db.notificationDao().getAll().observe(this, Observer { items ->
if (!isAdded) return@Observer
// load & configure the adapter
adapter.items = items
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
b.list.adapter = adapter
b.list.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
addOnScrollListener(onScrollListener)
}
}
adapter.notifyDataSetChanged()
setSwipeToRefresh(false) // TODO
// show/hide relevant views
b.progressBar.isVisible = false
if (items.isNullOrEmpty()) {
b.list.isVisible = false
b.noData.isVisible = true
} else {
b.list.isVisible = true
b.noData.isVisible = false
}
})
}; return true }
}

View file

@ -1,26 +1,30 @@
package pl.szczodrzynski.edziennik.ui.modules.base /*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.template
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.databinding.FragmentTemplateBinding import pl.szczodrzynski.edziennik.databinding.TemplatePageFragmentBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class TemplateFragment : Fragment(), CoroutineScope { class TemplatePageFragment : LazyFragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TemplateFragment" private const val TAG = "TemplatePagerFragment"
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTemplateBinding private lateinit var b: TemplatePageFragmentBinding
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -32,15 +36,16 @@ class TemplateFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
b = FragmentTemplateBinding.inflate(inflater) b = TemplatePageFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onPageCreated(): Boolean {
if (!isAdded) b.text.text = "Fragment $position"
return
b.button.addOnCheckedChangeListener { button, isChecked ->
setSwipeToRefresh(isChecked)
}
return true
} }
} }

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-30.
*/
package pl.szczodrzynski.edziennik.ui.modules.template
import androidx.fragment.app.FragmentManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyPagerAdapter
class TemplatePagerAdapter(fragmentManager: FragmentManager, swipeRefreshLayout: SwipeRefreshLayout) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
override fun getPage(position: Int) = TemplatePageFragment()
override fun getPageTitle(position: Int) = "Page $position"
override fun getCount() = 10
}

View file

@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.base.PagerFragment import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView import pl.szczodrzynski.edziennik.utils.ListenerScrollView
@ -36,7 +36,7 @@ import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
class TimetableDayFragment : PagerFragment(), CoroutineScope { class TimetableDayFragment : LazyFragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableDayFragment" private const val TAG = "TimetableDayFragment"
} }
@ -104,9 +104,6 @@ class TimetableDayFragment : PagerFragment(), CoroutineScope {
} }
override fun onPageCreated(): Boolean { override fun onPageCreated(): Boolean {
if (!isAdded)
return false
// observe lesson database // observe lesson database
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons -> app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons ->
launch { launch {
@ -216,23 +213,23 @@ class TimetableDayFragment : PagerFragment(), CoroutineScope {
LessonDetailsDialog(activity, it.tag as LessonFull) LessonDetailsDialog(activity, it.tag as LessonFull)
} }
val eventList = events.filter { it.startTime != null && it.startTime == lesson.displayStartTime }.take(3) val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3)
eventList.getOrNull(0).let { eventList.getOrNull(0).let {
lb.event1.visibility = if (it == null) View.GONE else View.VISIBLE lb.event1.visibility = if (it == null) View.GONE else View.VISIBLE
lb.event1.background = it?.let { lb.event1.background = it?.let {
R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.getColor()) R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor)
} }
} }
eventList.getOrNull(1).let { eventList.getOrNull(1).let {
lb.event2.visibility = if (it == null) View.GONE else View.VISIBLE lb.event2.visibility = if (it == null) View.GONE else View.VISIBLE
lb.event2.background = it?.let { lb.event2.background = it?.let {
R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.getColor()) R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor)
} }
} }
eventList.getOrNull(2).let { eventList.getOrNull(2).let {
lb.event3.visibility = if (it == null) View.GONE else View.VISIBLE lb.event3.visibility = if (it == null) View.GONE else View.VISIBLE
lb.event3.background = it?.let { lb.event3.background = it?.let {
R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.getColor()) R.drawable.bg_circle.resolveDrawable(activity).setTintColor(it.eventColor)
} }
} }

View file

@ -5,9 +5,9 @@
package pl.szczodrzynski.edziennik.ui.modules.timetable package pl.szczodrzynski.edziennik.ui.modules.timetable
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyPagerAdapter
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
@ -16,7 +16,7 @@ class TimetablePagerAdapter(
private val items: List<Date>, private val items: List<Date>,
private val startHour: Int, private val startHour: Int,
private val endHour: Int private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { ) : LazyPagerAdapter(fragmentManager, null) {
companion object { companion object {
private const val TAG = "TimetablePagerAdapter" private const val TAG = "TimetablePagerAdapter"
} }
@ -25,7 +25,7 @@ class TimetablePagerAdapter(
private val weekStart by lazy { today.weekStart } private val weekStart by lazy { today.weekStart }
private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) } private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) }
override fun getItem(position: Int): Fragment { override fun getPage(position: Int): LazyFragment {
return TimetableDayFragment().apply { return TimetableDayFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putInt("date", items[position].value) putInt("date", items[position].value)
@ -39,7 +39,7 @@ class TimetablePagerAdapter(
return items.size return items.size
} }
override fun getPageTitle(position: Int): CharSequence? { override fun getPageTitle(position: Int): CharSequence {
val date = items[position] val date = items[position]
val pageTitle = StringBuilder(Week.getFullDayName(date.weekDay)) val pageTitle = StringBuilder(Week.getFullDayName(date.weekDay))
if (date > weekEnd || date < weekStart) { if (date > weekEnd || date < weekStart) {

View file

@ -27,7 +27,7 @@ import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Event.TYPE_HOMEWORK import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_NO_LESSONS import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_NO_LESSONS
import pl.szczodrzynski.edziennik.ui.widgets.LessonDialogActivity import pl.szczodrzynski.edziennik.ui.widgets.LessonDialogActivity
@ -289,7 +289,7 @@ class WidgetTimetableProvider : AppWidgetProvider() {
} }
// get all events for the current date // get all events for the current date
val events = app.db.eventDao().getAllByDateNow(profile.id, timetableDate)?.filterNotNull() ?: emptyList() val events = app.db.eventDao().getAllByDateNow(profile.id, timetableDate)
lessons.forEachIndexed { pos, lesson -> lessons.forEachIndexed { pos, lesson ->
if (lesson.type == TYPE_NO_LESSONS) if (lesson.type == TYPE_NO_LESSONS)
@ -345,9 +345,9 @@ class WidgetTimetableProvider : AppWidgetProvider() {
// add every event on this lesson // add every event on this lesson
for (event in events) { for (event in events) {
if (event.startTime == null || event.startTime != lesson.displayStartTime) if (event.time == null || event.time != lesson.displayStartTime)
continue continue
model.eventColors.add(if (event.type == TYPE_HOMEWORK) ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK else event.getColor()) model.eventColors.add(if (event.type == TYPE_HOMEWORK) ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK else event.eventColor)
} }
models += model models += model

View file

@ -137,7 +137,7 @@ public class Date implements Comparable<Date> {
} }
public static int diffDays(Date d1, Date d2) { public static int diffDays(Date d1, Date d2) {
return (int) ((d1.getInMillis() - d2.getInMillis()) / (24 * 60 * 60 * 1000)); return Math.round((d1.getInMillis() - d2.getInMillis()) / (24 * 60 * 60 * 1000f));
} }
public static boolean isToday(Date date) { public static boolean isToday(Date date) {
@ -254,6 +254,7 @@ public class Date implements Comparable<Date> {
/** /**
* @return 2019-06-02 * @return 2019-06-02
*/ */
@NonNull
public String getStringY_m_d() { public String getStringY_m_d() {
return year + (month < 10 ? "-0" : "-") + month + (day < 10 ? "-0" : "-") + day; return year + (month < 10 ? "-0" : "-") + month + (day < 10 ? "-0" : "-") + day;
} }

View file

@ -0,0 +1,34 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-3-30.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m102,24h-74c-4.42,0 -8,3.58 -8,8v72c0,4.42 3.58,8 8,8h74c4.42,0 8,-3.58 8,-8v-72c0,-4.42 -3.58,-8 -8,-8z"
android:fillColor="#70d0f3"/>
<path
android:pathData="m20,32v8h90v-8c0,-4.42 -3.58,-8 -8,-8h-74c-4.42,0 -8,3.58 -8,8z"
android:fillColor="#3281e6"/>
<path
android:pathData="m88,92h-30c-2.21,0 -4,-1.79 -4,-4 0,-2.21 1.79,-4 4,-4h30c2.21,0 4,1.79 4,4 0,2.21 -1.79,4 -4,4zM92,74c0,-2.21 -1.79,-4 -4,-4h-30c-2.21,0 -4,1.79 -4,4 0,2.21 1.79,4 4,4h30c2.21,0 4,-1.79 4,-4zM92,60c0,-2.21 -1.79,-4 -4,-4h-30c-2.21,0 -4,1.79 -4,4 0,2.21 1.79,4 4,4h30c2.21,0 4,-1.79 4,-4zM48,88c0,-2.21 -1.79,-4 -4,-4h-2c-2.21,0 -4,1.79 -4,4 0,2.21 1.79,4 4,4h2c2.21,0 4,-1.79 4,-4zM48,74c0,-2.21 -1.79,-4 -4,-4h-2c-2.21,0 -4,1.79 -4,4 0,2.21 1.79,4 4,4h2c2.21,0 4,-1.79 4,-4zM48,60c0,-2.21 -1.79,-4 -4,-4h-2c-2.21,0 -4,1.79 -4,4 0,2.21 1.79,4 4,4h2c2.21,0 4,-1.79 4,-4z"
android:fillColor="#2b83d5"/>
<path
android:pathData="m127,86.3 l-7.42,-7.42c-1.12,-1.13 -2.96,-1.13 -4.09,0l-3.5,3.49 11.5,11.5 3.49,-3.5c1.13,-1.13 1.13,-2.96 0,-4.09"
android:fillColor="#fc657c"/>
<path
android:pathData="m93.4,124 l-11.5,-11.5 24.5,-24.5 11.5,11.5z"
android:fillColor="#ffa859"/>
<path
android:pathData="m118,99.6 l-11.5,-11.5 5.75,-5.76 11.5,11.5z"
android:fillColor="#c8c8c8"/>
<path
android:pathData="m81.9,113 l-3.88,15.4 15.4,-3.88z"
android:fillColor="#ffcd98"/>
<path
android:pathData="m80,120 l-1.96,7.78 7.78,-1.96z"
android:fillColor="#818181"/>
</vector>

View file

@ -81,14 +81,14 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper" android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{event.startTime == null ? @string/event_all_day : event.startTime.stringHM}" android:text="@{event.time == null ? @string/event_all_day : event.time.stringHM}"
tools:text="14:50"/> tools:text="14:50"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:text="@{Integer.toString(event.eventDate.day)}" android:text="@{Integer.toString(event.date.day)}"
android:textSize="36sp" android:textSize="36sp"
tools:text="14" /> tools:text="14" />
@ -107,13 +107,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper" android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_event_details_teacher" android:text="@string/dialog_event_details_teacher"
android:visibility="@{event.teacherFullName != null ? View.VISIBLE : View.GONE}"/> android:visibility="@{event.teacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{event.teacherFullName}" android:text="@{event.teacherName}"
android:textIsSelectable="true" android:textIsSelectable="true"
android:visibility="@{event.teacherFullName != null ? View.VISIBLE : View.GONE}" android:visibility="@{event.teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" /> tools:text="Janósz Kowalski" />
<TextView <TextView
@ -195,6 +195,18 @@
android:text="\uFCDA" android:text="\uFCDA"
android:textSize="20sp" android:textSize="20sp"
android:fontFamily="@font/community_material_font_v3_5_95_1" /> android:fontFamily="@font/community_material_font_v3_5_95_1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/checkDoneButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:minWidth="0dp"
android:checkable="true"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:text="\uFCE1"
android:textSize="20sp"
android:fontFamily="@font/community_material_font_v3_5_95_1" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -4,7 +4,8 @@
--> -->
<layout xmlns:tools="http://schemas.android.com/tools" <layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <data>
<import type="android.view.View"/> <import type="android.view.View"/>
@ -54,22 +55,35 @@
android:id="@+id/topic" android:id="@+id/topic"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_weight="1" android:layout_weight="1"
android:textAppearance="@style/NavView.TextView.Medium"
android:maxLines="@{simpleMode ? 2 : 3}"
android:ellipsize="end" android:ellipsize="end"
tools:maxLines="3" android:maxLines="3"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia. Nie wiem co się dzieje w tym roku nie będzie już religii w szkołach podstawowych w Polsce i Europie zachodniej Afryki" /> tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia. Nie wiem co się dzieje w tym roku nie będzie już religii w szkołach podstawowych w Polsce i Europie zachodniej Afryki" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/editButton" android:id="@+id/editButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v3_5_95_1"
android:minWidth="0dp" android:minWidth="0dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:text="\uFC92" android:text="\uFC92"
android:textSize="20sp" android:textSize="20sp"
android:fontFamily="@font/community_material_font_v3_5_95_1"/> tools:visibility="gone" />
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/isDone"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="top"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
app:iiv_color="@color/md_green_500"
app:iiv_icon="cmd-check"
tools:background="@sample/check" />
</LinearLayout> </LinearLayout>

View file

@ -21,7 +21,7 @@
app:tabSelectedTextColor="?colorPrimary" app:tabSelectedTextColor="?colorPrimary"
app:tabTextColor="?android:textColorPrimary"/> app:tabTextColor="?android:textColorPrimary"/>
<androidx.viewpager.widget.ViewPager <pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -28,7 +28,7 @@
android:id="@+id/tabLayout" android:id="@+id/tabLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:background="@color/colorSurface_6dp" android:background="@color/colorSurface_1dp"
app:rtl_tabIndicatorColor="?colorPrimary" app:rtl_tabIndicatorColor="?colorPrimary"
app:rtl_tabMaxWidth="300dp" app:rtl_tabMaxWidth="300dp"
app:rtl_tabMinWidth="90dp" app:rtl_tabMinWidth="90dp"
@ -40,7 +40,7 @@
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" /> app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -16,8 +16,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/gradesNoData" android:id="@+id/noData"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
@ -25,13 +31,17 @@
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:text="@string/grades_no_data" android:text="@string/grades_no_data"
android:textSize="24sp" android:textSize="24sp"
app:drawableTopCompat="@drawable/ic_no_grades" /> android:visibility="gone"
app:drawableTopCompat="@drawable/ic_no_grades"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/gradesRecyclerView" android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:listitem="@layout/grades_item_subject" /> android:visibility="gone"
tools:listitem="@layout/grades_item_subject"
tools:visibility="visible" />
</FrameLayout> </FrameLayout>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator> </pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout> </layout>

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