diff --git a/app/build.gradle b/app/build.gradle index 62f4c95c..96bb1176 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -172,10 +172,10 @@ dependencies { kapt "eu.szkolny.selective-dao:codegen:27f8f3f194" // Iconics & related - implementation "com.mikepenz:iconics-core:5.3.0-b01" - implementation "com.mikepenz:iconics-views:5.3.0-b01" + implementation "com.mikepenz:iconics-core:5.3.1" + implementation "com.mikepenz:iconics-views:5.3.1" 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 implementation "cat.ereza:customactivityoncrash:2.3.0" diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 99883f04..9d71f591 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,13 +1,6 @@ -

Wersja 4.9, 2021-09-11

+

Wersja 4.10, 2021-09-22



diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 9925362d..d3f11d9d 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 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); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index 71e6c7e6..b3ec1cc3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -71,6 +71,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { val permissionManager by lazy { PermissionManager(this) } val attendanceManager by lazy { AttendanceManager(this) } val buildManager by lazy { BuildManager(this) } + val availabilityManager by lazy { AvailabilityManager(this) } val db get() = App.db @@ -174,8 +175,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { App.config = Config(App.db) App.profile = Profile(0, 0, 0, "") debugMode = BuildConfig.DEBUG - devMode = config.debugMode || debugMode - enableChucker = config.enableChucker || devMode + devMode = config.devMode ?: debugMode + enableChucker = config.enableChucker ?: devMode if (!profileLoadById(config.lastProfileId)) { db.profileDao().firstId?.let { profileLoadById(it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 2cd4f7b7..b7540b44 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -85,6 +85,8 @@ import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.utils.Utils.d 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.NavTarget import pl.szczodrzynski.navlib.* @@ -634,45 +636,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope { return } - app.profile.registerName?.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) { - 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) { + val error = withContext(Dispatchers.IO) { + app.availabilityManager.check(app.profile) + } + when (error?.type) { + Type.NOT_AVAILABLE -> { swipeRefreshLayout.isRefreshing = false loadTarget(DRAWER_ITEM_HOME) - if (status != null) - RegisterUnavailableDialog(this, status) + RegisterUnavailableDialog(this, error.status!!) 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 @@ -699,10 +679,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope { @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) { EventBus.getDefault().removeStickyEvent(event) - app.profile.registerName?.let { registerName -> - event.data[registerName]?.let { - RegisterUnavailableDialog(this, it) - } + val error = app.availabilityManager.check(app.profile, cacheOnly = true) + if (error != null) { + RegisterUnavailableDialog(this, error.status!!) } } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt index 0274912f..e29d93ad 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -12,10 +12,7 @@ import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.config.db.ConfigEntry -import pl.szczodrzynski.edziennik.config.utils.ConfigMigration -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.config.utils.* import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.db.AppDb 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 } set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value } - private var mDebugMode: Boolean? = null - var debugMode: Boolean - get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false } - set(value) { set("debugMode", value); mDebugMode = value } + private var mDevMode: Boolean? = null + var devMode: Boolean? + get() { mDevMode = mDevMode ?: values.getBooleanOrNull("debugMode"); return mDevMode } + set(value) { set("debugMode", value?.toString()); mDevMode = value } private var mEnableChucker: Boolean? = null - var enableChucker: Boolean - get() { mEnableChucker = mEnableChucker ?: values.get("enableChucker", false); return mEnableChucker ?: false } - set(value) { set("enableChucker", value); mEnableChucker = value } + var enableChucker: Boolean? + get() { mEnableChucker = mEnableChucker ?: values.getBooleanOrNull("enableChucker"); return mEnableChucker } + set(value) { set("enableChucker", value?.toString()); mEnableChucker = value } private var mDevModePassword: String? = null var devModePassword: String? @@ -125,6 +122,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig { get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert } 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 = db.configDao().getAllNow() private val profileConfigs: HashMap = hashMapOf() init { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt index c98cf832..94bb87e6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt @@ -59,6 +59,9 @@ fun HashMap.get(key: String, default: String?): String? { fun HashMap.get(key: String, default: Boolean): Boolean { return this[key]?.toBoolean() ?: default } +fun HashMap.getBooleanOrNull(key: String): Boolean? { + return this[key]?.toBooleanStrictOrNull() +} fun HashMap.get(key: String, default: Int): Int { return this[key]?.toIntOrNull() ?: default } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt index 1095f511..58dc35f7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt @@ -67,7 +67,7 @@ class ConfigMigration(app: App, config: Config) { if (dataVersion < 3) { update = null privacyPolicyAccepted = false - debugMode = false + devMode = null devModePassword = null appInstalledTime = 0L appRateSnackbarTime = 0L diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt index e3692c33..ab27dda9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt @@ -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.EdziennikInterface 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.db.entity.LoginStore 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.MessageFull 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) { companion object { @@ -90,35 +90,21 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa return } - profile.registerName?.also { registerName -> - var status = app.config.sync.registerAvailability[registerName] - if (status == null || status.nextCheckAt < currentTimeUnix()) { - 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) { + val error = app.availabilityManager.check(profile) + when (error?.type) { + Type.NOT_AVAILABLE -> { if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { - EventBus.getDefault().postSticky( - RegisterAvailabilityEvent(app.config.sync.registerAvailability) - ) + EventBus.getDefault().postSticky(RegisterAvailabilityEvent()) } cancel() taskCallback.onCompleted() return } + Type.API_ERROR -> { + taskCallback.onError(error.apiError!!) + return + } + else -> return@let } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt index fe66c4b3..2a3531df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt @@ -4,8 +4,4 @@ package pl.szczodrzynski.edziennik.data.api.events -import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus - -data class RegisterAvailabilityEvent( - val data: Map< String, RegisterAvailabilityStatus> -) +class RegisterAvailabilityEvent() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 4c07bac8..2df7f141 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDkdkClKMQ===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDY8+Uq3So===.$param2".sha256() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt index 3ffdc1b8..94be0c34 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/AttendanceDao.kt @@ -64,6 +64,8 @@ abstract class AttendanceDao : BaseDao { getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") fun getNotNotifiedNow(profileId: Int) = 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 fun getByIdNow(profileId: Int, id: Long) = diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt index b3586a79..9c2552a6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt @@ -140,7 +140,7 @@ open class Profile( LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" LOGIN_TYPE_PODLASIE -> "podlasie" LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" - else -> null + else -> "unknown" } override fun getImageDrawable(context: Context): Drawable { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index 1d563c34..111fef4a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -60,7 +60,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: ) ?: return@launch app.config.sync.registerAvailability = data if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { - EventBus.getDefault().postSticky(RegisterAvailabilityEvent(data)) + EventBus.getDefault().postSticky(RegisterAvailabilityEvent()) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt index b9e733b8..10853812 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt @@ -42,8 +42,6 @@ class RegisterUnavailableDialog( init { run { if (activity.isFinishing) return@run - if (status.available && status.minVersionCode <= BuildConfig.VERSION_CODE) - return@run onShowListener?.invoke(TAG) app = activity.applicationContext as App diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt index 9b43f9be..2df41c36 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt @@ -8,15 +8,20 @@ import android.content.Intent import android.view.View import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager 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.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R 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.databinding.DialogLessonDetailsBinding 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.EventListAdapter 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.utils.BetterLink import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration @@ -34,6 +40,7 @@ import kotlin.coroutines.CoroutineContext class LessonDetailsDialog( val activity: AppCompatActivity, val lesson: LessonFull, + val attendance: AttendanceFull? = null, val onShowListener: ((tag: String) -> Unit)? = null, val onDismissListener: ((tag: String) -> Unit)? = null ) : CoroutineScope { @@ -52,6 +59,8 @@ class LessonDetailsDialog( private lateinit var adapter: EventListAdapter private val manager get() = app.timetableManager + private val attendanceManager + get() = app.attendanceManager init { run { if (activity.isFinishing) @@ -170,6 +179,27 @@ class LessonDetailsDialog( 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( activity, showWeekDay = false, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt index 60c30cf9..f7c8952d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabFragment.kt @@ -41,6 +41,7 @@ class LabFragment : Fragment(), CoroutineScope { app = activity.application as App b = TemplateFragmentBinding.inflate(inflater) b.refreshLayout.setParent(activity.swipeRefreshLayout) + b.refreshLayout.isEnabled = false return b.root } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt index 4d8205bf..19ae4697 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.config.Config import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding 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}") } - b.chucker.isChecked = app.config.enableChucker - + b.chucker.isChecked = App.enableChucker b.chucker.onChange { _, isChecked -> app.config.enableChucker = isChecked + App.enableChucker = isChecked MaterialAlertDialogBuilder(activity) .setTitle("Restart") .setMessage("Wymagany restart aplikacji") @@ -94,9 +95,9 @@ class LabPageFragment : LazyFragment(), CoroutineScope { .show() } - b.disableDebug.onClick { - app.config.debugMode = false + app.config.devMode = false + App.devMode = false MaterialAlertDialogBuilder(activity) .setTitle("Restart") .setMessage("Wymagany restart aplikacji") @@ -115,6 +116,14 @@ class LabPageFragment : LazyFragment(), CoroutineScope { app.profileSave() } + b.resetCert.onClick { + app.config.apiInvalidCert = null + } + + b.rebuildConfig.onClick { + App.config = Config(App.db) + } + val profiles = app.db.profileDao().allNow b.profile.clear() b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt index 944c60e8..5d5cbc45 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt @@ -163,10 +163,9 @@ class HomeFragment : Fragment(), CoroutineScope { if (app.profile.archived) 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 - if (update != null && update.versionCode > BuildConfig.VERSION_CODE - || status != null && (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)) { + if (update != null && update.versionCode > BuildConfig.VERSION_CODE || status?.userMessage != null) { items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile)) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt index fd54748e..613658f4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt @@ -50,7 +50,8 @@ class HomeAvailabilityCard( } 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 if (update == null && status == null) @@ -58,7 +59,8 @@ class HomeAvailabilityCard( 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.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityUpdate.isVisible = false @@ -69,6 +71,7 @@ class HomeAvailabilityCard( RegisterUnavailableDialog(activity, status) } } + // show "update available" when available OR version too old for the register else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) { b.homeAvailabilityTitle.setText(R.string.home_availability_title) b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName) @@ -78,6 +81,9 @@ class HomeAvailabilityCard( UpdateAvailableDialog(activity, update) } } + else { + b.root.isVisible = false + } b.homeAvailabilityUpdate.onClick { if (update == null) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt index 858b4a01..71a5b804 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt @@ -26,13 +26,12 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.* 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.ui.dialogs.RegisterUnavailableDialog import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type import pl.szczodrzynski.edziennik.utils.models.Date import kotlin.coroutines.CoroutineContext @@ -269,52 +268,23 @@ class LoginChooserFragment : Fragment(), CoroutineScope { } private suspend fun checkAvailability(loginType: Int): Boolean { - 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 -> 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 - }) - } + val error = withContext(Dispatchers.IO) { + app.availabilityManager.check(loginType) + } ?: return true - when (result) { - false -> { - Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() - return@let - } - is Throwable -> { - activity.errorSnackbar.addError(result.toApiError(TAG)).show() - return false - } - is RegisterAvailabilityStatus -> { - status = result - } - } + return when (error.type) { + Type.NOT_AVAILABLE -> { + RegisterUnavailableDialog(activity, error.status!!) + false } - - if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) { - if (status != null) - RegisterUnavailableDialog(activity, status) - return 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() + true } } - return true } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt index 1c9634fc..f14c6d19 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPrizeFragment.kt @@ -53,7 +53,7 @@ class LoginPrizeFragment : Fragment(), CoroutineScope { .setTitle(R.string.are_you_sure) .setMessage(R.string.dev_mode_enable_warning) .setPositiveButton(R.string.yes) { _, _ -> - app.config.debugMode = true + app.config.devMode = true App.devMode = true MaterialAlertDialogBuilder(activity) .setTitle("Restart") @@ -67,8 +67,8 @@ class LoginPrizeFragment : Fragment(), CoroutineScope { .show() } .setNegativeButton(R.string.no) { _, _ -> - app.config.debugMode = false - App.devMode = false + app.config.devMode = App.debugMode + App.devMode = App.debugMode activity.finish() } .show() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index 00ce872e..5d13ee86 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -9,19 +9,24 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.LinearLayout import android.widget.TextView import androidx.asynclayoutinflater.view.AsyncLayoutInflater -import androidx.core.view.isVisible -import androidx.core.view.marginTop -import androidx.core.view.setPadding -import androidx.core.view.updateLayoutParams +import androidx.core.view.* import com.linkedin.android.tachyon.DayView 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 pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE 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.full.AttendanceFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding @@ -61,6 +66,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { private val manager get() = app.timetableManager + private val attendanceManager + get() = app.attendanceManager // find SwipeRefreshLayout in the hierarchy private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } @@ -102,14 +109,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { val events = withContext(Dispatchers.Default) { 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 } - private fun processLessonList(lessons: List, events: List) { + private fun processLessonList(lessons: List, events: List, attendanceList: List) { // no lessons - timetable not downloaded yet if (lessons.isEmpty()) { inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ -> @@ -172,10 +182,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { 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, events: List) { + private fun buildLessonViews(lessons: List, events: List, attendanceList: List) { if (!isAdded) return @@ -192,6 +202,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) for (lesson in lessons) { + val attendance = attendanceList.find { it.startTime == lesson.startTime } val startTime = lesson.displayStartTime ?: continue val endTime = lesson.displayEndTime ?: continue @@ -208,11 +219,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { val lb = TimetableLessonBinding.bind(eventView) eventViews += eventView - eventView.tag = lesson + eventView.tag = lesson to attendance eventView.setOnClickListener { - if (isAdded && it.tag is LessonFull) - LessonDetailsDialog(activity, it.tag as LessonFull) + if (isAdded && it.tag is Pair<*, *>) { + 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) @@ -276,6 +293,18 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).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 if (!lesson.seen) { manager.markAsSeen(lesson) @@ -283,6 +312,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation) + val lessonNumberMargin = + if (lb.annotationVisible) (-8).dp + else 0 + lb.lessonNumberText.updateLayoutParams { + updateMargins(top = lessonNumberMargin, bottom = lessonNumberMargin) + } // The day view needs the event time ranges in the start minute/end minute format, // so calculate those here diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt index cd726797..77af40ed 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AttendanceManager.kt @@ -4,6 +4,9 @@ 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.Dispatchers import kotlinx.coroutines.Job @@ -63,6 +66,17 @@ class AttendanceManager(val app: App) : CoroutineScope { 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 + } + /* _ _ _____ _____ _ __ _ | | | |_ _| / ____| (_)/ _(_) | | | | | | | (___ _ __ ___ ___ _| |_ _ ___ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt new file mode 100644 index 00000000..0c93468e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt @@ -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) + } +} diff --git a/app/src/main/res/layout/dialog_lesson_details.xml b/app/src/main/res/layout/dialog_lesson_details.xml index bf9cc40d..a304c641 100644 --- a/app/src/main/res/layout/dialog_lesson_details.xml +++ b/app/src/main/res/layout/dialog_lesson_details.xml @@ -134,7 +134,8 @@ android:layout_marginTop="8dp" android:baselineAligned="false" android:gravity="center_vertical" - android:orientation="horizontal"> + android:orientation="horizontal" + android:visibility="gone"> + + + + + + + + + + +