Compare commits

..

No commits in common. "v3.9.9-dev" and "v3.9.6-dev" have entirely different histories.

50 changed files with 738 additions and 1494 deletions

View file

@ -169,7 +169,7 @@ dependencies {
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584' implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
implementation 'com.github.kuba2k2:Tachyon:551943a6b5' implementation 'com.linkedin.android.tachyon:tachyon:1.0.2'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View file

@ -4,9 +4,7 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
@ -15,10 +13,9 @@ import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.LongSparseArray import android.util.LongSparseArray
import android.util.SparseArray import android.util.SparseArray
import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.* import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -449,49 +446,3 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
} }
}) })
} }
/**
* Convert a value in dp to pixels.
*/
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
* Convert a value in pixels to dp.
*/
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ColorInt
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
val typedValue = TypedValue()
context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data
}
@ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(this, context.theme)
}
else {
context.resources.getColor(this)
}
}
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getDrawable(this, context.theme)
}
else {
context.resources.getDrawable(this)
}
}
fun View.findParentById(targetId: Int): View? {
if (id == targetId) {
return this
}
val viewParent = this.parent ?: return null
if (viewParent is View) {
return viewParent.findParentById(targetId)
}
return null
}

View file

@ -376,11 +376,6 @@ class MainActivity : AppCompatActivity() {
b.swipeRefreshLayout.isEnabled = true b.swipeRefreshLayout.isEnabled = true
b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() } b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() }
b.swipeRefreshLayout.setColorSchemeResources(
R.color.md_blue_500,
R.color.md_amber_500,
R.color.md_green_500
)
isStoragePermissionGranted() isStoragePermissionGranted()

View file

@ -311,14 +311,13 @@ public class Notifier {
\____/| .__/ \__,_|\__,_|\__\___||___/ \____/| .__/ \__,_|\__,_|\__\___||___/
| | | |
|*/ |*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) { public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) {
if (!app.appConfig.notifyAboutUpdates) if (!app.appConfig.notifyAboutUpdates)
return; return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class) Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion) .putExtra("update_version", updateVersion)
.putExtra("update_url", updateUrl) .putExtra("update_url", updateUrl)
.putExtra("update_filename", updateFilename) .putExtra("update_filename", updateFilename);
.putExtra("update_direct", updateDirect);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

View file

@ -185,12 +185,12 @@ class WidgetTimetable : AppWidgetProvider() {
// search for lessons to display // search for lessons to display
val timetableDate = Date.getToday() val timetableDate = Date.getToday()
var checkedDays = 0 var checkedDays = 0
var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS } var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate }
while ((lessons.isEmpty() || lessons.none { while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now) it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
}) && checkedDays < 7) { }) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1) timetableDate.stepForward(0, 0, 1)
lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS } lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate }
checkedDays++ checkedDays++
} }
@ -214,8 +214,8 @@ class WidgetTimetable : AppWidgetProvider() {
model.lessonId = lesson.id model.lessonId = lesson.id
model.lessonDate = timetableDate model.lessonDate = timetableDate
model.startTime = lesson.displayStartTime model.startTime = lesson.startTime
model.endTime = lesson.displayEndTime model.endTime = lesson.endTime
// check if the lesson has already passed or it's currently in progress // check if the lesson has already passed or it's currently in progress
if (lesson.displayDate == today) { if (lesson.displayDate == today) {

View file

@ -39,11 +39,9 @@ const val ERROR_REQUEST_HTTP_404 = 54
const val ERROR_REQUEST_HTTP_405 = 55 const val ERROR_REQUEST_HTTP_405 = 55
const val ERROR_REQUEST_HTTP_410 = 56 const val ERROR_REQUEST_HTTP_410 = 56
const val ERROR_REQUEST_HTTP_500 = 57 const val ERROR_REQUEST_HTTP_500 = 57
const val ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND = 60
const val ERROR_REQUEST_FAILURE_TIMEOUT = 61
const val ERROR_REQUEST_FAILURE_NO_INTERNET = 62
const val ERROR_RESPONSE_EMPTY = 100 const val ERROR_RESPONSE_EMPTY = 100
const val ERROR_LOGIN_DATA_MISSING = 101 const val ERROR_LOGIN_DATA_MISSING = 101
const val ERROR_LOGIN_DATA_INVALID = 102
const val ERROR_PROFILE_MISSING = 105 const val ERROR_PROFILE_MISSING = 105
const val ERROR_INVALID_LOGIN_MODE = 110 const val ERROR_INVALID_LOGIN_MODE = 110
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
@ -101,13 +99,6 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173 const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173
const val ERROR_LIBRUS_SYNERGIA_OTHER = 174 const val ERROR_LIBRUS_SYNERGIA_OTHER = 174
const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175 const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175
const val ERROR_LIBRUS_MESSAGES_MAINTENANCE = 176
const val ERROR_LIBRUS_MESSAGES_ERROR = 177
const val ERROR_LIBRUS_MESSAGES_OTHER = 178
const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
const val ERROR_LIBRUS_API_MAINTENANCE = 181
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202

View file

@ -37,16 +37,6 @@ object Regexes {
"""events: (.+),$""".toRegex(RegexOption.MULTILINE) """events: (.+),$""".toRegex(RegexOption.MULTILINE)
} }
val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy {
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy {
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy {
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
@ -71,8 +61,6 @@ object Regexes {
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL) """<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
} }
val VULCAN_SHITFT_ANNOTATION by lazy { val VULCAN_SHITFT_ANNOTATION by lazy {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
} }

View file

@ -24,7 +24,6 @@ const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023
const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024 const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025 const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026 const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026
const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031 const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032 const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033 const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033
@ -98,7 +97,6 @@ val LibrusFeatures = listOf(
ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API,

View file

@ -32,13 +32,6 @@ open class LibrusApi(open val data: DataLibrus) {
val callback = object : JsonCallbackHandler() { val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(tag, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null && response?.parserErrorBody == null) { if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -111,7 +104,6 @@ open class LibrusApi(open val data: DataLibrus) {
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN) .allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(callback) .callback(callback)
.build() .build()
.enqueue() .enqueue()

View file

@ -85,10 +85,6 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_grades) data.startProgress(R.string.edziennik_progress_endpoint_grades)
LibrusApiGrades(data, onSuccess) LibrusApiGrades(data, onSuccess)
} }
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_comments)
LibrusApiGradeComments(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GC -> { ENDPOINT_LIBRUS_API_NORMAL_GC -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
LibrusApiGradeCategories(data, onSuccess) LibrusApiGradeCategories(data, onSuccess)

View file

@ -15,6 +15,7 @@ import org.jsoup.parser.Parser
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
@ -42,19 +43,19 @@ open class LibrusMessages(open val data: DataLibrus) {
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) { override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) { if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(LibrusSynergia.TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
return return
} }
when { // TODO: Finish error handling
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text) if ("error" in text) {
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) when ("<type>(.*)</type>".toRegex().find(text)?.get(1)) {
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text) "eAccessDeny" -> data.error(ApiError(tag, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED)
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) .withResponse(response)
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) .withApiResponse(text))
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text) }
} }
try { try {

View file

@ -14,7 +14,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d
open class LibrusSynergia(open val data: DataLibrus) { open class LibrusSynergia(open val data: DataLibrus) {
companion object { companion object {
private const val TAG = "LibrusSynergia" const val TAG = "LibrusSynergia"
} }
val profileId val profileId

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-20
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
class LibrusApiGradeComments(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiGradeComments"
}
init {
apiGet(TAG, "Grades/Comments") { json ->
json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment ->
val id = comment.getLong("Id") ?: return@forEach
val text = comment.getString("Text")
val gradeCategoryObject = GradeCategory(
profileId,
id,
-1f,
-1,
text
).apply {
type = GradeCategory.TYPE_COMMENT
}
data.gradeCategories.put(id, gradeCategoryObject)
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View file

@ -6,7 +6,6 @@ import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -43,21 +42,12 @@ class LibrusApiGrades(override val data: DataLibrus,
weight = 0f weight = 0f
} }
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
if (comments.isNotEmpty()) {
data.gradeCategories.singleOrNull {
it.type == GradeCategory.TYPE_COMMENT
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
}?.text
} else null
} ?: ""
val gradeObject = Grade( val gradeObject = Grade(
profileId, profileId,
id, id,
categoryName, categoryName,
color, color,
description, "",
name, name,
value, value,
weight, weight,

View file

@ -16,7 +16,8 @@ import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.* import java.net.HttpURLConnection.HTTP_BAD_REQUEST
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
class LibrusLoginApi { class LibrusLoginApi {
companion object { companion object {
@ -116,13 +117,6 @@ class LibrusLoginApi {
private val tokenCallback = object : JsonCallbackHandler() { private val tokenCallback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(TAG, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null) { if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -182,7 +176,6 @@ class LibrusLoginApi {
.post() .post()
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(tokenCallback) .callback(tokenCallback)
.build() .build()
.enqueue() .enqueue()

View file

@ -6,57 +6,20 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie import okhttp3.Cookie
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object { companion object {
private const val TAG = "LoginLibrusMessages" private const val TAG = "LoginLibrusMessages"
} }
private val callback by lazy { object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
when {
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
location?.contains("AutoLogon") == true -> {
saveSessionId(response, text)
onSuccess()
}
text?.contains("<status>ok</status>") == true -> {
saveSessionId(response, text)
onSuccess()
}
text?.contains("<message>Niepoprawny login i/lub hasło.</message>") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
text?.contains("<status>error</status>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
text?.contains("<type>eVarWhitThisNameNotExists</type>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("<error>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}}
init { run { init { run {
if (data.profile == null) { if (data.profile == null) {
data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) data.error(ApiError(TAG, ERROR_PROFILE_MISSING))
@ -78,7 +41,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
loginWithSynergia() loginWithSynergia()
} }
else if (data.apiLogin != null && data.apiPassword != null) { else if (data.apiLogin != null && data.apiPassword != null && false) {
loginWithCredentials() loginWithCredentials()
} }
else { else {
@ -91,44 +54,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
* XML (Flash messages website) login method. Uses a Synergia login and password. * XML (Flash messages website) login method. Uses a Synergia login and password.
*/ */
private fun loginWithCredentials() { private fun loginWithCredentials() {
d(TAG, "Request: Librus/Login/Messages - $LIBRUS_MESSAGES_URL/Login")
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument()
val serviceElement = doc.createElement("service")
val headerElement = doc.createElement("header")
val dataElement = doc.createElement("data")
val loginElement = doc.createElement("login")
loginElement.appendChild(doc.createTextNode(data.apiLogin))
dataElement.appendChild(loginElement)
val passwordElement = doc.createElement("login")
passwordElement.appendChild(doc.createTextNode(data.apiPassword))
dataElement.appendChild(passwordElement)
val keyStrokeElement = doc.createElement("KeyStroke")
val keysElement = doc.createElement("Keys")
val upElement = doc.createElement("Up")
keysElement.appendChild(upElement)
val downElement = doc.createElement("Down")
keysElement.appendChild(downElement)
keyStrokeElement.appendChild(keysElement)
dataElement.appendChild(keyStrokeElement)
serviceElement.appendChild(headerElement)
serviceElement.appendChild(dataElement)
doc.appendChild(serviceElement)
val transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
val stringWriter = StringWriter()
transformer.transform(DOMSource(doc), StreamResult(stringWriter))
val requestXml = stringWriter.toString()
Request.builder()
.url("$LIBRUS_MESSAGES_URL/Login")
.userAgent(SYNERGIA_USER_AGENT)
.setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML)
.post()
.callback(callback)
.build()
.enqueue()
} }
/** /**
@ -137,6 +63,37 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") { private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") {
d(TAG, "Request: Librus/Login/Messages - $url") d(TAG, "Request: Librus/Login/Messages - $url")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
when {
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
location?.contains("AutoLogon") == true -> {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "")
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess()
}
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder() Request.builder()
.url(url) .url(url)
.userAgent(SYNERGIA_USER_AGENT) .userAgent(SYNERGIA_USER_AGENT)
@ -146,17 +103,4 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
.build() .build()
.enqueue() .enqueue()
} }
private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
}
} }

View file

@ -99,14 +99,6 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
.post() .post()
.callback(object : JsonCallbackHandler() { .callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) { override fun onSuccess(json: JsonObject?, response: Response) {
val location = response.headers()?.get("Location")
if (location == "http://localhost/bar?command=close") {
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null) { if (json == null) {
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) { if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED) data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED)
@ -128,7 +120,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
override fun onFailure(response: Response, throwable: Throwable) { override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) { if (response.code() == 403 || response.code() == 401) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN) data.error(ApiError(TAG, ERROR_LOGIN_DATA_INVALID)
.withResponse(response) .withResponse(response)
.withThrowable(throwable)) .withThrowable(throwable))
return return

View file

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie import okhttp3.Cookie
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
@ -15,6 +16,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -84,13 +86,6 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(json: String?, response: Response?) { override fun onSuccess(json: String?, response: Response?) {
val location = response?.headers()?.get("Location") val location = response?.headers()?.get("Location")
if (location?.endsWith("przerwa_techniczna") == true) {
data.error(ApiError(TAG, ERROR_LIBRUS_SYNERGIA_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (location?.endsWith("centrum_powiadomien") == true) { if (location?.endsWith("centrum_powiadomien") == true) {
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID") val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID")
if (sessionId == null) { if (sessionId == null) {

View file

@ -10,10 +10,8 @@ import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGetMessage
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
@ -65,11 +63,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
} }
override fun getMessage(message: MessageFull) { override fun getMessage(message: MessageFull) {
MobidziennikLoginWeb(data) {
MobidziennikWebGetMessage(data, message) {
completed()
}
}
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {

View file

@ -1,157 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-18.
*/
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web
import org.greenrobot.eventbus.EventBus
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.Utils.monthFromName
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikWebGetMessage(
override val data: DataMobidziennik,
private val message: MessageFull,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebGetMessage"
}
init {
val typeUrl = if (message.type == Message.TYPE_SENT)
"wiadwyslana"
else
"wiadodebrana"
webGet(TAG, "/dziennik/$typeUrl/?id=${message.id}") { text ->
MobidziennikLuckyNumberExtractor(data, text)
val messageRecipientList = mutableListOf<MessageRecipientFull>()
val doc = Jsoup.parse(text)
val content = doc.select("#content").first()
val body = content.select(".wiadomosc_tresc").first()
if (message.type == TYPE_RECEIVED) {
var readDate = System.currentTimeMillis()
Regexes.MOBIDZIENNIK_MESSAGE_READ_DATE.find(body.html())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,
monthFromName(it[2]),
it[1].toIntOrNull() ?: 1
)
val time = Time.fromH_m_s(
it[4] // TODO blank string safety
)
readDate = date.combineWith(time)
}
val recipient = MessageRecipientFull(
profileId,
-1,
-1,
readDate,
message.id
)
recipient.fullName = profile?.accountNameLong ?: profile?.studentNameLong
messageRecipientList.add(recipient)
} else {
message.senderId = -1
message.senderReplyId = -1
content.select("table.spis tr:has(td)")?.forEach { recipientEl ->
val senderEl = recipientEl.select("td:eq(0)").first()
val senderName = senderEl.text()
val teacher = data.teacherList.singleOrNull { it.fullNameLastFirst == senderName }
val receiverId = teacher?.id ?: -1
var readDate = 0L
val isReadEl = recipientEl.select("td:eq(2)").first()
if (isReadEl.ownText() != "NIE") {
val readDateEl = recipientEl.select("td:eq(3) small").first()
Regexes.MOBIDZIENNIK_MESSAGE_SENT_READ_DATE.find(readDateEl.ownText())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,
monthFromName(it[2]),
it[1].toIntOrNull() ?: 1
)
val time = Time.fromH_m_s(
it[4] // TODO blank string safety
)
readDate = date.combineWith(time)
}
}
val recipient = MessageRecipientFull(
profileId,
receiverId,
-1,
readDate,
message.id
)
recipient.fullName = teacher?.fullName ?: "?"
messageRecipientList.add(recipient)
}
}
// this line removes the sender and read date details
body.select("div").remove()
// this needs to be at the end
message.apply {
this.body = body.html()
clearAttachments()
content.select("ul li").map { it.select("a").first() }.forEach {
val attachmentName = it.ownText()
Regexes.MOBIDZIENNIK_MESSAGE_ATTACHMENT.find(it.outerHtml())?.let { match ->
val attachmentId = match[1].toLong()
var size = match[2].toFloatOrNull() ?: 0f
when (match[3]) {
"K" -> size *= 1024f
"M" -> size *= 1024f * 1024f
"G" -> size *= 1024f * 1024f * 1024f
}
message.addAttachment(attachmentId, attachmentName, size.toLong())
}
}
}
if (!message.seen) { // TODO discover why this monstrosity instead of MetadataDao.setSeen
data.messageMetadataList.add(Metadata(
message.profileId,
Metadata.TYPE_MESSAGE,
message.id,
true,
true,
message.addedDate
))
}
message.recipients = messageRecipientList
data.messageRecipientList.addAll(messageRecipientList)
data.messageList.add(message)
EventBus.getDefault().postSticky(MessageGetEvent(message))
onSuccess()
}
}
}

View file

@ -7,14 +7,15 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API
import pl.szczodrzynski.edziennik.api.v2.models.Data import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isApiLoginValid() = /*apiCertificateExpiryTime-30 > currentTimeUnix() fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix()
&&*/ apiCertificateKey.isNotNullNorEmpty() && apiCertificateKey.isNotNullNorEmpty()
&& apiCertificatePrivate.isNotNullNorEmpty() && apiCertificatePrivate.isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty() && symbol.isNotNullNorEmpty()

View file

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
import androidx.core.util.set
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.Regexes import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE
@ -15,7 +14,6 @@ import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.crc16 import pl.szczodrzynski.edziennik.utils.Utils.crc16
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
@ -26,180 +24,163 @@ class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Uni
const val TAG = "VulcanApiTimetable" const val TAG = "VulcanApiTimetable"
} }
init { data.profile?.also { profile -> init {
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) } data.profile?.also { profile ->
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate) val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6) val weekEnd = weekStart.clone().stepForward(0, 0, 6)
apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf( apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf(
"DataPoczatkowa" to weekStart.stringY_m_d, "DataPoczatkowa" to weekStart.stringY_m_d,
"DataKoncowa" to weekEnd.stringY_m_d, "DataKoncowa" to weekEnd.stringY_m_d,
"IdUczen" to data.studentId, "IdUczen" to data.studentId,
"IdOddzial" to data.studentClassId, "IdOddzial" to data.studentClassId,
"IdOkresKlasyfikacyjny" to data.studentSemesterId "IdOkresKlasyfikacyjny" to data.studentSemesterId
)) { json, _ -> )) { json, _ ->
val dates: MutableSet<Int> = mutableSetOf() val dates: MutableSet<Int> = mutableSetOf()
val lessons: MutableList<Lesson> = mutableListOf() val lessons: MutableList<Lesson> = mutableListOf()
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson -> json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson ->
if (lesson.getBoolean("PlanUcznia") != true) val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst"))
return@forEach val lessonNumber = lesson.getInt("NumerLekcji")
val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst")) val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
val lessonNumber = lesson.getInt("NumerLekcji") val startTime = lessonRange?.startTime
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber } val endTime = lessonRange?.endTime
val startTime = lessonRange?.startTime val teacherId = lesson.getLong("IdPracownik")
val endTime = lessonRange?.endTime val teamId = data.studentClassId.toLong()
val teacherId = lesson.getLong("IdPracownik") val classroom = lesson.getString("Classroom")
val classroom = lesson.getString("Sala")
val oldTeacherId = lesson.getLong("IdPracownikOld") val oldTeacherId = lesson.getLong("IdPracownikOld")
val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: "" val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: ""
val type = when { val type = when {
changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET
changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE
changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE
lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED
else -> Lesson.TYPE_NORMAL else -> Lesson.TYPE_NORMAL
}
val teamId = lesson.getString("PodzialSkrot")?.let { teamName ->
val name = "${data.teamClass?.name} $teamName"
val id = name.crc16().toLong()
var team = data.teamList.singleOrNull { it.name == name }
if (team == null) {
team = Team(
profileId,
id,
name,
Team.TYPE_VIRTUAL,
"${data.schoolName}:$name",
teacherId ?: oldTeacherId ?: -1
)
data.teamList[id] = team
} }
team.id
} ?: data.studentClassId.toLong()
val subjectId = lesson.getLong("IdPrzedmiot")?.let { val subjectId = lesson.getLong("IdPrzedmiot")?.let {
when (it) { when (it) {
0L -> { 0L -> {
val subjectName = lesson.getString("PrzedmiotNazwa") ?: "" val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id
?: { ?: {
/** /**
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST * CREATE A NEW SUBJECT IF IT DOESN'T EXIST
*/ */
val subjectObject = Subject( val subjectObject = Subject(
profileId, profileId,
-1 * crc16(subjectName.toByteArray()).toLong(), -1 * crc16(subjectName.toByteArray()).toLong(),
subjectName, subjectName,
subjectName subjectName
) )
data.subjectList.put(subjectObject.id, subjectObject) data.subjectList.put(subjectObject.id, subjectObject)
subjectObject.id subjectObject.id
}.invoke() }.invoke()
} }
else -> it else -> it
}
}
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
val lessonObject = Lesson(profileId, id).apply {
this.type = type
when (type) {
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
this.date = lessonDate
this.lessonNumber = lessonNumber
this.startTime = startTime
this.endTime = endTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
this.oldDate = lessonDate
this.oldLessonNumber = lessonNumber
this.oldStartTime = startTime
this.oldEndTime = endTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
} }
} }
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) { val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber } val lessonObject = Lesson(profileId, id).apply {
val oldStartTime = oldLessonRange?.startTime this.type = type
val oldEndTime = oldLessonRange?.endTime
when (type) { when (type) {
Lesson.TYPE_SHIFTED_SOURCE -> { Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
this.lessonNumber = oldLessonNumber this.date = lessonDate
this.date = oldLessonDate this.lessonNumber = lessonNumber
this.startTime = oldStartTime this.startTime = startTime
this.endTime = oldEndTime this.endTime = endTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
} }
Lesson.TYPE_SHIFTED_TARGET -> { Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
this.oldLessonNumber = oldLessonNumber this.oldDate = lessonDate
this.oldDate = oldLessonDate this.oldLessonNumber = lessonNumber
this.oldStartTime = oldStartTime this.oldStartTime = startTime
this.oldEndTime = oldEndTime this.oldEndTime = endTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
}
}
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber }
val oldStartTime = oldLessonRange?.startTime
val oldEndTime = oldLessonRange?.endTime
when (type) {
Lesson.TYPE_SHIFTED_SOURCE -> {
this.lessonNumber = oldLessonNumber
this.date = oldLessonDate
this.startTime = oldStartTime
this.endTime = oldEndTime
}
Lesson.TYPE_SHIFTED_TARGET -> {
this.oldLessonNumber = oldLessonNumber
this.oldDate = oldLessonDate
this.oldStartTime = oldStartTime
this.oldEndTime = oldEndTime
}
} }
} }
} }
if (type != Lesson.TYPE_NORMAL) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
dates.add(lessonDate.value)
lessons.add(lessonObject)
} }
if (type != Lesson.TYPE_NORMAL) { val date: Date = weekStart.clone()
data.metadataList.add(Metadata( while (date <= weekEnd) {
profileId, if (!dates.contains(date.value)) {
Metadata.TYPE_LESSON_CHANGE, lessons.add(Lesson(profileId, date.value.toLong()).apply {
id, this.type = Lesson.TYPE_NO_LESSONS
profile.empty, this.date = date.clone()
profile.empty, })
System.currentTimeMillis() }
))
date.stepForward(0, 0, 1)
} }
dates.add(lessonDate.value) d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
lessons.add(lessonObject)
data.lessonNewList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
onSuccess()
} }
val date: Date = weekStart.clone()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.lessonNewList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
onSuccess()
} }
}} }
} }

View file

@ -1,10 +1,10 @@
package pl.szczodrzynski.edziennik.data.db.modules.grades; package pl.szczodrzynski.edziennik.data.db.modules.grades;
import androidx.room.Entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.room.Entity;
@Entity(tableName = "gradeCategories", @Entity(tableName = "gradeCategories",
primaryKeys = {"profileId", "categoryId"}) primaryKeys = {"profileId", "categoryId"})
public class GradeCategory { public class GradeCategory {
@ -25,9 +25,6 @@ public class GradeCategory {
*/ */
public int type = 0; public int type = 0;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_COMMENT = 1;
public GradeCategory(int profileId, long categoryId, float weight, int color, String text) { public GradeCategory(int profileId, long categoryId, float weight, int color, String text) {
this.profileId = profileId; this.profileId = profileId;
this.categoryId = categoryId; this.categoryId = categoryId;

View file

@ -38,16 +38,16 @@ interface TimetableDao {
@Query("DELETE FROM timetable WHERE profileId = :profileId") @Query("DELETE FROM timetable WHERE profileId = :profileId")
fun clear(profileId: Int) fun clear(profileId: Int)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))") @Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom)")
fun clearFromDate(profileId: Int, dateFrom: Date) fun clearFromDate(profileId: Int, dateFrom: Date)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))") @Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo)")
fun clearToDate(profileId: Int, dateTo: Date) fun clearToDate(profileId: Int, dateTo: Date)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))") @Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)")
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
@Query(""" @Query("""
$QUERY $QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)) WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)
ORDER BY id, type ORDER BY id, type
""") """)
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>> fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
@ -58,15 +58,7 @@ interface TimetableDao {
ORDER BY id, type ORDER BY id, type
LIMIT 1 LIMIT 1
""") """)
fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull?> fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId AND timetable.teamId = :teamId
ORDER BY id, type
LIMIT 1
""")
fun getNextWithSubjectAndTeam(profileId: Int, today: Date, subjectId: Long, teamId: Long): LiveData<LessonFull?>
@Query(""" @Query("""
$QUERY $QUERY

View file

@ -125,22 +125,19 @@ public class BootReceiver extends BroadcastReceiver {
String updateUrl = result.get("update_url").getAsString(); String updateUrl = result.get("update_url").getAsString();
String updateFilename = result.get("update_filename").getAsString(); String updateFilename = result.get("update_filename").getAsString();
boolean updateMandatory = result.get("update_mandatory").getAsBoolean(); boolean updateMandatory = result.get("update_mandatory").getAsBoolean();
boolean updateDirect = result.get("update_direct").getAsBoolean();
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion; app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl; app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename; app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory; app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig(); app.saveConfig();
} }
if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) { if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
updateVersion, updateVersion,
updateUrl, updateUrl,
updateFilename, updateFilename);
updateDirect);
} }
} else { } else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
@ -262,7 +259,7 @@ public class BootReceiver extends BroadcastReceiver {
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (UPDATES_ON_PLAY_STORE && !intent.getBooleanExtra("update_direct", false)) { if (UPDATES_ON_PLAY_STORE) {
Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik"); Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik");
return; return;
} }
@ -273,8 +270,7 @@ public class BootReceiver extends BroadcastReceiver {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
intent.getStringExtra("update_version"), intent.getStringExtra("update_version"),
intent.getStringExtra("update_url"), intent.getStringExtra("update_url"),
intent.getStringExtra("update_filename"), intent.getStringExtra("update_filename"));
intent.getBooleanExtra("update_direct", false));
return; return;
} }
} }

View file

@ -154,22 +154,19 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
String updateUrl = remoteMessage.getData().get("update_url"); String updateUrl = remoteMessage.getData().get("update_url");
String updateFilename = remoteMessage.getData().get("update_filename"); String updateFilename = remoteMessage.getData().get("update_filename");
boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory")); boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory"));
boolean updateDirect = Boolean.parseBoolean(remoteMessage.getData().get("update_direct"));
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion; app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl; app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename; app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory; app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig("updateVersion", "updateUrl", "updateFilename", "updateMandatory"); app.saveConfig("updateVersion", "updateUrl", "updateFilename", "updateMandatory");
} }
if (!remoteMessage.getData().containsKey("update_silent")) { if (!remoteMessage.getData().containsKey("update_silent")) {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
updateVersion, updateVersion,
updateUrl, updateUrl,
updateFilename, updateFilename);
updateDirect);
} }
} else { } else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {

View file

@ -4,25 +4,19 @@
package pl.szczodrzynski.edziennik.ui.dialogs.event package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -52,14 +46,10 @@ class EventManualV2Dialog(
private val app by lazy { activity.application as App } private val app by lazy { activity.application as App }
private lateinit var b: DialogEventManualV2Binding private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var defaultLoaded = false private var defaultLoaded = false
private lateinit var event: Event
private var customColor: Int? = null
init { run { init { run {
if (activity.isFinishing)
return@run
job = Job() job = Job()
onShowListener?.invoke(TAG) onShowListener?.invoke(TAG)
b = DialogEventManualV2Binding.inflate(activity.layoutInflater) b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
@ -88,17 +78,6 @@ class EventManualV2Dialog(
}*/ }*/
} }
b.showMore.onClick { // TODO iconics is broken
it.apply {
refreshDrawableState()
if (isChecked)
Anim.expand(b.moreLayout, 200, null)
else
Anim.collapse(b.moreLayout, 200, null)
}
}
loadLists() loadLists()
}} }}
@ -133,33 +112,18 @@ class EventManualV2Dialog(
"" ""
) )
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) } b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
// get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
b.typeDropdown.clear()
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
} }
deferred.await() deferred.await()
b.teamDropdown.isEnabled = true b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true b.teacherDropdown.isEnabled = true
b.typeDropdown.isEnabled = true
b.typeDropdown.selected?.let { item ->
customColor = (item.tag as EventType).color
}
// copy IDs from event being edited // copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.teamDropdown.select(it.teamId) b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId) b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId) b.teacherDropdown.select(it.teacherId)
b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color
}
if (it.color != -1)
customColor = it.color
} }
// copy IDs from the LessonFull // copy IDs from the LessonFull
@ -169,30 +133,6 @@ class EventManualV2Dialog(
b.teacherDropdown.select(it.displayTeacherId) b.teacherDropdown.select(it.displayTeacherId)
} }
b.typeDropdown.setOnChangeListener {
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
customColor = null
return@setOnChangeListener true
}
(customColor ?: Event.COLOR_DEFAULT).let {
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
}
b.typeColor.onClick {
val currentColor = (b.typeDropdown?.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor)
.create()
colorPickerDialog.setColorPickerDialogListener(
object : ColorPickerDialogListener {
override fun onDialogDismissed(dialogId: Int) {}
override fun onColorSelected(dialogId: Int, color: Int) {
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
customColor = color
}
})
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
}
loadDates() loadDates()
}} }}
@ -263,20 +203,12 @@ class EventManualV2Dialog(
val dates = deferred.await() val dates = deferred.await()
b.dateDropdown.clear().append(dates) b.dateDropdown.clear().append(dates)
editingEvent?.eventDate?.let { editingEvent?.let {
b.dateDropdown.select(TextInputDropDown.Item( b.dateDropdown.select(it.eventDate.value.toLong())
it.value.toLong(),
it.formattedString,
tag = it
))
} }
defaultLesson?.displayDate?.let { defaultLesson?.let {
b.dateDropdown.select(TextInputDropDown.Item( b.dateDropdown.select(it.displayDate?.value?.toLong())
it.value.toLong(),
it.formattedString,
tag = it
))
} }
if (b.dateDropdown.selected == null) { if (b.dateDropdown.selected == null) {
@ -289,24 +221,22 @@ class EventManualV2Dialog(
when { when {
// next lesson with specified subject // next lesson with specified subject
item.id < -1 -> { item.id < -1 -> {
val teamId = defaultLesson?.teamId ?: -1 app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer {
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
when (teamId) {
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
}.observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.select(TextInputDropDown.Item( b.dateDropdown.selected = TextInputDropDown.Item(
lessonDate.value.toLong(), lessonDate.value.toLong(),
lessonDate.formattedString, lessonDate.formattedString,
tag = lessonDate tag = lessonDate
)) )
b.teamDropdown.select(it.displayTeamId) // TODO load correct hour when selecting next lesson
b.subjectDropdown.select(it.displaySubjectId) b.dateDropdown.updateText()
b.teacherDropdown.select(it.displayTeacherId) it.let {
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
defaultLoaded = false defaultLoaded = false
loadHours(it.displayStartTime) loadHours()
}) })
return@setOnChangeListener false return@setOnChangeListener false
} }
@ -314,17 +244,17 @@ class EventManualV2Dialog(
item.id == -1L -> { item.id == -1L -> {
MaterialDatePicker.Builder MaterialDatePicker.Builder
.datePicker() .datePicker()
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } .setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: Date.getToday()).inMillis)
?: Date.getToday()).inMillis)
.build() .build()
.apply { .apply {
addOnPositiveButtonClickListener { addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it) val dateSelected = Date.fromMillis(it)
b.dateDropdown.select(TextInputDropDown.Item( b.dateDropdown.selected = TextInputDropDown.Item(
dateSelected.value.toLong(), dateSelected.value.toLong(),
dateSelected.formattedString, dateSelected.formattedString,
tag = dateSelected tag = dateSelected
)) )
b.dateDropdown.updateText()
loadHours() loadHours()
} }
show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker") show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker")
@ -344,7 +274,7 @@ class EventManualV2Dialog(
loadHours() loadHours()
}} }}
private fun loadHours(defaultHour: Time? = null) { private fun loadHours() {
b.timeDropdown.isEnabled = false b.timeDropdown.isEnabled = false
// get the selected date // get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
@ -370,8 +300,7 @@ class EventManualV2Dialog(
lesson.displayStartTime?.stringHM ?: "", lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let { lesson.displaySubjectName?.let {
when { when {
lesson.type == Lesson.TYPE_CANCELLED lesson.type == Lesson.TYPE_CANCELLED -> it.asStrikethroughSpannable()
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable() lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it else -> it
} }
@ -407,10 +336,6 @@ class EventManualV2Dialog(
defaultLesson?.let { defaultLesson?.let {
b.timeDropdown.select(it.displayStartTime?.value?.toLong()) b.timeDropdown.select(it.displayStartTime?.value?.toLong())
} }
defaultHour?.let {
b.timeDropdown.select(it.value.toLong())
}
} }
defaultLoaded = true defaultLoaded = true
b.timeDropdown.isEnabled = true b.timeDropdown.isEnabled = true
@ -418,16 +343,16 @@ class EventManualV2Dialog(
// attach a listener to time dropdown // attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item -> b.timeDropdown.setOnChangeListener { item ->
when { when {
// no lessons this day
item.id == -2L -> {
b.timeDropdown.deselect()
return@setOnChangeListener false
}
// custom start hour // custom start hour
item.id == -1L -> { item.id == -1L -> {
return@setOnChangeListener false return@setOnChangeListener false
} }
// no lessons this day
item.id == -2L -> {
b.timeDropdown.deselect()
return@setOnChangeListener false
}
// selected a specific lesson // selected a specific lesson
else -> { else -> {
if (item.tag is LessonFull) { if (item.tag is LessonFull) {
@ -456,4 +381,4 @@ class EventManualV2Dialog(
private fun saveEvent() { private fun saveEvent() {
} }
} }

View file

@ -33,8 +33,6 @@ class LessonDetailsDialog(
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
init { run { init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG) onShowListener?.invoke(TAG)
b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) b = DialogLessonDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
@ -132,28 +130,28 @@ class LessonDetailsDialog(
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) {
b.oldSubjectName = lesson.oldSubjectName b.oldSubjectName = lesson.oldSubjectName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displaySubjectId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.subjectId != null) {
b.subjectName = lesson.subjectName b.subjectName = lesson.subjectName
} }
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) {
b.oldTeacherName = lesson.oldTeacherName b.oldTeacherName = lesson.oldTeacherName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeacherId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teacherId != null) {
b.teacherName = lesson.teacherName b.teacherName = lesson.teacherName
} }
if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) { if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) {
b.oldClassroom = lesson.oldClassroom b.oldClassroom = lesson.oldClassroom
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayClassroom != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.classroom != null) {
b.classroom = lesson.classroom b.classroom = lesson.classroom
} }
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) {
b.oldTeamName = lesson.oldTeamName b.oldTeamName = lesson.oldTeamName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeamId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teamId != null) {
b.teamName = lesson.teamName b.teamName = lesson.teamName
} }
} }

View file

@ -12,8 +12,8 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
@ -84,11 +84,7 @@ class HomeworkFragment : Fragment() {
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners() b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {}
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position

View file

@ -19,7 +19,7 @@ import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar; import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_DATA_INVALID;
import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS; import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
@ -57,7 +57,7 @@ public class LoginLibrusFragment extends Fragment {
ApiError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.getErrorCode()) { switch (error.getErrorCode()) {
case ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN: case ERROR_LOGIN_DATA_INVALID:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
case ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED: case ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED:

View file

@ -77,11 +77,7 @@ class MessagesFragment : Fragment() {
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners() b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {}
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position

View file

@ -11,7 +11,6 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -19,25 +18,16 @@ import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class TimetableFragment : Fragment(), CoroutineScope { class TimetableFragment : Fragment() {
companion object { companion object {
private const val TAG = "TimetableFragment" private const val TAG = "TimetableFragment"
const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE" const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE"
const val DEFAULT_START_HOUR = 6
const val DEFAULT_END_HOUR = 19
var pageSelection: Date? = null
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2Binding private lateinit var b: FragmentTimetableV2Binding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var fabShown = false private var fabShown = false
private val items = mutableListOf<Date>() private val items = mutableListOf<Date>()
@ -46,13 +36,11 @@ class TimetableFragment : Fragment(), CoroutineScope {
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
job = Job()
context!!.theme.applyStyle(Themes.appTheme, true) context!!.theme.applyStyle(Themes.appTheme, true)
if (app.profile == null) if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false) return inflater.inflate(R.layout.fragment_loading, container, false)
// activity, context and profile is valid // activity, context and profile is valid
b = FragmentTimetableV2Binding.inflate(inflater) b = FragmentTimetableV2Binding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
@ -74,64 +62,48 @@ class TimetableFragment : Fragment(), CoroutineScope {
activity.unregisterReceiver(broadcastReceiver) activity.unregisterReceiver(broadcastReceiver)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// TODO check if app, activity, b can be null // TODO check if app, activity, b can be null
if (app.profile == null || !isAdded) if (app.profile == null || !isAdded)
return@launch return
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) { if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
b.timetableLayout.visibility = View.GONE b.timetableLayout.visibility = View.GONE
b.timetableNotPublicLayout.visibility = View.VISIBLE b.timetableNotPublicLayout.visibility = View.VISIBLE
return@launch return
} }
b.timetableLayout.visibility = View.VISIBLE b.timetableLayout.visibility = View.VISIBLE
b.timetableNotPublicLayout.visibility = View.GONE b.timetableNotPublicLayout.visibility = View.GONE
items.clear()
val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val today = Date.getToday().value val today = Date.getToday().value
var startHour = DEFAULT_START_HOUR val yearStart = app.profile.dateSemester1Start?.clone() ?: return
var endHour = DEFAULT_END_HOUR val yearEnd = app.profile.dateYearEnd ?: return
val deferred = async(Dispatchers.Default) { while (yearStart.value <= yearEnd.value) {
items.clear() items += yearStart.clone()
var maxDays = monthDayCount[yearStart.month-1]
val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
val yearStart = app.profile.dateSemester1Start?.clone() ?: return@async yearStart.day++
val yearEnd = app.profile.dateYearEnd ?: return@async if (yearStart.day > maxDays) {
while (yearStart.value <= yearEnd.value) { yearStart.day = 1
items += yearStart.clone() yearStart.month++
var maxDays = monthDayCount[yearStart.month-1] }
if (yearStart.month == 2 && yearStart.isLeap) if (yearStart.month > 12) {
maxDays++ yearStart.month = 1
yearStart.day++ yearStart.year++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
} }
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
} }
deferred.await()
val pagerAdapter = TimetablePagerAdapter( val pagerAdapter = TimetablePagerAdapter(fragmentManager ?: return, items)
fragmentManager ?: return@launch,
items,
startHour,
endHour
)
b.viewPager.offscreenPageLimit = 2 b.viewPager.offscreenPageLimit = 2
b.viewPager.adapter = pagerAdapter b.viewPager.adapter = pagerAdapter
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
} }
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
@ -139,7 +111,6 @@ class TimetableFragment : Fragment(), CoroutineScope {
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = items[position]
activity.navView.bottomBar.fabEnable = items[position].value != today activity.navView.bottomBar.fabEnable = items[position].value != today
if (activity.navView.bottomBar.fabEnable && !fabShown) { if (activity.navView.bottomBar.fabEnable && !fabShown) {
activity.gainAttentionFAB() activity.gainAttentionFAB()
@ -152,10 +123,10 @@ class TimetableFragment : Fragment(), CoroutineScope {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false)
//activity.navView.bottomBar.fabEnable = true //activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today) activity.navView.bottomBar.fabExtendedText = getString(pl.szczodrzynski.edziennik.R.string.timetable_today)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener(View.OnClickListener {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
}) })
}} }
} }

View file

@ -8,12 +8,7 @@ import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragme
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
class TimetablePagerAdapter( class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: List<Date>) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
fragmentManager: FragmentManager,
private val items: List<Date>,
private val startHour: Int,
private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
companion object { companion object {
private const val TAG = "TimetablePagerAdapter" private const val TAG = "TimetablePagerAdapter"
} }
@ -26,8 +21,6 @@ class TimetablePagerAdapter(
return TimetableDayFragment().apply { return TimetableDayFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putInt("date", items[position].value) putInt("date", items[position].value)
putInt("startHour", startHour)
putInt("endHour", endHour)
} }
} }
/*return TimetableDayFragment().apply { /*return TimetableDayFragment().apply {

View file

@ -7,97 +7,42 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
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.api.v2.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2DayBinding
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.getColorFromAttr import pl.szczodrzynski.navlib.getColorFromAttr
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
class TimetableDayFragment : Fragment(), CoroutineScope { class TimetableDayFragment() : Fragment() {
companion object { companion object {
private const val TAG = "TimetableDayFragment" private const val TAG = "TimetableDayFragment"
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var inflater: AsyncLayoutInflater private lateinit var b: FragmentTimetableV2DayBinding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var date: Date private lateinit var date: Date
private var startHour = DEFAULT_START_HOUR
private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24*60
// find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
// the day ScrollView
private val dayScrollDelegate = lazy {
val dayScroll = ListenerScrollView(context!!)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
dayScroll.setOnRefreshLayoutEnabledListener { enabled ->
refreshLayout?.isEnabled = enabled
}
dayScroll
}
private val dayScroll by dayScrollDelegate
// the lesson DayView
private val dayView by lazy {
val dayView = DayView(context!!, DayViewConfig(
startHour = startHour,
endHour = endHour,
dividerHeight = 1.dp,
halfHourHeight = 60.dp,
hourDividerColor = R.attr.hourDividerColor.resolveAttr(context),
halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context),
hourLabelWidth = 40.dp,
hourLabelMarginEnd = 10.dp,
eventMargin = 2.dp
), true)
dayView.setPadding(10.dp)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
dayScroll.addView(dayView)
dayView
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
job = Job() b = FragmentTimetableV2DayBinding.inflate(inflater)
this.inflater = AsyncLayoutInflater(context!!)
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR return b.root
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
return FrameLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -107,42 +52,45 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
Log.d(TAG, "onViewCreated, date=$date") Log.d(TAG, "onViewCreated, date=$date")
// observe lesson database // Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>()
for (i in b.day.startHour..b.day.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, b.day, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
b.day.setHourLabelViews(hourLabelViews)
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons -> app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons ->
processLessonList(lessons) buildLessonViews(lessons)
}) })
} }
private fun processLessonList(lessons: List<LessonFull>) { private fun buildLessonViews(lessons: List<LessonFull>) {
// no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout) { view, _, parent -> b.dayScroll.visibility = View.GONE
parent?.removeAllViews() b.noTimetableLayout.visibility = View.VISIBLE
parent?.addView(view) b.noLessonsLayout.visibility = View.GONE
val b = TimetableNoTimetableBinding.bind(view) val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d
val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d b.noTimetableSync.onClick {
b.noTimetableSync.onClick { it.isEnabled = false
it.isEnabled = false EdziennikTask.syncProfile(
EdziennikTask.syncProfile( profileId = App.profileId,
profileId = App.profileId, viewIds = listOf(
viewIds = listOf( DRAWER_ITEM_TIMETABLE to 0
DRAWER_ITEM_TIMETABLE to 0 ),
), arguments = JsonObject(
arguments = JsonObject( "weekStart" to weekStart
"weekStart" to weekStart )
) ).enqueue(activity)
).enqueue(activity)
}
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
} }
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
return return
} }
// one lesson indicating a day without lessons
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout) { view, _, parent -> b.dayScroll.visibility = View.GONE
parent?.removeAllViews() b.noTimetableLayout.visibility = View.GONE
parent?.addView(view) b.noLessonsLayout.visibility = View.VISIBLE
}
return return
} }
@ -153,37 +101,23 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
return return
} }
// clear the root view and add the ScrollView b.dayScroll.visibility = View.VISIBLE
(view as FrameLayout).removeAllViews() b.noTimetableLayout.visibility = View.GONE
(view as FrameLayout).addView(dayScroll) b.noLessonsLayout.visibility = View.GONE
// Inflate a label view for each hour the day view will display var firstEventMinute = 24*60
val hourLabelViews = ArrayList<View>()
for (i in dayView.startHour..dayView.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, dayView, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
dayView.setHourLabelViews(hourLabelViews)
buildLessonViews(lessons)
}
private fun buildLessonViews(lessons: List<LessonFull>) {
if (!isAdded)
return
val eventViews = mutableListOf<View>() val eventViews = mutableListOf<View>()
val eventTimeRanges = mutableListOf<DayView.EventTimeRange>() val eventTimeRanges = mutableListOf<DayView.EventTimeRange>()
// Reclaim all of the existing event views so we can reuse them if needed, this process // Reclaim all of the existing event views so we can reuse them if needed, this process
// can be useful if your day view is hosted in a recycler view for example // can be useful if your day view is hosted in a recycler view for example
val recycled = dayView.removeEventViews() val recycled = b.day.removeEventViews()
var remaining = recycled?.size ?: 0 var remaining = recycled?.size ?: 0
val arrowRight = "" val arrowRight = ""
val bullet = "" val bullet = ""
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = getColorFromAttr(activity, android.R.attr.textColorSecondary)
for (lesson in lessons) { for (lesson in lessons) {
val startTime = lesson.displayStartTime ?: continue val startTime = lesson.displayStartTime ?: continue
@ -193,7 +127,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
// Try to recycle an existing event view if there are enough left, otherwise inflate // Try to recycle an existing event view if there are enough left, otherwise inflate
// a new one // a new one
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false)) val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, b.day, false))
?: continue ?: continue
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
@ -340,16 +274,9 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
} }
dayView.setEventViews(eventViews, eventTimeRanges) val minuteHeight = (b.day.getHourTop(1) - b.day.getHourTop(0)).toFloat() / 60f
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight val firstEventTop = (firstEventMinute - b.day.startHour * 60) * minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt()) b.day.setEventViews(eventViews, eventTimeRanges)
} b.dayScroll.scrollTo(0, firstEventTop.toInt())
override fun onResume() {
super.onResume()
if (dayScrollDelegate.isInitialized()) {
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt())
}
} }
} }

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-16.
*/
package pl.szczodrzynski.edziennik.utils
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
class ListenerScrollView(
context: Context,
attrs: AttributeSet? = null
) : ScrollView(context, attrs) {
private var onScrollChangedListener: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)? = null
private var onRefreshLayoutEnabledListener: ((enabled: Boolean) -> Unit)? = null
private var refreshLayoutEnabled = true
init {
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
refreshLayoutEnabled = scrollY < 10
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
false
}
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
onScrollChangedListener?.invoke(this, l, t, oldl, oldt)
if (t > 10 && refreshLayoutEnabled) {
refreshLayoutEnabled = false
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
}
fun setOnScrollChangedListener(l: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)?) {
onScrollChangedListener = l
}
fun setOnRefreshLayoutEnabledListener(l: ((enabled: Boolean) -> Unit)?) {
onRefreshLayoutEnabledListener = l
}
}

View file

@ -75,22 +75,21 @@ class TextInputDropDown : TextInputEditText {
} }
} }
fun select(item: Item): Item? { fun select(item: Item) {
selected = item selected = item
updateText() updateText()
return item
} }
fun select(id: Long?): Item? { fun select(id: Long?) {
return items.singleOrNull { it.id == id }?.let { select(it) } items.singleOrNull { it.id == id }?.let { select(it) }
} }
fun select(tag: Any?): Item? { fun select(tag: Any?) {
return items.singleOrNull { it.tag == tag }?.let { select(it) } items.singleOrNull { it.tag == tag }?.let { select(it) }
} }
fun select(index: Int): Item? { fun select(index: Int) {
return items.getOrNull(index)?.let { select(it) } items.getOrNull(index)?.let { select(it) }
} }
fun deselect(): TextInputDropDown { fun deselect(): TextInputDropDown {

View file

@ -95,7 +95,6 @@ public class AppConfig {
public String updateUrl = ""; public String updateUrl = "";
public String updateFilename = ""; public String updateFilename = "";
public boolean updateMandatory = false; public boolean updateMandatory = false;
public boolean updateDirect = false;
public boolean webPushEnabled = false; public boolean webPushEnabled = false;

View file

@ -173,8 +173,7 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
intent.putExtra("endTime", lesson.endTime.getStringValue()); intent.putExtra("endTime", lesson.endTime.getStringValue());
views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent); views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent);
if (lesson.startTime != null && lesson.endTime != null) views.setTextViewText(R.id.widgetTimetableTime, lesson.startTime.getStringHM() + " - " + lesson.endTime.getStringHM());
views.setTextViewText(R.id.widgetTimetableTime, lesson.startTime.getStringHM() + " - " + lesson.endTime.getStringHM());
views.setViewVisibility(R.id.widgetTimetableEvent1, View.GONE); views.setViewVisibility(R.id.widgetTimetableEvent1, View.GONE);
views.setViewVisibility(R.id.widgetTimetableEvent2, View.GONE); views.setViewVisibility(R.id.widgetTimetableEvent2, View.GONE);

View file

@ -4,17 +4,11 @@
--> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
xmlns:app="http://schemas.android.com/apk/res-auto">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:padding="24dp"> android:padding="24dp">
@ -52,63 +46,41 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_team"> android:hint="@string/dialog_event_manual_team">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown <pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teamDropdown" android:id="@+id/teamDropdown"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:enabled="false" android:enabled="false"
tools:text="2b3T" /> tools:text="2b3T"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.textfield.TextInputLayout
android:id="@+id/shareSwitch" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_event_manual_share_enabled" />
<TextView
android:id="@+id/shareDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_event_manual_share_first_notice"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:baselineAligned="false" android:hint="@string/dialog_event_manual_subject">
android:orientation="horizontal"> <pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/subjectDropdown"
<com.google.android.material.textfield.TextInputLayout android:layout_width="match_parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:enabled="false"
android:hint="@string/dialog_event_manual_type"> tools:text="2b3T"/>
</com.google.android.material.textfield.TextInputLayout>
<pl.szczodrzynski.edziennik.utils.TextInputDropDown <com.google.android.material.textfield.TextInputLayout
android:id="@+id/typeDropdown" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:enabled="false" android:layout_marginTop="8dp"
tools:text="2b3T" /> android:hint="@string/dialog_event_manual_teacher">
</com.google.android.material.textfield.TextInputLayout> <pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teacherDropdown"
<FrameLayout android:layout_width="match_parent"
android:id="@+id/typeColor" android:layout_height="wrap_content"
android:layout_width="40dp" android:enabled="false"
android:layout_height="40dp" tools:text="2b3T"/>
android:layout_gravity="center_vertical" </com.google.android.material.textfield.TextInputLayout>
android:layout_marginTop="2dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_circle"
android:foreground="?selectableItemBackgroundBorderless" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
@ -126,64 +98,5 @@
tools:text="2b3T" /> tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.mikepenz.iconics.view.IconicsCheckableTextView
android:id="@+id/showMore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:text="@string/dialog_event_manual_more_options"
android:background="?selectableItemBackground"
app:iiv_end_icon="cmd-chevron-down"
app:iiv_end_color="?android:textColorSecondary"
app:iiv_end_size="16dp"
app:iiv_end_checked_icon="cmd-chevron-up"
app:iiv_end_checked_color="?android:textColorSecondary"
app:iiv_end_checked_size="16dp"/>
<LinearLayout
android:id="@+id/moreLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView>
</layout> </layout>

View file

@ -21,207 +21,217 @@
<variable name="oldTeamName" type="String" /> <variable name="oldTeamName" type="String" />
<variable name="teamName" type="String" /> <variable name="teamName" type="String" />
</data> </data>
<ScrollView <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:padding="24dp">
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="24dp"> android:orientation="horizontal"
android:baselineAligned="false">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_gravity="center_vertical"
android:baselineAligned="false"> android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{oldSubjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:textColor="?android:textColorTertiary"
android:visibility="@{oldSubjectName == null ? View.GONE : View.VISIBLE}"
app:strikeThrough="@{true}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{subjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Title"
android:visibility="@{subjectName == null ? View.GONE : View.VISIBLE}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:id="@+id/lessonDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="czwartek, 14 listopada 2019"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_number"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:text="@{lesson.displayLessonNumber.toString()}"
android:textSize="36sp"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"
tools:text="4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{lesson.displayStartTime.stringHM + ` - ` + lesson.displayEndTime.stringHM}"
tools:text="14:55 - 15:40" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/shiftedLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView <TextView
android:id="@+id/shiftedText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="italic"
tools:text="Lekcja przeniesiona na czwartek, 17 października" />
<Button
android:id="@+id/shiftedGoTo"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Przejdź" /> android:text="@{oldSubjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:textColor="?android:textColorTertiary"
android:visibility="@{oldSubjectName == null ? View.GONE : View.VISIBLE}"
app:strikeThrough="@{true}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{subjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Title"
android:visibility="@{subjectName == null ? View.GONE : View.VISIBLE}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:id="@+id/lessonDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="czwartek, 14 listopada 2019"
tools:visibility="visible" />
</LinearLayout> </LinearLayout>
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:orientation="vertical"
android:textAppearance="@style/NavView.TextView.Helper" android:gravity="center">
android:text="@string/dialog_lesson_details_teacher"
android:visibility="@{teacherName != null || oldTeacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeacherName}"
android:textIsSelectable="true"
android:visibility="@{oldTeacherName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teacherName}"
android:textIsSelectable="true"
android:visibility="@{teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:textAppearance="@style/NavView.TextView.Helper"
android:textAppearance="@style/NavView.TextView.Helper" android:text="@string/dialog_lesson_details_number"
android:text="@string/dialog_lesson_details_classroom" android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"/>
android:visibility="@{classroom != null || oldClassroom != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldClassroom}"
android:textIsSelectable="true"
android:visibility="@{oldClassroom != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{classroom}"
android:textIsSelectable="true"
android:visibility="@{classroom != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:fontFamily="sans-serif-light"
android:textAppearance="@style/NavView.TextView.Helper" android:text="@{lesson.displayLessonNumber.toString()}"
android:text="@string/dialog_lesson_details_team" android:textSize="36sp"
android:visibility="@{teamName != null || oldTeamName != null ? View.VISIBLE : View.GONE}"/> android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"
<TextView tools:text="4" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeamName}"
android:textIsSelectable="true"
android:visibility="@{oldTeamName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teamName}"
android:textIsSelectable="true"
android:visibility="@{teamName != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:text="@{lesson.displayStartTime.stringHM + ` - ` + lesson.displayEndTime.stringHM}"
android:textAppearance="@style/NavView.TextView.Helper" tools:text="14:55 - 15:40" />
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
android:text="@string/dialog_lesson_details_id" />
<TextView </LinearLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@{Long.toString(lesson.id)}"
android:textIsSelectable="true"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
tools:text="12345" />
</LinearLayout> </LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/shiftedLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/shiftedText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="italic"
tools:text="Lekcja przeniesiona na czwartek, 17 października" />
<Button
android:id="@+id/shiftedGoTo"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przejdź" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_teacher"
android:visibility="@{teacherName != null || oldTeacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeacherName}"
android:textIsSelectable="true"
android:visibility="@{oldTeacherName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teacherName}"
android:textIsSelectable="true"
android:visibility="@{teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_classroom"
android:visibility="@{classroom != null || oldClassroom != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldClassroom}"
android:textIsSelectable="true"
android:visibility="@{oldClassroom != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{classroom}"
android:textIsSelectable="true"
android:visibility="@{classroom != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_team"
android:visibility="@{teamName != null || oldTeamName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeamName}"
android:textIsSelectable="true"
android:visibility="@{oldTeamName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teamName}"
android:textIsSelectable="true"
android:visibility="@{teamName != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
android:text="@string/dialog_lesson_details_id" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@{Long.toString(lesson.id)}"
android:textIsSelectable="true"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
tools:text="12345" />
<!--<androidx.core.widget.NestedScrollView
android:id="@+id/gradeHistoryNest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{historyVisible ? View.VISIBLE : View.GONE}">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gradeHistoryList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/row_grades_list_item" />
</androidx.core.widget.NestedScrollView>-->
</LinearLayout>
</layout> </layout>

View file

@ -2,96 +2,89 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/timetableLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:visibility="gone">
<androidx.coordinatorlayout.widget.CoordinatorLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/timetableLayout" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface"
style="@style/Widget.MaterialComponents.AppBarLayout.Surface">
<com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/colorSurface_6dp"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab"
app:rtl_tabIndicatorColor="?colorPrimary"
app:rtl_tabMinWidth="90dp"
app:rtl_tabMaxWidth="300dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabPaddingBottom="12dp"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:visibility="gone"> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.appbar.AppBarLayout </androidx.coordinatorlayout.widget.CoordinatorLayout>
style="@style/Widget.MaterialComponents.AppBarLayout.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface">
<com.nshmura.recyclertablayout.RecyclerTabLayout <LinearLayout
android:id="@+id/tabLayout" android:id="@+id/timetableNotPublicLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="match_parent"
android:background="@color/colorSurface_6dp" android:orientation="vertical"
app:rtl_tabIndicatorColor="?colorPrimary" android:visibility="gone"
app:rtl_tabMaxWidth="300dp" android:gravity="center"
app:rtl_tabMinWidth="90dp" tools:visibility="visible">
app:rtl_tabPaddingBottom="12dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <ImageView
android:id="@+id/viewPager" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="match_parent" app:srcCompat="@drawable/ic_no_timetable" />
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
<LinearLayout <TextView
android:id="@+id/timetableNotPublicLayout" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="match_parent" android:layout_marginTop="16dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:text="@string/timetable_not_public_text"
android:visibility="gone" android:textSize="16sp" />
tools:visibility="visible">
<ImageView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_no_timetable" /> android:layout_marginTop="16dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
<TextView </LinearLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
<TextView </FrameLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_text"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout>
</FrameLayout>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout> </layout>

View file

@ -28,5 +28,126 @@
tools:visibility="gone"/> tools:visibility="gone"/>
</ScrollView> </ScrollView>
<LinearLayout
android:id="@+id/noLessonsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_timetable" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sunbed" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sync" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/timetable_no_timetable_week"
android:textSize="12sp"
android:textStyle="italic"/>
</LinearLayout>
</FrameLayout> </FrameLayout>
</layout> </layout>

View file

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_sunbed"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:textSize="16sp"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTop="@drawable/ic_timetable"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />

View file

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_sync"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="italic"
tools:text="@string/timetable_no_timetable_week" />
</LinearLayout>
</layout>

View file

@ -72,12 +72,6 @@
<string name="error_173" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string> <string name="error_173" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string>
<string name="error_174" translatable="false">ERROR_LIBRUS_SYNERGIA_OTHER</string> <string name="error_174" translatable="false">ERROR_LIBRUS_SYNERGIA_OTHER</string>
<string name="error_175" translatable="false">ERROR_LIBRUS_SYNERGIA_MAINTENANCE</string> <string name="error_175" translatable="false">ERROR_LIBRUS_SYNERGIA_MAINTENANCE</string>
<string name="error_176" translatable="false">ERROR_LIBRUS_MESSAGES_MAINTENANCE</string>
<string name="error_177" translatable="false">ERROR_LIBRUS_MESSAGES_ERROR</string>
<string name="error_178" translatable="false">ERROR_LIBRUS_MESSAGES_OTHER</string>
<string name="error_179" translatable="false">ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN</string>
<string name="error_180" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN</string>
<string name="error_181" translatable="false">ERROR_LIBRUS_API_MAINTENANCE</string>
<string name="error_201" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string> <string name="error_201" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string>
<string name="error_202" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string> <string name="error_202" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string>
@ -171,7 +165,7 @@
<string name="error_127_reason">Wymagana akceptacja regulaminu</string> <string name="error_127_reason">Wymagana akceptacja regulaminu</string>
<string name="error_128_reason">Błąd zmiany hasła</string> <string name="error_128_reason">Błąd zmiany hasła</string>
<string name="error_129_reason">Wymagana zmiana hasła</string> <string name="error_129_reason">Wymagana zmiana hasła</string>
<string name="error_130_reason">Librus API: nieprawidłowe dane logowania</string> <string name="error_130_reason">Nieprawidłowe dane logowania</string>
<string name="error_131_reason">Inny błąd logowania do API</string> <string name="error_131_reason">Inny błąd logowania do API</string>
<string name="error_132_reason">Brak tokenu CSRF</string> <string name="error_132_reason">Brak tokenu CSRF</string>
<string name="error_133_reason">Konto LIBRUS nie zostało aktywowane</string> <string name="error_133_reason">Konto LIBRUS nie zostało aktywowane</string>
@ -212,13 +206,7 @@
<string name="error_172_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID</string> <string name="error_172_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID</string>
<string name="error_173_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string> <string name="error_173_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string>
<string name="error_174_reason">ERROR_LIBRUS_SYNERGIA_OTHER</string> <string name="error_174_reason">ERROR_LIBRUS_SYNERGIA_OTHER</string>
<string name="error_175_reason">Librus Synergia: przerwa techniczna</string> <string name="error_175_reason">ERROR_LIBRUS_SYNERGIA_MAINTENANCE</string>
<string name="error_176_reason">Librus Wiadomości: przerwa techniczna</string>
<string name="error_177_reason">ERROR_LIBRUS_MESSAGES_ERROR</string>
<string name="error_178_reason">ERROR_LIBRUS_MESSAGES_OTHER</string>
<string name="error_179_reason">Librus Wiadomości: nieprawidłowe dane logowania</string>
<string name="error_180_reason">Librus Portal: nieprawidłowe dane logowania</string>
<string name="error_181_reason">Librus API: przerwa techniczna</string>
<string name="error_201_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string> <string name="error_201_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string>
<string name="error_202_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string> <string name="error_202_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string>

View file

@ -1026,6 +1026,4 @@
<string name="snackbar_error_text">Wystąpił błąd</string> <string name="snackbar_error_text">Wystąpił błąd</string>
<string name="dialog_sync_view_list_title">Synchronizacja ręczna</string> <string name="dialog_sync_view_list_title">Synchronizacja ręczna</string>
<string name="timetable_no_subject_name">(brak nazwy)</string> <string name="timetable_no_subject_name">(brak nazwy)</string>
<string name="dialog_event_manual_more_options">Więcej opcji</string>
<string name="edziennik_progress_endpoint_grade_comments">Pobieranie komentarzy ocen...</string>
</resources> </resources>

View file

@ -101,8 +101,6 @@
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_source_color">#A1887F</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item> <item name="timetable_lesson_shifted_target_color">#4caf50</item>
<item name="hourDividerColor">#b0b0b0</item>
<item name="halfHourDividerColor">#e0e0e0</item>
</style> </style>
<style name="AppTheme.Dark" parent="NavView.Dark"> <style name="AppTheme.Dark" parent="NavView.Dark">
<item name="colorPrimary">#64b5f6</item> <item name="colorPrimary">#64b5f6</item>
@ -133,8 +131,6 @@
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_source_color">#A1887F</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item> <item name="timetable_lesson_shifted_target_color">#4caf50</item>
<item name="hourDividerColor">#7fffffff</item>
<item name="halfHourDividerColor">#40ffffff</item>
</style> </style>

View file

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.3.50' kotlin_version = '1.3.50'
release = [ release = [
versionName: "3.9.9-dev", versionName: "3.9.6-dev",
versionCode: 3090900 versionCode: 3090600
] ]
setup = [ setup = [

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip