Compare commits

...

24 Commits

Author SHA1 Message Date
da48c059ec [4.0-rc.4] Update build.gradle, signing and changelog. 2020-03-30 23:29:34 +02:00
ee5566d1ef [Events] Add toast hint to mark as done button. 2020-03-30 23:28:50 +02:00
b794b30346 [UI] Fix disabling pull to refresh when changing page using tab layout. 2020-03-30 23:16:35 +02:00
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
fcc3c55110 [Events] Fix preserving isDone value. Improve DataRemoveModel method of keeping items. 2020-03-30 22:37:48 +02:00
328c07eaf4 [Messages] Fix Librus attachment downloading. Add option to force (re)download an attachment. 2020-03-30 19:48:56 +02:00
b004ec048e [UI] Refactor Grades, Notifications, Homework fragments to better match unified templates. 2020-03-30 18:55:28 +02:00
b9f83875a0 [Event] Add isDone attribute and marking events as done. 2020-03-30 18:19:19 +02:00
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
043f8210ba [UI] Add lazy loading to fragments with view pager. 2020-03-29 23:11:17 +02:00
41a79caf83 [API/Mobidziennik] Change data remove model to include only possible types. 2020-03-29 21:06:39 +02:00
0427fa6087 [Events] Add support for selective updates and upserting. 2020-03-29 18:05:56 +02:00
2f3c912dbe [Config] Disable teacher absence notifications by default. Add missing migration values. 2020-03-29 16:27:05 +02:00
219a7443c0 [4.0-rc.3] Update build.gradle, signing and changelog. 2020-03-29 15:31:49 +02:00
6deb408d80 [API/Librus] Fix attachment downloading, once again. 2020-03-29 15:30:30 +02:00
c6e1ff2164 [Events] Fix event sorting. Fix showing event teacher name. 2020-03-29 15:26:48 +02:00
bc0918a115 [API/Librus] Fix attachment downloading. 2020-03-29 15:16:35 +02:00
55ff9173be [API/Liburs] Fix unseen teacher absence metadata and add notifications for new teacher absences. 2020-03-28 17:08:36 +01:00
d4d548846f [Refactor] Refactor EventDao class. 2020-03-28 11:17:39 +01:00
ef4527f140 [Refactor] Rewrite events to Kotlin. 2020-03-27 18:51:56 +01:00
0b1e7242bb [API/Mobidziennik] Fix some errors. 2020-03-27 14:05:03 +01:00
30b6ac2a06 [4.0-rc.2] Update build.gradle, singing and changelog. 2020-03-26 20:46:03 +01:00
a7fa7cb5e4 [API/Librus] Fix a typo. 2020-03-26 20:45:46 +01:00
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 'com.github.kuba2k2:NumberSlidingPicker:2921225f76'
implementation project(":annotation")
kapt project(":codegen")
}
repositories {
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>
<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>
@ -22,7 +28,7 @@
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
</ul>
</ul>-->
<br>
<br>
Dzięki za korzystanie ze Szkolnego!<br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
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);

View File

@ -40,6 +40,9 @@ import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
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.gson.JsonArray
import com.google.gson.JsonElement
@ -141,6 +144,10 @@ fun CharSequence?.isNotNullNorEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
fun <T> Collection<T>?.isNotNullNorEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
fun CharSequence?.isNotNullNorBlank(): Boolean {
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")
inline fun <T : CompoundButton> T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
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_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement
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
else -> R.string.notification_type_general
})
@ -1166,3 +1181,19 @@ fun TextView.getTextPosition(range: IntRange): Rect {
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.feedback.FeedbackFragment
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.home.HomeFragment
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.MessagesFragment
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.SettingsNewFragment
import pl.szczodrzynski.edziennik.ui.modules.template.TemplateFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
@ -129,6 +130,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
const val TARGET_MESSAGES_DETAILS = 503
const val TARGET_MESSAGES_COMPOSE = 504
const val TARGET_WEB_PUSH = 140
const val TARGET_TEMPLATE = 1000
const val HOME_ID = DRAWER_ITEM_HOME
@ -153,7 +155,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
.withBadgeTypeId(TYPE_EVENT)
.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)
.withBadgeTypeId(TYPE_GRADE)
.isInDrawer(true)
@ -185,7 +187,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// 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)
.isInDrawer(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_WEB_PUSH, R.string.menu_web_push, WebPushFragment::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
}
@ -1068,6 +1077,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
.also { if (target.icon != null) it.withIcon(target.icon!!) }
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
.also { if (target.badgeTypeId != null) it.withBadgeStyle(drawer.badgeStyle)}
.withSelectedBackgroundAnimated(false)
if (target.badgeTypeId != null)
drawer.addUnreadCounterType(target.badgeTypeId!!, target.id)

View File

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

View File

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

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.config.utils
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.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
@ -14,11 +15,23 @@ class ProfileConfigMigration(config: ProfileConfig) {
if (dataVersion < 1) {
grades.colorMode = COLOR_MODE_WEIGHTED
grades.dontCountEnabled = false
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
// no migration for ui.homeCards
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)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.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)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.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
val eventObject = Event(
profileId,
id,
date,
null,
title,
-1,
Event.TYPE_CLASS_EVENT,
false,
-1,
-1,
data.teamClass?.id ?: -1
profileId = profileId,
id = id,
date = date,
time = null,
topic = title,
color = null,
type = Event.TYPE_CLASS_EVENT,
teacherId = -1,
subjectId = -1,
teamId = data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,17 +51,16 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List<String>) {
val eventObject = Event(
data.profileId,
id,
eventDate,
startTime,
topic,
-1,
type,
false,
teacherId,
subjectId,
teamId)
profileId = data.profileId,
id = id,
date = eventDate,
time = startTime,
topic = topic,
color = null,
type = type,
teacherId = teacherId,
subjectId = subjectId,
teamId = teamId)
data.eventList.add(eventObject)
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
import android.text.Html
import androidx.core.util.contains
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
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 teacherId = cols[7].toLong()
val subjectId = cols[6].toLong()
val topic = cols[1]
val topic = Html.fromHtml(cols[1])?.toString() ?: ""
val eventDate = Date.fromYmd(cols[2])
val startTime = Time.fromYmdHm(cols[3])
val eventObject = Event(
data.profileId,
id,
eventDate,
startTime,
topic,
-1,
Event.TYPE_HOMEWORK,
false,
teacherId,
subjectId,
teamId)
profileId = data.profileId,
id = id,
date = eventDate,
time = startTime,
topic = topic,
color = null,
type = Event.TYPE_HOMEWORK,
teacherId = teacherId,
subjectId = subjectId,
teamId = teamId)
data.eventList.add(eventObject)
data.metadataList.add(

View File

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

View File

@ -61,26 +61,25 @@ class MobidziennikWebCalendar(override val data: DataMobidziennik,
val title = event.getString("title")
val comment = event.getString("comment")
var topic = title
var topic = title ?: ""
if (title != comment) {
topic += "\n" + comment
}
if (id == -1L) {
id = crc16(topic?.toByteArray()).toLong()
id = crc16(topic.toByteArray()).toLong()
}
val eventObject = Event(
profileId,
id,
eventDate, null,
topic,
-1,
eventType,
false,
-1,
-1,
data.teamClass?.id ?: -1
profileId = profileId,
id = id,
date = eventDate, time = null,
topic = topic,
color = null,
type = eventType,
teacherId = -1,
subjectId = -1,
teamId = data.teamClass?.id ?: -1
)
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 eventObject = Event(
profileId,
id,
eventDate,
startTime,
topic,
-1,
type,
false,
teacherId,
subjectId,
teamId
profileId = profileId,
id = id,
date = eventDate,
time = startTime,
topic = topic,
color = null,
type = type,
teacherId = teacherId,
subjectId = subjectId,
teamId = teamId
)
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)
}
if (eventList.isNotEmpty()) {
db.eventDao().addAll(eventList)
db.eventDao().upsertAll(eventList, removeNotKept = true)
}
if (noticeList.isNotEmpty()) {
db.noticeDao().clear(profile.id)

View File

@ -56,9 +56,9 @@ open class DataRemoveModel {
}
fun commit(profileId: Int, dao: EventDao) {
type?.let { dao.removeFutureWithType(profileId, Date.getToday(), it) }
exceptType?.let { dao.removeFutureExceptType(profileId, Date.getToday(), it) }
exceptTypes?.let { dao.removeFutureExceptTypes(profileId, Date.getToday(), it) }
type?.let { dao.dontKeepFutureWithType(profileId, Date.getToday(), it) }
exceptType?.let { dao.dontKeepFutureExceptType(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
if (event.id in blacklistedIds)
return@forEach
// force nullable non-negative colors
if (event.color == -1)
event.color = null
// create the event for every matching team and profile
teams.filter { it.code == event.teamCode }.onEach { team ->
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 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 sharedByName: String,
/* If null, the server shows an error */
val sharedByName: String?,
val shareTeamCode: String? = null,
val unshareTeamCode: 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()) {
val today = Date.getToday()
app.db.metadataDao().addAllIgnore(events.map { event ->
val isPast = event.eventDate < today
val isPast = event.date < today
Metadata(
event.profileId,
Metadata.TYPE_EVENT,
@ -44,7 +44,7 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
event.addedDate
)
})
return app.db.eventDao().addAll(events).size
return app.db.eventDao().upsertAll(events).size
}
return 0;
}

View File

@ -34,6 +34,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
announcementNotifications()
messageNotifications()
luckyNumberNotifications()
teacherAbsenceNotifications()
}
private fun timetableNotifications() {
@ -58,7 +59,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
}
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)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
@ -66,7 +67,7 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
event.date.formattedString
)
else
app.getString(
@ -74,8 +75,8 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.typeName ?: "wydarzenie",
event.date.formattedString,
event.subjectLongName
)
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,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
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() {
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(
R.string.notification_shared_event_format,
event.sharedByName,
event.typeName ?: "wydarzenie",
event.eventDate.formattedString,
event.date.formattedString,
event.topic
)
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,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
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,
TimetableManual::class,
Metadata::class
], version = 79)
], version = 83)
@TypeConverters(
ConverterTime::class,
ConverterDate::class,
@ -164,7 +164,11 @@ abstract class AppDb : RoomDatabase() {
Migration76(),
Migration77(),
Migration78(),
Migration79()
Migration79(),
Migration80(),
Migration81(),
Migration82(),
Migration83()
).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 (add(new Metadata(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, seen, false, 0)) == -1) {
updateSeen(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, seen);
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).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), seen);
}
}
if (o instanceof LessonFull) {
@ -117,8 +117,8 @@ public abstract class MetadataDao {
}
}
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) {
updateNotified(profileId, ((Event) o).type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).id, notified);
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).getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, ((Event) o).getId(), notified);
}
}
if (o instanceof LessonFull) {
@ -141,9 +141,9 @@ public abstract class MetadataDao {
@Transaction
public void setBoth(int profileId, Event o, boolean seen, boolean notified, long addedDate) {
if (o != null) {
if (add(new Metadata(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, seen, notified, addedDate)) == -1) {
updateSeen(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, seen);
updateNotified(profileId, o.type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.id, notified);
if (add(new Metadata(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen, notified, addedDate)) == -1) {
updateSeen(profileId, o.getType() == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, o.getId(), seen);
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")
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")
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_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17
const val TYPE_TEACHER_ABSENCE = 19
fun buildId(profileId: Int, type: Int, itemId: Long): Long {
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)
return@forEach
val event = Event(
team.profileId,
json.getLong("id") ?: return,
json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return,
json.getInt("startTime")?.let { Time.fromValue(it) },
json.getString("topic") ?: "",
json.getInt("color") ?: -1,
json.getLong("type") ?: 0,
true,
json.getLong("teacherId") ?: -1,
json.getLong("subjectId") ?: -1,
team.id
profileId = team.profileId,
id = json.getLong("id") ?: return,
date = json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return,
time = json.getInt("startTime")?.let { Time.fromValue(it) },
topic = json.getString("topic") ?: "",
color = json.getInt("color"),
type = json.getLong("type") ?: 0,
teacherId = json.getLong("teacherId") ?: -1,
subjectId = json.getLong("subjectId") ?: -1,
teamId = team.id
)
if (event.color == -1)
event.color = null
event.sharedBy = json.getString("sharedBy")
event.sharedByName = json.getString("sharedByName")
@ -144,14 +145,14 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
profileName = profile.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
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
}
events += event
metadataList += metadata
}
app.db.eventDao().addAll(events)
app.db.eventDao().upsertAll(events)
app.db.metadataDao().addAllReplace(metadataList)
if (notificationList.isNotEmpty()) {
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(
activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
onItemClick = {
EventDetailsDialog(
activity,

View File

@ -87,11 +87,11 @@ class EventDetailsDialog(
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
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) {}
b.typeColor.background?.setTintColor(event.getColor())
b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf(
event.subjectLongName,
@ -102,14 +102,14 @@ class EventDetailsDialog(
when (event.sharedBy) {
null -> when {
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
}
"self" -> R.string.event_details_shared_by_self_format
else -> R.string.event_details_shared_by_format
},
Date.fromMillis(event.addedDate).formattedString,
event.sharedByName ?: event.teacherFullName ?: ""
event.sharedByName ?: event.teacherName ?: ""
)
b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE
@ -125,7 +125,7 @@ class EventDetailsDialog(
b.goToTimetableButton.setOnClickListener {
dialog.dismiss()
val dateStr = event.eventDate?.stringY_m_d ?: return@setOnClickListener
val dateStr = event.date.stringY_m_d
val intent =
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE)
@ -148,6 +148,10 @@ class EventDetailsDialog(
openInCalendar()
}
b.checkDoneButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_mark_as_done, Toast.LENGTH_SHORT).show()
true
}
b.goToTimetableButton.setOnLongClickListener {
Toast.makeText(activity, R.string.hint_go_to_timetable, Toast.LENGTH_SHORT).show()
true
@ -161,6 +165,14 @@ class EventDetailsDialog(
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
BetterLink.attach(b.topic) {
dialog.dismiss()
@ -245,7 +257,7 @@ class EventDetailsDialog(
}
private fun openInCalendar() { launch {
val title = (event.typeName ?: "") +
val title = event.typeName ?: "" +
(if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") +
(event.subjectLongName ?: "")
@ -254,12 +266,12 @@ class EventDetailsDialog(
putExtra(Events.TITLE, title)
putExtra(Events.DESCRIPTION, event.topic)
if (event.startTime == null) {
if (event.time == null) {
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.eventDate.inMillis)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.eventDate.inMillis)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.date.inMillis)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.date.inMillis)
} else {
val startTime = event.eventDate.combineWith(event.startTime)
val startTime = event.date.combineWith(event.time)
val endTime = startTime + 45 * 60 * 1000 /* 45 min */
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.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull
@ -19,8 +19,11 @@ import pl.szczodrzynski.edziennik.utils.models.Week
class EventListAdapter(
val context: Context,
val simpleMode: Boolean = false,
val showDate: Boolean = false,
val showWeekDay: Boolean = false,
val showDate: Boolean = false,
val showType: Boolean = true,
val showTime: Boolean = true,
val showSubject: Boolean = true,
val onItemClick: ((event: EventFull) -> Unit)? = null,
val onEventEditClick: ((event: EventFull) -> Unit)? = null
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>() {
@ -48,37 +51,41 @@ class EventListAdapter(
b.simpleMode = simpleMode
b.topic.text = event.topic
b.topic.maxLines = if (simpleMode) 2 else 3
b.details.text = mutableListOf<CharSequence?>(
if (showWeekDay) Week.getFullDayName(event.eventDate.weekDay) else null,
if (showDate) event.eventDate.getRelativeString(context, 7) ?: event.eventDate.formattedStringShort else null,
event.typeName,
if (simpleMode) null else event.startTime?.stringHM ?: app.getString(R.string.event_all_day),
if (simpleMode) null else event.subjectLongName
if (showWeekDay) Week.getFullDayName(event.date.weekDay) else null,
if (showDate) event.date.getRelativeString(context, 7) ?: event.date.formattedStringShort else null,
if (showType) event.typeName else null,
if (showTime) event.time?.stringHM ?: app.getString(R.string.event_all_day) else null,
if (showSubject) event.subjectLongName else null
).concat(bullet)
b.addedBy.setText(
when (event.sharedBy) {
null -> when {
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
}
"self" -> R.string.event_list_shared_by_self_format
else -> R.string.event_list_shared_by_format
},
Date.fromMillis(event.addedDate).formattedString,
event.sharedByName ?: event.teacherFullName ?: "",
event.sharedByName ?: event.teacherName ?: "",
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 {
onEventEditClick?.invoke(event)
}
b.isDone.isVisible = event.isDone
b.editButton.setOnLongClickListener {
Toast.makeText(context, R.string.hint_edit_event, Toast.LENGTH_SHORT).show()
true

View File

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

View File

@ -222,22 +222,22 @@ class AgendaFragment : Fragment(), CoroutineScope {
events.forEach { event ->
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.teacherFullName?.let { ", $it" } ?: "") +
(event.teacherName?.let { ", $it" } ?: "") +
(event.teamName?.let { ", $it" } ?: ""),
event.getColor(),
Colors.legibleTextColor(event.getColor()),
event.eventColor,
Colors.legibleTextColor(event.eventColor),
event.startTimeCalendar,
event.endTimeCalendar,
event.startTime == null,
event.time == null,
event.id,
!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 {
@ -281,11 +281,11 @@ class AgendaFragment : Fragment(), CoroutineScope {
val eventIcon = IconicsDrawable(activity)
.icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle)
.size(IconicsSize.dp(10))
.color(IconicsColor.colorInt(event.getColor()))
.color(IconicsColor.colorInt(event.eventColor))
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)

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.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 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.Icon2
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.Metadata.TYPE_GRADE
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.settings.GradesConfigDialog
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.math.max
class GradesFragment : Fragment(), CoroutineScope {
class GradesListFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "GradesFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: GradesFragmentBinding
private lateinit var b: GradesListFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
private val adapter by lazy {
GradesAdapter(activity)
}
private val manager by lazy { app.gradesManager }
private val dontCountEnabled by lazy { manager.dontCountEnabled }
private val dontCountGrades by lazy { manager.dontCountGrades }
@ -63,51 +58,49 @@ class GradesFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
b = GradesFragmentBinding.inflate(inflater)
b = GradesListFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded)
return
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { startCoroutineTimer(100L) {
if (!isAdded) return@startCoroutineTimer
expandSubjectId = arguments?.getLong("gradesSubjectId") ?: 0L
app.db.gradeDao()
.getAllOrderBy(App.profileId, app.gradesManager.getOrderByString())
.observe(this, Observer { grades ->
if (b.gradesRecyclerView.adapter == null) {
b.gradesRecyclerView.adapter = adapter
b.gradesRecyclerView.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
//addItemDecoration(SimpleDividerItemDecoration(context))
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
}
}
})
}
}
val adapter = GradesAdapter(activity)
var firstRun = true
launch(Dispatchers.Default) {
processGrades(grades)
}
app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(this, Observer { items -> launch {
if (!isAdded) return@launch
if (grades != null && grades.isNotEmpty()) {
b.gradesRecyclerView.visibility = View.VISIBLE
b.gradesNoData.visibility = View.GONE
} else {
b.gradesRecyclerView.visibility = View.GONE
b.gradesNoData.visibility = View.VISIBLE
}
})
// 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)
layoutManager = LinearLayoutManager(context)
addOnScrollListener(b.refreshLayout.onScrollListener)
}
}
adapter.notifyDataSetChanged()
if (firstRun) {
expandSubject(adapter)
firstRun = false
}
// 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
}
}})
adapter.onGradeClick = {
GradeDetailsDialog(activity, it)
@ -153,10 +146,30 @@ class GradesFragment : Fragment(), CoroutineScope {
})
)
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")
private suspend fun processGrades(grades: List<GradeFull>) {
private fun processGrades(grades: List<GradeFull>): MutableList<Any> {
val items = mutableListOf<GradesSubject>()
var subjectId = -1L
@ -284,30 +297,7 @@ class GradesFragment : Fragment(), CoroutineScope {
GradesManager.ORDER_BY_DATE_ASC -> items.sortBy { it.lastAddedDate }
}
adapter.items = items.toMutableList()
adapter.items.add(stats)
var expandSubjectModel: GradesSubject? = null
if (expandSubjectId != 0L) {
expandSubjectModel = items.firstOrNull { it.subjectId == expandSubjectId }
adapter.expandModel(
model = expandSubjectModel,
view = null,
notifyAdapter = false
)
}
withContext(Dispatchers.Main) {
adapter.notifyDataSetChanged()
}
startCoroutineTimer(500L, 0L) {
if (expandSubjectModel != null) {
b.gradesRecyclerView.smoothScrollToPosition(
items.indexOf(expandSubjectModel) + expandSubjectModel.semesters.size + (expandSubjectModel.semesters.firstOrNull()?.grades?.size ?: 0)
)
}
}
return (items + stats).toMutableList()
}
private fun countGrade(grade: Grade, averages: GradesAverages) {

View File

@ -60,8 +60,11 @@ class HomeEventsCard(
adapter = EventListAdapter(
activity,
simpleMode = true,
showDate = true,
showWeekDay = true,
showDate = true,
showType = true,
showTime = false,
showSubject = false,
onItemClick = {
EventDetailsDialog(
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
if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter

View File

@ -50,17 +50,17 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
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.homeworkItemTopic.setText(homework.topic);
holder.homeworkItemSubjectTeacher.setText(context.getString(R.string.homework_subject_teacher_format, bs(homework.subjectLongName), bs(homework.teacherFullName)));
holder.homeworkItemTeamDate.setText(context.getString(R.string.homework_team_date_format, bs(homework.teamName), Date.fromMillis(homework.addedDate).getFormattedStringShort()));
holder.homeworkItemHomeworkDate.setText(app.getString(R.string.date_relative_format, homework.getDate().getFormattedString(), Date.dayDiffString(context, diffDays)));
holder.homeworkItemTopic.setText(homework.getTopic());
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.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.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY));
homework.seen = true;
homework.setSeen(true);
AsyncTask.execute(() -> {
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.homeworkItemEdit.setVisibility((homework.addedManually ? View.VISIBLE : View.GONE));
holder.homeworkItemEdit.setVisibility((homework.getAddedManually() ? View.VISIBLE : View.GONE));
holder.homeworkItemEdit.setOnClickListener(v -> {
new EventManualDialog(
(MainActivity) context,
homework.profileId,
homework.getProfileId(),
null,
null,
null,
@ -83,11 +83,11 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
null);
});
if (homework.sharedBy == null) {
if (homework.getSharedBy() == null) {
holder.homeworkItemSharedBy.setVisibility(View.GONE);
}
else if (homework.sharedByName != 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)));
else if (homework.getSharedByName() != null) {
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
import android.os.AsyncTask
@ -7,46 +11,48 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
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.szkolny.font.SzkolnyFont
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
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.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext
class HomeworkFragment : Fragment() {
class HomeworkFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "HomeworkFragment"
var pageSelection = 0
}
private lateinit var app: App
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? {
activity = (getActivity() as MainActivity?) ?: return null
if (context == null)
return null
context ?: return null
app = activity.application as App
context!!.theme.applyStyle(Themes.appTheme, true)
// activity, context and profile is valid
b = FragmentHomeworkBinding.inflate(inflater)
b = HomeworkFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
if (app.profile == null || !isAdded)
return
if (!isAdded) return
activity.bottomSheet.prependItems(
BottomSheetPrimaryItem(true)
@ -67,34 +73,29 @@ class HomeworkFragment : Fragment() {
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
}))
b.viewPager.adapter = MessagesFragment.Adapter(childFragmentManager).also { adapter ->
adapter.addFragment(HomeworkListFragment().also { fragment ->
fragment.arguments = Bundle().also { args ->
args.putInt("homeworkDate", HomeworkDate.CURRENT)
}
}, getString(R.string.homework_tab_current))
val pagerAdapter = FragmentLazyPagerAdapter(
fragmentManager ?: return,
b.refreshLayout,
listOf(
HomeworkListFragment().apply {
arguments = Bundle("homeworkDate" to HomeworkDate.CURRENT)
} to getString(R.string.homework_tab_current),
adapter.addFragment(HomeworkListFragment().also { fragment ->
fragment.arguments = Bundle().also { args ->
args.putInt("homeworkDate", HomeworkDate.PAST)
}
}, getString(R.string.homework_tab_past))
HomeworkListFragment().apply {
arguments = Bundle("homeworkDate" to HomeworkDate.PAST)
} to getString(R.string.homework_tab_past)
)
)
b.viewPager.apply {
offscreenPageLimit = 1
adapter = pagerAdapter
currentItem = pageSelection
addOnPageSelectedListener {
pageSelection = it
}
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 {
bottomBar.apply {
fabEnable = true

View File

@ -4,67 +4,106 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.databinding.HomeworkListBinding
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.databinding.HomeworkListFragmentBinding
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 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 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? {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
b = HomeworkListBinding.inflate(inflater)
b = HomeworkListFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null
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
override fun onPageCreated(): Boolean { startCoroutineTimer(100L) {
val homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT)
val today = Date.getToday()
val filter = when(homeworkDate) {
HomeworkDate.CURRENT -> "eventDate >= '" + Date.getToday().stringY_m_d + "'"
else -> "eventDate < '" + Date.getToday().stringY_m_d + "'"
HomeworkDate.CURRENT -> "eventDate >= '${today.stringY_m_d}' AND eventIsDone = 0"
else -> "eventDate < '${today.stringY_m_d}' OR eventIsDone = 1"
}
app.db.eventDao()
.getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter)
.observe(this, Observer { homeworkList ->
if (!isAdded) return@Observer
val adapter = EventListAdapter(
activity,
showWeekDay = true,
showDate = true,
showType = false,
showTime = true,
showSubject = true,
onItemClick = {
EventDetailsDialog(
activity,
it
)
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it
)
}
)
if (homeworkList != null && homeworkList.size > 0) {
val adapter = HomeworkAdapter(context, homeworkList)
b.homeworkView.adapter = adapter
b.homeworkView.visibility = View.VISIBLE
b.homeworkNoData.visibility = View.GONE
} else {
b.homeworkView.visibility = View.GONE
b.homeworkNoData.visibility = View.VISIBLE
app.db.eventDao().getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter).observe(this@HomeworkListFragment, 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).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 {
b.list.isVisible = true
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.ApiTaskProgressEvent
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_ENABLED
import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncBinding
@ -62,20 +60,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
else
REGISTRATION_DISABLED
val typeList = listOf(
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.eventTypeDao().addDefaultTypes(activity, it.id)
}
app.db.profileDao().addAll(profiles)

View File

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

View File

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

View File

@ -10,11 +10,10 @@ import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
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.MessageRecipientFull;
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.Themes;
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;
public class MessagesListFragment extends Fragment {
public class MessagesListFragment extends LazyFragment {
private App app = null;
private MainActivity activity = null;
@ -65,9 +66,9 @@ public class MessagesListFragment extends Fragment {
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
public boolean onPageCreated() {
if (app == null || activity == null || b == null || !isAdded())
return;
return false;
long messageId = -1;
if (getArguments() != null) {
@ -78,7 +79,7 @@ public class MessagesListFragment extends Fragment {
args.putLong("messageId", messageId);
getArguments().remove("messageId");
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args);
return;
return false;
}
if (getArguments() != null) {
@ -161,11 +162,22 @@ public class MessagesListFragment extends Fragment {
// TODO ANIMATION
//postponeEnterTransition();
viewParent = (ViewGroup) view.getParent();
viewParent = (ViewGroup) getView().getParent();
b.emailList.setLayoutManager(new LinearLayoutManager(view.getContext()));
b.emailList.addItemDecoration(new SimpleDividerItemDecoration(view.getContext()));
b.emailList.setLayoutManager(new LinearLayoutManager(getView().getContext()));
b.emailList.addItemDecoration(new SimpleDividerItemDecoration(getView().getContext()));
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) {
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) {

View File

@ -1,71 +1,62 @@
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.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
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.utils.Utils.d
import pl.szczodrzynski.edziennik.databinding.NotificationsListItemBinding
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class NotificationsAdapter(
private val context: Context
) : RecyclerView.Adapter<NotificationsAdapter.ViewHolder>() {
private val activity: AppCompatActivity,
val onItemClick: ((item: Notification) -> Unit)? = null
) : RecyclerView.Adapter<NotificationsAdapter.ViewHolder>(), CoroutineScope {
companion object {
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>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.row_notifications_item, parent, false)
val inflater = LayoutInflater.from(activity)
val view = NotificationsListItemBinding.inflate(inflater, parent, false)
return ViewHolder(view)
}
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
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(context)
holder.title.text = notification.text
holder.profileDate.text = listOf(
notification.profileName ?: "",
b.title.text = item.text
b.profileDate.text = listOf(
item.profileName ?: "",
"",
date.asColoredSpannable(colorSecondary)
).concat()
holder.type.text = context.getNotificationTitle(notification.type)
date
).concat().asColoredSpannable(colorSecondary)
b.type.text = activity.getNotificationTitle(item.type)
holder.root.onClick {
val intent = Intent("android.intent.action.MAIN")
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)
onItemClick?.let { listener ->
b.root.onClick { listener(item) }
}
}
override fun getItemCount() = items.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
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)
}
class ViewHolder(val b: NotificationsListItemBinding) : RecyclerView.ViewHolder(b.root)
}

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.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.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.databinding.FragmentTemplateBinding
import pl.szczodrzynski.edziennik.databinding.TemplatePageFragmentBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import kotlin.coroutines.CoroutineContext
class TemplateFragment : Fragment(), CoroutineScope {
class TemplatePageFragment : LazyFragment(), CoroutineScope {
companion object {
private const val TAG = "TemplateFragment"
private const val TAG = "TemplatePagerFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: FragmentTemplateBinding
private lateinit var b: TemplatePageFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
@ -32,15 +36,16 @@ class TemplateFragment : Fragment(), CoroutineScope {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
b = FragmentTemplateBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
b = TemplatePageFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded)
return
override fun onPageCreated(): Boolean {
b.text.text = "Fragment $position"
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.TimetableNoTimetableBinding
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_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
@ -36,7 +36,7 @@ import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.min
class TimetableDayFragment : PagerFragment(), CoroutineScope {
class TimetableDayFragment : LazyFragment(), CoroutineScope {
companion object {
private const val TAG = "TimetableDayFragment"
}
@ -104,9 +104,6 @@ class TimetableDayFragment : PagerFragment(), CoroutineScope {
}
override fun onPageCreated(): Boolean {
if (!isAdded)
return false
// observe lesson database
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons ->
launch {
@ -216,23 +213,23 @@ class TimetableDayFragment : PagerFragment(), CoroutineScope {
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 {
lb.event1.visibility = if (it == null) View.GONE else View.VISIBLE
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 {
lb.event2.visibility = if (it == null) View.GONE else View.VISIBLE
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 {
lb.event3.visibility = if (it == null) View.GONE else View.VISIBLE
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
import android.os.Bundle
import androidx.fragment.app.Fragment
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.Week
@ -16,7 +16,7 @@ class TimetablePagerAdapter(
private val items: List<Date>,
private val startHour: Int,
private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
) : LazyPagerAdapter(fragmentManager, null) {
companion object {
private const val TAG = "TimetablePagerAdapter"
}
@ -25,7 +25,7 @@ class TimetablePagerAdapter(
private val weekStart by lazy { today.weekStart }
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 {
arguments = Bundle().apply {
putInt("date", items[position].value)
@ -39,7 +39,7 @@ class TimetablePagerAdapter(
return items.size
}
override fun getPageTitle(position: Int): CharSequence? {
override fun getPageTitle(position: Int): CharSequence {
val date = items[position]
val pageTitle = StringBuilder(Week.getFullDayName(date.weekDay))
if (date > weekEnd || date < weekStart) {

View File

@ -27,7 +27,7 @@ import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import pl.szczodrzynski.edziennik.*
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.Companion.TYPE_NO_LESSONS
import pl.szczodrzynski.edziennik.ui.widgets.LessonDialogActivity
@ -289,7 +289,7 @@ class WidgetTimetableProvider : AppWidgetProvider() {
}
// 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 ->
if (lesson.type == TYPE_NO_LESSONS)
@ -345,9 +345,9 @@ class WidgetTimetableProvider : AppWidgetProvider() {
// add every event on this lesson
for (event in events) {
if (event.startTime == null || event.startTime != lesson.displayStartTime)
if (event.time == null || event.time != lesson.displayStartTime)
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

View File

@ -137,7 +137,7 @@ public class Date implements Comparable<Date> {
}
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) {
@ -254,6 +254,7 @@ public class Date implements Comparable<Date> {
/**
* @return 2019-06-02
*/
@NonNull
public String getStringY_m_d() {
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_height="wrap_content"
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"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:text="@{Integer.toString(event.eventDate.day)}"
android:text="@{Integer.toString(event.date.day)}"
android:textSize="36sp"
tools:text="14" />
@ -107,13 +107,13 @@
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
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
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{event.teacherFullName}"
android:text="@{event.teacherName}"
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" />
<TextView
@ -195,6 +195,18 @@
android:text="\uFCDA"
android:textSize="20sp"
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>
</androidx.core.widget.NestedScrollView>

View File

@ -4,7 +4,8 @@
-->
<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>
<import type="android.view.View"/>
@ -54,22 +55,35 @@
android:id="@+id/topic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_weight="1"
android:textAppearance="@style/NavView.TextView.Medium"
android:maxLines="@{simpleMode ? 2 : 3}"
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" />
<com.google.android.material.button.MaterialButton
android:id="@+id/editButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v3_5_95_1"
android:minWidth="0dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:text="\uFC92"
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>

View File

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

View File

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

View File

@ -16,8 +16,14 @@
android:layout_width="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
android:id="@+id/gradesNoData"
android:id="@+id/noData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -25,13 +31,17 @@
android:fontFamily="sans-serif-light"
android:text="@string/grades_no_data"
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
android:id="@+id/gradesRecyclerView"
android:id="@+id/list"
android:layout_width="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>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout>

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