Merge branch 'develop'

This commit is contained in:
Kuba Szczodrzyński 2021-09-23 22:09:20 +02:00
commit 41217190bb
No known key found for this signature in database
GPG Key ID: 70CB8A85BA1633CB
32 changed files with 398 additions and 203 deletions

View File

@ -172,10 +172,10 @@ dependencies {
kapt "eu.szkolny.selective-dao:codegen:27f8f3f194" kapt "eu.szkolny.selective-dao:codegen:27f8f3f194"
// Iconics & related // Iconics & related
implementation "com.mikepenz:iconics-core:5.3.0-b01" implementation "com.mikepenz:iconics-core:5.3.1"
implementation "com.mikepenz:iconics-views:5.3.0-b01" implementation "com.mikepenz:iconics-views:5.3.1"
implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar" implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar"
implementation "eu.szkolny:szkolny-font:1.3" implementation "eu.szkolny:szkolny-font:77e33acc2a"
// Other dependencies // Other dependencies
implementation "cat.ereza:customactivityoncrash:2.3.0" implementation "cat.ereza:customactivityoncrash:2.3.0"

View File

@ -1,13 +1,6 @@
<h3>Wersja 4.9, 2021-09-11</h3> <h3>Wersja 4.10, 2021-09-22</h3>
<ul> <ul>
<li>Vulcan: naprawiono brakujące lekcje w planie. @Antoni-Czaplicki</li> <li>Dodano wyświetlanie informacji o frekwencji w planie lekcji. @Antoni-Czaplicki</li>
<li>Vulcan: naprawiono wysyłanie wiadomości. @Antoni-Czaplicki</li>
<li>Vulcan: naprawiono brak frekwencji.</li>
<li>Naprawiono eksportowanie planu lekcji oraz pobieranie załączników. @doteq</li>
<li>Mobidziennik: naprawiono możliwość pobierania przyszłego planu lekcji.</li>
<li>Mobidziennik: poprawiono brak nowych linii w wysłanej wiadomości.</li>
<li>Dodano ekran "Twórcy aplikacji" w Ustawieniach. @Pengwius</li>
<li>Zmieniono domyślne tło nagłówka menu. 😋</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] = {
0x36, 0x60, 0xb0, 0x4b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 0xda, 0x2a, 0x5f, 0xbe, 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

@ -71,6 +71,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
val permissionManager by lazy { PermissionManager(this) } val permissionManager by lazy { PermissionManager(this) }
val attendanceManager by lazy { AttendanceManager(this) } val attendanceManager by lazy { AttendanceManager(this) }
val buildManager by lazy { BuildManager(this) } val buildManager by lazy { BuildManager(this) }
val availabilityManager by lazy { AvailabilityManager(this) }
val db val db
get() = App.db get() = App.db
@ -174,8 +175,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
App.config = Config(App.db) App.config = Config(App.db)
App.profile = Profile(0, 0, 0, "") App.profile = Profile(0, 0, 0, "")
debugMode = BuildConfig.DEBUG debugMode = BuildConfig.DEBUG
devMode = config.debugMode || debugMode devMode = config.devMode ?: debugMode
enableChucker = config.enableChucker || devMode enableChucker = config.enableChucker ?: devMode
if (!profileLoadById(config.lastProfileId)) { if (!profileLoadById(config.lastProfileId)) {
db.profileDao().firstId?.let { profileLoadById(it) } db.profileDao().firstId?.let { profileLoadById(it) }

View File

@ -85,6 +85,8 @@ import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.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.managers.AvailabilityManager
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
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.*
@ -634,45 +636,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
return return
} }
app.profile.registerName?.let { registerName -> val error = withContext(Dispatchers.IO) {
var status = app.config.sync.registerAvailability[registerName] app.availabilityManager.check(app.profile)
if (status == null || status.nextCheckAt < currentTimeUnix()) {
val api = SzkolnyApi(app)
val result = withContext(Dispatchers.IO) {
return@withContext api.runCatching({
val availability = getRegisterAvailability()
app.config.sync.registerAvailability = availability
availability[registerName]
}, onError = {
if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) {
return@withContext false
} }
return@withContext it when (error?.type) {
}) Type.NOT_AVAILABLE -> {
}
when (result) {
false -> {
Toast.makeText(this@MainActivity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
return@let
}
is Throwable -> {
errorSnackbar.addError(result.toApiError(TAG)).show()
return
}
is RegisterAvailabilityStatus -> {
status = result
}
}
}
if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) {
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
loadTarget(DRAWER_ITEM_HOME) loadTarget(DRAWER_ITEM_HOME)
if (status != null) RegisterUnavailableDialog(this, error.status!!)
RegisterUnavailableDialog(this, status)
return return
} }
Type.API_ERROR -> {
errorSnackbar.addError(error.apiError!!).show()
return
}
Type.NO_API_ACCESS -> {
Toast.makeText(this, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
}
} }
swipeRefreshLayout.isRefreshing = true swipeRefreshLayout.isRefreshing = true
@ -699,10 +679,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true) @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) { fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) {
EventBus.getDefault().removeStickyEvent(event) EventBus.getDefault().removeStickyEvent(event)
app.profile.registerName?.let { registerName -> val error = app.availabilityManager.check(app.profile, cacheOnly = true)
event.data[registerName]?.let { if (error != null) {
RegisterUnavailableDialog(this, it) RegisterUnavailableDialog(this, error.status!!)
}
} }
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -12,10 +12,7 @@ import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.config.db.ConfigEntry import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.config.utils.ConfigMigration import pl.szczodrzynski.edziennik.config.utils.*
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.config.utils.toHashMap
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -75,15 +72,15 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false } get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false }
set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value } set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value }
private var mDebugMode: Boolean? = null private var mDevMode: Boolean? = null
var debugMode: Boolean var devMode: Boolean?
get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false } get() { mDevMode = mDevMode ?: values.getBooleanOrNull("debugMode"); return mDevMode }
set(value) { set("debugMode", value); mDebugMode = value } set(value) { set("debugMode", value?.toString()); mDevMode = value }
private var mEnableChucker: Boolean? = null private var mEnableChucker: Boolean? = null
var enableChucker: Boolean var enableChucker: Boolean?
get() { mEnableChucker = mEnableChucker ?: values.get("enableChucker", false); return mEnableChucker ?: false } get() { mEnableChucker = mEnableChucker ?: values.getBooleanOrNull("enableChucker"); return mEnableChucker }
set(value) { set("enableChucker", value); mEnableChucker = value } set(value) { set("enableChucker", value?.toString()); mEnableChucker = value }
private var mDevModePassword: String? = null private var mDevModePassword: String? = null
var devModePassword: String? var devModePassword: String?
@ -125,6 +122,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert } get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert }
set(value) { set("apiInvalidCert", value); mApiInvalidCert = value } set(value) { set("apiInvalidCert", value); mApiInvalidCert = value }
private var mApiAvailabilityCheck: Boolean? = null
var apiAvailabilityCheck: Boolean
get() { mApiAvailabilityCheck = mApiAvailabilityCheck ?: values.get("apiAvailabilityCheck", true); return mApiAvailabilityCheck ?: true }
set(value) { set("apiAvailabilityCheck", value); mApiAvailabilityCheck = value }
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow() private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf() private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
init { init {

View File

@ -59,6 +59,9 @@ fun HashMap<String, String?>.get(key: String, default: String?): String? {
fun HashMap<String, String?>.get(key: String, default: Boolean): Boolean { fun HashMap<String, String?>.get(key: String, default: Boolean): Boolean {
return this[key]?.toBoolean() ?: default return this[key]?.toBoolean() ?: default
} }
fun HashMap<String, String?>.getBooleanOrNull(key: String): Boolean? {
return this[key]?.toBooleanStrictOrNull()
}
fun HashMap<String, String?>.get(key: String, default: Int): Int { fun HashMap<String, String?>.get(key: String, default: Int): Int {
return this[key]?.toIntOrNull() ?: default return this[key]?.toIntOrNull() ?: default
} }

View File

@ -67,7 +67,7 @@ class ConfigMigration(app: App, config: Config) {
if (dataVersion < 3) { if (dataVersion < 3) {
update = null update = null
privacyPolicyAccepted = false privacyPolicyAccepted = false
debugMode = false devMode = null
devModePassword = null devModePassword = null
appInstalledTime = 0L appInstalledTime = 0L
appRateSnackbarTime = 0L appRateSnackbarTime = 0L

View File

@ -18,7 +18,6 @@ import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.task.IApiTask import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
@ -27,6 +26,7 @@ import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) { open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
companion object { companion object {
@ -90,35 +90,21 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
return return
} }
profile.registerName?.also { registerName -> val error = app.availabilityManager.check(profile)
var status = app.config.sync.registerAvailability[registerName] when (error?.type) {
if (status == null || status.nextCheckAt < currentTimeUnix()) { Type.NOT_AVAILABLE -> {
val api = SzkolnyApi(app)
api.runCatching({
val availability = getRegisterAvailability()
app.config.sync.registerAvailability = availability
status = availability[registerName]
}, onError = {
val apiError = it.toApiError(TAG)
if (apiError.errorCode == ERROR_API_INVALID_SIGNATURE) {
return@also
}
taskCallback.onError(apiError)
return
})
}
if (status?.available != true
|| status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) {
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
EventBus.getDefault().postSticky( EventBus.getDefault().postSticky(RegisterAvailabilityEvent())
RegisterAvailabilityEvent(app.config.sync.registerAvailability)
)
} }
cancel() cancel()
taskCallback.onCompleted() taskCallback.onCompleted()
return return
} }
Type.API_ERROR -> {
taskCallback.onError(error.apiError!!)
return
}
else -> return@let
} }
} }

View File

@ -4,8 +4,4 @@
package pl.szczodrzynski.edziennik.data.api.events package pl.szczodrzynski.edziennik.data.api.events
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus class RegisterAvailabilityEvent()
data class RegisterAvailabilityEvent(
val data: Map< String, RegisterAvailabilityStatus>
)

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.MTIzNDU2Nzg5MDkdkClKMQ===.$param2".sha256() return "$param1.MTIzNDU2Nzg5MDY8+Uq3So===.$param2".sha256()
} }
} }

View File

@ -64,6 +64,8 @@ abstract class AttendanceDao : BaseDao<Attendance, AttendanceFull> {
getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") getRawNow("$QUERY WHERE notified = 0 $ORDER_BY")
fun getNotNotifiedNow(profileId: Int) = fun getNotNotifiedNow(profileId: Int) =
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY") getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY")
fun getAllByDateNow(profileId: Int, date: Date) =
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceDate = '${date.stringY_m_d}' $ORDER_BY")
// GET ONE - NOW // GET ONE - NOW
fun getByIdNow(profileId: Int, id: Long) = fun getByIdNow(profileId: Int, id: Long) =

View File

@ -140,7 +140,7 @@ open class Profile(
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
LOGIN_TYPE_PODLASIE -> "podlasie" LOGIN_TYPE_PODLASIE -> "podlasie"
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
else -> null else -> "unknown"
} }
override fun getImageDrawable(context: Context): Drawable { override fun getImageDrawable(context: Context): Drawable {

View File

@ -60,7 +60,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
) ?: return@launch ) ?: return@launch
app.config.sync.registerAvailability = data app.config.sync.registerAvailability = data
if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) {
EventBus.getDefault().postSticky(RegisterAvailabilityEvent(data)) EventBus.getDefault().postSticky(RegisterAvailabilityEvent())
} }
} }
} }

View File

@ -42,8 +42,6 @@ class RegisterUnavailableDialog(
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
return@run return@run
if (status.available && status.minVersionCode <= BuildConfig.VERSION_CODE)
return@run
onShowListener?.invoke(TAG) onShowListener?.invoke(TAG)
app = activity.applicationContext as App app = activity.applicationContext as App

View File

@ -8,15 +8,20 @@ import android.content.Intent
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.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
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 com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.onClick
@ -24,6 +29,7 @@ import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog 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.attendance.AttendanceDetailsDialog
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.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
@ -34,6 +40,7 @@ import kotlin.coroutines.CoroutineContext
class LessonDetailsDialog( class LessonDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val lesson: LessonFull, val lesson: LessonFull,
val attendance: AttendanceFull? = 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 {
@ -52,6 +59,8 @@ class LessonDetailsDialog(
private lateinit var adapter: EventListAdapter private lateinit var adapter: EventListAdapter
private val manager private val manager
get() = app.timetableManager get() = app.timetableManager
private val attendanceManager
get() = app.attendanceManager
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
@ -170,6 +179,27 @@ class LessonDetailsDialog(
b.teamName = lesson.teamName b.teamName = lesson.teamName
} }
b.attendanceDivider.isVisible = attendance != null
b.attendanceLayout.isVisible = attendance != null
if (attendance != null) {
b.attendanceView.setAttendance(attendance, app.attendanceManager, bigView = true)
b.attendanceType.text = attendance.typeName
b.attendanceIcon.isVisible = attendance.let {
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
val color = attendanceManager.getAttendanceColor(it)
b.attendanceIcon.setImageDrawable(
IconicsDrawable(activity, icon).apply {
colorInt = color
sizeDp = 24
}
)
true
}
b.attendanceDetails.onClick {
AttendanceDetailsDialog(activity, attendance, onShowListener, onDismissListener)
}
}
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
showWeekDay = false, showWeekDay = false,

View File

@ -41,6 +41,7 @@ class LabFragment : Fragment(), CoroutineScope {
app = activity.application as App app = activity.application as App
b = TemplateFragmentBinding.inflate(inflater) b = TemplateFragmentBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout) b.refreshLayout.setParent(activity.swipeRefreshLayout)
b.refreshLayout.isEnabled = false
return b.root return b.root
} }

View File

@ -16,6 +16,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.profile.ProfileRemoveDialog import pl.szczodrzynski.edziennik.ui.dialogs.profile.ProfileRemoveDialog
@ -78,10 +79,10 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
} }
b.chucker.isChecked = app.config.enableChucker b.chucker.isChecked = App.enableChucker
b.chucker.onChange { _, isChecked -> b.chucker.onChange { _, isChecked ->
app.config.enableChucker = isChecked app.config.enableChucker = isChecked
App.enableChucker = isChecked
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setTitle("Restart") .setTitle("Restart")
.setMessage("Wymagany restart aplikacji") .setMessage("Wymagany restart aplikacji")
@ -94,9 +95,9 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
.show() .show()
} }
b.disableDebug.onClick { b.disableDebug.onClick {
app.config.debugMode = false app.config.devMode = false
App.devMode = false
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setTitle("Restart") .setTitle("Restart")
.setMessage("Wymagany restart aplikacji") .setMessage("Wymagany restart aplikacji")
@ -115,6 +116,14 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
app.profileSave() app.profileSave()
} }
b.resetCert.onClick {
app.config.apiInvalidCert = null
}
b.rebuildConfig.onClick {
App.config = Config(App.db)
}
val profiles = app.db.profileDao().allNow val profiles = app.db.profileDao().allNow
b.profile.clear() b.profile.clear()
b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) } b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) }

View File

@ -163,10 +163,9 @@ class HomeFragment : Fragment(), CoroutineScope {
if (app.profile.archived) if (app.profile.archived)
items.add(0, HomeArchiveCard(101, app, activity, this, app.profile)) items.add(0, HomeArchiveCard(101, app, activity, this, app.profile))
val status = app.config.sync.registerAvailability[app.profile.registerName] val status = app.availabilityManager.check(app.profile, cacheOnly = true)?.status
val update = app.config.update val update = app.config.update
if (update != null && update.versionCode > BuildConfig.VERSION_CODE if (update != null && update.versionCode > BuildConfig.VERSION_CODE || status?.userMessage != null) {
|| status != null && (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)) {
items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile)) items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile))
} }

View File

@ -50,7 +50,8 @@ class HomeAvailabilityCard(
} }
holder.root += b.root holder.root += b.root
val status = app.config.sync.registerAvailability[profile.registerName] val error = app.availabilityManager.check(profile, cacheOnly = true)
val status = error?.status
val update = app.config.update val update = app.config.update
if (update == null && status == null) if (update == null && status == null)
@ -58,7 +59,8 @@ class HomeAvailabilityCard(
var onInfoClick = { _: View -> } var onInfoClick = { _: View -> }
if (status != null && !status.available && status.userMessage != null) { // show "register unavailable" only when disabled
if (status?.userMessage != null) {
b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY)
b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY)
b.homeAvailabilityUpdate.isVisible = false b.homeAvailabilityUpdate.isVisible = false
@ -69,6 +71,7 @@ class HomeAvailabilityCard(
RegisterUnavailableDialog(activity, status) RegisterUnavailableDialog(activity, status)
} }
} }
// show "update available" when available OR version too old for the register
else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) { else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) {
b.homeAvailabilityTitle.setText(R.string.home_availability_title) b.homeAvailabilityTitle.setText(R.string.home_availability_title)
b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName) b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName)
@ -78,6 +81,9 @@ class HomeAvailabilityCard(
UpdateAvailableDialog(activity, update) UpdateAvailableDialog(activity, update)
} }
} }
else {
b.root.isVisible = false
}
b.homeAvailabilityUpdate.onClick { b.homeAvailabilityUpdate.onClick {
if (update == null) if (update == null)

View File

@ -26,13 +26,12 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -269,52 +268,23 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
} }
private suspend fun checkAvailability(loginType: Int): Boolean { private suspend fun checkAvailability(loginType: Int): Boolean {
when (loginType) { val error = withContext(Dispatchers.IO) {
LOGIN_TYPE_LIBRUS -> "librus" app.availabilityManager.check(loginType)
LOGIN_TYPE_VULCAN -> "vulcan" } ?: return true
LOGIN_TYPE_IDZIENNIK -> "idziennik"
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
LOGIN_TYPE_PODLASIE -> "podlasie"
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
else -> null
}?.let { registerName ->
var status = app.config.sync.registerAvailability[registerName]
if (status == null || status.nextCheckAt < currentTimeUnix()) {
val api = SzkolnyApi(app)
val result = withContext(Dispatchers.IO) {
return@withContext api.runCatching({
val availability = getRegisterAvailability()
app.config.sync.registerAvailability = availability
availability[registerName]
}, onError = {
if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) {
return@withContext false
}
return@withContext it
})
}
when (result) { return when (error.type) {
false -> { Type.NOT_AVAILABLE -> {
RegisterUnavailableDialog(activity, error.status!!)
false
}
Type.API_ERROR -> {
activity.errorSnackbar.addError(error.apiError!!).show()
false
}
Type.NO_API_ACCESS -> {
Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
return@let true
}
is Throwable -> {
activity.errorSnackbar.addError(result.toApiError(TAG)).show()
return false
}
is RegisterAvailabilityStatus -> {
status = result
} }
} }
} }
if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) {
if (status != null)
RegisterUnavailableDialog(activity, status)
return false
}
}
return true
}
} }

View File

@ -53,7 +53,7 @@ class LoginPrizeFragment : Fragment(), CoroutineScope {
.setTitle(R.string.are_you_sure) .setTitle(R.string.are_you_sure)
.setMessage(R.string.dev_mode_enable_warning) .setMessage(R.string.dev_mode_enable_warning)
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
app.config.debugMode = true app.config.devMode = true
App.devMode = true App.devMode = true
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setTitle("Restart") .setTitle("Restart")
@ -67,8 +67,8 @@ class LoginPrizeFragment : Fragment(), CoroutineScope {
.show() .show()
} }
.setNegativeButton(R.string.no) { _, _ -> .setNegativeButton(R.string.no) { _, _ ->
app.config.debugMode = false app.config.devMode = App.debugMode
App.devMode = false App.devMode = App.debugMode
activity.finish() activity.finish()
} }
.show() .show()

View File

@ -9,19 +9,24 @@ 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.LinearLayout
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.*
import androidx.core.view.marginTop
import androidx.core.view.setPadding
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 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 eu.szkolny.font.SzkolnyFont
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
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.TimetableDayFragmentBinding
@ -61,6 +66,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
private val manager private val manager
get() = app.timetableManager get() = app.timetableManager
private val attendanceManager
get() = app.attendanceManager
// 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) }
@ -102,14 +109,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
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) val attendanceList = withContext(Dispatchers.Default) {
app.db.attendanceDao().getAllByDateNow(App.profileId, date)
}
processLessonList(lessons, events, attendanceList)
} }
} }
return true return true
} }
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) { private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>, attendanceList: List<AttendanceFull>) {
// 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, b.root) { view, _, _ -> inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
@ -172,10 +182,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
lessons.forEach { it.showAsUnseen = !it.seen } lessons.forEach { it.showAsUnseen = !it.seen }
buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events) buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events, attendanceList)
} }
private fun buildLessonViews(lessons: List<LessonFull>, events: List<EventFull>) { private fun buildLessonViews(lessons: List<LessonFull>, events: List<EventFull>, attendanceList: List<AttendanceFull>) {
if (!isAdded) if (!isAdded)
return return
@ -192,6 +202,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
for (lesson in lessons) { for (lesson in lessons) {
val attendance = attendanceList.find { it.startTime == lesson.startTime }
val startTime = lesson.displayStartTime ?: continue val startTime = lesson.displayStartTime ?: continue
val endTime = lesson.displayEndTime ?: continue val endTime = lesson.displayEndTime ?: continue
@ -208,11 +219,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
eventView.tag = lesson eventView.tag = lesson to attendance
eventView.setOnClickListener { eventView.setOnClickListener {
if (isAdded && it.tag is LessonFull) if (isAdded && it.tag is Pair<*, *>) {
LessonDetailsDialog(activity, it.tag as LessonFull) val (lessonObj, attendanceObj) = it.tag as Pair<*, *>
LessonDetailsDialog(
activity = activity,
lesson = lessonObj as LessonFull,
attendance = attendanceObj as AttendanceFull?
)
}
} }
val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3) val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3)
@ -276,6 +293,18 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
lb.attendanceIcon.isVisible = attendance?.let {
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
val color = attendanceManager.getAttendanceColor(it)
lb.attendanceIcon.setImageDrawable(
IconicsDrawable(activity, icon).apply {
colorInt = color
sizeDp = 24
}
)
true
} ?: false
lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen
if (!lesson.seen) { if (!lesson.seen) {
manager.markAsSeen(lesson) manager.markAsSeen(lesson)
@ -283,6 +312,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation) lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
val lessonNumberMargin =
if (lb.annotationVisible) (-8).dp
else 0
lb.lessonNumberText.updateLayoutParams<LinearLayout.LayoutParams> {
updateMargins(top = lessonNumberMargin, bottom = lessonNumberMargin)
}
// The day view needs the event time ranges in the start minute/end minute format, // The day view needs the event time ranges in the start minute/end minute format,
// so calculate those here // so calculate those here

View File

@ -4,6 +4,9 @@
package pl.szczodrzynski.edziennik.utils.managers package pl.szczodrzynski.edziennik.utils.managers
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import eu.szkolny.font.SzkolnyFont
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -63,6 +66,17 @@ class AttendanceManager(val app: App) : CoroutineScope {
else getAttendanceColor(attendance.baseType) else getAttendanceColor(attendance.baseType)
} }
fun getAttendanceIcon(attendance: Attendance): IIcon? = when (attendance.baseType) {
Attendance.TYPE_PRESENT, Attendance.TYPE_PRESENT_CUSTOM -> CommunityMaterial.Icon.cmd_check
Attendance.TYPE_ABSENT -> CommunityMaterial.Icon.cmd_close
Attendance.TYPE_ABSENT_EXCUSED -> CommunityMaterial.Icon3.cmd_progress_close
Attendance.TYPE_RELEASED -> CommunityMaterial.Icon.cmd_account_arrow_right_outline
Attendance.TYPE_BELATED -> CommunityMaterial.Icon.cmd_clock_alert_outline
Attendance.TYPE_BELATED_EXCUSED -> CommunityMaterial.Icon.cmd_clock_check_outline
Attendance.TYPE_DAY_FREE -> SzkolnyFont.Icon.szf_umbrella_beach_outline
else -> null
}
/* _ _ _____ _____ _ __ _ /* _ _ _____ _____ _ __ _
| | | |_ _| / ____| (_)/ _(_) | | | |_ _| / ____| (_)/ _(_)
| | | | | | | (___ _ __ ___ ___ _| |_ _ ___ | | | | | | | (___ _ __ ___ ___ _| |_ _ ___

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-9-18.
*/
package pl.szczodrzynski.edziennik.utils.managers
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.toApiError
class AvailabilityManager(val app: App) {
companion object {
private const val TAG = "AvailabilityManager"
}
private val api = SzkolnyApi(app)
data class Error(
val type: Type,
val status: RegisterAvailabilityStatus?,
val apiError: ApiError?
) {
companion object {
fun notAvailable(status: RegisterAvailabilityStatus) =
Error(Type.NOT_AVAILABLE, status, null)
fun apiError(apiError: ApiError) =
Error(Type.API_ERROR, null, apiError)
fun noApiAccess() =
Error(Type.NO_API_ACCESS, null, null)
}
enum class Type {
NOT_AVAILABLE,
API_ERROR,
NO_API_ACCESS,
}
}
fun check(profile: Profile, cacheOnly: Boolean = false): Error? {
return check(profile.registerName, cacheOnly)
}
fun check(loginType: Int, cacheOnly: Boolean = false): Error? {
val registerName = when (loginType) {
LOGIN_TYPE_LIBRUS -> "librus"
LOGIN_TYPE_VULCAN -> "vulcan"
LOGIN_TYPE_IDZIENNIK -> "idziennik"
LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik"
LOGIN_TYPE_PODLASIE -> "podlasie"
LOGIN_TYPE_EDUDZIENNIK -> "edudziennik"
else -> "unknown"
}
return check(registerName, cacheOnly)
}
fun check(registerName: String, cacheOnly: Boolean = false): Error? {
if (!app.config.apiAvailabilityCheck)
return null
val status = app.config.sync.registerAvailability[registerName]
if (status != null && status.nextCheckAt > currentTimeUnix()) {
return reportStatus(status)
}
if (cacheOnly) {
return reportStatus(status)
}
return try {
val availability = api.getRegisterAvailability()
app.config.sync.registerAvailability = availability
reportStatus(availability[registerName])
} catch (e: Throwable) {
reportApiError(e)
}
}
private fun reportStatus(status: RegisterAvailabilityStatus?): Error? {
if (status == null)
return null
if (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)
return Error.notAvailable(status)
return null
}
private fun reportApiError(throwable: Throwable): Error {
val apiError = throwable.toApiError(TAG)
if (apiError.errorCode == ERROR_API_INVALID_SIGNATURE) {
app.config.sync.registerAvailability = mapOf()
return Error.noApiAccess()
}
return Error.apiError(apiError)
}
}

View File

@ -134,7 +134,8 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:baselineAligned="false" android:baselineAligned="false"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal"
android:visibility="gone">
<TextView <TextView
android:id="@+id/shiftedText" android:id="@+id/shiftedText"
@ -290,6 +291,60 @@
</LinearLayout> </LinearLayout>
<View
android:id="@+id/attendanceDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@drawable/divider"/>
<LinearLayout
android:id="@+id/attendanceLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
android:id="@+id/attendanceView"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
tools:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="#f44336"
tools:gravity="center"
tools:text="nb"
tools:textSize="22sp" />
<TextView
android:id="@+id/attendanceType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:maxLines="2"
android:ellipsize="end"
android:textSize="16sp"
tools:text="nieobecność usprawiedliweniowsza1234324" />
<ImageView
android:id="@+id/attendanceIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="8dp"
tools:srcCompat="@sample/check" />
<Button
android:id="@+id/attendanceDetails"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_lesson_attendance_details" />
</LinearLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"

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 2020-4-3. ~ Copyright (c) Kuba Szczodrzyński 2020-4-3.
--> -->
<layout xmlns:app="http://schemas.android.com/apk/res-auto" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="HardcodedText"> tools:ignore="HardcodedText">
<data> <data>
<variable name="app" type="pl.szczodrzynski.edziennik.App"/>
<variable
name="app"
type="pl.szczodrzynski.edziennik.App" />
</data> </data>
<ScrollView <ScrollView
@ -39,7 +41,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />--> app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
<Switch <com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/chucker" android:id="@+id/chucker"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -87,7 +89,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="monospace" android:fontFamily="monospace"
tools:text="Cookies:\n\nsynergia.librus.pl\n DZIENNIKSID=L01~1234567890abcdef"/> tools:text="Cookies:\n\nsynergia.librus.pl\n DZIENNIKSID=L01~1234567890abcdef" />
<Button <Button
android:id="@+id/unarchive" android:id="@+id/unarchive"
@ -99,9 +101,9 @@
<pl.szczodrzynski.edziennik.utils.TextInputDropDown <pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/profile" android:id="@+id/profile"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
<com.google.android.material.checkbox.MaterialCheckBox <com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent" android:layout_width="match_parent"
@ -109,13 +111,36 @@
android:checked="@={app.config.archiverEnabled}" android:checked="@={app.config.archiverEnabled}"
android:text="Archiver enabled" /> android:text="Archiver enabled" />
<com.google.android.material.checkbox.MaterialCheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@={app.config.apiAvailabilityCheck}"
android:text="Availability check enabled" />
<Button
android:id="@+id/resetCert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Reset API signature"
android:textAllCaps="false" />
<Button <Button
android:id="@+id/disableDebug" android:id="@+id/disableDebug"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Disable Dev Mode" android:text="Disable Dev Mode"
android:textAllCaps="false" android:textAllCaps="false"
app:backgroundTint="@color/windowBackgroundRed" /> app:backgroundTint="@color/windowBackgroundRed" />
<Button
android:id="@+id/rebuildConfig"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Rebuild App.config"
android:textAllCaps="false" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</layout> </layout>

View File

@ -48,16 +48,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top" android:layout_gravity="top"
android:paddingHorizontal="8dp" android:baselineAligned="false"
android:orientation="horizontal" android:orientation="horizontal"
android:baselineAligned="false"> android:paddingHorizontal="8dp"
android:paddingVertical="4dp">
<!--tools:background="@drawable/timetable_subject_color_rounded"--> <!--tools:background="@drawable/timetable_subject_color_rounded"-->
<TextView <TextView
android:id="@+id/subjectName" android:id="@+id/subjectName"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="end" android:ellipsize="end"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
@ -75,8 +75,8 @@
android:layout_height="12dp" android:layout_height="12dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:visibility="@{unread ? View.VISIBLE : View.GONE}" android:background="@drawable/unread_red_circle"
android:background="@drawable/unread_red_circle" /> android:visibility="@{unread ? View.VISIBLE : View.GONE}" />
<ImageView <ImageView
android:id="@+id/attendanceIcon" android:id="@+id/attendanceIcon"
@ -87,29 +87,18 @@
tools:srcCompat="@sample/check" tools:srcCompat="@sample/check"
tools:visibility="visible" /> tools:visibility="visible" />
<ImageView
android:id="@+id/imageView4"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_weight="0"
app:srcCompat="@drawable/bg_circle"
android:visibility="gone" />
<TextView <TextView
android:id="@+id/textView6" android:id="@+id/lessonNumberText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-condensed-light" android:fontFamily="sans-serif-condensed-light"
android:includeFontPadding="false" android:includeFontPadding="false"
android:layout_marginBottom="-4dp"
android:paddingHorizontal="4dp" android:paddingHorizontal="4dp"
android:text="@{Integer.toString(lessonNumber)}" android:text="@{Integer.toString(lessonNumber)}"
android:textSize="28sp" android:textSize="28sp"
android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}" android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}"
tools:text="3"/> tools:text="3" />
<!--android:layout_marginTop="@{annotationVisible ? `-4dp` : `4dp`}"
android:layout_marginBottom="@{annotationVisible ? `-4dp` : `0dp`}"-->
</LinearLayout> </LinearLayout>
@ -149,7 +138,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="end|bottom" android:gravity="end|bottom"
android:orientation="horizontal"> android:orientation="horizontal"
android:paddingBottom="2dp">
<View <View
android:id="@+id/event3" android:id="@+id/event3"

View File

@ -856,7 +856,7 @@
<string name="settings_about_licenses_text">Open-Source-Lizenzen</string> <string name="settings_about_licenses_text">Open-Source-Lizenzen</string>
<string name="settings_about_privacy_policy_text">Datenschutzrichtlinie</string> <string name="settings_about_privacy_policy_text">Datenschutzrichtlinie</string>
<string name="settings_card_register_title">E-Klassenbuch</string> <string name="settings_card_register_title">E-Klassenbuch</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - Juni 2021</string> <string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - 2021</string>
<string name="settings_about_update_subtext">Klicken Sie hier, um nach Aktualisierungen zu suchen</string> <string name="settings_about_update_subtext">Klicken Sie hier, um nach Aktualisierungen zu suchen</string>
<string name="settings_about_update_text">Aktualisierung</string> <string name="settings_about_update_text">Aktualisierung</string>
<string name="settings_about_version_text">Version</string> <string name="settings_about_version_text">Version</string>

View File

@ -858,7 +858,7 @@
<string name="settings_about_licenses_text">Open-source licenses</string> <string name="settings_about_licenses_text">Open-source licenses</string>
<string name="settings_about_privacy_policy_text">Privacy policy</string> <string name="settings_about_privacy_policy_text">Privacy policy</string>
<string name="settings_card_register_title">E-register</string> <string name="settings_card_register_title">E-register</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - June 2021</string> <string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - 2021</string>
<string name="settings_about_update_subtext">Click to check for updates</string> <string name="settings_about_update_subtext">Click to check for updates</string>
<string name="settings_about_update_text">Update</string> <string name="settings_about_update_text">Update</string>
<string name="settings_about_version_text">Version</string> <string name="settings_about_version_text">Version</string>

View File

@ -921,7 +921,7 @@
<string name="settings_about_licenses_text">Licencje open-source</string> <string name="settings_about_licenses_text">Licencje open-source</string>
<string name="settings_about_privacy_policy_text">Polityka prywatności</string> <string name="settings_about_privacy_policy_text">Polityka prywatności</string>
<string name="settings_card_register_title">E-dziennik</string> <string name="settings_card_register_title">E-dziennik</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nwrzesień 2018 - czerwiec 2021</string> <string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nwrzesień 2018 - 2021</string>
<string name="settings_about_update_subtext">Kliknij, aby sprawdzić aktualizacje</string> <string name="settings_about_update_subtext">Kliknij, aby sprawdzić aktualizacje</string>
<string name="settings_about_update_text">Aktualizacja</string> <string name="settings_about_update_text">Aktualizacja</string>
<string name="settings_about_version_text">Wersja</string> <string name="settings_about_version_text">Wersja</string>
@ -1463,4 +1463,5 @@
<string name="notification_attendance_long_format">Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s</string> <string name="notification_attendance_long_format">Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s</string>
<string name="contributors_subtext_format" translatable="false">\@%s - %s</string> <string name="contributors_subtext_format" translatable="false">\@%s - %s</string>
<string name="contributors_headline">Najłatwiejszy sposób na korzystanie z e-dziennika.</string> <string name="contributors_headline">Najłatwiejszy sposób na korzystanie z e-dziennika.</string>
<string name="dialog_lesson_attendance_details">Szczegóły</string>
</resources> </resources>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.5.20' kotlin_version = '1.5.20'
release = [ release = [
versionName: "4.9", versionName: "4.10",
versionCode: 4090099 versionCode: 4100099
] ]
setup = [ setup = [