diff --git a/annotation/.gitignore b/annotation/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/annotation/.gitignore @@ -0,0 +1 @@ +/build diff --git a/annotation/build.gradle b/annotation/build.gradle new file mode 100644 index 00000000..ee7f6d51 --- /dev/null +++ b/annotation/build.gradle @@ -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" + } +} diff --git a/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/SelectiveDao.kt b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/SelectiveDao.kt new file mode 100644 index 00000000..d37e5b0a --- /dev/null +++ b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/SelectiveDao.kt @@ -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<*> +) diff --git a/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/UpdateSelective.kt b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/UpdateSelective.kt new file mode 100644 index 00000000..224fca1c --- /dev/null +++ b/annotation/src/main/java/pl/szczodrzynski/edziennik/annotation/UpdateSelective.kt @@ -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, + val skippedColumns: Array = [] +) diff --git a/app/build.gradle b/app/build.gradle index ca5c9fcd..21392ed3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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() diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 011fb6ab..4a34237a 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,4 +1,10 @@ -

Wersja 4.0-rc.1, 2020-03-26

+

Wersja 4.0-rc.4, 2020-03-30

+ +

Dzięki za korzystanie ze Szkolnego!
diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 70297de3..28d15d2e 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -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); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index 0d7b1f56..311513f4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -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 Collection?.isNotNullNorEmpty(): Boolean { + return this != null && this.isNotEmpty() +} + fun CharSequence?.isNotNullNorBlank(): Boolean { return this != null && this.isNotBlank() } @@ -722,6 +729,13 @@ inline fun T.onClick(crossinline onClickListener: (v: T) -> Unit) { } } +@Suppress("UNCHECKED_CAST") +inline fun T.onLongClick(crossinline onLongClickListener: (v: T) -> Boolean) { + setOnLongClickListener { v: View -> + onLongClickListener(v as T) + } +} + @Suppress("UNCHECKED_CAST") inline fun 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 + } + } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 8f53b625..6abe7be6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt index bf4f743f..98240be5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -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() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt index 6281225d..d956c1ec 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -18,7 +18,7 @@ import kotlin.coroutines.CoroutineContext class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List) : CoroutineScope, AbstractConfig { companion object { - const val DATA_VERSION = 1 + const val DATA_VERSION = 2 } private val job = Job() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt index a30c996a..1095f511 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt @@ -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 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt index 67861d25..17f071f2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt @@ -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 + } }} } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt index e2955190..66b764cd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt @@ -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 } ) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt index cf846fce..52833b9a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt index 8eb7d04e..8e7836fd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt index 9d9a336a..1a9ea5ae 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt index 42202f4e..3a3b9d3b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt index 345e7da4..71ea7ea9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt index bccaa1f9..dc8d8c47 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt @@ -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() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt index a04c2863..1dff3e7d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt index 16d66c6f..d47fbfdf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt index 3a58d8e1..de696a99 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt index 04c44efb..3d4db1aa 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt @@ -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() )) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt index f7234891..30031684 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt @@ -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, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt index f4aa2b3a..5574970f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt @@ -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( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt index 87cd88cf..20c122d3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt index f5cb912b..be389d79 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt @@ -51,17 +51,16 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { 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) { } } - 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)) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt index 4c5b4e03..fe75674f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt @@ -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) { 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( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt index 8fd5677b..6440b04e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt @@ -44,7 +44,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List? 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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt index 9664a68b..c1749d76 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebCalendar.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt index c55dac5d..0c4a0aa3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiEvents.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index b5086539..753160ac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt index ccccc00e..f247ee19 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/DataRemoveModel.kt @@ -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) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt index eb70ab9a..bdd14f33 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt @@ -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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 446ae7ab..8d27929e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -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() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/EventShareRequest.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/EventShareRequest.kt index 624b8b0f..1977d1b2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/EventShareRequest.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/EventShareRequest.kt @@ -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, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt index 56d5132a..63d4ec88 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/AppSync.kt @@ -34,7 +34,7 @@ class AppSync(val app: App, val notifications: MutableList, 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, val pr event.addedDate ) }) - return app.db.eventDao().addAll(events).size + return app.db.eventDao().upsertAll(events).size } return 0; } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt index 7c049f5a..c29d96bb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/task/Notifications.kt @@ -34,6 +34,7 @@ class Notifications(val app: App, val notifications: MutableList, announcementNotifications() messageNotifications() luckyNumberNotifications() + teacherAbsenceNotifications() } private fun timetableNotifications() { @@ -58,7 +59,7 @@ class Notifications(val app: App, val notifications: MutableList, } 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, 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, 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, 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, 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, ) } } + + 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()) + } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index 5b01d476..dfbf8cbe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -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() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt new file mode 100644 index 00000000..8c13dca9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/BaseDao.kt @@ -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 { + @RawQuery + fun getRaw(query: SupportSQLiteQuery): LiveData> + fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query)) + @RawQuery + fun getRawNow(query: SupportSQLiteQuery): List + 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): 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) + + /** + * 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): 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, removeNotKept: Boolean = false): LongArray { + val insertResult = addAll(items) + val updateList = mutableListOf() + + insertResult.forEachIndexed { index, result -> + if (result == -1L) updateList.add(items[index]) + } + + if (updateList.isNotEmpty()) updateAll(items) + if (removeNotKept) removeNotKept() + return insertResult + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.java deleted file mode 100644 index 083b499a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.java +++ /dev/null @@ -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 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> getAll(SupportSQLiteQuery query); - public LiveData> 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> getAll(int profileId) { - return getAll(profileId, "1", ""); - } - public List getAllNow(int profileId) { - return getAllNow(profileId, "1"); - } - public LiveData> getAllWhere(int profileId, String filter) { - return getAll(profileId, filter, ""); - } - public LiveData> getAllByType(int profileId, long type, String filter) { - return getAll(profileId, "eventType = "+type+" AND "+filter, ""); - } - public LiveData> getAllByDate(int profileId, @NonNull Date date) { - return getAll(profileId, "eventDate = '"+date.getStringY_m_d()+"'", ""); - } - public List getAllByDateNow(int profileId, @NonNull Date date) { - return getAllNow(profileId, "eventDate = '"+date.getStringY_m_d()+"'"); - } - public LiveData> 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> getAllNearest(int profileId, @NonNull Date today, int limit) { - return getAll(profileId, "eventDate >= '"+today.getStringY_m_d()+"'", "LIMIT "+limit); - } - - @RawQuery - abstract List getAllNow(SupportSQLiteQuery query); - public List 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 getNotNotifiedNow(int profileId) { - return getAllNow(profileId, "notified = 0"); - } - - @Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1") - public abstract List getBlacklistedIds(int profileId); - @Query("SELECT eventId FROM events WHERE eventBlacklisted = 1") - public abstract List 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 getNotNotifiedNow(); - - public EventFull getByIdNow(int profileId, long eventId) { - List 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 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); -} - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt new file mode 100644 index 00000000..2610537a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventDao.kt @@ -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 { + 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> + + // 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) = 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 + + @get:Query("SELECT eventId FROM events WHERE eventBlacklisted = 1") + abstract val blacklistedIds: List + + /*@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) { + 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) + } + +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.java deleted file mode 100644 index 25366699..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.java +++ /dev/null @@ -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 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> getAll(int profileId); - - @Query("SELECT * FROM eventTypes WHERE profileId = :profileId") - List getAllNow(int profileId); - - @Query("SELECT * FROM eventTypes") - List getAllNow(); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt new file mode 100644 index 00000000..28be6d65 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/EventTypeDao.kt @@ -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) + + @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> + + @Query("SELECT * FROM eventTypes WHERE profileId = :profileId") + abstract fun getAllNow(profileId: Int): List + + @get:Query("SELECT * FROM eventTypes") + abstract val allNow: List + + fun addDefaultTypes(context: Context, profileId: Int): List { + 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 + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java index 845941de..510306fe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/MetadataDao.java @@ -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); } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt index 3da88080..1d4881bb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TeacherAbsenceDao.kt @@ -49,6 +49,17 @@ interface TeacherAbsenceDao { "AND :date BETWEEN teacherAbsenceDateFrom AND teacherAbsenceDateTo") fun getAllByDateNow(profileId: Int, date: Date): List + @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 + @Query("DELETE FROM teacherAbsence WHERE profileId = :profileId") fun clear(profileId: Int) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.java deleted file mode 100644 index bed95bdf..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.java +++ /dev/null @@ -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 + - '}'; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt new file mode 100644 index 00000000..ce8cc3ca --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Event.kt @@ -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? = null + var attachmentNames: List? = 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) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Keepable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Keepable.kt new file mode 100644 index 00000000..eb0da986 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Keepable.kt @@ -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 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt index 242d5999..1e8b8e0e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Notification.kt @@ -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; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.java deleted file mode 100644 index beaf794e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.java +++ /dev/null @@ -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 + - '}'; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt new file mode 100644 index 00000000..a3dcc2cb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/EventFull.kt @@ -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() +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration80.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration80.kt new file mode 100644 index 00000000..86394792 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration80.kt @@ -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") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt new file mode 100644 index 00000000..bf547244 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration81.kt @@ -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}") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration82.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration82.kt new file mode 100644 index 00000000..b0e1c769 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration82.kt @@ -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") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration83.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration83.kt new file mode 100644 index 00000000..340d5768 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration83.kt @@ -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") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index 78d03873..c394c7cc 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -105,18 +105,19 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, 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, 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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/TemplateAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/TemplateAdapter.kt deleted file mode 100644 index 8366862e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/TemplateAdapter.kt +++ /dev/null @@ -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() { - - private val app by lazy { context.applicationContext as App } - - var items = listOf() - - 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) -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt index b46925f0..12b7c71a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt @@ -146,6 +146,11 @@ class DayDialog( adapter = EventListAdapter( activity, + showWeekDay = false, + showDate = false, + showType = true, + showTime = true, + showSubject = true, onItemClick = { EventDetailsDialog( activity, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt index e4d1c144..f2819dd1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt index c70e97cf..9c5fb456 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt @@ -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() { @@ -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( - 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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt index 7af42647..d6d175d9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt index eb8aecd3..ad31c02d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt @@ -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 ) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt index b384cb46..2957f593 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt @@ -170,6 +170,11 @@ class LessonDetailsDialog( adapter = EventListAdapter( activity, + showWeekDay = false, + showDate = false, + showType = true, + showTime = true, + showSubject = true, onItemClick = { EventDetailsDialog( activity, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index bddd805a..9ee93be9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/PagerFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/PagerFragment.kt deleted file mode 100644 index 9a0a7b9c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/PagerFragment.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/FragmentLazyPagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/FragmentLazyPagerAdapter.kt new file mode 100644 index 00000000..4aae09ee --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/FragmentLazyPagerAdapter.kt @@ -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> +) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) { + override fun getPage(position: Int) = fragments[position].first + override fun getPageTitle(position: Int) = fragments[position].second + override fun getCount() = fragments.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt new file mode 100644 index 00000000..1d011510 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt new file mode 100644 index 00000000..b70de59e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt @@ -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 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt new file mode 100644 index 00000000..45337990 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt @@ -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() + } + } + }) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt similarity index 81% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesFragment.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt index ba125d9c..81b990a9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/grades/GradesListFragment.kt @@ -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) { + private fun processGrades(grades: List): MutableList { val items = mutableListOf() 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) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeEventsCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeEventsCard.kt index e6b2b23b..b0c8ec79 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeEventsCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeEventsCard.kt @@ -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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkAdapter.java index c8b42fbb..0a568464 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkAdapter.java @@ -50,17 +50,17 @@ public class HomeworkAdapter extends RecyclerView.Adapter { App.db.metadataDao().setSeen(App.Companion.getProfileId(), homework, true); }); @@ -69,11 +69,11 @@ public class HomeworkAdapter extends RecyclerView.Adapter { new EventManualDialog( (MainActivity) context, - homework.profileId, + homework.getProfileId(), null, null, null, @@ -83,11 +83,11 @@ public class HomeworkAdapter extends RecyclerView.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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt index 950e6172..0cbba646 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt @@ -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 } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt index 572008ab..789bcb07 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt @@ -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) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt index ef6f6518..7a22850f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessageFragment.kt @@ -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 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt index abea430a..c4cbf7f7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt @@ -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() - private val mFragmentTitleList = ArrayList() + internal class Adapter(manager: FragmentManager, swipeRefreshLayout: SwipeRefreshLayout) : LazyPagerAdapter(manager, swipeRefreshLayout) { + private val mFragmentList = mutableListOf() + private val mFragmentTitleList = mutableListOf() - 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] } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java index bfbf749d..fb48c6f5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java @@ -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 messageFulls) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt index 473b9855..8df33a8c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsAdapter.kt @@ -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() { + private val activity: AppCompatActivity, + val onItemClick: ((item: Notification) -> Unit)? = null +) : RecyclerView.Adapter(), 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() 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) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsFragment.kt deleted file mode 100644 index 74d01bd1..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsFragment.kt +++ /dev/null @@ -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 - } - }) - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsListFragment.kt new file mode 100644 index 00000000..96f25bd5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/notifications/NotificationsListFragment.kt @@ -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 + } + }) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateAdapter.kt new file mode 100644 index 00000000..fe78396f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateAdapter.kt @@ -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(), 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() + + 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) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/TemplateDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateDialog.kt similarity index 95% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/TemplateDialog.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateDialog.kt index a5e2dbeb..e5d68ebe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/TemplateDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateDialog.kt @@ -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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateFragment.kt new file mode 100644 index 00000000..1dff7db0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateFragment.kt @@ -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) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateListFragment.kt new file mode 100644 index 00000000..e2d6c220 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateListFragment.kt @@ -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 + } + }) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateListPageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateListPageFragment.kt new file mode 100644 index 00000000..ce5c5001 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplateListPageFragment.kt @@ -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 } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/TemplateFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplatePageFragment.kt similarity index 56% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/TemplateFragment.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplatePageFragment.kt index 3151dcb4..f2404ea4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/TemplateFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplatePageFragment.kt @@ -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 } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplatePagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplatePagerAdapter.kt new file mode 100644 index 00000000..eaa8ba1d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/template/TemplatePagerAdapter.kt @@ -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 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index da2450e4..848d0801 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -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) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt index 31851498..289860ac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt @@ -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, 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) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt index 7b68a10d..bc284ba2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/widgets/timetable/WidgetTimetableProvider.kt @@ -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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java index 5f889bfb..c1d32538 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java @@ -137,7 +137,7 @@ public class Date implements Comparable { } 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 { /** * @return 2019-06-02 */ + @NonNull public String getStringY_m_d() { return year + (month < 10 ? "-0" : "-") + month + (day < 10 ? "-0" : "-") + day; } diff --git a/app/src/main/res/drawable/ic_no_homework.xml b/app/src/main/res/drawable/ic_no_homework.xml new file mode 100644 index 00000000..d8ba8c38 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_homework.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_event_details.xml b/app/src/main/res/layout/dialog_event_details.xml index f23e9093..d06c71bb 100644 --- a/app/src/main/res/layout/dialog_event_details.xml +++ b/app/src/main/res/layout/dialog_event_details.xml @@ -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"/> @@ -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}"/> + + diff --git a/app/src/main/res/layout/event_list_item.xml b/app/src/main/res/layout/event_list_item.xml index f43a7637..62c0922e 100644 --- a/app/src/main/res/layout/event_list_item.xml +++ b/app/src/main/res/layout/event_list_item.xml @@ -4,7 +4,8 @@ --> + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> @@ -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" /> + tools:visibility="gone" /> + + diff --git a/app/src/main/res/layout/fragment_messages.xml b/app/src/main/res/layout/fragment_messages.xml index c3da1ff5..bb22dc0b 100644 --- a/app/src/main/res/layout/fragment_messages.xml +++ b/app/src/main/res/layout/fragment_messages.xml @@ -21,7 +21,7 @@ app:tabSelectedTextColor="?colorPrimary" app:tabTextColor="?android:textColorPrimary"/> - - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_timetable_v2.xml b/app/src/main/res/layout/fragment_timetable_v2.xml index c14b26e5..953638cf 100644 --- a/app/src/main/res/layout/fragment_timetable_v2.xml +++ b/app/src/main/res/layout/fragment_timetable_v2.xml @@ -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" /> - - \ No newline at end of file + diff --git a/app/src/main/res/layout/grades_fragment.xml b/app/src/main/res/layout/grades_list_fragment.xml similarity index 67% rename from app/src/main/res/layout/grades_fragment.xml rename to app/src/main/res/layout/grades_list_fragment.xml index a9702e8c..faf8264d 100644 --- a/app/src/main/res/layout/grades_fragment.xml +++ b/app/src/main/res/layout/grades_list_fragment.xml @@ -16,8 +16,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + android:visibility="gone" + app:drawableTopCompat="@drawable/ic_no_grades" + tools:visibility="visible" /> + android:visibility="gone" + tools:listitem="@layout/grades_item_subject" + tools:visibility="visible" /> diff --git a/app/src/main/res/layout/fragment_homework.xml b/app/src/main/res/layout/homework_fragment.xml similarity index 86% rename from app/src/main/res/layout/fragment_homework.xml rename to app/src/main/res/layout/homework_fragment.xml index 91d904be..9a015c15 100644 --- a/app/src/main/res/layout/fragment_homework.xml +++ b/app/src/main/res/layout/homework_fragment.xml @@ -1,7 +1,10 @@ + + + xmlns:app="http://schemas.android.com/apk/res-auto"> - - diff --git a/app/src/main/res/layout/homework_list.xml b/app/src/main/res/layout/homework_list.xml deleted file mode 100644 index bfcc4960..00000000 --- a/app/src/main/res/layout/homework_list.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/homework_list_fragment.xml b/app/src/main/res/layout/homework_list_fragment.xml new file mode 100644 index 00000000..d53b9d17 --- /dev/null +++ b/app/src/main/res/layout/homework_list_fragment.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/message_fragment.xml b/app/src/main/res/layout/message_fragment.xml index d33f3666..ded95825 100644 --- a/app/src/main/res/layout/message_fragment.xml +++ b/app/src/main/res/layout/message_fragment.xml @@ -231,8 +231,7 @@ android:ellipsize="middle" android:text="Wyniki sprawdzianu z matematyki.pdf" android:visibility="visible" - app:chipIcon="@drawable/googleg_standard_color_18" - app:chipMinHeight="36dp" /> + app:chipIcon="@drawable/googleg_standard_color_18" /> + + + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + - + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/notifications_list_item.xml b/app/src/main/res/layout/notifications_list_item.xml new file mode 100644 index 00000000..498f7e21 --- /dev/null +++ b/app/src/main/res/layout/notifications_list_item.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/row_notifications_item.xml b/app/src/main/res/layout/row_notifications_item.xml deleted file mode 100644 index 3ef2f6d8..00000000 --- a/app/src/main/res/layout/row_notifications_item.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/template_fragment.xml b/app/src/main/res/layout/template_fragment.xml new file mode 100644 index 00000000..9a015c15 --- /dev/null +++ b/app/src/main/res/layout/template_fragment.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/template_list_fragment.xml b/app/src/main/res/layout/template_list_fragment.xml new file mode 100644 index 00000000..79646608 --- /dev/null +++ b/app/src/main/res/layout/template_list_fragment.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/template_list_item.xml b/app/src/main/res/layout/template_list_item.xml index 7d566475..d47227b3 100644 --- a/app/src/main/res/layout/template_list_item.xml +++ b/app/src/main/res/layout/template_list_item.xml @@ -3,16 +3,39 @@ ~ Copyright (c) Kuba Szczodrzyński 2019-12-19. --> - + + android:padding="8dp"> + + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/template_list_page_fragment.xml b/app/src/main/res/layout/template_list_page_fragment.xml new file mode 100644 index 00000000..09f8bfda --- /dev/null +++ b/app/src/main/res/layout/template_list_page_fragment.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/template_page_fragment.xml b/app/src/main/res/layout/template_page_fragment.xml new file mode 100644 index 00000000..a83f29b6 --- /dev/null +++ b/app/src/main/res/layout/template_page_fragment.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index a5e3c23a..f8bc7c51 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -493,6 +493,7 @@ %s shared %s on %s - %s %s changed %s na %s - %s %s removed %s na %s - %s + New teacher absence for %s Szkolny.eu: %s Attendance Profile archiving @@ -506,6 +507,7 @@ New homework New message New shared event + New teacher absence New notice Server message Timetable change diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ccb3a8fc..b0cc74fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -554,6 +554,7 @@ %s udostępnił %s na %s: %s %s zmienił %s na %s: %s %s usunął %s na %s: %s + Nowa nieobecność nauczyciela dla %s Szkolny.eu: %s Wpis frekwencji Archiwizacja profilu @@ -567,6 +568,7 @@ Nowe zadanie domowe Nowa wiadomość Udostępniono wydarzenie + Nowa nieobecność nauczyciela Wpis zachowania Wiadomość z serwera Zmiana planu zajęć @@ -1276,4 +1278,7 @@ Rejestracja na serwerze Rejestracja jest automatyczna, jeśli ta opcja jest włączona. Pozwala na tworzenie i odbieranie wydarzeń udostępnionych innym uczniom z Twojej klasy. Dzięki temu, można dodawać do dziennika pozycje nie zapisane przez nauczyciela.\n\nUpewnij się, że zapoznałeś się z warunkami Polityki prywatności i akceptujesz jej postanowienia. Dodaj lub usuń karty + Template + Pobierz ponownie + Oznacz jako wykonane diff --git a/build.gradle b/build.gradle index eb9afa6b..387b5c80 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { kotlin_version = '1.3.61' release = [ - versionName: "4.0-rc.1", - versionCode: 4000019 + versionName: "4.0-rc.4", + versionCode: 4000049 ] setup = [ diff --git a/codegen/.gitignore b/codegen/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/codegen/.gitignore @@ -0,0 +1 @@ +/build diff --git a/codegen/build.gradle b/codegen/build.gradle new file mode 100644 index 00000000..e2630bd0 --- /dev/null +++ b/codegen/build.gradle @@ -0,0 +1,39 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +apply plugin: 'java-library' +apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' + +kapt { + generateStubs = true +} + +sourceSets { + main { + java { + srcDir "${buildDir.absolutePath}/tmp/kapt/main/kotlinGenerated/" + } + } +} + + +dependencies { + kapt project(":annotation") + compileOnly project(':annotation') + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + // configuration generator for service providers + implementation "com.google.auto.service:auto-service:1.0-rc4" + kapt "com.google.auto.service:auto-service:1.0-rc4" + kapt "androidx.room:room-compiler:${versions.room}" + implementation "androidx.room:room-runtime:${versions.room}" + implementation "com.squareup:kotlinpoet:1.5.0" + implementation "androidx.sqlite:sqlite:2.1.0@aar" + +} + +sourceCompatibility = "7" +targetCompatibility = "7" diff --git a/codegen/src/main/java/pl/szczodrzynski/edziennik/codegen/FileGenerator.kt b/codegen/src/main/java/pl/szczodrzynski/edziennik/codegen/FileGenerator.kt new file mode 100644 index 00000000..d1a9fefa --- /dev/null +++ b/codegen/src/main/java/pl/szczodrzynski/edziennik/codegen/FileGenerator.kt @@ -0,0 +1,339 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-28. + */ + +package pl.szczodrzynski.edziennik.codegen + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.TypeConverters +import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import pl.szczodrzynski.edziennik.annotation.SelectiveDao +import pl.szczodrzynski.edziennik.annotation.UpdateSelective +import java.io.File +import javax.annotation.processing.* +import javax.lang.model.SourceVersion +import javax.lang.model.element.* +import javax.lang.model.type.* +import javax.lang.model.util.ElementFilter +import javax.tools.Diagnostic +import kotlin.reflect.KClass + +@Suppress("unused") +@AutoService(Processor::class) +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedOptions(FileGenerator.KAPT_KOTLIN_GENERATED_OPTION_NAME) +class FileGenerator : AbstractProcessor() { + companion object { + const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated" + } + + private data class TypeConverter(val dataType: TypeMirror, val converterType: TypeElement, val methodName: Name, val returnType: TypeMirror) + + private inline fun Element.getAnnotationClassValue(f: T.() -> KClass<*>) = try { + getAnnotation(T::class.java).f() + throw Exception("Expected to get a MirroredTypeException") + } catch (e: MirroredTypeException) { + e.typeMirror + } + private inline fun Element.getAnnotationClassValues(f: T.() -> Array>) = try { + getAnnotation(T::class.java).f() + throw Exception("Expected to get a MirroredTypesException") + } catch (e: MirroredTypesException) { + e.typeMirrors + } + + override fun process(set: MutableSet?, roundEnvironment: RoundEnvironment?): Boolean { + roundEnvironment?.getElementsAnnotatedWith(SelectiveDao::class.java)?.forEach { it -> + if (it.kind != ElementKind.CLASS) { + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can only be applied to classes, element: $it") + return false + } + + val generatedSourcesRoot = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME] + if (generatedSourcesRoot?.isEmpty() != false) { + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can't find the target directory for generated Kotlin files.") + return false + } + + val file = File(generatedSourcesRoot) + file.mkdirs() + + val dao = it as TypeElement + processClass(dao, file) + + //processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "package = $packageName, className = $className, methodName = $methodName, tableName = $tableName, paramName = $paramName, paramClass = $paramClass") + } + return true + } + + private fun processClass(dao: TypeElement, file: File) { + val daoName = dao.simpleName.toString() + val packageName = processingEnv.elementUtils.getPackageOf(dao).toString() + + val dbType = processingEnv.typeUtils.asElement(dao.getAnnotationClassValue { db }) as TypeElement + val typeConverters = dbType.getAnnotationClassValues { value }.map { + processingEnv.typeUtils.asElement(it) as TypeElement + }.map { type -> + processingEnv.elementUtils.getAllMembers(type).mapNotNull { element -> + if (element is ExecutableElement) { + if (element.returnType.toString() == "java.lang.String" + || element.returnType.toString() == "java.lang.Long" + || element.returnType.toString() == "java.lang.Integer" + || element.returnType.kind.isPrimitive) { + if (element.simpleName.startsWith("to") && element.parameters.isNotEmpty()) + return@mapNotNull TypeConverter(element.parameters.first().asType(), type, element.simpleName, element.returnType) + } + } + null + } + }.flatten() + + //processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "c = ${typeConverters.joinToString()}") + + val roomDatabase = ClassName("androidx.room", "RoomDatabase") + val selective = TypeSpec.classBuilder("${daoName}Selective") + .primaryConstructor(FunSpec.constructorBuilder() + .addParameter("__db", roomDatabase, KModifier.PRIVATE) + .build()) + .addProperty(PropertySpec.builder("__db", roomDatabase) + .initializer("__db") + .addModifiers(KModifier.PRIVATE) + .build()) + + val usedTypeConverters = mutableSetOf() + + processingEnv.elementUtils.getAllMembers(dao).forEach { element -> + if (element.kind != ElementKind.METHOD) + return@forEach + val method = element as ExecutableElement + val annotation = method.getAnnotation(UpdateSelective::class.java) ?: return@forEach + usedTypeConverters.addAll(processMethod(selective, method, annotation, typeConverters)) + } + + usedTypeConverters.forEach { converter -> + selective.addProperty(PropertySpec.builder("__${converter.converterType.simpleName}", converter.converterType.asType().asTypeName(), KModifier.PRIVATE) + .delegate(CodeBlock.builder() + .beginControlFlow("lazy") + .addStatement("%T()", converter.converterType.asType().asTypeName()) + .endControlFlow() + .build()) + .build()) + } + + FileSpec.builder(packageName, "${daoName}Selective") + .addType(selective.build()) + .build() + .writeTo(file) + } + + private fun VariableElement.name() = getAnnotation(ColumnInfo::class.java)?.name ?: simpleName.toString() + + private fun processMethod(cls: TypeSpec.Builder, method: ExecutableElement, annotation: UpdateSelective, typeConverters: List): List { + val methodName = method.simpleName.toString() + val parameter = method.parameters.first() + val paramName = parameter.simpleName.toString() + val paramTypeElement = processingEnv.typeUtils.asElement(parameter.asType()) as TypeElement + val paramTypeAnnotation = paramTypeElement.getAnnotation(Entity::class.java) + + val tableName = paramTypeAnnotation.tableName + val primaryKeys = annotation.primaryKeys + val skippedColumns = annotation.skippedColumns + + + var members = processingEnv.elementUtils.getAllMembers(paramTypeElement) + val allFields = ElementFilter.fieldsIn(members) + + // check all super classes + var superType = paramTypeElement.superclass + while (superType !is NoType) { + val superTypeElement = processingEnv.typeUtils.asElement(superType) as TypeElement + members = processingEnv.elementUtils.getAllMembers(superTypeElement) + allFields += ElementFilter.fieldsIn(members) + superType = superTypeElement.superclass + } + + allFields.removeAll { skippedColumns.contains(it.name()) } + allFields.removeAll { it.getAnnotation(Ignore::class.java) != null } + allFields.removeAll { field -> field.modifiers.any { it == Modifier.STATIC || it == Modifier.FINAL } } + val allFieldsDistinct = allFields.distinct() + + val fields = allFieldsDistinct.filterNot { primaryKeys.contains(it.name()) } + val primaryFields = allFieldsDistinct.filter { primaryKeys.contains(it.name()) } + val fieldNames = fields.map { it.name() } + val primaryFieldNames = primaryFields.map { it.name() } + + val fieldNamesQuery = fieldNames.joinToString { "$it = ?" } + val primaryFieldNamesQuery = primaryFieldNames.joinToString(" AND ") { "$it = ?" } + val query = "\"\"\"UPDATE $tableName SET $fieldNamesQuery WHERE $primaryFieldNamesQuery\"\"\"" + + val entityInsertionAdapter = ClassName("androidx.room", "EntityInsertionAdapter") + val supportSQLiteStatement = ClassName("androidx.sqlite.db", "SupportSQLiteStatement") + + val usedTypeConverters = mutableListOf() + + val bind = CodeBlock.builder() + (fields+primaryFields).forEachIndexed { i, field -> + val index = i+1 + val fieldName = field.simpleName.toString() + val name = "${paramName}_$fieldName" + val realName = "${paramName}.$fieldName" + val nullable = field.getAnnotation(org.jetbrains.annotations.Nullable::class.java) != null + + var param = when (field.asType().kind) { + TypeKind.BOOLEAN -> "if ($name) 1L else 0L" + TypeKind.BYTE, + TypeKind.SHORT, + TypeKind.INT -> "$name.toLong()" + TypeKind.CHAR -> "$name.toString()" + TypeKind.FLOAT -> "$name.toDouble()" + else -> when (field.asType().toString()) { + "java.lang.String" -> name + "java.lang.Boolean" -> "if ($name == true) 1L else 0L" + "java.lang.Byte", + "java.lang.Short", + "java.lang.Integer" -> "$name.toLong()" + "java.lang.Long" -> name + "java.lang.Char" -> "$name.toString()" + "java.lang.Float" -> "$name.toDouble()" + "java.lang.Double" -> name + else -> name + } + } + + var isConvert = false + val bindMethod = when (field.asType().kind) { + TypeKind.BOOLEAN -> "bindLong" + TypeKind.BYTE -> "bindLong" + TypeKind.SHORT -> "bindLong" + TypeKind.INT -> "bindLong" + TypeKind.LONG -> "bindLong" + TypeKind.CHAR -> "bindString" + TypeKind.FLOAT -> "bindDouble" + TypeKind.DOUBLE -> "bindDouble" + else -> when (field.asType().toString()) { + "java.lang.String" -> "bindString" + "java.lang.Boolean" -> "bindLong" + "java.lang.Byte" -> "bindLong" + "java.lang.Short" -> "bindLong" + "java.lang.Integer" -> "bindLong" + "java.lang.Long" -> "bindLong" + "java.lang.Char" -> "bindString" + "java.lang.Float" -> "bindDouble" + "java.lang.Double" -> "bindDouble" + else -> { + val converter = typeConverters.firstOrNull { + it.dataType.toString() == field.asType().toString() + } + if (converter != null) { + param = "__${converter.converterType.simpleName}.${converter.methodName}($realName)" + param = when (converter.returnType.toString()) { + "java.lang.Integer", "int", + "java.lang.Short", "short", + "java.lang.Byte", "byte" -> "$param.toLong()" + "java.lang.Boolean", "boolean" -> "if ($param) 1L else 0L" + "java.lang.Char", "char" -> "$param.toString()" + "java.lang.Float", "float" -> "$param.toDouble()" + else -> param + } + isConvert = true + usedTypeConverters += converter + when (converter.returnType.toString()) { + "java.lang.Integer", "int", + "java.lang.Short", "short", + "java.lang.Byte", "byte", + "java.lang.Boolean", "boolean" -> "bindLong" + "java.lang.Char", "char" -> "bindString" + "java.lang.Float", "float" -> "bindDouble" + else -> "bindString" + } + } + else "bind${field.asType()}" + } + } + } + + if (!isConvert) { + bind.addStatement("val $name = $realName") + } + else { + bind.addStatement("val $name = $param") + param = name + } + if (nullable) { + bind.beginControlFlow("if ($name == null)") + .addStatement("stmt.bindNull($index)") + .endControlFlow() + .beginControlFlow("else") + .addStatement("stmt.$bindMethod($index, $param)") + .endControlFlow() + } + else { + bind.addStatement("stmt.$bindMethod($index, $param)") + } + } + + val adapterName = "__insertionAdapterOf$methodName" + val delegate = CodeBlock.builder().add(""" + |lazy { + | object : EntityInsertionAdapter<%T>(__db) { + | override fun createQuery() = $query + | override fun bind(stmt: %T, $paramName: %T) { + |${bind.indent().indent().indent().build()} + | } + | } + |}""".trimMargin(), paramTypeElement.asClassName(), supportSQLiteStatement, paramTypeElement.asClassName()) + + cls.addProperty(PropertySpec.builder(adapterName, entityInsertionAdapter.parameterizedBy(paramTypeElement.asClassName()), KModifier.PRIVATE) + .delegate(delegate.build()) + .build()) + + val list = ClassName("kotlin.collections", "List") + val longArray = ClassName("kotlin", "LongArray") + + val function = FunSpec.builder(methodName) + .addModifiers(KModifier.INTERNAL) + .addParameter("item", parameter.asType().asTypeName()) + .returns(Long::class.java) + .addStatement("__db.assertNotSuspendingTransaction()") + .addStatement("__db.beginTransaction()") + .addCode(""" + |try { + | val _result = $adapterName.insertAndReturnId(item) + | __db.setTransactionSuccessful() + | return _result + |} finally { + | __db.endTransaction() + |} + """.trimMargin()) + .build() + + val functionAll = FunSpec.builder(methodName+"All") + .addModifiers(KModifier.INTERNAL) + .addParameter("items", list.parameterizedBy(parameter.asType().asTypeName())) + .returns(longArray) + .addStatement("__db.assertNotSuspendingTransaction()") + .addStatement("__db.beginTransaction()") + .addCode(""" + |try { + | val _result = $adapterName.insertAndReturnIdsArray(items) + | __db.setTransactionSuccessful() + | return _result + |} finally { + | __db.endTransaction() + |} + """.trimMargin()) + .build() + + cls.addFunction(function) + cls.addFunction(functionAll) + return usedTypeConverters + } + + override fun getSupportedAnnotationTypes(): MutableSet { + return mutableSetOf(SelectiveDao::class.java.canonicalName) + } +} diff --git a/settings.gradle b/settings.gradle index d2e0a356..3625fac9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ +include ':codegen' +include ':annotation' rootProject.name='Szkolny.eu' include ':app', ':agendacalendarview', ':mhttp', ':material-about-library', ':cafebar', ':szkolny-font', ':nachos' /*