Merge branch 'develop'

This commit is contained in:
Kuba Szczodrzyński 2021-05-26 22:32:14 +02:00
commit c011f550bb
No known key found for this signature in database
GPG Key ID: 70CB8A85BA1633CB
117 changed files with 3345 additions and 1623 deletions

View File

@ -15,6 +15,7 @@
<match> <match>
<AND> <AND>
<NAME>xmlns:android</NAME> <NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -25,6 +26,7 @@
<match> <match>
<AND> <AND>
<NAME>xmlns:.*</NAME> <NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -36,6 +38,7 @@
<match> <match>
<AND> <AND>
<NAME>.*:id</NAME> <NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -46,6 +49,7 @@
<match> <match>
<AND> <AND>
<NAME>.*:name</NAME> <NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -56,6 +60,7 @@
<match> <match>
<AND> <AND>
<NAME>name</NAME> <NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -66,6 +71,7 @@
<match> <match>
<AND> <AND>
<NAME>style</NAME> <NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -76,6 +82,7 @@
<match> <match>
<AND> <AND>
<NAME>.*</NAME> <NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE> <XML_NAMESPACE>^$</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -87,6 +94,7 @@
<match> <match>
<AND> <AND>
<NAME>.*</NAME> <NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND> </AND>
</match> </match>
@ -98,6 +106,7 @@
<match> <match>
<AND> <AND>
<NAME>.*</NAME> <NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE> <XML_NAMESPACE>.*</XML_NAMESPACE>
</AND> </AND>
</match> </match>

View File

@ -5,6 +5,7 @@
<w>ciasteczko</w> <w>ciasteczko</w>
<w>csrf</w> <w>csrf</w>
<w>edziennik</w> <w>edziennik</w>
<w>elearning</w>
<w>gson</w> <w>gson</w>
<w>hebe</w> <w>hebe</w>
<w>idziennik</w> <w>idziennik</w>

View File

@ -152,7 +152,8 @@ dependencies {
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
// Szkolny.eu libraries/forks // Szkolny.eu libraries/forks
implementation "eu.szkolny:agendacalendarview:1799f8ef47" implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
implementation "eu.szkolny:agendacalendarview:5431f03098"
implementation "eu.szkolny:cafebar:5bf0c618de" implementation "eu.szkolny:cafebar:5bf0c618de"
implementation "eu.szkolny.fslogin:lib:2.0.0" implementation "eu.szkolny.fslogin:lib:2.0.0"
implementation "eu.szkolny:material-about-library:1d5ebaf47c" implementation "eu.szkolny:material-about-library:1d5ebaf47c"
@ -180,7 +181,6 @@ dependencies {
implementation "com.github.bassaer:chatmessageview:2.0.1" implementation "com.github.bassaer:chatmessageview:2.0.1"
implementation "com.github.CanHub:Android-Image-Cropper:2.2.2" implementation "com.github.CanHub:Android-Image-Cropper:2.2.2"
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1" implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
implementation "com.github.jetradarmobile:android-snowfall:1.2.0"
implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31" implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31"
implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } } implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
implementation "com.hypertrack:hyperlog:0.0.10" implementation "com.hypertrack:hyperlog:0.0.10"

View File

@ -32,8 +32,9 @@
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider -keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); } -keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); } -keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepclassmembernames class androidx.appcompat.view.menu.MenuItemImpl { private *; }
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; } -keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }

View File

@ -1,13 +1,13 @@
<h3>Wersja 4.7, 2021-04-07</h3> <h3>Wersja 4.8, 2021-05-26</h3>
<ul> <ul>
<li><u>Szkolny.eu jest teraz open source!</u> Zapraszamy na stronę <a href="https://szkolny.eu/">https://szkolny.eu/</a> po więcej ważnych informacji.</li> <li>Dodano ikony dla powiadomień. @Luncenok</li>
<li>Poprawiono wybieranie obrazków (tła nagłówka, tła aplikacji oraz profilu) z dowolnego źródła.</li> <li>Terminarz: opcje konfiguracji, widok kompaktowy, grupowanie wydarzeń, znaczki nieprzeczytanych, nowe ikony i wiele innych usprawnień.</li>
<li>Ukończono tłumaczenie na język angielski. @MarcinK50</li> <li>Wiadomości: usprawiono wyszukiwanie - zapisywanie szukanego tekstu po wejściu w wiadomość.</li>
<li>Dodano ekran informacji o kompilacji w Ustawieniach.</li> <li>Wiadomości: dodano opcję konfiguracji podpisu przy wysyłaniu wiadomości.</li>
<li>Zaktualizowano ekran licencji open source.</li> <li>Plan lekcji: dodano znacznik aktualnej pory dnia w planie lekcji.</li>
<li>Naprawiono zatrzymanie aplikacji na Androidzie 4.4 i starszych.</li> <li>Powiadomienia: dodano szczegółowy opis po rozwinięciu.</li>
<li>Naprawiono problemy z połączeniem internetowym na Androidzie 4.4 i starszych.</li> <li>Wydarzenia: nowy rodzaj "lekcja online".</li>
<li>Zoptymalizowano wielkość aplikacji.</li> <li>Naprawiono odbieranie nagrody w easter egg'u.</li>
</ul> </ul>
<br> <br>
<br> <br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/ /*secret password - removed for source code publication*/
static toys AES_IV[16] = { static toys AES_IV[16] = {
0xda, 0x9f, 0xd4, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 0x71, 0xcf, 0xdf, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
import android.text.style.CharacterStyle
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
@ -552,28 +553,46 @@ fun CharSequence?.asBoldSpannable(): Spannable {
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable return spannable
} }
fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable { fun CharSequence.asSpannable(
vararg spans: CharacterStyle,
substring: CharSequence? = null,
ignoreCase: Boolean = false,
ignoreDiacritics: Boolean = false
): Spannable {
val spannable = SpannableString(this) val spannable = SpannableString(this)
if (substring == null) { substring?.let { substr ->
spans.forEach { val string = if (ignoreDiacritics)
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
else if (substring.isNotEmpty()) {
val string =
if (ignoreDiacritics)
this.cleanDiacritics() this.cleanDiacritics()
else this else
this
val search = if (ignoreDiacritics)
substr.cleanDiacritics()
else
substr.toString()
var index = string.indexOf(substring, ignoreCase = ignoreCase) var index = 0
.takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase) do {
while (index >= 0) { index = string.indexOf(
string = search,
startIndex = index,
ignoreCase = ignoreCase
)
if (index >= 0) {
spans.forEach { spans.forEach {
spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(
CharacterStyle.wrap(it),
index,
index + substring.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase) index += substring.length.coerceAtLeast(1)
.takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
} }
} while (index >= 0)
} ?: spans.forEach {
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} }
return spannable return spannable
} }

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.Observer
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.danimahardhika.cafebar.CafeBar import com.danimahardhika.cafebar.CafeBar
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jetradarmobile.snowfall.SnowfallView
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
@ -81,12 +82,9 @@ import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch import pl.szczodrzynski.edziennik.utils.*
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
import pl.szczodrzynski.edziennik.utils.appManagerIntentList
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.edziennik.utils.models.NavTarget
import pl.szczodrzynski.navlib.* import pl.szczodrzynski.navlib.*
@ -470,9 +468,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// IT'S WINTER MY DUDES // IT'S WINTER MY DUDES
val today = Date.getToday() val today = Date.getToday()
if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) { if ((today.month % 11 == 1) && app.config.ui.snowfall) {
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
} }
else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
val eggfall = layoutInflater.inflate(R.layout.eggfall, b.rootFrame, false) as SnowfallView
eggfall.setSnowflakeBitmaps(listOf(
BitmapFactory.decodeResource(resources, R.drawable.egg1),
BitmapFactory.decodeResource(resources, R.drawable.egg2),
BitmapFactory.decodeResource(resources, R.drawable.egg3),
BitmapFactory.decodeResource(resources, R.drawable.egg4),
BitmapFactory.decodeResource(resources, R.drawable.egg5),
BitmapFactory.decodeResource(resources, R.drawable.egg6)
))
b.rootFrame.addView(eggfall)
}
// WHAT'S NEW DIALOG // WHAT'S NEW DIALOG
if (app.config.appVersion < BuildConfig.VERSION_CODE) { if (app.config.appVersion < BuildConfig.VERSION_CODE) {

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.config
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.config.utils.get import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.getIntList import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set import pl.szczodrzynski.edziennik.config.utils.set
@ -123,6 +124,19 @@ class ConfigSync(private val config: Config) {
private var mRegisterAvailability: Map<String, RegisterAvailabilityStatus>? = null private var mRegisterAvailability: Map<String, RegisterAvailabilityStatus>? = null
var registerAvailability: Map<String, RegisterAvailabilityStatus> var registerAvailability: Map<String, RegisterAvailabilityStatus>
get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson<Map<String, RegisterAvailabilityStatus>>(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type) }; return mRegisterAvailability ?: mapOf() } get() {
set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value } val flavor = config.values.get("registerAvailabilityFlavor", null as String?)
if (BuildConfig.FLAVOR != flavor)
return mapOf()
mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it ->
gson.fromJson(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type)
}
return mRegisterAvailability ?: mapOf()
}
set(value) {
config.setMap("registerAvailability", value)
config.set("registerAvailabilityFlavor", BuildConfig.FLAVOR)
mRegisterAvailability = value
}
} }

View File

@ -49,6 +49,11 @@ class ConfigUI(private val config: Config) {
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false } get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
set(value) { config.set("snowfall", value); mSnowfall = value } set(value) { config.set("snowfall", value); mSnowfall = value }
private var mEggfall: Boolean? = null
var eggfall: Boolean
get() { mEggfall = mEggfall ?: config.values.get("eggfall", false); return mEggfall ?: false }
set(value) { config.set("eggfall", value); mEggfall = value }
private var mBottomSheetOpened: Boolean? = null private var mBottomSheetOpened: Boolean? = null
var bottomSheetOpened: Boolean var bottomSheetOpened: Boolean
get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false } get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false }

View File

@ -15,8 +15,58 @@ class ProfileConfigUI(private val config: ProfileConfig) {
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT } get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value } set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
private var mAgendaCompactMode: Boolean? = null
var agendaCompactMode: Boolean
get() { mAgendaCompactMode = mAgendaCompactMode ?: config.values.get("agendaCompactMode", false); return mAgendaCompactMode ?: false }
set(value) { config.set("agendaCompactMode", value); mAgendaCompactMode = value }
private var mAgendaGroupByType: Boolean? = null
var agendaGroupByType: Boolean
get() { mAgendaGroupByType = mAgendaGroupByType ?: config.values.get("agendaGroupByType", false); return mAgendaGroupByType ?: false }
set(value) { config.set("agendaGroupByType", value); mAgendaGroupByType = value }
private var mAgendaLessonChanges: Boolean? = null
var agendaLessonChanges: Boolean
get() { mAgendaLessonChanges = mAgendaLessonChanges ?: config.values.get("agendaLessonChanges", true); return mAgendaLessonChanges ?: true }
set(value) { config.set("agendaLessonChanges", value); mAgendaLessonChanges = value }
private var mAgendaTeacherAbsence: Boolean? = null
var agendaTeacherAbsence: Boolean
get() { mAgendaTeacherAbsence = mAgendaTeacherAbsence ?: config.values.get("agendaTeacherAbsence", true); return mAgendaTeacherAbsence ?: true }
set(value) { config.set("agendaTeacherAbsence", value); mAgendaTeacherAbsence = value }
private var mAgendaElearningMark: Boolean? = null
var agendaElearningMark: Boolean
get() { mAgendaElearningMark = mAgendaElearningMark ?: config.values.get("agendaElearningMark", false); return mAgendaElearningMark ?: false }
set(value) { config.set("agendaElearningMark", value); mAgendaElearningMark = value }
private var mAgendaElearningGroup: Boolean? = null
var agendaElearningGroup: Boolean
get() { mAgendaElearningGroup = mAgendaElearningGroup ?: config.values.get("agendaElearningGroup", true); return mAgendaElearningGroup ?: true }
set(value) { config.set("agendaElearningGroup", value); mAgendaElearningGroup = value }
private var mHomeCards: List<HomeCardModel>? = null private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel> var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
set(value) { config.set("homeCards", value); mHomeCards = value } set(value) { config.set("homeCards", value); mHomeCards = value }
private var mMessagesGreetingOnCompose: Boolean? = null
var messagesGreetingOnCompose: Boolean
get() { mMessagesGreetingOnCompose = mMessagesGreetingOnCompose ?: config.values.get("messagesGreetingOnCompose", true); return mMessagesGreetingOnCompose ?: true }
set(value) { config.set("messagesGreetingOnCompose", value); mMessagesGreetingOnCompose = value }
private var mMessagesGreetingOnReply: Boolean? = null
var messagesGreetingOnReply: Boolean
get() { mMessagesGreetingOnReply = mMessagesGreetingOnReply ?: config.values.get("messagesGreetingOnReply", true); return mMessagesGreetingOnReply ?: true }
set(value) { config.set("messagesGreetingOnReply", value); mMessagesGreetingOnReply = value }
private var mMessagesGreetingOnForward: Boolean? = null
var messagesGreetingOnForward: Boolean
get() { mMessagesGreetingOnForward = mMessagesGreetingOnForward ?: config.values.get("messagesGreetingOnForward", false); return mMessagesGreetingOnForward ?: false }
set(value) { config.set("messagesGreetingOnForward", value); mMessagesGreetingOnForward = value }
private var mMessagesGreetingText: String? = null
var messagesGreetingText: String?
get() { mMessagesGreetingText = mMessagesGreetingText ?: config.values["messagesGreetingText"]; return mMessagesGreetingText }
set(value) { config.set("messagesGreetingText", value); mMessagesGreetingText = value }
} }

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.fixName import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.net.URLEncoder
class MobidziennikWebMessagesAll(override val data: DataMobidziennik, class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
override val lastSync: Long?, override val lastSync: Long?,
@ -27,7 +28,8 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
} }
init { init {
webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=+") { text -> val query = URLEncoder.encode(data.profile?.studentNameLong ?: "a", "UTF-8")
webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=$query") { text ->
MobidziennikLuckyNumberExtractor(data, text) MobidziennikLuckyNumberExtractor(data, text)
val doc = Jsoup.parse(text) val doc = Jsoup.parse(text)

View File

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

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.getNotificationTitle import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) { class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
companion object { companion object {
@ -42,13 +43,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
val text = app.getString( val text = app.getString(
R.string.notification_lesson_change_format, R.string.notification_lesson_change_format,
lesson.getDisplayChangeType(app), lesson.getDisplayChangeType(app),
if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString, lesson.displayDate?.formattedString ?: "",
lesson.changeSubjectName lesson.changeSubjectName
) )
val textLong = app.getString(
R.string.notification_lesson_change_long_format,
lesson.getDisplayChangeType(app),
lesson.displayDate?.formattedString ?: "-",
lesson.displayDate?.weekDay?.let { Week.getFullDayName(it) } ?: "-",
lesson.changeSubjectName,
lesson.changeTeacherName
)
notifications += Notification( notifications += Notification(
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id), id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE), title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE, type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
profileId = lesson.profileId, profileId = lesson.profileId,
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name, profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name,
@ -79,11 +89,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
event.date.formattedString, event.date.formattedString,
event.subjectLongName event.subjectLongName
) )
val textLong = app.getString(
R.string.notification_event_long_format,
event.typeName ?: "-",
event.subjectLongName ?: "-",
event.date.formattedString,
Week.getFullDayName(event.date.weekDay),
event.time?.stringHM ?: app.getString(R.string.event_all_day),
event.topic.take(200)
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification( notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = text, text = text,
textLong = textLong,
type = type, type = type,
profileId = event.profileId, profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
@ -102,11 +122,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
event.date.formattedString, event.date.formattedString,
event.topic event.topic
) )
val textLong = app.getString(
R.string.notification_shared_event_long_format,
event.sharedByName,
event.typeName ?: "-",
event.subjectLongName ?: "-",
event.date.formattedString,
Week.getFullDayName(event.date.weekDay),
event.time?.stringHM ?: app.getString(R.string.event_all_day),
event.topic.take(200)
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification( notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = text, text = text,
textLong = textLong,
type = type, type = type,
profileId = event.profileId, profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
@ -130,10 +161,20 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
gradeName, gradeName,
grade.subjectLongName grade.subjectLongName
) )
val textLong = app.getString(
R.string.notification_grade_long_format,
gradeName,
grade.weight.toString(),
grade.subjectLongName ?: "-",
grade.category ?: "-",
grade.description ?: "-",
grade.teacherName ?: "-"
)
notifications += Notification( notifications += Notification(
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id), id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE), title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_NEW_GRADE, type = Notification.TYPE_NEW_GRADE,
profileId = grade.profileId, profileId = grade.profileId,
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name, profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
@ -158,10 +199,17 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
notice.teacherName, notice.teacherName,
Date.fromMillis(notice.addedDate).formattedString Date.fromMillis(notice.addedDate).formattedString
) )
val textLong = app.getString(
R.string.notification_notice_long_format,
noticeTypeStr,
notice.teacherName ?: "-",
notice.text.take(200)
)
notifications += Notification( notifications += Notification(
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id), id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE), title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_NEW_NOTICE, type = Notification.TYPE_NEW_NOTICE,
profileId = notice.profileId, profileId = notice.profileId,
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name, profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
@ -193,10 +241,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
attendance.subjectLongName, attendance.subjectLongName,
attendance.date.formattedString attendance.date.formattedString
) )
val textLong = app.getString(
R.string.notification_attendance_long_format,
attendanceTypeStr,
attendance.date.formattedString,
attendance.startTime?.stringHM ?: "-",
attendance.lessonNumber ?: "-",
attendance.subjectLongName ?: "-",
attendance.teacherName ?: "-",
attendance.lessonTopic ?: "-"
)
notifications += Notification( notifications += Notification(
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id), id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE), title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
text = text, text = text,
textLong = textLong,
type = Notification.TYPE_NEW_ATTENDANCE, type = Notification.TYPE_NEW_ATTENDANCE,
profileId = attendance.profileId, profileId = attendance.profileId,
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name, profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name,

View File

@ -8,6 +8,9 @@ import android.util.SparseIntArray
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.core.util.set import androidx.core.util.set
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -107,6 +110,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts)) .setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText) .setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app).apply {
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
colorRes = R.color.colorPrimary
}.toBitmap())
.setStyle(NotificationCompat.InboxStyle() .setStyle(NotificationCompat.InboxStyle()
.also { .also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -137,8 +144,11 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title) .setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
.setTicker("${it.profileName}: ${it.title}") .setTicker("${it.profileName}: ${it.title}")
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app, it.getLargeIcon()).apply {
colorRes = R.color.colorPrimary
}.toBitmap())
.setStyle(NotificationCompat.BigTextStyle() .setStyle(NotificationCompat.BigTextStyle()
.bigText(it.text)) .bigText(it.textLong ?: it.text))
.setWhen(it.addedDate) .setWhen(it.addedDate)
.addDefaults() .addDefaults()
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
@ -160,6 +170,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
.setContentText(buildSummaryText(summaryCounts)) .setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText) .setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(IconicsDrawable(app).apply {
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
colorRes = R.color.colorPrimary
}.toBitmap())
.addDefaults() .addDefaults()
.setGroupSummary(true) .setGroupSummary(true)
.setContentIntent(summaryIntent) .setContentIntent(summaryIntent)

View File

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
LibrusLesson::class, LibrusLesson::class,
TimetableManual::class, TimetableManual::class,
Metadata::class Metadata::class
], version = 91) ], version = 93)
@TypeConverters( @TypeConverters(
ConverterTime::class, ConverterTime::class,
ConverterDate::class, ConverterDate::class,
@ -176,7 +176,9 @@ abstract class AppDb : RoomDatabase() {
Migration88(), Migration88(),
Migration89(), Migration89(),
Migration90(), Migration90(),
Migration91() Migration91(),
Migration92(),
Migration93()
).allowMainThreadQueries().build() ).allowMainThreadQueries().build()
} }
} }

View File

@ -9,30 +9,9 @@ import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query 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_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 import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
@Dao @Dao
abstract class EventTypeDao { abstract class EventTypeDao {
@ -58,19 +37,18 @@ abstract class EventTypeDao {
abstract val allNow: List<EventType> abstract val allNow: List<EventType>
fun addDefaultTypes(context: Context, profileId: Int): List<EventType> { fun addDefaultTypes(context: Context, profileId: Int): List<EventType> {
val typeList = listOf( var order = 100
EventType(profileId, TYPE_HOMEWORK, context.getString(R.string.event_type_homework), COLOR_HOMEWORK), val colorMap = EventType.getTypeColorMap()
EventType(profileId, TYPE_DEFAULT, context.getString(R.string.event_other), COLOR_DEFAULT), val typeList = EventType.getTypeNameMap().map { (id, name) ->
EventType(profileId, TYPE_EXAM, context.getString(R.string.event_exam), COLOR_EXAM), EventType(
EventType(profileId, TYPE_SHORT_QUIZ, context.getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ), profileId = profileId,
EventType(profileId, TYPE_ESSAY, context.getString(R.string.event_essay), COLOR_ESSAY), id = id,
EventType(profileId, TYPE_PROJECT, context.getString(R.string.event_project), COLOR_PROJECT), name = context.getString(name),
EventType(profileId, TYPE_PT_MEETING, context.getString(R.string.event_pt_meeting), COLOR_PT_MEETING), color = colorMap[id] ?: COLOR_DEFAULT,
EventType(profileId, TYPE_EXCURSION, context.getString(R.string.event_excursion), COLOR_EXCURSION), order = order++,
EventType(profileId, TYPE_READING, context.getString(R.string.event_reading), COLOR_READING), source = SOURCE_DEFAULT
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) addAll(typeList)
return typeList return typeList
} }

View File

@ -84,6 +84,8 @@ abstract class TimetableDao : BaseDao<Lesson, LessonFull> {
"LIMIT 1") "LIMIT 1")
fun getBetweenDates(dateFrom: Date, dateTo: Date) = fun getBetweenDates(dateFrom: Date, dateTo: Date) =
getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY") getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY")
fun getChanges(profileId: Int) =
getRaw("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY")
// GET ALL - NOW // GET ALL - NOW
fun getAllNow(profileId: Int) = fun getAllNow(profileId: Int) =

View File

@ -45,6 +45,7 @@ open class Event(
var addedDate: Long = System.currentTimeMillis() var addedDate: Long = System.currentTimeMillis()
) : Keepable() { ) : Keepable() {
companion object { companion object {
const val TYPE_ELEARNING = -5L
const val TYPE_UNDEFINED = -2L const val TYPE_UNDEFINED = -2L
const val TYPE_HOMEWORK = -1L const val TYPE_HOMEWORK = -1L
const val TYPE_DEFAULT = 0L const val TYPE_DEFAULT = 0L
@ -57,7 +58,7 @@ open class Event(
const val TYPE_READING = 7L const val TYPE_READING = 7L
const val TYPE_CLASS_EVENT = 8L const val TYPE_CLASS_EVENT = 8L
const val TYPE_INFORMATION = 9L const val TYPE_INFORMATION = 9L
const val TYPE_TEACHER_ABSENCE = 10L const val COLOR_ELEARNING = 0xfff57f17.toInt()
const val COLOR_HOMEWORK = 0xff795548.toInt() const val COLOR_HOMEWORK = 0xff795548.toInt()
const val COLOR_DEFAULT = 0xffffc107.toInt() const val COLOR_DEFAULT = 0xffffc107.toInt()
const val COLOR_EXAM = 0xfff44336.toInt() const val COLOR_EXAM = 0xfff44336.toInt()
@ -69,7 +70,6 @@ open class Event(
const val COLOR_READING = 0xFFFFEB3B.toInt() const val COLOR_READING = 0xFFFFEB3B.toInt()
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt() const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
const val COLOR_INFORMATION = 0xff039be5.toInt() const val COLOR_INFORMATION = 0xff039be5.toInt()
const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt()
} }
@ColumnInfo(name = "eventAddedManually") @ColumnInfo(name = "eventAddedManually")
@ -116,14 +116,7 @@ open class Event(
var showAsUnseen: Boolean? = null var showAsUnseen: Boolean? = null
val startTimeCalendar: Calendar val startTimeCalendar: Calendar
get() = Calendar.getInstance().also { it.set( get() = date.getAsCalendar(time)
date.year,
date.month - 1,
date.day,
time?.hour ?: 0,
time?.minute ?: 0,
time?.second ?: 0
) }
val endTimeCalendar: Calendar val endTimeCalendar: Calendar
get() = startTimeCalendar.also { get() = startTimeCalendar.also {

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
*/
package pl.szczodrzynski.edziennik.data.db.entity;
import android.graphics.Color;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
@Entity(tableName = "eventTypes",
primaryKeys = {"profileId", "eventType"})
public class EventType {
public int profileId;
@ColumnInfo(name = "eventType")
public long id;
@ColumnInfo(name = "eventTypeName")
public String name;
@ColumnInfo(name = "eventTypeColor")
public int color;
public EventType(int profileId, long id, String name, int color) {
this.profileId = profileId;
this.id = id;
this.name = name;
this.color = color;
}
public EventType(int profileId, int id, String name, String color) {
this(profileId, id, name, Color.parseColor(color));
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-19.
*/
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
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_ELEARNING
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_ELEARNING
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
@Entity(
tableName = "eventTypes",
primaryKeys = ["profileId", "eventType"]
)
class EventType(
val profileId: Int,
@ColumnInfo(name = "eventType")
val id: Long,
@ColumnInfo(name = "eventTypeName")
val name: String,
@ColumnInfo(name = "eventTypeColor")
val color: Int,
@ColumnInfo(name = "eventTypeOrder")
var order: Int = id.toInt(),
@ColumnInfo(name = "eventTypeSource")
val source: Int = SOURCE_REGISTER
) {
companion object {
const val SOURCE_DEFAULT = 0
const val SOURCE_REGISTER = 1
const val SOURCE_CUSTOM = 2
const val SOURCE_SHARED = 3
fun getTypeColorMap() = mapOf(
TYPE_ELEARNING to COLOR_ELEARNING,
TYPE_HOMEWORK to COLOR_HOMEWORK,
TYPE_DEFAULT to COLOR_DEFAULT,
TYPE_EXAM to COLOR_EXAM,
TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ,
TYPE_ESSAY to COLOR_ESSAY,
TYPE_PROJECT to COLOR_PROJECT,
TYPE_PT_MEETING to COLOR_PT_MEETING,
TYPE_EXCURSION to COLOR_EXCURSION,
TYPE_READING to COLOR_READING,
TYPE_CLASS_EVENT to COLOR_CLASS_EVENT,
TYPE_INFORMATION to COLOR_INFORMATION
)
fun getTypeNameMap() = mapOf(
TYPE_ELEARNING to R.string.event_type_elearning,
TYPE_HOMEWORK to R.string.event_type_homework,
TYPE_DEFAULT to R.string.event_other,
TYPE_EXAM to R.string.event_exam,
TYPE_SHORT_QUIZ to R.string.event_short_quiz,
TYPE_ESSAY to R.string.event_essay,
TYPE_PROJECT to R.string.event_project,
TYPE_PT_MEETING to R.string.event_pt_meeting,
TYPE_EXCURSION to R.string.event_excursion,
TYPE_READING to R.string.event_reading,
TYPE_CLASS_EVENT to R.string.event_class_event,
TYPE_INFORMATION to R.string.event_information
)
}
}

View File

@ -10,6 +10,8 @@ import android.content.Intent
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
@Entity(tableName = "notifications") @Entity(tableName = "notifications")
@ -19,6 +21,7 @@ data class Notification(
val title: String, val title: String,
val text: String, val text: String,
val textLong: String? = null,
val type: Int, val type: Int,
@ -96,4 +99,19 @@ data class Notification(
fillIntent(intent) fillIntent(intent)
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT) return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
} }
fun getLargeIcon(): IIcon = when (type) {
TYPE_TIMETABLE_LESSON_CHANGE -> CommunityMaterial.Icon3.cmd_timetable
TYPE_NEW_GRADE -> CommunityMaterial.Icon3.cmd_numeric_5_box_outline
TYPE_NEW_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline
TYPE_NEW_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline
TYPE_NEW_SHARED_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline
TYPE_NEW_SHARED_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline
TYPE_NEW_MESSAGE -> CommunityMaterial.Icon.cmd_email_outline
TYPE_NEW_NOTICE -> CommunityMaterial.Icon.cmd_emoticon_outline
TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline
TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline
TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline
else -> CommunityMaterial.Icon.cmd_bell_ring_outline
}
} }

View File

@ -129,6 +129,9 @@ open class Profile(
val isParent val isParent
get() = accountName != null get() = accountName != null
val accountOwnerName
get() = accountName ?: studentNameLong
val registerName val registerName
get() = when (loginStoreType) { get() = when (loginStoreType) {
LOGIN_TYPE_LIBRUS -> "librus" LOGIN_TYPE_LIBRUS -> "librus"

View File

@ -30,7 +30,7 @@ class MessageFull(
@Ignore @Ignore
var filterWeight = 0 var filterWeight = 0
@Ignore @Ignore
var searchHighlightText: String? = null var searchHighlightText: CharSequence? = null
// metadata // metadata
var seen = false var seen = false

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-15.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_REGISTER
import pl.szczodrzynski.edziennik.getInt
class Migration92 : Migration(91, 92) {
override fun migrate(database: SupportSQLiteDatabase) {
// make eventTypeName not nullable
database.execSQL("ALTER TABLE eventTypes RENAME TO _eventTypes;")
database.execSQL("CREATE TABLE eventTypes (" +
"profileId INTEGER NOT NULL, " +
"eventType INTEGER NOT NULL, " +
"eventTypeName TEXT NOT NULL, " +
"eventTypeColor INTEGER NOT NULL, " +
"PRIMARY KEY(profileId,eventType)" +
");")
database.execSQL("INSERT INTO eventTypes " +
"(profileId, eventType, eventTypeName, eventTypeColor) " +
"SELECT profileId, eventType, eventTypeName, eventTypeColor " +
"FROM _eventTypes;")
database.execSQL("DROP TABLE _eventTypes;")
// add columns for order and source
database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeOrder INTEGER NOT NULL DEFAULT 0;")
database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeSource INTEGER NOT NULL DEFAULT 0;")
// migrate existing types to show correct order and source
database.execSQL("UPDATE eventTypes SET eventTypeOrder = eventType + 102;")
database.execSQL("UPDATE eventTypes SET eventTypeSource = $SOURCE_REGISTER WHERE eventType > $TYPE_INFORMATION;")
// add new e-learning type
val cursor = database.query("SELECT profileId FROM profiles;")
cursor.use {
while (it.moveToNext()) {
val values = ContentValues().apply {
put("profileId", it.getInt("profileId"))
put("eventType", TYPE_ELEARNING)
put("eventTypeName", "lekcja online")
put("eventTypeColor", COLOR_ELEARNING)
put("eventTypeOrder", 100)
put("eventTypeSource", SOURCE_DEFAULT)
}
database.insert("eventTypes", SQLiteDatabase.CONFLICT_REPLACE, values)
}
}
}
}

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-5-26.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration93 : Migration(92, 93) {
override fun migrate(database: SupportSQLiteDatabase) {
// notifications - long text
database.execSQL("ALTER TABLE notifications ADD COLUMN textLong TEXT DEFAULT NULL;")
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.dialogs
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.MessagesConfigDialogBinding
class MessagesConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
const val TAG = "MessagesConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
private val profileConfig by lazy { app.config.forProfile().ui }
private lateinit var b: MessagesConfigDialogBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = MessagesConfigDialogBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_messages_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private fun loadConfig() {
b.config = profileConfig
b.greetingText.setText(
profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
)
}
private fun saveConfig() {
val greetingText = b.greetingText.text?.toString()?.trim()
if (greetingText.isNullOrEmpty())
profileConfig.messagesGreetingText = null
else
profileConfig.messagesGreetingText = "\n\n$greetingText"
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.agenda
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import java.util.*
class AgendaConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
const val TAG = "AgendaConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
private val profileConfig by lazy { app.config.forProfile().ui }
private lateinit var b: DialogConfigAgendaBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = DialogConfigAgendaBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_agenda_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private fun loadConfig() {
b.config = profileConfig
b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT
b.eventSharingEnabled.isChecked = app.profile.enableSharedEvents
&& app.profile.registration == REGISTRATION_ENABLED
b.eventSharingEnabled.onChange { _, isChecked ->
if (isChecked && app.profile.registration != REGISTRATION_ENABLED) {
b.eventSharingEnabled.isChecked = false
val dialog = RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled ->
b.eventSharingEnabled.isChecked = enabled
setEventSharingEnabled(enabled)
}, onShowListener, onDismissListener)
dialog.showEnableDialog()
return@onChange
}
setEventSharingEnabled(isChecked)
}
}
private fun setEventSharingEnabled(enabled: Boolean) {
if (enabled == app.profile.enableSharedEvents)
return
app.profile.enableSharedEvents = enabled
app.profileSave()
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.event_sharing)
.setMessage(
if (enabled)
R.string.settings_register_shared_events_dialog_enabled_text
else
R.string.settings_register_shared_events_dialog_disabled_text
)
.setPositiveButton(R.string.ok, null)
.show()
}
private fun saveConfig() {
}
}

View File

@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.day
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -19,6 +19,10 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -29,6 +33,7 @@ class DayDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val profileId: Int, val profileId: Int,
val date: Date, val date: Date,
val eventTypeId: Long? = null,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -109,10 +114,16 @@ class DayDialog(
} }
lessonChanges.ifNotEmpty { lessonChanges.ifNotEmpty {
b.lessonChangeContainer.root.visibility = View.VISIBLE LessonChangesEventRenderer().render(
b.lessonChangeContainer.lessonChangeCount.text = it.size.toString() b.lessonChanges, LessonChangesEvent(
profileId = profileId,
date = date,
count = it.size,
showBadge = false
)
)
b.lessonChangeLayout.onClick { b.lessonChangesFrame.onClick {
LessonChangeDialog( LessonChangeDialog(
activity, activity,
profileId, profileId,
@ -122,16 +133,22 @@ class DayDialog(
) )
} }
} }
b.lessonChangesFrame.isVisible = lessonChanges.isNotEmpty()
val teacherAbsences = withContext(Dispatchers.Default) { val teacherAbsences = withContext(Dispatchers.Default) {
app.db.teacherAbsenceDao().getAllByDateNow(profileId, date) app.db.teacherAbsenceDao().getAllByDateNow(profileId, date)
} }
teacherAbsences.ifNotEmpty { teacherAbsences.ifNotEmpty {
b.teacherAbsenceContainer.root.visibility = View.VISIBLE TeacherAbsenceEventRenderer().render(
b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString() b.teacherAbsence, TeacherAbsenceEvent(
profileId = profileId,
date = date,
count = it.size
)
)
b.teacherAbsenceLayout.onClick { b.teacherAbsenceFrame.onClick {
TeacherAbsenceDialog( TeacherAbsenceDialog(
activity, activity,
profileId, profileId,
@ -141,6 +158,7 @@ class DayDialog(
) )
} }
} }
b.teacherAbsenceFrame.isVisible = teacherAbsences.isNotEmpty()
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
@ -169,8 +187,12 @@ class DayDialog(
} }
) )
app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events -> app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events ->
adapter.items = events adapter.items = if (eventTypeId != null)
events.filter { it.type == eventTypeId }
else
events
if (b.eventsView.adapter == null) { if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter b.eventsView.adapter = adapter
b.eventsView.apply { b.eventsView.apply {
@ -189,6 +211,6 @@ class DayDialog(
b.eventsView.visibility = View.GONE b.eventsView.visibility = View.GONE
b.eventsNoData.visibility = View.VISIBLE b.eventsNoData.visibility = View.VISIBLE
} }
}) }
}} }}
} }

View File

@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext
class EventDetailsDialog( class EventDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val event: EventFull, var event: EventFull,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -46,6 +46,8 @@ class EventDetailsDialog(
private var removeEventDialog: AlertDialog? = null private var removeEventDialog: AlertDialog? = null
private val eventShared = event.sharedBy != null private val eventShared = event.sharedBy != null
private val eventOwn = event.sharedBy == "self" private val eventOwn = event.sharedBy == "self"
private val manager
get() = app.eventManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -92,6 +94,10 @@ class EventDetailsDialog(
b.eventShared = eventShared b.eventShared = eventShared
b.eventOwn = eventOwn b.eventOwn = eventOwn
if (!event.seen) {
manager.markAsSeen(event)
}
val bullet = "" val bullet = ""
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
@ -100,6 +106,8 @@ class EventDetailsDialog(
} }
catch (_: Exception) {} catch (_: Exception) {}
manager.setLegendText(b.legend, event)
b.typeColor.background?.setTintColor(event.eventColor) b.typeColor.background?.setTintColor(event.eventColor)
b.details = mutableListOf( b.details = mutableListOf(
@ -135,6 +143,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
app.db.eventDao().replace(event) app.db.eventDao().replace(event)
} }
update()
b.checkDoneButton.isChecked = true b.checkDoneButton.isChecked = true
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -145,6 +154,7 @@ class EventDetailsDialog(
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
app.db.eventDao().replace(event) app.db.eventDao().replace(event)
} }
update()
} }
} }
b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done) b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done)
@ -156,6 +166,14 @@ class EventDetailsDialog(
activity, activity,
event.profileId, event.profileId,
editingEvent = event, editingEvent = event,
onSaveListener = {
if (it == null) {
dialog.dismiss()
return@EventManualDialog
}
event = it
update()
},
onShowListener = onShowListener, onShowListener = onShowListener,
onDismissListener = onDismissListener onDismissListener = onDismissListener
) )
@ -199,10 +217,14 @@ class EventDetailsDialog(
} }
b.downloadButton.attachToastHint(R.string.hint_download_again) b.downloadButton.attachToastHint(R.string.hint_download_again)
BetterLink.attach(b.topic, onActionSelected = dialog::dismiss)
b.topic.text = event.topic event.teacherName?.let { name ->
BetterLink.attach(b.topic) { BetterLink.attach(
dialog.dismiss() b.teacherName,
teachers = mapOf(event.teacherId to name),
onActionSelected = dialog::dismiss
)
} }
if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) { if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) {
@ -220,10 +242,7 @@ class EventDetailsDialog(
b.bodyTitle.isVisible = true b.bodyTitle.isVisible = true
b.bodyProgressBar.isVisible = false b.bodyProgressBar.isVisible = false
b.body.isVisible = true b.body.isVisible = true
b.body.text = event.homeworkBody BetterLink.attach(b.body, onActionSelected = dialog::dismiss)
BetterLink.attach(b.body) {
dialog.dismiss()
}
} }
if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) { if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) {
@ -322,8 +341,6 @@ class EventDetailsDialog(
removeEventDialog?.dismiss() removeEventDialog?.dismiss()
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_AGENDA)
activity.reloadTarget()
} }
private fun openInCalendar() { launch { private fun openInCalendar() { launch {

View File

@ -33,7 +33,8 @@ class EventListAdapter(
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope { ) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope {
private val app = context.applicationContext as App private val app = context.applicationContext as App
private val manager = app.eventManager private val manager
get() = app.eventManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -67,7 +68,7 @@ class EventListAdapter(
b.simpleMode = simpleMode b.simpleMode = simpleMode
b.topic.text = event.topic manager.setEventTopic(b.topic, event, showType = false)
b.topic.maxLines = if (simpleMode) 2 else 3 b.topic.maxLines = if (simpleMode) 2 else 3
b.details.text = mutableListOf<CharSequence?>( b.details.text = mutableListOf<CharSequence?>(
@ -102,8 +103,6 @@ class EventListAdapter(
} }
b.editButton.attachToastHint(R.string.hint_edit_event) b.editButton.attachToastHint(R.string.hint_edit_event)
b.isDone.isVisible = event.isDone
if (event.showAsUnseen == null) if (event.showAsUnseen == null)
event.showAsUnseen = !event.seen event.showAsUnseen = !event.seen

View File

@ -4,8 +4,6 @@
package pl.szczodrzynski.edziennik.ui.dialogs.event package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -20,23 +18,18 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -49,6 +42,7 @@ class EventManualDialog(
val defaultTime: Time? = null, val defaultTime: Time? = null,
val defaultType: Long? = null, val defaultType: Long? = null,
val editingEvent: EventFull? = null, val editingEvent: EventFull? = null,
val onSaveListener: ((event: EventFull?) -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -323,57 +317,41 @@ class EventManualDialog(
selectDefault(defaultLesson?.displayTeacherId) selectDefault(defaultLesson?.displayTeacherId)
} }
with (b.typeDropdown) {
db = app.db
profileId = this@EventManualDialog.profileId
loadItems()
selectDefault(editingEvent?.type)
selectDefault(defaultType)
val deferred = async(Dispatchers.Default) { onTypeSelected = {
// get the event type list b.typeColor.background.setTintColor(it.color)
var eventTypes = app.db.eventTypeDao().getAllNow(profileId) customColor = null
}
if (eventTypes.none { it.id in -1L..10L }) {
eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId)
} }
b.typeDropdown.clear() // copy data from event being edited
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
}
deferred.await()
b.typeDropdown.isEnabled = true
defaultType?.let {
b.typeDropdown.select(it)
}
b.typeDropdown.selected?.let { item ->
customColor = (item.tag as EventType).color
}
// copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.topic.setText(it.topic) b.topic.setText(it.topic)
b.typeDropdown.select(it.type)?.let { item -> if (it.color != -1)
customColor = (item.tag as EventType).color
}
if (it.color != null && it.color != -1)
customColor = it.color customColor = it.color
} }
b.typeColor.background.setTintColor(
customColor
?: b.typeDropdown.getSelected()?.color
?: Event.COLOR_DEFAULT
)
// copy IDs from the LessonFull // copy IDs from the LessonFull
defaultLesson?.let { defaultLesson?.let {
b.teamDropdown.select(it.displayTeamId) b.teamDropdown.select(it.displayTeamId)
} }
b.typeDropdown.setOnChangeListener {
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
customColor = null
return@setOnChangeListener true
}
(customColor ?: Event.COLOR_DEFAULT).let {
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
}
b.typeColor.onClick { b.typeColor.onClick {
val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT val currentColor = customColor
?: b.typeDropdown.getSelected()?.color
?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder() val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor) .setColor(currentColor)
.create() .create()
@ -381,7 +359,7 @@ class EventManualDialog(
object : ColorPickerDialogListener { object : ColorPickerDialogListener {
override fun onDialogDismissed(dialogId: Int) {} override fun onDialogDismissed(dialogId: Int) {}
override fun onColorSelected(dialogId: Int, color: Int) { override fun onColorSelected(dialogId: Int, color: Int) {
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) b.typeColor.background.setTintColor(color)
customColor = color customColor = color
} }
}) })
@ -416,11 +394,11 @@ class EventManualDialog(
private fun saveEvent() { private fun saveEvent() {
val date = b.dateDropdown.getSelected() as? Date val date = b.dateDropdown.getSelected() as? Date
val timeSelected = b.timeDropdown.getSelected() val timeSelected = b.timeDropdown.getSelected()
val teamId = b.teamDropdown.getSelected() as? Long val team = b.teamDropdown.getSelected()
val type = b.typeDropdown.selected?.id val type = b.typeDropdown.getSelected()
val topic = b.topic.text?.toString() val topic = b.topic.text?.toString()
val subjectId = b.subjectDropdown.getSelected() as? Long val subject = b.subjectDropdown.getSelected() as? Subject
val teacherId = b.teacherDropdown.getSelected() val teacher = b.teacherDropdown.getSelected()
val share = b.shareSwitch.isChecked val share = b.shareSwitch.isChecked
@ -451,7 +429,7 @@ class EventManualDialog(
isError = true isError = true
} }
if (share && teamId == null) { if (share && team == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose) b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown) if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
isError = true isError = true
@ -487,10 +465,10 @@ class EventManualDialog(
time = startTime, time = startTime,
topic = topic, topic = topic,
color = customColor, color = customColor,
type = type ?: Event.TYPE_DEFAULT, type = type?.id ?: Event.TYPE_DEFAULT,
teacherId = teacherId ?: -1, teacherId = teacher?.id ?: -1,
subjectId = subjectId ?: -1, subjectId = subject?.id ?: -1,
teamId = teamId ?: -1, teamId = team?.id ?: -1,
addedDate = editingEvent?.addedDate ?: System.currentTimeMillis() addedDate = editingEvent?.addedDate ?: System.currentTimeMillis()
).also { ).also {
it.addedManually = true it.addedManually = true
@ -498,7 +476,7 @@ class EventManualDialog(
val metadataObject = Metadata( val metadataObject = Metadata(
profileId, profileId,
when (type) { when (type?.id) {
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
else -> Metadata.TYPE_EVENT else -> Metadata.TYPE_EVENT
}, },
@ -597,10 +575,14 @@ class EventManualDialog(
} }
} }
onSaveListener?.invoke(eventObject.withMetadata(metadataObject).also {
it.subjectLongName = (b.subjectDropdown.getSelected() as? Subject)?.longName
it.teacherName = b.teacherDropdown.getSelected()?.fullName
it.teamName = b.teamDropdown.getSelected()?.name
it.typeName = b.typeDropdown.getSelected()?.name
})
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
activity.reloadTarget()
} }
private fun finishRemoving() { private fun finishRemoving() {
editingEvent ?: return editingEvent ?: return
@ -611,9 +593,8 @@ class EventManualDialog(
} }
removeEventDialog?.dismiss() removeEventDialog?.dismiss()
onSaveListener?.invoke(null)
dialog.dismiss() dialog.dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
activity.reloadTarget()
} }
} }

View File

@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding
import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.setTintColor import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -68,6 +69,14 @@ class GradeDetailsDialog(
GradesConfigDialog(activity, reloadOnDismiss = true) GradesConfigDialog(activity, reloadOnDismiss = true)
} }
grade.teacherName?.let { name ->
BetterLink.attach(
b.teacherName,
teachers = mapOf(grade.teacherId to name),
onActionSelected = dialog::dismiss
)
}
launch { launch {
val historyList = withContext(Dispatchers.Default) { val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id) app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)

View File

@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
@ -49,7 +50,8 @@ class LessonDetailsDialog(
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private lateinit var adapter: EventListAdapter private lateinit var adapter: EventListAdapter
private val manager by lazy { app.timetableManager } private val manager
get() = app.timetableManager
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
@ -216,5 +218,19 @@ class LessonDetailsDialog(
b.eventsNoData.visibility = View.VISIBLE b.eventsNoData.visibility = View.VISIBLE
} }
}) })
lesson.displayTeacherName?.let { name ->
lesson.displayTeacherId ?: return@let
BetterLink.attach(
b.teacherNameView,
teachers = mapOf(lesson.displayTeacherId!! to name),
onActionSelected = dialog::dismiss
)
BetterLink.attach(
b.oldTeacherNameView,
teachers = mapOf(lesson.displayTeacherId!! to name),
onActionSelected = dialog::dismiss
)
}
} }
} }

View File

@ -12,10 +12,6 @@ import android.widget.Toast
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.applandeo.materialcalendarview.EventDay import com.applandeo.materialcalendarview.EventDay
import com.github.tibolte.agendacalendarview.CalendarPickerController
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
@ -29,17 +25,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@ -59,7 +47,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private var type: Int = Profile.AGENDA_DEFAULT private var type: Int = Profile.AGENDA_DEFAULT
private var actualDate: Date? = null
private var agendaDefault: AgendaFragmentDefault? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (getActivity() == null || context == null) return null if (getActivity() == null || context == null) return null
@ -82,38 +71,61 @@ class AgendaFragment : Fragment(), CoroutineScope {
.withTitle(R.string.menu_add_event) .withTitle(R.string.menu_add_event)
.withDescription(R.string.menu_add_event_desc) .withDescription(R.string.menu_add_event_desc)
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline) .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener { .withOnClickListener {
activity.bottomSheet.close() activity.bottomSheet.close()
EventManualDialog(activity, app.profileId, defaultDate = actualDate) EventManualDialog(
}), activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
},
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
AgendaConfigDialog(activity, true, null, null)
},
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_change_view) .withTitle(R.string.menu_agenda_change_view)
.withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square) .withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square)
.withOnClickListener(View.OnClickListener { .withOnClickListener {
activity.bottomSheet.close() activity.bottomSheet.close()
type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT type =
if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT
app.config.forProfile().ui.agendaViewType = type app.config.forProfile().ui.agendaViewType = type
activity.reloadTarget() activity.reloadTarget()
}), },
BottomSheetSeparatorItem(true), BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true) BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_mark_as_read) .withTitle(R.string.menu_mark_as_read)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.withOnClickListener(View.OnClickListener { launch { .withOnClickListener {
launch {
activity.bottomSheet.close() activity.bottomSheet.close()
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) App.db.metadataDao()
.setAllSeen(app.profileId, Metadata.TYPE_EVENT, true)
}
Toast.makeText(
activity,
R.string.main_menu_mark_as_read_success,
Toast.LENGTH_SHORT
).show()
}
} }
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
}})
) )
activity.navView.bottomBar.fabEnable = true activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(R.string.add) activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener {
EventManualDialog(activity, app.profileId, defaultDate = actualDate) EventManualDialog(
}) activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
}
activity.gainAttention() activity.gainAttention()
activity.gainAttentionFAB() activity.gainAttentionFAB()
@ -129,143 +141,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
return@launch return@launch
delay(500) delay(500)
val eventList = mutableListOf<CalendarEvent>() agendaDefault = AgendaFragmentDefault(activity, app, b)
agendaDefault?.initView(this@AgendaFragment)
val minDate = Calendar.getInstance().apply {
add(Calendar.MONTH, -2)
set(Calendar.DAY_OF_MONTH, 1)
}
val maxDate = Calendar.getInstance().apply { add(Calendar.MONTH, 2) }
/**
* LESSON CHANGES
*/
if (!isAdded)
return@launch
val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) }
val lessonChangeCounters = mutableListOf<LessonChangeCounter>()
lessons.forEach { lesson ->
lessonChangeCounters.firstOrNull { it.lessonChangeDate == lesson.displayDate }?.let {
it.lessonChangeCount += 1
} ?: run {
lessonChangeCounters.add(LessonChangeCounter(
lesson.displayDate ?: return@forEach,
1
))
}
}
lessonChangeCounters.forEach { counter ->
eventList.add(LessonChangeEvent(
counter.lessonChangeDate.inMillis,
0xff78909c.toInt(),
Colors.legibleTextColor(0xff78909c.toInt()),
counter.startTime,
counter.endTime,
app.profileId,
counter.lessonChangeDate,
counter.lessonChangeCount
))
}
/**
* TEACHER ABSENCES
*/
if (!isAdded)
return@launch
val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true)
if (showTeacherAbsences) {
val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) }
val teacherAbsenceCounters = mutableListOf<TeacherAbsenceCounter>()
teacherAbsenceList.forEach { absence ->
val date = absence.dateFrom.clone()
while (date <= absence.dateTo) {
teacherAbsenceCounters.firstOrNull { it.teacherAbsenceDate == date }?.let {
it.teacherAbsenceCount += 1
} ?: run {
teacherAbsenceCounters.add(TeacherAbsenceCounter(date.clone(), 1))
}
date.stepForward(0, 0, 1)
}
}
teacherAbsenceCounters.forEach { counter ->
eventList.add(TeacherAbsenceEvent(
counter.teacherAbsenceDate.inMillis,
0xffff1744.toInt(),
Colors.legibleTextColor(0xffff1744.toInt()),
counter.startTime,
counter.endTime,
app.profileId,
counter.teacherAbsenceDate,
counter.teacherAbsenceCount
))
}
}
/**
* EVENTS
*/
if (!isAdded)
return@launch
val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) }
val unreadEventDates = mutableSetOf<Int>()
events.forEach { event ->
eventList.add(BaseCalendarEvent(
"${event.typeName ?: "wydarzenie"} - ${event.topic}",
"",
(if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) +
(event.subjectLongName?.let { ", $it" } ?: "") +
(event.teacherName?.let { ", $it" } ?: "") +
(event.teamName?.let { ", $it" } ?: ""),
event.eventColor,
Colors.legibleTextColor(event.eventColor),
event.startTimeCalendar,
event.endTimeCalendar,
event.time == null,
event.id,
!event.seen
))
if (!event.seen) unreadEventDates.add(event.date.value)
}
b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController {
override fun onDaySelected(dayItem: IDayItem?) {}
override fun onScrollToDate(calendar: Calendar) { this@AgendaFragment.launch {
val date = Date.fromCalendar(calendar)
actualDate = date
// Mark as read scrolled date
if (date.value in unreadEventDates) {
withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) }
unreadEventDates.remove(date.value)
}
}}
override fun onEventSelected(event: CalendarEvent) {
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is BaseCalendarEvent -> DayDialog(activity, app.profileId, date)
is LessonChangeEvent -> LessonChangeDialog(activity, app.profileId, date)
is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date)
}
}
}, LessonChangeEventRenderer(), TeacherAbsenceEventRenderer())
b.progressBar.visibility = View.GONE
}}} }}}
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {

View File

@ -0,0 +1,310 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda
import android.util.SparseIntArray
import android.widget.AbsListView
import android.widget.AbsListView.OnScrollListener
import androidx.core.util.forEach
import androidx.core.util.set
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.CalendarManager
import com.github.tibolte.agendacalendarview.CalendarPickerController
import com.github.tibolte.agendacalendarview.agenda.AgendaAdapter
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroup
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroupRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class AgendaFragmentDefault(
private val activity: MainActivity,
private val app: App,
private val b: FragmentAgendaDefaultBinding
) : OnScrollListener, CoroutineScope {
companion object {
var selectedDate: Date = Date.getToday()
}
override val coroutineContext = Job() + Dispatchers.Main
private val unreadDates = mutableSetOf<Int>()
private val events = mutableListOf<CalendarEvent>()
private var isInitialized = false
private val profileConfig by lazy { app.config.forProfile().ui }
private val listView
get() = b.agendaDefaultView.agendaView.agendaListView
private val adapter
get() = listView.adapter as? AgendaAdapter
private val manager
get() = CalendarManager.getInstance()
private var scrollState = OnScrollListener.SCROLL_STATE_IDLE
private var updatePending = false
private var notifyPending = false
override fun onScrollStateChanged(view: AbsListView?, newScrollState: Int) {
b.agendaDefaultView.agendaScrollListener.onScrollStateChanged(view, scrollState)
scrollState = newScrollState
if (updatePending) updateData()
if (notifyPending) notifyDataSetChanged()
}
override fun onScroll(
view: AbsListView?,
firstVisibleItem: Int,
visibleItemCount: Int,
totalItemCount: Int
) = b.agendaDefaultView.agendaScrollListener.onScroll(
view,
firstVisibleItem,
visibleItemCount,
totalItemCount
)
/**
* Mark the data as needing update, either after 1 second (when
* not scrolling) or 1 second after scrolling stops.
*/
private fun updateData() = launch {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
updatePending = false
delay(1000)
notifyDataSetChanged()
} else updatePending = true
}
/**
* Notify the adapter about changes, either instantly or after
* scrolling stops.
*/
private fun notifyDataSetChanged() {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
notifyPending = false
adapter?.notifyDataSetChanged()
} else notifyPending = true
}
suspend fun initView(fragment: AgendaFragment) {
isInitialized = false
withContext(Dispatchers.Default) {
if (profileConfig.agendaLessonChanges)
addLessonChanges(events)
if (profileConfig.agendaTeacherAbsence)
addTeacherAbsence(events)
}
app.db.eventDao().getAll(app.profileId).observe(fragment) {
addEvents(events, it)
if (isInitialized)
updateView()
else
initViewPriv()
}
}
private fun initViewPriv() {
val dateStart = app.profile.dateSemester1Start.asCalendar
val dateEnd = app.profile.dateYearEnd.asCalendar
val isCompactMode = profileConfig.agendaCompactMode
b.agendaDefaultView.init(
events,
dateStart,
dateEnd,
Locale.getDefault(),
object : CalendarPickerController {
override fun onDaySelected(dayItem: IDayItem) {
val c = Calendar.getInstance()
c.time = dayItem.date
if (c.timeInMillis == selectedDate.inMillis) {
DayDialog(activity, app.profileId, selectedDate)
}
}
override fun onEventSelected(event: CalendarEvent) {
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is AgendaEvent -> EventDetailsDialog(activity, event.event)
is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date)
is TeacherAbsenceEvent -> TeacherAbsenceDialog(
activity,
app.profileId,
date
)
is AgendaEventGroup -> DayDialog(activity, app.profileId, date, eventTypeId = event.typeId)
is BaseCalendarEvent -> if (event.isPlaceHolder)
DayDialog(activity, app.profileId, date)
}
if (event is BaseEvent && event.showItemBadge) {
val unreadCount = manager.events.count {
it.instanceDay.equals(event.instanceDay) && it.showBadge
}
// only clicked event is unread, remove the day badge
if (unreadCount == 1 && event.showBadge) {
event.dayReference.showBadge = false
unreadDates.remove(date.value)
}
setAsRead(event)
}
}
override fun onScrollToDate(calendar: Calendar) {
selectedDate = Date.fromCalendar(calendar)
// Mark as read scrolled date
if (selectedDate.value in unreadDates) {
setAsRead(calendar)
activity.launch(Dispatchers.Default) {
app.db.eventDao().setSeenByDate(app.profileId, selectedDate, true)
}
unreadDates.remove(selectedDate.value)
}
}
},
AgendaEventRenderer(app.eventManager, isCompactMode),
AgendaEventGroupRenderer(),
LessonChangesEventRenderer(),
TeacherAbsenceEventRenderer()
)
listView.setOnScrollListener(this)
isInitialized = true
b.progressBar.isVisible = false
}
private fun updateView() {
manager.events.clear()
manager.loadEvents(events, BaseCalendarEvent())
adapter?.updateEvents(manager.events)
//listView.scrollToCurrentDate(selectedDate.asCalendar)
}
private fun setAsRead(date: Calendar) {
// get all events matching the date
val events = manager.events.filter {
if (it.instanceDay.equals(date) && it.showBadge && it is AgendaEvent) {
// hide the day badge for the date
it.dayReference.showBadge = false
return@filter true
}
false
}
// set this date's events as read
setAsRead(*events.toTypedArray())
}
private fun setAsRead(vararg event: CalendarEvent) {
// hide per-event badges
for (e in event) {
events.firstOrNull {
it == e
}?.showBadge = false
e.showBadge = false
}
listView.setOnScrollListener(this)
updateData()
}
private fun addEvents(
events: MutableList<CalendarEvent>,
eventList: List<EventFull>
) {
events.removeAll { it is AgendaEvent || it is AgendaEventGroup }
if (!profileConfig.agendaGroupByType) {
events += eventList.map {
if (!it.seen)
unreadDates.add(it.date.value)
AgendaEvent(it)
}
return
}
eventList.groupBy {
it.date.value to it.type
}.forEach { (_, list) ->
val event = list.first()
if (list.size == 1) {
if (!event.seen)
unreadDates.add(event.date.value)
events += AgendaEvent(event)
} else {
events.add(0, AgendaEventGroup(
profileId = event.profileId,
date = event.date,
typeId = event.type,
typeName = event.typeName ?: "-",
typeColor = event.typeColor ?: event.eventColor,
count = list.size,
showBadge = list.any { !it.seen }
))
}
}
}
private fun addLessonChanges(events: MutableList<CalendarEvent>) {
val lessons = app.db.timetableDao().getChangesNow(app.profileId)
val grouped = lessons.groupBy {
it.displayDate
}
events += grouped.mapNotNull { (date, changes) ->
LessonChangesEvent(
app.profileId,
date = date ?: return@mapNotNull null,
count = changes.size,
showBadge = changes.any { !it.seen }
)
}
}
private fun addTeacherAbsence(events: MutableList<CalendarEvent>) {
val teacherAbsence = app.db.teacherAbsenceDao().getAllNow(app.profileId)
val countMap = SparseIntArray()
for (absence in teacherAbsence) {
while (absence.dateFrom <= absence.dateTo) {
countMap[absence.dateFrom.value] += 1
absence.dateFrom.stepForward(0, 0, 1)
}
}
countMap.forEach { dateInt, count ->
events += TeacherAbsenceEvent(
app.profileId,
date = Date.fromValue(dateInt),
count = count
)
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-9.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda
import com.github.tibolte.agendacalendarview.models.CalendarEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import java.util.*
open class BaseEvent(
private val id: Long,
private val time: Calendar,
private val color: Int,
private var showBadge: Boolean,
var showItemBadge: Boolean = showBadge
) : CalendarEvent {
override fun copy() = BaseEvent(id, time, color, showBadge)
private lateinit var date: Calendar
override fun getInstanceDay() = date
override fun setInstanceDay(value: Calendar) {
date = value
}
private lateinit var dayReference: IDayItem
override fun getDayReference() = dayReference
override fun setDayReference(value: IDayItem) {
dayReference = value
}
private lateinit var weekReference: IWeekItem
override fun getWeekReference() = weekReference
override fun setWeekReference(value: IWeekItem) {
weekReference = value
}
override fun getShowBadge() = showBadge
override fun setShowBadge(value: Boolean) {
showBadge = value
showItemBadge = value
}
override fun getId() = id
override fun getStartTime() = time
override fun getEndTime() = time
override fun getTitle() = ""
override fun getDescription() = ""
override fun getLocation() = ""
override fun getColor() = color
override fun getTextColor() = 0
override fun isPlaceholder() = false
override fun isAllDay() = false
override fun setId(value: Long) = Unit
override fun setStartTime(value: Calendar) = Unit
override fun setEndTime(value: Calendar) = Unit
override fun setTitle(value: String) = Unit
override fun setDescription(value: String) = Unit
override fun setLocation(value: String) = Unit
override fun setTextColor(value: Int) = Unit
override fun setPlaceholder(value: Boolean) = Unit
override fun setAllDay(value: Boolean) = Unit
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
class AgendaEvent(
val event: EventFull,
showBadge: Boolean = !event.seen
) : BaseEvent(
id = event.id,
time = event.startTimeCalendar,
color = event.eventColor,
showBadge = showBadge
) {
override fun copy() = AgendaEvent(event, showBadge)
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
class AgendaEventGroup(
val profileId: Int,
val date: Date,
val typeId: Long,
val typeName: String,
val typeColor: Int,
val count: Int,
showBadge: Boolean
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = typeColor,
showBadge = showBadge
) {
override fun copy() = AgendaEventGroup(profileId, date, typeId, typeName, typeColor, count, showBadge)
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import android.view.View
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedGroupBinding
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class AgendaEventGroupRenderer : EventRenderer<AgendaEventGroup>() {
override fun render(view: View, event: AgendaEventGroup) {
val b = AgendaWrappedGroupBinding.bind(view).item
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.text = event.typeName
b.name.setTextColor(Colors.legibleTextColor(event.color))
b.count.text = event.count.toString()
b.count.background.setTintColor(android.R.attr.colorBackground.resolveAttr(view.context))
b.badge.isVisible = event.showItemBadge
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_group
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
import android.annotation.SuppressLint
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import com.mikepenz.iconics.view.IconicsTextView
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventCompactBinding
import pl.szczodrzynski.edziennik.join
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.managers.EventManager
class AgendaEventRenderer(
val manager: EventManager,
val isCompact: Boolean
) : EventRenderer<AgendaEvent>() {
@SuppressLint("SetTextI18n")
override fun render(view: View, aEvent: AgendaEvent) {
if (isCompact) {
val b = AgendaWrappedEventCompactBinding.bind(view).item
bindView(aEvent, b.card, b.title, null, b.badgeBackground, b.badge)
} else {
val b = AgendaWrappedEventBinding.bind(view).item
bindView(aEvent, b.card, b.title, b.subtitle, b.badgeBackground, b.badge)
}
}
private fun bindView(
aEvent: AgendaEvent,
card: FrameLayout,
title: IconicsTextView,
subtitle: TextView?,
badgeBackground: View,
badge: View
) {
val event = aEvent.event
val textColor = Colors.legibleTextColor(event.eventColor)
val timeText = if (event.time == null)
card.context.getString(R.string.agenda_event_all_day)
else
event.time!!.stringHM
val eventSubtitle = listOfNotNull(
timeText,
event.subjectLongName,
event.teacherName,
event.teamName
).join(", ")
card.foreground.setTintColor(event.eventColor)
card.background.setTintColor(event.eventColor)
manager.setEventTopic(title, event, doneIconColor = textColor)
title.setTextColor(textColor)
subtitle?.text = eventSubtitle
subtitle?.setTextColor(textColor)
badgeBackground.isVisible = aEvent.showItemBadge
badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(card.context)
)
badge.isVisible = aEvent.showItemBadge
}
override fun getEventLayout() = if (isCompact)
R.layout.agenda_wrapped_event_compact
else
R.layout.agenda_wrapped_event
}

View File

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class LessonChangeCounter(
val lessonChangeDate: Date,
var lessonChangeCount: Int
) {
val startTime: Calendar
get() = Calendar.getInstance().apply {
set(lessonChangeDate.year, lessonChangeDate.month - 1, lessonChangeDate.day, 10, 0, 0)
}
val endTime: Calendar
get() = Calendar.getInstance().apply {
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
}
}

View File

@ -1,243 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange;
import com.github.tibolte.agendacalendarview.models.CalendarEvent;
import com.github.tibolte.agendacalendarview.models.IDayItem;
import com.github.tibolte.agendacalendarview.models.IWeekItem;
import java.util.Calendar;
import pl.szczodrzynski.edziennik.utils.models.Date;
public class LessonChangeEvent implements CalendarEvent {
/**
* Id of the event.
*/
private long mId;
/**
* Color to be displayed in the agenda view.
*/
private int mColor;
/**
* Text color displayed on the background color
*/
private int mTextColor;
/**
* Calendar instance helping sorting the events per section in the agenda view.
*/
private Calendar mInstanceDay;
/**
* Start time of the event.
*/
private Calendar mStartTime;
/**
* End time of the event.
*/
private Calendar mEndTime;
/**
* References to a DayItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private IDayItem mDayReference;
/**
* References to a WeekItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private IWeekItem mWeekReference;
private int profileId;
private Date lessonChangeDate;
private int lessonChangeCount;
public LessonChangeEvent(LessonChangeEvent calendarEvent) {
this.mId = calendarEvent.getId();
this.mColor = calendarEvent.getColor();
this.mTextColor = calendarEvent.getTextColor();
this.mStartTime = calendarEvent.getStartTime();
this.mEndTime = calendarEvent.getEndTime();
this.profileId = calendarEvent.getProfileId();
this.lessonChangeDate = calendarEvent.getLessonChangeDate();
this.lessonChangeCount = calendarEvent.getLessonChangeCount();
}
public LessonChangeEvent(long mId, int mColor, int mTextColor, Calendar mStartTime, Calendar mEndTime, int profileId, Date lessonChangeDate, int lessonChangeCount) {
this.mId = mId;
this.mColor = mColor;
this.mTextColor = mTextColor;
this.mStartTime = mStartTime;
this.mEndTime = mEndTime;
this.profileId = profileId;
this.lessonChangeDate = lessonChangeDate;
this.lessonChangeCount = lessonChangeCount;
}
public int getProfileId() {
return profileId;
}
public Date getLessonChangeDate() {
return lessonChangeDate;
}
public int getLessonChangeCount() {
return lessonChangeCount;
}
public void setProfileId(int profileId) {
this.profileId = profileId;
}
public void setLessonChangeDate(Date lessonChangeDate) {
this.lessonChangeDate = lessonChangeDate;
}
public void setLessonChangeCount(int lessonChangeCount) {
this.lessonChangeCount = lessonChangeCount;
}
@Override
public void setPlaceholder(boolean placeholder) {
}
@Override
public boolean isPlaceholder() {
return false;
}
@Override
public String getLocation() {
return null;
}
@Override
public void setLocation(String mLocation) {
}
@Override
public long getId() {
return mId;
}
@Override
public void setId(long mId) {
this.mId = mId;
}
@Override
public boolean getShowBadge() {
return false;
}
@Override
public void setShowBadge(boolean mShowBadge) {
}
@Override
public int getTextColor() {
return mTextColor;
}
@Override
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
}
@Override
public String getDescription() {
return null;
}
@Override
public void setDescription(String mDescription) {
}
@Override
public boolean isAllDay() {
return false;
}
@Override
public void setAllDay(boolean allDay) {
}
@Override
public Calendar getStartTime() {
return mStartTime;
}
@Override
public void setStartTime(Calendar mStartTime) {
this.mStartTime = mStartTime;
}
@Override
public Calendar getEndTime() {
return mEndTime;
}
@Override
public void setEndTime(Calendar mEndTime) {
this.mEndTime = mEndTime;
}
@Override
public String getTitle() {
return null;
}
@Override
public void setTitle(String mTitle) {
}
@Override
public Calendar getInstanceDay() {
return mInstanceDay;
}
@Override
public void setInstanceDay(Calendar mInstanceDay) {
this.mInstanceDay = mInstanceDay;
this.mInstanceDay.set(Calendar.HOUR, 0);
this.mInstanceDay.set(Calendar.MINUTE, 0);
this.mInstanceDay.set(Calendar.SECOND, 0);
this.mInstanceDay.set(Calendar.MILLISECOND, 0);
this.mInstanceDay.set(Calendar.AM_PM, 0);
}
@Override
public IDayItem getDayReference() {
return mDayReference;
}
@Override
public void setDayReference(IDayItem mDayReference) {
this.mDayReference = mDayReference;
}
@Override
public IWeekItem getWeekReference() {
return mWeekReference;
}
@Override
public void setWeekReference(IWeekItem mWeekReference) {
this.mWeekReference = mWeekReference;
}
@Override
public CalendarEvent copy() {
return new LessonChangeEvent(this);
}
@Override
public int getColor() {
return mColor;
}
}

View File

@ -1,21 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
import android.view.View
import android.widget.TextView
import androidx.cardview.widget.CardView
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
class LessonChangeEventRenderer : EventRenderer<LessonChangeEvent>() {
override fun render(view: View?, event: LessonChangeEvent) {
val card = view?.findViewById<CardView>(R.id.lesson_change_card)
val changeText = view?.findViewById<TextView>(R.id.lesson_change_text)
val changeCount = view?.findViewById<TextView>(R.id.lessonChangeCount)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor)
changeCount?.setTextColor(event.textColor)
changeCount?.text = event.lessonChangeCount.toString()
}
override fun getEventLayout(): Int = R.layout.agenda_event_lesson_change
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import pl.szczodrzynski.edziennik.utils.models.Date
class LessonChangesEvent(
val profileId: Int,
val date: Date,
val count: Int,
showBadge: Boolean
) : BaseEvent(
id = date.value.toLong(),
time = date.asCalendar,
color = 0xff78909c.toInt(),
showBadge = false,
showItemBadge = showBadge
) {
override fun copy() = LessonChangesEvent(profileId, date, count, showItemBadge)
override fun getShowBadge() = false
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
import android.view.View
import androidx.core.view.isVisible
import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class LessonChangesEventRenderer : EventRenderer<LessonChangesEvent>() {
override fun render(view: View, event: LessonChangesEvent) {
val b = AgendaWrappedCounterBinding.bind(view).item
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_lesson_changes)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = event.showItemBadge
b.badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(view.context)
)
b.badge.isVisible = event.showItemBadge
}
fun render(b: AgendaCounterItemBinding, event: LessonChangesEvent) {
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_lesson_changes)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = event.showItemBadge
b.badgeBackground.background.setTintColor(
android.R.attr.colorBackground.resolveAttr(b.root.context)
)
b.badge.isVisible = event.showItemBadge
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
}

View File

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceCounter (
val teacherAbsenceDate: Date,
var teacherAbsenceCount: Int = 0
) {
val startTime: Calendar
get() = Calendar.getInstance().apply {
set(teacherAbsenceDate.year, teacherAbsenceDate.month - 1, teacherAbsenceDate.day, 10, 0, 0)
}
val endTime: Calendar
get() = Calendar.getInstance().apply {
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
}
}

View File

@ -1,188 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import com.github.tibolte.agendacalendarview.models.CalendarEvent import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
import com.github.tibolte.agendacalendarview.models.IDayItem
import com.github.tibolte.agendacalendarview.models.IWeekItem
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class TeacherAbsenceEvent : CalendarEvent { class TeacherAbsenceEvent(
/** val profileId: Int,
* Id of the event. val date: Date,
*/ val count: Int
private var mId: Long = 0 ) : BaseEvent(
/** id = date.value.toLong(),
* Color to be displayed in the agenda view. time = date.asCalendar,
*/ color = 0xffff1744.toInt(),
private var mColor: Int = 0 showBadge = false
/** ) {
* Text color displayed on the background color override fun copy() = TeacherAbsenceEvent(profileId, date, count)
*/
private var mTextColor: Int = 0
/**
* Calendar instance helping sorting the events per section in the agenda view.
*/
private var mInstanceDay: Calendar? = null
/**
* Start time of the event.
*/
private var mStartTime: Calendar? = null
/**
* End time of the event.
*/
private var mEndTime: Calendar? = null
/**
* References to a DayItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private var mDayReference: IDayItem? = null
/**
* References to a WeekItem instance for that event, used to link interaction between the
* calendar view and the agenda view.
*/
private var mWeekReference: IWeekItem? = null
private var profileId: Int = 0
var teacherAbsenceDate: Date? = null
var teacherAbsenceCount: Int = 0
constructor(calendarEvent: TeacherAbsenceEvent) {
this.mId = calendarEvent.id
this.mColor = calendarEvent.color
this.mTextColor = calendarEvent.textColor
this.mStartTime = calendarEvent.startTime
this.mEndTime = calendarEvent.endTime
this.profileId = calendarEvent.profileId
this.teacherAbsenceDate = calendarEvent.teacherAbsenceDate
this.teacherAbsenceCount = calendarEvent.teacherAbsenceCount
}
constructor(mId: Long, mColor: Int, mTextColor: Int, mStartTime: Calendar, mEndTime: Calendar, profileId: Int, teacherAbsenceDate: Date, teacherAbsenceCount: Int) {
this.mId = mId
this.mColor = mColor
this.mTextColor = mTextColor
this.mStartTime = mStartTime
this.mEndTime = mEndTime
this.profileId = profileId
this.teacherAbsenceDate = teacherAbsenceDate
this.teacherAbsenceCount = teacherAbsenceCount
}
override fun setPlaceholder(placeholder: Boolean) {
}
override fun isPlaceholder(): Boolean {
return false
}
override fun getLocation(): String? {
return null
}
override fun setLocation(mLocation: String) {
}
override fun getId(): Long {
return mId
}
override fun setId(mId: Long) {
this.mId = mId
}
override fun getShowBadge(): Boolean {
return false
}
override fun setShowBadge(mShowBadge: Boolean) {
}
override fun getTextColor(): Int {
return mTextColor
}
override fun setTextColor(mTextColor: Int) {
this.mTextColor = mTextColor
}
override fun getDescription(): String? {
return null
}
override fun setDescription(mDescription: String) {
}
override fun isAllDay(): Boolean {
return false
}
override fun setAllDay(allDay: Boolean) {
}
override fun getStartTime(): Calendar? {
return mStartTime
}
override fun setStartTime(mStartTime: Calendar) {
this.mStartTime = mStartTime
}
override fun getEndTime(): Calendar? {
return mEndTime
}
override fun setEndTime(mEndTime: Calendar) {
this.mEndTime = mEndTime
}
override fun getTitle(): String? {
return null
}
override fun setTitle(mTitle: String) {
}
override fun getInstanceDay(): Calendar? {
return mInstanceDay
}
override fun setInstanceDay(mInstanceDay: Calendar) {
this.mInstanceDay = mInstanceDay
this.mInstanceDay!!.set(Calendar.HOUR, 0)
this.mInstanceDay!!.set(Calendar.MINUTE, 0)
this.mInstanceDay!!.set(Calendar.SECOND, 0)
this.mInstanceDay!!.set(Calendar.MILLISECOND, 0)
this.mInstanceDay!!.set(Calendar.AM_PM, 0)
}
override fun getDayReference(): IDayItem? {
return mDayReference
}
override fun setDayReference(mDayReference: IDayItem) {
this.mDayReference = mDayReference
}
override fun getWeekReference(): IWeekItem? {
return mWeekReference
}
override fun setWeekReference(mWeekReference: IWeekItem) {
this.mWeekReference = mWeekReference
}
override fun copy(): CalendarEvent {
return TeacherAbsenceEvent(this)
}
override fun getColor(): Int {
return mColor
}
} }

View File

@ -1,21 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
import android.view.View import android.view.View
import android.widget.TextView import androidx.core.view.isVisible
import androidx.cardview.widget.CardView
import com.github.tibolte.agendacalendarview.render.EventRenderer import com.github.tibolte.agendacalendarview.render.EventRenderer
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.Colors
class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() { class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
override fun render(view: View?, event: TeacherAbsenceEvent) {
val card = view?.findViewById<CardView>(R.id.teacherAbsenceCard) override fun render(view: View, event: TeacherAbsenceEvent) {
val changeText = view?.findViewById<TextView>(R.id.teacherAbsenceText) val b = AgendaWrappedCounterBinding.bind(view).item
val changeCount = view?.findViewById<TextView>(R.id.teacherAbsenceCount) val textColor = Colors.legibleTextColor(event.color)
card?.setCardBackgroundColor(event.color)
changeText?.setTextColor(event.textColor) b.card.foreground.setTintColor(event.color)
changeCount?.setTextColor(event.textColor) b.card.background.setTintColor(event.color)
changeCount?.text = event.teacherAbsenceCount.toString() b.name.setText(R.string.agenda_teacher_absence)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = false
b.badge.isVisible = false
} }
override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence fun render(b: AgendaCounterItemBinding, event: TeacherAbsenceEvent) {
val textColor = Colors.legibleTextColor(event.color)
b.card.foreground.setTintColor(event.color)
b.card.background.setTintColor(event.color)
b.name.setText(R.string.agenda_teacher_absence)
b.name.setTextColor(textColor)
b.count.text = event.count.toString()
b.count.setTextColor(textColor)
b.badgeBackground.isVisible = false
b.badge.isVisible = false
}
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
} }

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding
import pl.szczodrzynski.edziennik.setTintColor import pl.szczodrzynski.edziennik.setTintColor
import pl.szczodrzynski.edziennik.utils.BetterLink
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class AttendanceDetailsDialog( class AttendanceDetailsDialog(
@ -60,5 +61,13 @@ class AttendanceDetailsDialog(
b.attendanceName.background.setTintColor(attendanceColor) b.attendanceName.background.setTintColor(attendanceColor)
b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no) b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no)
attendance.teacherName?.let { name ->
BetterLink.attach(
b.teacherName,
teachers = mapOf(attendance.teacherId to name),
onActionSelected = dialog::dismiss
)
}
}} }}
} }

View File

@ -41,7 +41,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val manager by lazy { app.attendanceManager } private val manager
get() = app.attendanceManager
private var viewType = AttendanceFragment.VIEW_DAYS private var viewType = AttendanceFragment.VIEW_DAYS
private var expandSubjectId = 0L private var expandSubjectId = 0L

View File

@ -47,7 +47,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val manager by lazy { app.attendanceManager } private val manager
get() = app.attendanceManager
private var expandSubjectId = 0L private var expandSubjectId = 0L
private var attendance = listOf<AttendanceFull>() private var attendance = listOf<AttendanceFull>()

View File

@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.Utils.bs import pl.szczodrzynski.edziennik.utils.Utils.bs
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -83,6 +84,14 @@ class NoticesAdapter//getting the context and product list with constructor
} else { } else {
holder.noticesItemReason.background = null holder.noticesItemReason.background = null
} }
BetterLink.attach(holder.noticesItemReason)
notice.teacherName?.let { name ->
BetterLink.attach(holder.noticesItemTeacherName, teachers = mapOf(
notice.teacherId to name
))
}
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {

View File

@ -40,7 +40,8 @@ class GradesAdapter(
} }
private val app = activity.applicationContext as App private val app = activity.applicationContext as App
private val manager = app.gradesManager private val manager
get() = app.gradesManager
private val job = Job() private val job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext

View File

@ -48,9 +48,12 @@ class GradesListFragment : Fragment(), CoroutineScope {
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
// local/private variables go here // local/private variables go here
private val manager by lazy { app.gradesManager } private val manager
private val dontCountEnabled by lazy { manager.dontCountEnabled } get() = app.gradesManager
private val dontCountGrades by lazy { manager.dontCountGrades } private val dontCountEnabled
get() = manager.dontCountEnabled
private val dontCountGrades
get() = manager.dontCountGrades
private var expandSubjectId = 0L private var expandSubjectId = 0L
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

View File

@ -95,9 +95,11 @@ class LoginEggsFragment : Fragment(), CoroutineScope {
anim.interpolator = AccelerateDecelerateInterpolator() anim.interpolator = AccelerateDecelerateInterpolator()
anim.duration = 10 anim.duration = 10
anim.fillAfter = true anim.fillAfter = true
activity.runOnUiThread {
activity.getRootView().startAnimation(anim) activity.getRootView().startAnimation(anim)
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions) nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
} }
}
}, "EggInterface") }, "EggInterface")
loadUrl("https://szkolny.eu/game/runner.html") loadUrl("https://szkolny.eu/game/runner.html")
webViewClient = object : WebViewClient() { webViewClient = object : WebViewClient() {

View File

@ -30,10 +30,12 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.colorAttr import pl.szczodrzynski.navlib.colorAttr
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
@ -64,10 +66,20 @@ class MessageFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return if (!isAdded) return
activity.bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_messages_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
}
)
b.closeButton.setImageDrawable( b.closeButton.setImageDrawable(
IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply { IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply {
colorAttr(activity, android.R.attr.textColorSecondary) colorAttr(activity, android.R.attr.textColorSecondary)
sizeDp = 16 sizeDp = 24
} }
) )
b.closeButton.setOnClickListener { activity.navigateUp() } b.closeButton.setOnClickListener { activity.navigateUp() }

View File

@ -1,11 +1,8 @@
package pl.szczodrzynski.edziennik.ui.modules.messages package pl.szczodrzynski.edziennik.ui.modules.messages
import android.graphics.Typeface import android.graphics.Typeface
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable import android.widget.Filterable
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -13,17 +10,14 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.cleanDiacritics
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.MessagesFilter
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder
import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min
class MessagesAdapter( class MessagesAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
@ -43,41 +37,10 @@ class MessagesAdapter(
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
var items = mutableListOf<Any>() var items = listOf<Any>()
var allItems = mutableListOf<Any>() var allItems = listOf<Any>()
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) } val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) } val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
private val comparator by lazy { Comparator { o1: Any, o2: Any ->
if (o1 !is MessageFull || o2 !is MessageFull)
return@Comparator 0
when {
// standard sorting
o1.filterWeight > o2.filterWeight -> return@Comparator 1
o1.filterWeight < o2.filterWeight -> return@Comparator -1
else -> when {
// reversed sorting
o1.addedDate > o2.addedDate -> return@Comparator -1
o1.addedDate < o2.addedDate -> return@Comparator 1
else -> return@Comparator 0
}
}
}}
val textWatcher by lazy {
object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
getFilter().filter(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
/*items.getOrNull(0)?.let {
if (it is MessagesSearch) {
it.searchText = s?.toString() ?: ""
}
}*/
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
@ -103,138 +66,16 @@ class MessagesAdapter(
return return
when { when {
holder is MessageViewHolder && item is MessageFull -> holder.onBind(activity, app, item, position, this) holder is MessageViewHolder
holder is SearchViewHolder && item is MessagesSearch -> holder.onBind(activity, app, item, position, this) && item is MessageFull -> holder.onBind(activity, app, item, position, this)
holder is SearchViewHolder
&& item is MessagesSearch -> holder.onBind(activity, app, item, position, this)
} }
} }
private val messagesFilter by lazy {
MessagesFilter(this)
}
override fun getItemCount() = items.size override fun getItemCount() = items.size
override fun getFilter() = filter override fun getFilter() = messagesFilter
private var prevCount = -1
private val filter by lazy { object : Filter() {
override fun performFiltering(prefix: CharSequence?): FilterResults {
val results = FilterResults()
if (prevCount == -1)
prevCount = allItems.size
if (prefix.isNullOrEmpty()) {
allItems.forEach {
if (it is MessageFull)
it.searchHighlightText = null
}
results.values = allItems.toList()
results.count = allItems.size
return results
}
val items = mutableListOf<Any>()
val prefixString = prefix.toString()
allItems.forEach {
if (it !is MessageFull) {
items.add(it)
return@forEach
}
it.filterWeight = 100
it.searchHighlightText = null
var weight: Int
if (it.type == Message.TYPE_SENT) {
it.recipients?.forEach { recipient ->
weight = getMatchWeight(recipient.fullName, prefixString)
if (weight != 100) {
if (weight == 3)
weight = 31
it.filterWeight = min(it.filterWeight, 10 + weight)
}
}
}
else {
weight = getMatchWeight(it.senderName, prefixString)
if (weight != 100) {
if (weight == 3)
weight = 31
it.filterWeight = min(it.filterWeight, 10 + weight)
}
}
weight = getMatchWeight(it.subject, prefixString)
if (weight != 100) {
if (weight == 3)
weight = 22
it.filterWeight = min(it.filterWeight, 20 + weight)
}
if (it.filterWeight != 100) {
it.searchHighlightText = prefixString
items.add(it)
}
}
Collections.sort(items, comparator)
results.values = items
results.count = items.size
return results
}
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
results.values?.let { items = it as MutableList<Any> }
// do not re-bind the search box
val count = results.count - 1
// this tries to update every item except the search field
when {
count > prevCount -> {
notifyItemRangeInserted(prevCount + 1, count - prevCount)
notifyItemRangeChanged(1, prevCount)
}
count < prevCount -> {
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
notifyItemRangeChanged(1, count)
}
else -> {
notifyItemRangeChanged(1, count)
}
}
/*if (prevCount != count) {
items.getOrNull(0)?.let {
if (it is MessagesSearch) {
it.count = count
notifyItemChanged(0)
}
}
}*/
prevCount = count
}
}}
private fun getMatchWeight(name: CharSequence?, prefix: String): Int {
if (name == null)
return 100
val nameClean = name.cleanDiacritics()
// First match against the whole, non-split value
if (nameClean.startsWith(prefix, ignoreCase = true) || name.startsWith(prefix, ignoreCase = true)) {
return 1
} else {
// check if prefix matches any of the words
val words = nameClean.split(" ").toTypedArray() + name.split(" ").toTypedArray()
for (word in words) {
if (word.startsWith(prefix, ignoreCase = true)) {
return 2
}
}
}
// finally check if the prefix matches any part of the name
if (nameClean.contains(prefix, ignoreCase = true) || name.contains(prefix, ignoreCase = true)) {
return 3
}
return 100
}
} }

View File

@ -12,7 +12,9 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class MessagesFragment : Fragment(), CoroutineScope { class MessagesFragment : Fragment(), CoroutineScope {
@ -100,9 +102,19 @@ class MessagesFragment : Fragment(), CoroutineScope {
fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline
} }
setFabOnClickListener(View.OnClickListener { bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_messages_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
}
)
setFabOnClickListener {
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE) activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE)
}) }
} }
activity.gainAttentionFAB() activity.gainAttentionFAB()

View File

@ -33,6 +33,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: MessagesListFragmentBinding private lateinit var b: MessagesListFragmentBinding
private var adapter: MessagesAdapter? = null
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -53,21 +54,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED) val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
var topPosition = arguments.getInt("topPosition", NO_POSITION) var topPosition = arguments.getInt("topPosition", NO_POSITION)
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION) var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
val searchText = arguments.getString("searchText", "")
teachers = withContext(Dispatchers.Default) { teachers = withContext(Dispatchers.Default) {
app.db.teacherDao().getAllNow(App.profileId) app.db.teacherDao().getAllNow(App.profileId)
} }
val adapter = MessagesAdapter(activity, teachers) { adapter = MessagesAdapter(activity, teachers) {
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle( activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
"messageId" to it.id "messageId" to it.id
)) ))
} }
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { items -> app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages ->
if (!isAdded) return@Observer if (!isAdded) return@Observer
items.forEach { message -> messages.forEach { message ->
message.recipients?.removeAll { it.profileId != message.profileId } message.recipients?.removeAll { it.profileId != message.profileId }
message.recipients?.forEach { recipient -> message.recipients?.forEach { recipient ->
if (recipient.fullName == null) { if (recipient.fullName == null) {
@ -77,13 +79,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
} }
// load & configure the adapter // load & configure the adapter
adapter.items = items.toMutableList() val items = messages.toMutableList<Any>()
adapter.items.add(0, MessagesSearch().also { items.add(0, MessagesSearch().also {
it.count = items.size it.searchText = searchText
}) })
adapter.allItems = adapter.items.toMutableList()
adapter?.items = items
adapter?.allItems = items
if (items.isNotNullNorEmpty() && b.list.adapter == null) { if (items.isNotNullNorEmpty() && b.list.adapter == null) {
if (searchText.isNotBlank())
adapter?.filter?.filter(searchText) {
b.list.adapter = adapter b.list.adapter = adapter
}
else
b.list.adapter = adapter
b.list.apply { b.list.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
@ -92,7 +103,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
addOnScrollListener(onScrollListener) addOnScrollListener(onScrollListener)
} }
} }
adapter.notifyDataSetChanged()
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty()) setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty())
(b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager -> (b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
@ -119,10 +130,15 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!isAdded) return if (!isAdded)
return
val layoutManager = (b.list.layoutManager as? LinearLayoutManager)
val searchItem = adapter?.items?.firstOrNull { it is MessagesSearch } as? MessagesSearch
onPageDestroy?.invoke(position, Bundle( onPageDestroy?.invoke(position, Bundle(
"topPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition(), "topPosition" to layoutManager?.findFirstVisibleItemPosition(),
"bottomPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition() "bottomPosition" to layoutManager?.findLastCompletelyVisibleItemPosition(),
"searchText" to searchItem?.searchText?.toString()
)) ))
} }
} }

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.ui.modules.messages.compose package pl.szczodrzynski.edziennik.ui.modules.messages.compose
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
@ -43,6 +44,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
import pl.szczodrzynski.edziennik.utils.Colors import pl.szczodrzynski.edziennik.utils.Colors
@ -50,6 +52,7 @@ import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.elevateSurface import pl.szczodrzynski.navlib.elevateSurface
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.text.replace import kotlin.text.replace
@ -67,6 +70,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private val profileConfig by lazy { app.config.forProfile().ui }
private val greetingText
get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
private var teachers = mutableListOf<Teacher>() private var teachers = mutableListOf<Teacher>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -131,6 +138,16 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
} }
}*/ }*/
activity.bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_messages_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
}
)
launch { launch {
delay(100) delay(100)
getRecipientList() getRecipientList()
@ -290,7 +307,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
b.recipients.setIllegalCharacterIdentifier { c -> b.recipients.setIllegalCharacterIdentifier { c ->
c.toString().matches("[\\n;:_ ]".toRegex()) c.toString().matches("[\\n;:_ ]".toRegex())
} }
b.recipients.setOnChipRemoveListener { _ -> b.recipients.setOnChipRemoveListener {
b.recipients.setSelection(b.recipients.text.length) b.recipients.setSelection(b.recipients.text.length)
} }
@ -318,14 +335,15 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
fabExtendedText = getString(R.string.messages_compose_send) fabExtendedText = getString(R.string.messages_compose_send)
fabIcon = CommunityMaterial.Icon3.cmd_send_outline fabIcon = CommunityMaterial.Icon3.cmd_send_outline
setFabOnClickListener(View.OnClickListener { setFabOnClickListener {
sendMessage() sendMessage()
}) }
} }
activity.gainAttentionFAB() activity.gainAttentionFAB()
} }
@SuppressLint("SetTextI18n")
private fun updateRecipientList(list: List<Teacher>) { launch { private fun updateRecipientList(list: List<Teacher>) { launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
teachers = list.sortedBy { it.fullName }.toMutableList() teachers = list.sortedBy { it.fullName }.toMutableList()
@ -344,10 +362,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
val adapter = MessagesComposeSuggestionAdapter(activity, teachers) val adapter = MessagesComposeSuggestionAdapter(activity, teachers)
b.recipients.setAdapter(adapter) b.recipients.setAdapter(adapter)
if (profileConfig.messagesGreetingOnCompose)
b.text.setText(greetingText)
handleReplyMessage() handleReplyMessage()
handleMailToIntent()
}} }}
private fun handleReplyMessage() { launch { private fun handleReplyMessage() = launch {
val replyMessage = arguments?.getString("message") val replyMessage = arguments?.getString("message")
if (replyMessage != null) { if (replyMessage != null) {
val chipList = mutableListOf<ChipInfo>() val chipList = mutableListOf<ChipInfo>()
@ -369,8 +391,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
if (arguments?.getString("type") == "reply") { if (arguments?.getString("type") == "reply") {
// add greeting text // add greeting text
span.replace(0, 0, "\n\nZ poważaniem,\n${app.profile.accountName if (profileConfig.messagesGreetingOnReply)
?: app.profile.studentNameLong ?: ""}\n\n\n") span.replace(0, 0, "$greetingText\n\n\n")
else
span.replace(0, 0, "\n\n")
teachers.firstOrNull { it.id == msg.senderId }?.let { teacher -> teachers.firstOrNull { it.id == msg.senderId }?.let { teacher ->
teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName) teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
@ -378,7 +402,12 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
} }
subject = "Re: ${msg.subject}" subject = "Re: ${msg.subject}"
} else { } else {
// add greeting text
if (profileConfig.messagesGreetingOnForward)
span.replace(0, 0, "$greetingText\n\n\n")
else
span.replace(0, 0, "\n\n") span.replace(0, 0, "\n\n")
subject = "Fwd: ${msg.subject}" subject = "Fwd: ${msg.subject}"
} }
body = MessagesUtils.htmlToSpannable(activity, msg.body body = MessagesUtils.htmlToSpannable(activity, msg.body
@ -400,7 +429,23 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
else { else {
b.recipients.requestFocus() b.recipients.requestFocus()
} }
}} }
private fun handleMailToIntent() {
val teacherId = arguments?.getLong("messageRecipientId")
if (teacherId == 0L)
return
val chipList = mutableListOf<ChipInfo>()
teachers.firstOrNull { it.id == teacherId }?.let { teacher ->
teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
chipList += ChipInfo(teacher.fullName, teacher)
}
b.recipients.addTextWithChips(chipList)
val subject = arguments?.getString("messageSubject")
b.subject.setText(subject ?: return)
}
private fun sendMessage() { private fun sendMessage() {
b.recipientsLayout.error = null b.recipientsLayout.error = null

View File

@ -5,7 +5,5 @@
package pl.szczodrzynski.edziennik.ui.modules.messages.models package pl.szczodrzynski.edziennik.ui.modules.messages.models
class MessagesSearch { class MessagesSearch {
var isFocused = false var searchText: CharSequence = ""
var searchText = ""
var count = 0
} }

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
class MessagesComparator : Comparator<Any> {
override fun compare(o1: Any?, o2: Any?): Int {
if (o1 !is MessageFull || o2 !is MessageFull)
return 0
return when {
// standard sorting
o1.filterWeight > o2.filterWeight -> 1
o1.filterWeight < o2.filterWeight -> -1
else -> when {
// reversed sorting
o1.addedDate > o2.addedDate -> -1
o1.addedDate < o2.addedDate -> 1
else -> 0
}
}
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
import android.widget.Filter
import pl.szczodrzynski.edziennik.cleanDiacritics
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
import java.util.*
import kotlin.math.min
class MessagesFilter(
private val adapter: MessagesAdapter
) : Filter() {
companion object {
private const val NO_MATCH = 1000
}
private val comparator = MessagesComparator()
private var prevCount = -1
private val allItems
get() = adapter.allItems
private fun getMatchWeight(name: CharSequence?, prefix: CharSequence): Int {
if (name == null)
return NO_MATCH
val prefixClean = prefix.cleanDiacritics()
val nameClean = name.cleanDiacritics()
return when {
// First match against the whole, non-split value
nameClean.startsWith(prefixClean, ignoreCase = true) -> 1
// check if prefix matches any of the words
nameClean.split(" ").any {
it.startsWith(prefixClean, ignoreCase = true)
} -> 2
// finally check if the prefix matches any part of the name
nameClean.contains(prefixClean, ignoreCase = true) -> 3
else -> NO_MATCH
}
}
override fun performFiltering(prefix: CharSequence?): FilterResults {
val results = FilterResults()
if (prevCount == -1)
prevCount = allItems.size
if (prefix.isNullOrBlank()) {
allItems.forEach {
if (it is MessageFull)
it.searchHighlightText = null
}
results.values = allItems.toList()
results.count = allItems.size
return results
}
val items = mutableListOf<Any>()
allItems.forEach {
if (it !is MessageFull) {
items.add(it)
return@forEach
}
it.filterWeight = NO_MATCH
it.searchHighlightText = null
var weight: Int
// weights 11..13 and 110
if (it.type == Message.TYPE_SENT) {
it.recipients?.forEach { recipient ->
weight = getMatchWeight(recipient.fullName, prefix)
if (weight != NO_MATCH) {
if (weight == 3)
weight = 100
it.filterWeight = min(it.filterWeight, 10 + weight)
}
}
} else {
weight = getMatchWeight(it.senderName, prefix)
if (weight != NO_MATCH) {
if (weight == 3)
weight = 100
it.filterWeight = min(it.filterWeight, 10 + weight)
}
}
// weights 21..23 and 120
weight = getMatchWeight(it.subject, prefix)
if (weight != NO_MATCH) {
if (weight == 3)
weight = 100
it.filterWeight = min(it.filterWeight, 20 + weight)
}
// weights 31..33 and 130
weight = getMatchWeight(it.body, prefix)
if (weight != NO_MATCH) {
if (weight == 3)
weight = 100
it.filterWeight = min(it.filterWeight, 30 + weight)
}
if (it.filterWeight != NO_MATCH) {
it.searchHighlightText = prefix
items.add(it)
}
}
Collections.sort(items, comparator)
results.values = items
results.count = items.size
return results
}
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
results.values?.let {
adapter.items = it as MutableList<Any>
}
// do not re-bind the search box
val count = results.count - 1
// this tries to update every item except the search field
with(adapter) {
when {
count > prevCount -> {
notifyItemRangeInserted(prevCount + 1, count - prevCount)
notifyItemRangeChanged(1, prevCount)
}
count < prevCount -> {
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
notifyItemRangeChanged(1, count)
}
else -> {
notifyItemRangeChanged(1, count)
}
}
}
prevCount = count
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
import android.text.Editable
import android.text.TextWatcher
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
class SearchTextWatcher(
private val b: MessagesListItemSearchBinding,
private val filter: MessagesFilter,
private val item: MessagesSearch
) : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) {
item.searchText = s ?: ""
filter.filter(s) { count ->
if (s.isNullOrBlank())
b.searchLayout.helperText = " "
else
b.searchLayout.helperText =
b.root.context.getString(R.string.messages_search_results, count - 1)
}
}
override fun equals(other: Any?): Boolean {
return other is SearchTextWatcher
}
override fun hashCode(): Int {
var result = b.hashCode()
result = 31 * result + filter.hashCode()
result = 31 * result + item.hashCode()
return result
}
}

View File

@ -30,9 +30,13 @@ class MessageViewHolder(
private const val TAG = "MessageViewHolder" private const val TAG = "MessageViewHolder"
} }
override fun onBind(activity: AppCompatActivity, app: App, item: MessageFull, position: Int, adapter: MessagesAdapter) { override fun onBind(
val manager = app.gradesManager activity: AppCompatActivity,
app: App,
item: MessageFull,
position: Int,
adapter: MessagesAdapter
) {
b.messageSubject.text = item.subject b.messageSubject.text = item.subject
b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort
b.messageAttachmentImage.isVisible = item.hasAttachments b.messageAttachmentImage.isVisible = item.hasAttachments
@ -55,15 +59,17 @@ class MessageViewHolder(
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage) b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
b.messageSender.text = messageInfo.profileName b.messageSender.text = messageInfo.profileName
item.searchHighlightText?.let { highlight -> item.searchHighlightText?.toString()?.let { highlight ->
val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity) val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity)
b.messageSubject.text = b.messageSubject.text.asSpannable( b.messageSubject.text = b.messageSubject.text.asSpannable(
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight), StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
substring = highlight, ignoreCase = true, ignoreDiacritics = true) substring = highlight, ignoreCase = true, ignoreDiacritics = true
)
b.messageSender.text = b.messageSender.text.asSpannable( b.messageSender.text = b.messageSender.text.asSpannable(
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight), StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
substring = highlight, ignoreCase = true, ignoreDiacritics = true) substring = highlight, ignoreCase = true, ignoreDiacritics = true
)
} }
adapter.onItemClick?.let { listener -> adapter.onItemClick?.let { listener ->

View File

@ -9,38 +9,43 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.SearchTextWatcher
class SearchViewHolder( class SearchViewHolder(
inflater: LayoutInflater, inflater: LayoutInflater,
parent: ViewGroup, parent: ViewGroup,
val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(inflater, parent, false) val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(
inflater,
parent,
false
)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessagesSearch, MessagesAdapter> { ) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessagesSearch, MessagesAdapter> {
companion object { companion object {
private const val TAG = "SearchViewHolder" private const val TAG = "SearchViewHolder"
} }
override fun onBind(activity: AppCompatActivity, app: App, item: MessagesSearch, position: Int, adapter: MessagesAdapter) { override fun onBind(
b.searchEdit.removeTextChangedListener(adapter.textWatcher) activity: AppCompatActivity,
b.searchEdit.addTextChangedListener(adapter.textWatcher) app: App,
item: MessagesSearch,
position: Int,
adapter: MessagesAdapter
) {
val watcher = SearchTextWatcher(b, adapter.filter, item)
b.searchEdit.removeTextChangedListener(watcher)
/*b.searchEdit.setOnKeyboardListener(object : TextInputKeyboardEdit.KeyboardListener { if (adapter.items.isEmpty() || adapter.items.size == adapter.allItems.size)
override fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean) { b.searchLayout.helperText = " "
item.isFocused = showing else
} b.searchLayout.helperText =
})*/ b.root.context.getString(R.string.messages_search_results, adapter.items.size - 1)
/*if (b.searchEdit.text.toString() != item.searchText) {
b.searchEdit.setText(item.searchText) b.searchEdit.setText(item.searchText)
b.searchEdit.setSelection(item.searchText.length)
}*/
//b.searchLayout.helperText = app.getString(R.string.messages_search_results, item.count) b.searchEdit.addTextChangedListener(watcher)
/*if (item.isFocused && !b.searchEdit.isFocused)
b.searchEdit.requestFocus()*/
} }
} }

View File

@ -4,6 +4,8 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.colorRes
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -43,6 +45,10 @@ class NotificationsAdapter(
val date = Date.fromMillis(item.addedDate).formattedString val date = Date.fromMillis(item.addedDate).formattedString
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
b.notificationIcon.background = IconicsDrawable(app, item.getLargeIcon()).apply {
colorRes = R.color.colorPrimary
}
b.title.text = item.text b.title.text = item.text
b.profileDate.text = listOf( b.profileDate.text = listOf(
item.profileName ?: "", item.profileName ?: "",

View File

@ -12,6 +12,8 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.after import pl.szczodrzynski.edziennik.after
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog
@ -58,6 +60,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
} }
override fun getItems() = listOfNotNull( override fun getItems() = listOfNotNull(
util.createActionItem(
text = R.string.menu_agenda_config,
icon = CommunityMaterial.Icon.cmd_calendar_outline
) {
AgendaConfigDialog(activity, reloadOnDismiss = false)
},
util.createActionItem( util.createActionItem(
text = R.string.menu_grades_config, text = R.string.menu_grades_config,
icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline
@ -65,6 +74,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
GradesConfigDialog(activity, reloadOnDismiss = false) GradesConfigDialog(activity, reloadOnDismiss = false)
}, },
util.createActionItem(
text = R.string.menu_messages_config,
icon = CommunityMaterial.Icon.cmd_calendar_outline
) {
MessagesConfigDialog(activity, reloadOnDismiss = false)
},
util.createActionItem( util.createActionItem(
text = R.string.menu_attendance_config, text = R.string.menu_attendance_config,
icon = CommunityMaterial.Icon.cmd_calendar_remove_outline icon = CommunityMaterial.Icon.cmd_calendar_remove_outline

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.settings.MiniMenuConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ThemeChooserDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.ThemeChooserDialog
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
import pl.szczodrzynski.edziennik.utils.BigNightUtil
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -36,6 +37,18 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
} }
else null, else null,
if (BigNightUtil().isDataWielkanocyNearDzisiaj()) // cool klasa for utility to dzień wielkanocy
util.createPropertyItem(
text = R.string.settings_theme_eggfall_text,
subText = R.string.settings_theme_eggfall_subtext,
icon = CommunityMaterial.Icon.cmd_egg_easter,
value = configGlobal.ui.eggfall
) { _, it ->
configGlobal.ui.eggfall = it
activity.recreate()
}
else null,
util.createActionItem( util.createActionItem(
text = R.string.settings_theme_theme_text, text = R.string.settings_theme_theme_text,
subText = Themes.getThemeNameRes(), subText = Themes.getThemeNameRes(),

View File

@ -5,16 +5,16 @@
package pl.szczodrzynski.edziennik.ui.modules.timetable package pl.szczodrzynski.edziennik.ui.modules.timetable
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.core.view.setPadding import androidx.core.view.setPadding
import androidx.lifecycle.Observer import androidx.core.view.updateLayoutParams
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -24,14 +24,15 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
@ -44,33 +45,28 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var inflater: AsyncLayoutInflater private lateinit var inflater: AsyncLayoutInflater
private lateinit var b: TimetableDayFragmentBinding
private val job: Job = Job() private val job: Job = Job()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
private var timeIndicatorJob: Job? = null
private lateinit var date: Date private lateinit var date: Date
private var startHour = DEFAULT_START_HOUR private var startHour = DEFAULT_START_HOUR
private var endHour = DEFAULT_END_HOUR private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24 * 60 private var firstEventMinute = 24 * 60
private var paddingTop = 0
private val manager by lazy { app.timetableManager } private val manager
get() = app.timetableManager
// find SwipeRefreshLayout in the hierarchy // find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
// the day ScrollView
private val dayScrollDelegate = lazy {
val dayScroll = ListenerScrollView(context!!)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
dayScroll.setOnRefreshLayoutEnabledListener { enabled ->
refreshLayout?.isEnabled = enabled
}
dayScroll
}
private val dayScroll by dayScrollDelegate
// the lesson DayView
private val dayView by lazy { private val dayView by lazy {
val dayView = DayView(context!!, DayViewConfig( val dayView = DayView(activity, DayViewConfig(
startHour = startHour, startHour = startHour,
endHour = endHour, endHour = endHour,
dividerHeight = 1.dp, dividerHeight = 1.dp,
@ -82,37 +78,33 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
eventMargin = 2.dp eventMargin = 2.dp
), true) ), true)
dayView.setPadding(10.dp) dayView.setPadding(10.dp)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) return@lazy dayView
dayScroll.addView(dayView)
dayView
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
context ?: return null context ?: return null
app = activity.application as App app = activity.application as App
this.inflater = AsyncLayoutInflater(context!!) this.inflater = AsyncLayoutInflater(requireContext())
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
return FrameLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) b = TimetableDayFragmentBinding.inflate(inflater, null, false)
addView(ProgressBar(activity).apply { return b.root
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
})
}
} }
override fun onPageCreated(): Boolean { override fun onPageCreated(): Boolean {
// observe lesson database // observe lesson database
app.db.timetableDao().getAllForDate(App.profileId, date).observe(this, Observer { lessons -> app.db.timetableDao().getAllForDate(App.profileId, date).observe(this) { lessons ->
launch { launch {
val events = withContext(Dispatchers.Default) { val events = withContext(Dispatchers.Default) {
app.db.eventDao().getAllByDateNow(App.profileId, date) app.db.eventDao().getAllByDateNow(App.profileId, date)
} }
processLessonList(lessons, events) processLessonList(lessons, events)
} }
}) }
return true return true
} }
@ -120,9 +112,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) { private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
// no lessons - timetable not downloaded yet // no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout?) { view, _, parent -> inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
parent?.removeAllViews() b.root.removeAllViews()
parent?.addView(view) b.root.addView(view)
val b = TimetableNoTimetableBinding.bind(view) val b = TimetableNoTimetableBinding.bind(view)
val weekStart = date.weekStart.stringY_m_d val weekStart = date.weekStart.stringY_m_d
b.noTimetableSync.onClick { b.noTimetableSync.onClick {
@ -143,9 +136,9 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
} }
// one lesson indicating a day without lessons // one lesson indicating a day without lessons
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout?) { view, _, parent -> inflater.inflate(R.layout.timetable_no_lessons, b.root) { view, _, _ ->
parent?.removeAllViews() b.root.removeAllViews()
parent?.addView(view) b.root.addView(view)
} }
return return
} }
@ -157,12 +150,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
return return
} }
// clear the root view and add the ScrollView b.scrollView.isVisible = true
(view as FrameLayout?)?.removeAllViews() b.dayFrame.removeView(b.dayView)
(view as FrameLayout?)?.addView(dayScroll) b.dayFrame.addView(dayView, 0)
// Inflate a label view for each hour the day view will display // Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>() val hourLabelViews = mutableListOf<View>()
for (i in dayView.startHour..dayView.endHour) { for (i in dayView.startHour..dayView.endHour) {
if (!isAdded) if (!isAdded)
continue continue
@ -171,6 +164,11 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
hourLabelViews.add(hourLabelView) hourLabelViews.add(hourLabelView)
} }
dayView.setHourLabelViews(hourLabelViews) dayView.setHourLabelViews(hourLabelViews)
// measure dayView top padding needed for the timeIndicator
hourLabelViews.getOrNull(0)?.let {
it.measure(0, 0)
paddingTop = it.measuredHeight / 2 + dayView.paddingTop
}
lessons.forEach { it.showAsUnseen = !it.seen } lessons.forEach { it.showAsUnseen = !it.seen }
@ -201,8 +199,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
// Try to recycle an existing event view if there are enough left, otherwise inflate // Try to recycle an existing event view if there are enough left, otherwise inflate
// a new one // a new one
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false)) val eventView =
?: continue (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(
R.layout.timetable_lesson,
dayView,
false
)) ?: continue
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
@ -290,16 +292,50 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
} }
updateTimeIndicator()
dayView.setEventViews(eventViews, eventTimeRanges) dayView.setEventViews(eventViews, eventTimeRanges)
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt()) b.scrollView.scrollTo(0, firstEventTop.toInt())
b.progressBar.isVisible = false
}
private fun updateTimeIndicator() {
val time = Time.getNow()
val isTimeInView =
date == Date.getToday() && time.hour in dayView.startHour..dayView.endHour
b.timeIndicator.isVisible = isTimeInView
b.timeIndicatorMarker.isVisible = isTimeInView
if (isTimeInView) {
val startTime = Time(dayView.startHour, 0, 0)
val seconds = time.inSeconds - startTime.inSeconds * 1f
b.timeIndicator.updateLayoutParams<FrameLayout.LayoutParams> {
topMargin = (seconds * dayView.minuteHeight / 60f).toInt() + paddingTop
}
b.timeIndicatorMarker.updateLayoutParams<FrameLayout.LayoutParams> {
topMargin = b.timeIndicator.marginTop - (16.dp / 2) + (1.dp / 2)
}
}
if (timeIndicatorJob == null) {
timeIndicatorJob = startCoroutineTimer(repeatMillis = 30000) {
updateTimeIndicator()
}
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (dayScrollDelegate.isInitialized()) {
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt()) b.scrollView.scrollTo(0, firstEventTop.toInt())
} updateTimeIndicator()
}
override fun onPause() {
super.onPause()
timeIndicatorJob?.cancel()
timeIndicatorJob = null
} }
} }

View File

@ -175,7 +175,7 @@ class DateDropdown : TextInputDropDown {
} }
} }
fun pickerDialog() { private fun pickerDialog() {
val date = getSelected() as? Date ?: Date.getToday() val date = getSelected() as? Date ?: Date.getToday()
MaterialDatePicker.Builder.datePicker() MaterialDatePicker.Builder.datePicker()

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
*/
package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context
import android.util.AttributeSet
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class EventTypeDropdown : TextInputDropDown {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
lateinit var db: AppDb
var profileId: Int = 0
var onTypeSelected: ((eventType: EventType) -> Unit)? = null
override fun create(context: Context) {
super.create(context)
isEnabled = false
}
suspend fun loadItems() {
val types = withContext(Dispatchers.Default) {
val list = mutableListOf<Item>()
var types = db.eventTypeDao().getAllNow(profileId)
if (types.none { it.id in -1L..10L }) {
types = db.eventTypeDao().addDefaultTypes(context, profileId)
}
list += types.map {
Item(it.id, it.name, tag = it, icon = IconicsDrawable(context).apply {
icon = CommunityMaterial.Icon.cmd_circle
sizeDp = 24
colorInt = it.color
})
}
list
}
clear().append(types)
isEnabled = true
setOnChangeListener {
when (it.tag) {
is EventType -> {
// selected an event type
onTypeSelected?.invoke(it.tag)
true
}
else -> false
}
}
}
/**
* Select an event type by the [typeId].
*/
fun selectType(typeId: Long) = select(typeId)
/**
* Select an event type by the [typeId] **if it's not selected yet**.
*/
fun selectDefault(typeId: Long?) {
if (typeId == null || selected != null)
return
selectType(typeId)
}
/**
* Get the currently selected event type.
* ### Returns:
* - null if no valid type is selected
* - [EventType] - the selected event type
*/
fun getSelected(): EventType? {
return when (selected?.tag) {
is EventType -> selected?.tag as EventType
else -> null
}
}
}

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.crc16 import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.ui.dialogs.input import pl.szczodrzynski.edziennik.ui.dialogs.input
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
@ -40,7 +41,7 @@ class SubjectDropdown : TextInputDropDown {
var showNoSubject = true var showNoSubject = true
var showCustomSubject = false var showCustomSubject = false
var customSubjectName = "" var customSubjectName = ""
var onSubjectSelected: ((subjectId: Long?) -> Unit)? = null var onSubjectSelected: ((subject: Subject?) -> Unit)? = null
var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null
override fun create(context: Context) { override fun create(context: Context) {
@ -73,7 +74,7 @@ class SubjectDropdown : TextInputDropDown {
list += subjects.map { Item( list += subjects.map { Item(
it.id, it.id,
it.longName, it.longName,
tag = it.id tag = it
) } ) }
list list
@ -91,10 +92,11 @@ class SubjectDropdown : TextInputDropDown {
} }
-1L -> { -1L -> {
// no subject // no subject
deselect()
onSubjectSelected?.invoke(null) onSubjectSelected?.invoke(null)
true false
} }
is Long -> { is Subject -> {
// selected a subject // selected a subject
onSubjectSelected?.invoke(it.tag) onSubjectSelected?.invoke(it.tag)
true true
@ -104,7 +106,7 @@ class SubjectDropdown : TextInputDropDown {
} }
} }
fun customNameDialog() { private fun customNameDialog() {
activity ?: return activity ?: return
MaterialAlertDialogBuilder(activity!!) MaterialAlertDialogBuilder(activity!!)
.setTitle("Własny przedmiot") .setTitle("Własny przedmiot")
@ -127,32 +129,37 @@ class SubjectDropdown : TextInputDropDown {
.show() .show()
} }
fun selectSubject(subjectId: Long) { /**
if (select(subjectId) == null) * Select a subject by the [subjectId].
select(Item( */
subjectId, fun selectSubject(subjectId: Long): Item? {
"nieznany przedmiot ($subjectId)", if (subjectId == -1L) {
tag = subjectId deselect()
)) return null
}
return select(subjectId)
} }
fun selectDefault(subjectId: Long?) { /**
* Select a subject by the [subjectId] **if it's not selected yet**.
*/
fun selectDefault(subjectId: Long?): Item? {
if (subjectId == null || selected != null) if (subjectId == null || selected != null)
return return null
selectSubject(subjectId) return selectSubject(subjectId)
} }
/** /**
* Get the currently selected subject. * Get the currently selected subject.
* ### Returns: * ### Returns:
* - null if no valid subject is selected * - null if no valid subject is selected
* - [Long] - the selected subject's ID * - [Subject] - the selected subject
* - [String] - a custom subject name entered, if [showCustomSubject] == true * - [String] - a custom subject name entered, if [showCustomSubject] == true
*/ */
fun getSelected(): Any? { fun getSelected(): Any? {
return when (selected?.tag) { return when (selected?.tag) {
-1L -> null -1L -> null
is Long -> selected?.tag as Long is Subject -> selected?.tag as Subject
is String -> selected?.tag as String is String -> selected?.tag as String
else -> null else -> null
} }

View File

@ -5,13 +5,12 @@
package pl.szczodrzynski.edziennik.ui.modules.views package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
class TeacherDropdown : TextInputDropDown { class TeacherDropdown : TextInputDropDown {
@ -19,22 +18,10 @@ class TeacherDropdown : TextInputDropDown {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb lateinit var db: AppDb
var profileId: Int = 0 var profileId: Int = 0
var showNoTeacher = true var showNoTeacher = true
var onTeacherSelected: ((teacherId: Long?) -> Unit)? = null var onTeacherSelected: ((teacher: Teacher?) -> Unit)? = null
override fun create(context: Context) { override fun create(context: Context) {
super.create(context) super.create(context)
@ -58,7 +45,7 @@ class TeacherDropdown : TextInputDropDown {
list += teachers.map { Item( list += teachers.map { Item(
it.id, it.id,
it.fullName, it.fullName,
tag = it.id tag = it
) } ) }
list list
@ -71,10 +58,11 @@ class TeacherDropdown : TextInputDropDown {
when (it.tag) { when (it.tag) {
-1L -> { -1L -> {
// no teacher // no teacher
deselect()
onTeacherSelected?.invoke(null) onTeacherSelected?.invoke(null)
true false
} }
is Long -> { is Teacher -> {
// selected a teacher // selected a teacher
onTeacherSelected?.invoke(it.tag) onTeacherSelected?.invoke(it.tag)
true true
@ -84,31 +72,36 @@ class TeacherDropdown : TextInputDropDown {
} }
} }
fun selectTeacher(teacherId: Long) { /**
if (select(teacherId) == null) * Select a teacher by the [teacherId].
select(Item( */
teacherId, fun selectTeacher(teacherId: Long): Item? {
"nieznany nauczyciel ($teacherId)", if (teacherId == -1L) {
tag = teacherId deselect()
)) return null
}
return select(teacherId)
} }
fun selectDefault(teacherId: Long?) { /**
* Select a teacher by the [teacherId] **if it's not selected yet**.
*/
fun selectDefault(teacherId: Long?): Item? {
if (teacherId == null || selected != null) if (teacherId == null || selected != null)
return return null
selectTeacher(teacherId) return selectTeacher(teacherId)
} }
/** /**
* Get the currently selected teacher. * Get the currently selected teacher.
* ### Returns: * ### Returns:
* - null if no valid teacher is selected * - null if no valid teacher is selected
* - [Long] - the selected teacher's ID * - [Teacher] - the selected teacher
*/ */
fun getSelected(): Long? { fun getSelected(): Teacher? {
return when (selected?.tag) { return when (selected?.tag) {
-1L -> null -1L -> null
is Long -> selected?.tag as Long is Teacher -> selected?.tag as Teacher
else -> null else -> null
} }
} }

View File

@ -5,9 +5,7 @@
package pl.szczodrzynski.edziennik.ui.modules.views package pl.szczodrzynski.edziennik.ui.modules.views
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -20,22 +18,10 @@ class TeamDropdown : TextInputDropDown {
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val activity: AppCompatActivity?
get() {
var context: Context? = context ?: return null
if (context is AppCompatActivity) return context
while (context is ContextWrapper) {
if (context is AppCompatActivity)
return context
context = context.baseContext
}
return null
}
lateinit var db: AppDb lateinit var db: AppDb
var profileId: Int = 0 var profileId: Int = 0
var showNoTeam = true var showNoTeam = true
var onTeamSelected: ((teamId: Long?) -> Unit)? = null var onTeamSelected: ((team: Team?) -> Unit)? = null
override fun create(context: Context) { override fun create(context: Context) {
super.create(context) super.create(context)
@ -59,7 +45,7 @@ class TeamDropdown : TextInputDropDown {
list += teams.map { Item( list += teams.map { Item(
it.id, it.id,
it.name, it.name,
tag = it.id tag = it
) } ) }
list list
@ -72,10 +58,11 @@ class TeamDropdown : TextInputDropDown {
when (it.tag) { when (it.tag) {
-1L -> { -1L -> {
// no team // no team
deselect()
onTeamSelected?.invoke(null) onTeamSelected?.invoke(null)
true false
} }
is Long -> { is Team -> {
// selected a team // selected a team
onTeamSelected?.invoke(it.tag) onTeamSelected?.invoke(it.tag)
true true
@ -85,21 +72,29 @@ class TeamDropdown : TextInputDropDown {
} }
} }
fun selectTeam(teamId: Long) { /**
if (select(teamId) == null) * Select a teacher by the [teamId].
select(Item( */
teamId, fun selectTeam(teamId: Long): Item? {
"nieznana grupa ($teamId)", if (teamId == -1L) {
tag = teamId deselect()
)) return null
}
return select(teamId)
} }
fun selectDefault(teamId: Long?) { /**
* Select a team by the [teamId] **if it's not selected yet**.
*/
fun selectDefault(teamId: Long?): Item? {
if (teamId == null || selected != null) if (teamId == null || selected != null)
return return null
selectTeam(teamId) return selectTeam(teamId)
} }
/**
* Select a team of the [Team.TYPE_CLASS] type.
*/
fun selectTeamClass() { fun selectTeamClass() {
select(items.singleOrNull { select(items.singleOrNull {
it.tag is Team && it.tag.type == Team.TYPE_CLASS it.tag is Team && it.tag.type == Team.TYPE_CLASS
@ -110,12 +105,12 @@ class TeamDropdown : TextInputDropDown {
* Get the currently selected team. * Get the currently selected team.
* ### Returns: * ### Returns:
* - null if no valid team is selected * - null if no valid team is selected
* - [Long] - the team's ID * - [Team] - the selected team
*/ */
fun getSelected(): Any? { fun getSelected(): Team? {
return when (selected?.tag) { return when (selected?.tag) {
-1L -> null -1L -> null
is Long -> selected?.tag as Long is Team -> selected?.tag as Team
else -> null else -> null
} }
} }

View File

@ -175,7 +175,7 @@ class TimeDropdown : TextInputDropDown {
return !noTimetable return !noTimetable
} }
fun pickerDialog() { private fun pickerDialog() {
val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow() val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow()
MaterialTimePicker.Builder() MaterialTimePicker.Builder()

View File

@ -45,6 +45,7 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot
getLong("id") ?: 0, getLong("id") ?: 0,
getString("title") ?: "", getString("title") ?: "",
getString("text") ?: "", getString("text") ?: "",
getString("textLong"),
getInt("type") ?: 0, getInt("type") ?: 0,
getInt("profileId"), getInt("profileId"),
getString("profileName"), getString("profileName"),

View File

@ -7,6 +7,8 @@
package pl.szczodrzynski.edziennik.utils package pl.szczodrzynski.edziennik.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
@ -19,141 +21,268 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuPopupHelper import androidx.appcompat.view.menu.MenuPopupHelper
import pl.szczodrzynski.edziennik.Intent import androidx.core.widget.addTextChangedListener
import pl.szczodrzynski.edziennik.copyToClipboard import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.Regexes import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getTextPosition
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@SuppressLint("RestrictedApi")
object BetterLink { object BetterLink {
@SuppressLint("RestrictedApi") /**
fun attach(textView: TextView, onActionSelected: (() -> Unit)? = null) { * Used in conjunction with the item's ID to execute the
textView.autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES * [attach]'s onActionSelected listener when the item is
BetterLinkMovementMethod.linkify(textView.autoLinkMask, textView).setOnLinkClickListener { v, span: BetterLinkMovementMethod.ClickableSpanWithText -> * clicked.
val url = span.text() */
val c = v.context private const val FLAG_ACTION = 0x8000
val s = v.text as Spanned private fun MenuBuilder.setTitle(title: CharSequence): MenuBuilder {
val start = s.getSpanStart(span.span()) this::class.java.getDeclaredMethod("setHeaderTitleInt", CharSequence::class.java).let {
val end = s.getSpanEnd(span.span()) it.isAccessible = true
it.invoke(this, title)
}
return this
}
val parent = v.rootView.findViewById<ViewGroup>(android.R.id.content) private fun MenuItem.addListener(listener: (item: MenuItem) -> Boolean): MenuItem {
this::class.java.getDeclaredField("mClickListener").let {
it.isAccessible = true
val oldListener = it.get(this) as? MenuItem.OnMenuItemClickListener
it.set(this, object : MenuItem.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
oldListener?.onMenuItemClick(item)
return listener(item)
}
})
}
return this
}
private fun createUrlItems(menu: MenuBuilder, context: Context, url: String) {
menu.setTitle(url)
menu.add(
1,
2,
2,
"Otwórz w przeglądarce"
).setOnMenuItemClickListener {
Utils.openUrl(context, url)
true
}
}
private fun createMailtoItems(menu: MenuBuilder, context: Context, url: String) {
menu.add(
1,
3,
3,
"Napisz e-mail"
).setOnMenuItemClickListener {
Utils.openUrl(context, url)
true
}
}
private fun createDateItems(menu: MenuBuilder, context: Context, date: Date?) {
date ?: return
menu.setTitle(date.formattedString)
menu.add(
1,
4 or FLAG_ACTION,
4,
"Utwórz wydarzenie"
).setOnMenuItemClickListener {
val intent = Intent(
Intent.ACTION_MAIN,
"action" to "createManualEvent",
"eventDate" to date.stringY_m_d
)
context.sendBroadcast(intent)
true
}
}
private fun createTeacherItems(menu: MenuBuilder, context: Context, teacherId: Long, fullName: String) {
menu.setTitle(fullName)
menu.add(
1,
5 or FLAG_ACTION,
5,
"Napisz wiadomość"
).setOnMenuItemClickListener {
val intent = Intent(
Intent.ACTION_MAIN,
"fragmentId" to MainActivity.TARGET_MESSAGES_COMPOSE,
"messageRecipientId" to teacherId
)
context.sendBroadcast(intent)
true
}
}
private fun onClickListener(
view: TextView,
span: BetterLinkMovementMethod.ClickableSpanWithText,
onActionSelected: (() -> Unit)?
): Boolean {
val context = view.context
val spanned = view.text as Spanned
val start = spanned.getSpanStart(span.span())
val end = spanned.getSpanEnd(span.span())
val parent = view.rootView.findViewById<ViewGroup>(android.R.id.content)
val parentLocation = intArrayOf(0, 0) val parentLocation = intArrayOf(0, 0)
parent.getLocationOnScreen(parentLocation) parent.getLocationOnScreen(parentLocation)
val rect = textView.getTextPosition(start..end) val rect = view.getTextPosition(start..end)
val view = View(c) val popupView = View(context)
view.layoutParams = ViewGroup.LayoutParams(rect.width(), rect.height()) popupView.layoutParams = ViewGroup.LayoutParams(rect.width(), rect.height())
view.setBackgroundColor(Color.TRANSPARENT) popupView.setBackgroundColor(Color.TRANSPARENT)
parent.addView(view) parent.addView(popupView)
view.x = rect.left.toFloat() - parentLocation[0] popupView.x = rect.left.toFloat() - parentLocation[0]
view.y = rect.top.toFloat() - parentLocation[1] popupView.y = rect.top.toFloat() - parentLocation[1]
val menu = MenuBuilder(c) val menu = MenuBuilder(context)
val helper = MenuPopupHelper(c, menu, view) val helper = MenuPopupHelper(context, menu, popupView)
val popup = helper.popup val popup = helper.popup
var menuTitle = url.substringAfter(":") val spanUrl = span.text()
var date: Date? = null val spanParameter = spanUrl.substringAfter(":")
val spanText = spanned.substring(start, end)
var urlItem: MenuItem? = null //goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
var createEventItem: MenuItem? = null
//var goToTimetableItem: MenuItem? = null // TODO 2020-03-19: implement this
var mailItem: MenuItem? = null
var copyItem: MenuItem? = null
// create appropriate items for spans
when { when {
url.startsWith("mailto:") -> { spanUrl.startsWith("mailto:") -> createMailtoItems(menu, context, spanUrl)
mailItem = menu.add(1, 20, 2, "Napisz e-mail") spanUrl.startsWith("dateYmd:") -> createDateItems(menu, context, parseDateYmd(spanParameter))
} spanUrl.startsWith("dateDmy:") -> createDateItems(menu, context, parseDateDmy(spanParameter))
url.startsWith("dateYmd:") -> { spanUrl.startsWith("dateAbs:") -> createDateItems(menu, context, parseDateAbs(spanParameter))
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie") spanUrl.startsWith("dateRel:") -> createDateItems(menu, context, parseDateRel(spanParameter))
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji") spanUrl.startsWith("teacher:") -> createTeacherItems(
date = parseDateYmd(menuTitle) menu,
} context,
url.startsWith("dateDmy:") -> { teacherId = spanParameter.toLongOrNull() ?: -1,
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie") fullName = spanText
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
date = parseDateDmy(menuTitle)
}
url.startsWith("dateAbs:") -> {
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie")
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
date = parseDateAbs(menuTitle)
}
url.startsWith("dateRel:") -> {
createEventItem = menu.add(1, 10, 2, "Utwórz wydarzenie")
//goToTimetableItem = menu.add(1, 11, 3, "Idź do planu lekcji")
date = parseDateRel(menuTitle)
}
else -> {
urlItem = menu.add(1, 1, 2, "Otwórz w przeglądarce")
menuTitle = url
}
}
copyItem = menu.add(1, 1000, 1000, "Kopiuj tekst")
helper.setOnDismissListener { parent.removeView(view) }
urlItem?.setOnMenuItemClickListener { Utils.openUrl(c, url); true }
mailItem?.setOnMenuItemClickListener { Utils.openUrl(c, url); true }
copyItem?.setOnMenuItemClickListener { menuTitle.copyToClipboard(c); true }
createEventItem?.setOnMenuItemClickListener {
onActionSelected?.invoke()
val intent = Intent(
android.content.Intent.ACTION_MAIN,
"action" to "createManualEvent",
"eventDate" to date?.stringY_m_d
) )
c.sendBroadcast(intent) else -> createUrlItems(menu, context, spanUrl)
}
menu.add(1, 1000, 1000, "Kopiuj tekst").setOnMenuItemClickListener {
spanParameter.copyToClipboard(context)
true true
} }
menu::class.java.getDeclaredMethod("setHeaderTitleInt", CharSequence::class.java).let { helper.setOnDismissListener { parent.removeView(popupView) }
it.isAccessible = true
it.invoke(menu, menuTitle) menu.visibleItems.forEach { item ->
if ((item.itemId and FLAG_ACTION) != FLAG_ACTION)
return@forEach
item.addListener {
onActionSelected?.invoke()
true
} }
}
popup::class.java.getDeclaredField("mShowTitle").let { popup::class.java.getDeclaredField("mShowTitle").let {
it.isAccessible = true it.isAccessible = true
it.set(popup, true) it.set(popup, true)
} }
helper::class.java.getDeclaredMethod("showPopup", Int::class.java, Int::class.java, Boolean::class.java, Boolean::class.java).let { helper::class.java.getDeclaredMethod(
"showPopup",
Int::class.java,
Int::class.java,
Boolean::class.java,
Boolean::class.java
).let {
it.isAccessible = true it.isAccessible = true
it.invoke(helper, 0, 0, false, true) it.invoke(helper, 0, 0, false, true)
} }
true return true
} }
val spanned = textView.text as? Spannable ?: { fun attach(
SpannableString(textView.text) textView: TextView,
}() teachers: Map<Long, String>? = null,
onActionSelected: (() -> Unit)? = null
) {
textView.autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES
BetterLinkMovementMethod
.linkify(textView.autoLinkMask, textView)
.setOnLinkClickListener { view, span ->
onClickListener(view, span, onActionSelected)
}
textView.addTextChangedListener {
attachSpan(textView, teachers)
}
attachSpan(textView, teachers)
}
private fun attachSpan(
textView: TextView,
teachers: Map<Long, String>? = null
) {
val spanned = textView.text as? Spannable ?: SpannableString(textView.text)
teachers?.forEach { (id, fullName) ->
val index = textView.text.indexOf(fullName)
if (index == -1)
return@forEach
val span = URLSpan("teacher:$id")
spanned.setSpan(
span,
index,
index + fullName.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
Regexes.LINKIFY_DATE_YMD.findAll(textView.text).forEach { match -> Regexes.LINKIFY_DATE_YMD.findAll(textView.text).forEach { match ->
val span = URLSpan("dateYmd:" + match.value) val span = URLSpan("dateYmd:" + match.value)
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spanned.setSpan(
span,
match.range.first,
match.range.last + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
Regexes.LINKIFY_DATE_DMY.findAll(textView.text).forEach { match -> Regexes.LINKIFY_DATE_DMY.findAll(textView.text).forEach { match ->
val span = URLSpan("dateDmy:" + match.value) val span = URLSpan("dateDmy:" + match.value)
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spanned.setSpan(
span,
match.range.first,
match.range.last + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
Regexes.LINKIFY_DATE_ABSOLUTE.findAll(textView.text).forEach { match -> Regexes.LINKIFY_DATE_ABSOLUTE.findAll(textView.text).forEach { match ->
val span = URLSpan("dateAbs:" + match.value) val span = URLSpan("dateAbs:" + match.value)
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spanned.setSpan(
span,
match.range.first,
match.range.last + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
Regexes.LINKIFY_DATE_RELATIVE.findAll(textView.text).forEach { match -> Regexes.LINKIFY_DATE_RELATIVE.findAll(textView.text).forEach { match ->
val span = URLSpan("dateRel:" + match.value) val span = URLSpan("dateRel:" + match.value)
spanned.setSpan(span, match.range.first, match.range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) spanned.setSpan(
span,
match.range.first,
match.range.last + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
} }
//Linkify.addLinks(textView, LINKIFY_DATE_ABSOLUTE.toPattern(), "dateAbs:")
//Linkify.addLinks(textView, LINKIFY_DATE_RELATIVE.toPattern(), "dateRel:")
} }
private val monthNames = listOf("sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru") private val monthNames =
listOf("sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru")
private fun parseDateYmd(text: String): Date? { private fun parseDateYmd(text: String): Date? {
return Regexes.LINKIFY_DATE_YMD.find(text)?.let { return Regexes.LINKIFY_DATE_YMD.find(text)?.let {
@ -163,6 +292,7 @@ object BetterLink {
Date(year, month, day) Date(year, month, day)
} }
} }
private fun parseDateDmy(text: String): Date? { private fun parseDateDmy(text: String): Date? {
return Regexes.LINKIFY_DATE_DMY.find(text)?.let { return Regexes.LINKIFY_DATE_DMY.find(text)?.let {
val day = it[1].toIntOrNull() ?: 1 val day = it[1].toIntOrNull() ?: 1

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
*/
package pl.szczodrzynski.edziennik.utils
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.math.absoluteValue
// Obliczanie daty wielkanocy - algorytm Gaussa
// www.algorytm.org
// (c) 2008 by Tomasz Lubinski
// http://www.algorytm.org/przetwarzanie-dat/wyznaczanie-daty-wielkanocy-algortym-gaussa/dwg-j.html
class BigNightUtil {
/* Pobierz wartosc A z tabeli lat */
private fun getA(rok: Int) = when {
rok <= 1582 -> 15
rok <= 1699 -> 22
rok <= 1899 -> 23
rok <= 2199 -> 24
rok <= 2299 -> 25
rok <= 2399 -> 26
rok <= 2499 -> 25
else -> 0
}
/* Pobierz wartosc B z tabeli lat */
private fun getB(rok: Int) = when {
rok <= 1582 -> 6
rok <= 1699 -> 2
rok <= 1799 -> 3
rok <= 1899 -> 4
rok <= 2099 -> 5
rok <= 2199 -> 6
rok <= 2299 -> 0
rok <= 2499 -> 1
else -> 0
}
/* oblicz ile dni po 22 marca przypada wielkanoc */
private fun Oblicz_Date_wielkanocy(rok: Int): Int {
val a = rok % 19
val b = rok % 4
val c = rok % 7
var d = (a * 19 + getA(rok)) % 30
val e = (2 * b + 4 * c + 6 * d + getB(rok)) % 7
if (d == 29 && e == 6 || d == 28 && e == 6) {
d -= 7
}
return d + e
}
private fun get_dataOf_bigNight(): Date {
val date = Date.getToday()
date.month = 4
date.day = 22 + Oblicz_Date_wielkanocy(date.year)
if (date.day > 31)
date.day = date.day % 31
else
date.month = 3
return date
}
fun isDataWielkanocyNearDzisiaj() =
Date.diffDays(Date.getToday(), get_dataOf_bigNight()).absoluteValue < 7
}

View File

@ -1,7 +1,11 @@
package pl.szczodrzynski.edziennik.utils package pl.szczodrzynski.edziennik.utils
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuPopupHelper
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
@ -29,10 +33,11 @@ open class TextInputDropDown : TextInputEditText {
val selectedId val selectedId
get() = selected?.id get() = selected?.id
fun updateText() { private fun updateText() {
setText(selected?.displayText ?: selected?.text) setText(selected?.displayText ?: selected?.text)
} }
@SuppressLint("RestrictedApi")
open fun create(context: Context) { open fun create(context: Context) {
val drawable = IconicsDrawable(context, CommunityMaterial.Icon.cmd_chevron_down).apply { val drawable = IconicsDrawable(context, CommunityMaterial.Icon.cmd_chevron_down).apply {
colorInt = Themes.getPrimaryTextColor(context) colorInt = Themes.getPrimaryTextColor(context)
@ -58,7 +63,9 @@ open class TextInputDropDown : TextInputEditText {
val popup = PopupMenu(context, this) val popup = PopupMenu(context, this)
items.forEachIndexed { index, item -> items.forEachIndexed { index, item ->
popup.menu.add(0, item.id.toInt(), index, item.text) popup.menu.add(0, item.id.toInt(), index, item.text).also {
it.icon = item.icon
}
} }
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
@ -70,29 +77,46 @@ open class TextInputDropDown : TextInputEditText {
true true
} }
popup.setOnDismissListener { val helper = MenuPopupHelper(context, popup.menu as MenuBuilder, this)
helper.setForceShowIcon(true)
helper.setOnDismissListener {
clearFocus() clearFocus()
} }
helper.show()
popup.show()
} }
} }
fun select(item: Item): Item? { /**
* Select an arbitrary [item]. Allows to select an item not present
* in the original list.
*/
fun select(item: Item): Item {
selected = item selected = item
updateText() updateText()
error = null error = null
return item return item
} }
/**
* Select an item by its ID. Returns the selected item
* if found.
*/
fun select(id: Long?): Item? { fun select(id: Long?): Item? {
return items.singleOrNull { it.id == id }?.let { select(it) } return items.singleOrNull { it.id == id }?.let { select(it) }
} }
/**
* Select an item by its tag. Returns the selected item
* if found.
*/
fun select(tag: Any?): Item? { fun select(tag: Any?): Item? {
return items.singleOrNull { it.tag == tag }?.let { select(it) } return items.singleOrNull { it.tag == tag }?.let { select(it) }
} }
/**
* Select an item by its index. Returns the selected item
* if the index exists.
*/
fun select(index: Int): Item? { fun select(index: Int): Item? {
return items.getOrNull(index)?.let { select(it) } return items.getOrNull(index)?.let { select(it) }
} }
@ -143,5 +167,11 @@ open class TextInputDropDown : TextInputEditText {
} }
} }
class Item(val id: Long, val text: CharSequence, val displayText: CharSequence? = null, val tag: Any? = null) class Item(
val id: Long,
val text: CharSequence,
val displayText: CharSequence? = null,
val tag: Any? = null,
val icon: Drawable? = null
)
} }

View File

@ -4,12 +4,17 @@
package pl.szczodrzynski.edziennik.utils.managers package pl.szczodrzynski.edziennik.utils.managers
import androidx.core.view.isVisible
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.mikepenz.iconics.view.IconicsTextView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.startCoroutineTimer
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class EventManager(val app: App) : CoroutineScope { class EventManager(val app: App) : CoroutineScope {
@ -32,4 +37,41 @@ class EventManager(val app: App) : CoroutineScope {
app.db.metadataDao().setSeen(event.profileId, event, true) app.db.metadataDao().setSeen(event.profileId, event, true)
} }
} }
fun setEventTopic(
title: IconicsTextView,
event: EventFull,
showType: Boolean = true,
doneIconColor: Int? = null
) {
var eventTopic = if (showType)
"${event.typeName ?: "wydarzenie"} - ${event.topic}"
else
event.topic
if (event.addedManually) {
eventTopic = "{cmd-clipboard-edit-outline} $eventTopic"
}
title.text = eventTopic
title.setCompoundDrawables(
null,
null,
if (event.isDone) IconicsDrawable(title.context).apply {
icon = CommunityMaterial.Icon.cmd_check
colorInt = doneIconColor ?: R.color.md_green_500.resolveColor(title.context)
sizeDp = 24
} else null,
null
)
}
fun setLegendText(legend: IconicsTextView, event: EventFull) {
legend.text = listOfNotNull(
if (event.addedManually) R.string.legend_event_added_manually else null,
if (event.isDone) R.string.legend_event_is_done else null
).map { legend.context.getString(it) }.join("\n")
legend.isVisible = legend.text.isNotBlank()
}
} }

View File

@ -94,17 +94,29 @@ public class Date implements Comparable<Date> {
} }
} }
public long getInMillis() { public Calendar getAsCalendar() {
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
c.set(year, month - 1, day, 0, 0, 0); c.set(year, month - 1, day, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0); c.set(Calendar.MILLISECOND, 0);
return c;
}
public Calendar getAsCalendar(Time time) {
if (time == null)
return getAsCalendar();
Calendar c = Calendar.getInstance();
c.set(year, month - 1, day, time.hour, time.minute, time.second);
c.set(Calendar.MILLISECOND, 0);
return c;
}
public long getInMillis() {
Calendar c = getAsCalendar();
return c.getTimeInMillis(); return c.getTimeInMillis();
} }
public long getInMillisUtc() { public long getInMillisUtc() {
Calendar c = Calendar.getInstance(); Calendar c = getAsCalendar();
c.set(year, month - 1, day, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
c.setTimeZone(TimeZone.getTimeZone("UTC")); c.setTimeZone(TimeZone.getTimeZone("UTC"));
return c.getTimeInMillis(); return c.getTimeInMillis();
} }
@ -179,13 +191,7 @@ public class Date implements Comparable<Date> {
} }
public long combineWith(Time time) { public long combineWith(Time time) {
if (time == null) { return getAsCalendar(time).getTimeInMillis();
return getInMillis();
}
Calendar c = Calendar.getInstance();
c.set(this.year, this.month - 1, this.day, time.hour, time.minute, time.second);
c.set(Calendar.MILLISECOND, 0);
return c.getTimeInMillis();
} }
public int getWeekDay() { public int getWeekDay() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-14.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/md_red_500"
android:pathData="M0,4V20L12,12.25" />
</vector>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-11.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="#ff1744"
tools:foregroundTint="#ff1744">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="10dp"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="@string/agenda_lesson_changes"
tools:textColor="@color/md_white_1000" />
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginVertical="2dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:textSize="20sp"
tools:text="3"
tools:textColor="@color/md_white_1000" />
<View
android:id="@+id/badgeBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_rounded_8dp"
android:layout_marginRight="-24dp"
android:layout_marginEnd="-24dp"
android:layout_marginTop="-24dp"
tools:backgroundTint="?android:colorBackground"/>
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-8.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="@color/blue_selected"
tools:foregroundTint="@color/blue_selected">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:padding="10dp">
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="2"
android:textSize="16sp"
tools:text="sprawdzian - Język polski"
tools:textColor="@color/md_white_1000" />
</LinearLayout>
<View
android:id="@+id/badgeBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="-24dp"
android:layout_marginEnd="-24dp"
android:layout_marginRight="-24dp"
android:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="?android:colorBackground" />
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-8.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="@color/blue_selected"
tools:foregroundTint="@color/blue_selected">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:padding="10dp">
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="3"
android:textSize="16sp"
tools:text="sprawdzian - Język polski"
tools:textColor="@color/md_white_1000" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="12sp"
tools:text="9:05, biologia, Jan Kowalski, 7a"
tools:textColor="@color/md_white_1000" />
</LinearLayout>
<View
android:id="@+id/badgeBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="-24dp"
android:layout_marginEnd="-24dp"
android:layout_marginRight="-24dp"
android:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="?android:colorBackground" />
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-10.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_8dp"
android:foreground="@drawable/bg_rounded_8dp_outline"
tools:backgroundTint="@color/blue_selected"
tools:foregroundTint="@color/blue_selected">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:padding="10dp"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="informacja"
tools:textColor="@color/md_white_1000" />
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginVertical="2dp"
android:layout_marginRight="-1dp"
android:background="@drawable/bg_rounded_8dp"
android:gravity="center"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingEnd="18dp"
android:paddingRight="18dp"
android:textSize="20sp"
tools:backgroundTint="?android:colorBackground"
tools:text="3" />
</LinearLayout>
<View
android:id="@+id/badge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end"
android:layout_margin="6dp"
android:background="@drawable/unread_red_circle" />
</FrameLayout>

View File

@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-11.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android" <com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<include <include
layout="@layout/row_lesson_change_item" android:id="@+id/item"
layout="@layout/agenda_counter_item"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />

View File

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-9.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android" <com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<include <include
layout="@layout/row_teacher_absence_item" android:id="@+id/item"
layout="@layout/agenda_event_item"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-11.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<include
android:id="@+id/item"
layout="@layout/agenda_event_compact_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</com.github.tibolte.agendacalendarview.agenda.AgendaEventView>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-10.
-->
<com.github.tibolte.agendacalendarview.agenda.AgendaEventView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<include
android:id="@+id/item"
layout="@layout/agenda_group_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</com.github.tibolte.agendacalendarview.agenda.AgendaEventView>

View File

@ -94,6 +94,7 @@
android:textAppearance="@style/NavView.TextView.Helper" /> android:textAppearance="@style/NavView.TextView.Helper" />
<TextView <TextView
android:id="@+id/teacherName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) Kuba Szczodrzyński 2021-4-10.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="config"
type="pl.szczodrzynski.edziennik.config.ProfileConfigUI" />
<variable
name="isAgendaMode"
type="boolean" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/agenda_config_appearance"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaLessonChanges}"
android:minHeight="32dp"
android:text="@string/agenda_config_lesson_changes" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaTeacherAbsence}"
android:minHeight="32dp"
android:text="@string/agenda_config_teacher_absence" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaCompactMode}"
android:enabled="@{isAgendaMode}"
android:minHeight="32dp"
android:text="@string/agenda_config_compact_mode"
tools:enabled="false" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:enabled="@{isAgendaMode}"
android:text="@string/agenda_config_compact_mode_hint"
android:textAppearance="@style/NavView.TextView.Helper"
tools:enabled="false" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaGroupByType}"
android:enabled="@{isAgendaMode}"
android:minHeight="32dp"
android:text="@string/agenda_config_group_by_type"
tools:enabled="false" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/agenda_config_mode_unavailable"
android:textStyle="italic"
android:visibility="@{isAgendaMode ? View.GONE : View.VISIBLE}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/agenda_config_event_sharing"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/eventSharingEnabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:minHeight="32dp"
android:text="@string/agenda_config_event_sharing_enabled" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/agenda_config_elearning"
android:textAppearance="@style/NavView.TextView.Subtitle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/agenda_config_not_available_yet"
android:textAppearance="@style/NavView.TextView.Small"
android:textStyle="italic" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/elearningEnabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaElearningMark}"
android:enabled="false"
android:minHeight="32dp"
android:onClick="@{() -> elearningType.setEnabled(elearningEnabled.isChecked())}"
android:text="@string/agenda_config_elearning_mark" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/elearningType"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{config.agendaElearningMark}"
android:hint="@string/agenda_config_elearning_type">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/elearningTypeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:focusable="true"
android:focusableInTouchMode="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:checked="@={config.agendaElearningGroup}"
android:enabled="false"
android:minHeight="32dp"
android:text="@string/agenda_config_elearning_group" />
</LinearLayout>
</ScrollView>
</layout>

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