forked from github/szkolny
Compare commits
14 Commits
v3.9.3-dev
...
v3.9.4-dev
Author | SHA1 | Date | |
---|---|---|---|
810976d976 | |||
eb0540b5cb | |||
124437fd73 | |||
69b512e3d1 | |||
29d74e14bd | |||
f42ec8435a | |||
1052b824db | |||
0742a6a74c | |||
d4e9e1730f | |||
4eeaa54a47 | |||
5fa7409317 | |||
0bcd190714 | |||
563f08b0ab | |||
1b75424604 |
@ -166,6 +166,10 @@ dependencies {
|
||||
implementation "androidx.work:work-runtime-ktx:${versions.work}"
|
||||
|
||||
implementation 'com.hypertrack:hyperlog:0.0.10'
|
||||
|
||||
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
|
||||
|
||||
implementation 'com.linkedin.android.tachyon:tachyon:1.0.2'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
9
app/sampledata/check/ic_check.xml
Normal file
9
app/sampledata/check/ic_check.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF4caf50"
|
||||
android:pathData="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/>
|
||||
</vector>
|
@ -6,8 +6,13 @@ import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StrikethroughSpan
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.util.forEach
|
||||
import com.google.gson.JsonArray
|
||||
@ -326,4 +331,81 @@ fun String.crc32(): Long {
|
||||
return crc.value
|
||||
}
|
||||
|
||||
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
|
||||
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
|
||||
|
||||
fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
|
||||
val spannable = SpannableString(this)
|
||||
spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
fun CharSequence?.asStrikethroughSpannable(): Spannable {
|
||||
val spannable = SpannableString(this)
|
||||
spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new read-only list only of those given elements, that are not empty.
|
||||
* Applies for CharSequence and descendants.
|
||||
*/
|
||||
fun <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
|
||||
|
||||
fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
|
||||
if (this.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (this.size == 1) {
|
||||
return this[0]
|
||||
}
|
||||
|
||||
var spanned = false
|
||||
for (piece in this) {
|
||||
if (piece is Spanned) {
|
||||
spanned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var first = true
|
||||
if (spanned) {
|
||||
val ssb = SpannableStringBuilder()
|
||||
for (piece in this) {
|
||||
if (!first && delimiter != null)
|
||||
ssb.append(delimiter)
|
||||
first = false
|
||||
ssb.append(piece)
|
||||
}
|
||||
return SpannedString(ssb)
|
||||
} else {
|
||||
val sb = StringBuilder()
|
||||
for (piece in this) {
|
||||
if (!first && delimiter != null)
|
||||
sb.append(delimiter)
|
||||
first = false
|
||||
sb.append(piece)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
|
||||
text = context.getString(resid, *formatArgs)
|
||||
}
|
||||
|
||||
fun JsonObject(vararg properties: Pair<String, Any>): JsonObject {
|
||||
return JsonObject().apply {
|
||||
for (property in properties) {
|
||||
when (property.second) {
|
||||
is JsonElement -> add(property.first, property.second as JsonElement)
|
||||
is String -> addProperty(property.first, property.second as String)
|
||||
is Char -> addProperty(property.first, property.second as Char)
|
||||
is Number -> addProperty(property.first, property.second as Number)
|
||||
is Boolean -> addProperty(property.first, property.second as Boolean)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
|
||||
fun JsonArray.isEmpty(): Boolean = this.size() == 0
|
@ -62,7 +62,7 @@ import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
|
||||
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
@ -14,6 +14,14 @@ val SYSTEM_USER_AGENT = System.getProperty("http.agent") ?: "Dalvik/2.1.0 Androi
|
||||
|
||||
val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT"
|
||||
|
||||
const val FAKE_LIBRUS_API = "http://librus.szkolny.eu/api"
|
||||
const val FAKE_LIBRUS_PORTAL = "http://librus.szkolny.eu"
|
||||
const val FAKE_LIBRUS_AUTHORIZE = "http://librus.szkolny.eu/authorize.php"
|
||||
const val FAKE_LIBRUS_LOGIN = "http://librus.szkolny.eu/login_action.php"
|
||||
const val FAKE_LIBRUS_TOKEN = "http://librus.szkolny.eu/access_token.php"
|
||||
const val FAKE_LIBRUS_ACCOUNT = "/synergia_accounts_fresh.php?login="
|
||||
const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
|
||||
|
||||
val LIBRUS_USER_AGENT = "$SYSTEM_USER_AGENT LibrusMobileApp"
|
||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"
|
||||
|
@ -66,13 +66,13 @@ val librusLoginMethods = listOf(
|
||||
},
|
||||
|
||||
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
|
||||
.withIsPossible { _, _ -> true }
|
||||
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
|
||||
.withRequiredLoginMethod { profile, _ ->
|
||||
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
|
||||
},
|
||||
|
||||
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java)
|
||||
.withIsPossible { _, _ -> true }
|
||||
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
|
||||
.withRequiredLoginMethod { profile, _ ->
|
||||
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package pl.szczodrzynski.edziennik.api.v2.events.task
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.api.v2.*
|
||||
@ -18,7 +19,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
|
||||
fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore))
|
||||
fun sync() = EdziennikTask(-1, SyncRequest())
|
||||
fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds))
|
||||
fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, arguments))
|
||||
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
|
||||
fun messageGet(profileId: Int, messageId: Int) = EdziennikTask(profileId, MessageGetRequest(messageId))
|
||||
fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest())
|
||||
@ -50,7 +51,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
|
||||
private var edziennikInterface: EdziennikInterface? = null
|
||||
|
||||
fun run(app: App, taskCallback: EdziennikCallback) {
|
||||
internal fun run(app: App, taskCallback: EdziennikCallback) {
|
||||
edziennikInterface = when (loginStore.type) {
|
||||
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
|
||||
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
|
||||
@ -66,7 +67,8 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
when (request) {
|
||||
is SyncProfileRequest -> edziennikInterface?.sync(
|
||||
featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } ?: Features.getAllIds(),
|
||||
viewId = request.viewIds?.get(0)?.first)
|
||||
viewId = request.viewIds?.get(0)?.first,
|
||||
arguments = request.arguments)
|
||||
is MessageGetRequest -> edziennikInterface?.getMessage(request.messageId)
|
||||
is FirstLoginRequest -> edziennikInterface?.firstLogin()
|
||||
is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead()
|
||||
@ -83,7 +85,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
||||
|
||||
data class FirstLoginRequest(val loginStore: LoginStore)
|
||||
class SyncRequest
|
||||
data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null)
|
||||
data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null, val arguments: JsonObject? = null)
|
||||
data class SyncProfileListRequest(val profileList: List<Int>)
|
||||
data class MessageGetRequest(val messageId: Int)
|
||||
class AnnouncementsReadRequest
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.idziennik
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
|
||||
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikData
|
||||
@ -48,7 +49,8 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId)
|
||||
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
|
||||
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.interfaces
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
interface EdziennikInterface {
|
||||
fun sync(featureIds: List<Int>, viewId: Int? = null)
|
||||
fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
|
||||
fun getMessage(messageId: Int)
|
||||
fun markAllAnnouncementsAsRead()
|
||||
fun firstLogin()
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.librus
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
@ -49,7 +50,8 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId)
|
||||
login()
|
||||
}
|
||||
@ -173,7 +175,10 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
login()
|
||||
}
|
||||
// TODO PORTAL CAPTCHA
|
||||
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC,
|
||||
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> {
|
||||
loginStore.putLoginData("timetableNotPublic", true)
|
||||
data()
|
||||
}
|
||||
ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE,
|
||||
ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> {
|
||||
data()
|
||||
|
@ -28,7 +28,7 @@ open class LibrusApi(open val data: DataLibrus) {
|
||||
|
||||
fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject) -> Unit) {
|
||||
|
||||
d(tag, "Request: Librus/Api - $LIBRUS_API_URL/$endpoint")
|
||||
d(tag, "Request: Librus/Api - ${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
|
||||
|
||||
val callback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
@ -90,7 +90,7 @@ open class LibrusApi(open val data: DataLibrus) {
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url("$LIBRUS_API_URL/$endpoint")
|
||||
.url("${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.addHeader("Authorization", "Bearer ${data.apiAccessToken}")
|
||||
.apply {
|
||||
|
@ -76,7 +76,10 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
LibrusApiClassrooms(data, onSuccess)
|
||||
}
|
||||
// TODO push config
|
||||
// TODO timetable
|
||||
ENDPOINT_LIBRUS_API_TIMETABLES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||
LibrusApiTimetables(data, onSuccess)
|
||||
}
|
||||
|
||||
ENDPOINT_LIBRUS_API_NORMAL_GRADES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_grades)
|
||||
|
@ -24,7 +24,7 @@ open class LibrusPortal(open val data: DataLibrus) {
|
||||
|
||||
fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) {
|
||||
|
||||
d(tag, "Request: Librus/Portal - $LIBRUS_PORTAL_URL$endpoint")
|
||||
d(tag, "Request: Librus/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL}$endpoint")
|
||||
|
||||
val callback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
@ -81,7 +81,7 @@ open class LibrusPortal(open val data: DataLibrus) {
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url(LIBRUS_PORTAL_URL + endpoint)
|
||||
.url((if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL) + endpoint)
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.addHeader("Authorization", "Bearer ${data.portalAccessToken}")
|
||||
.apply {
|
||||
|
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2019-11-10.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
|
||||
|
||||
import androidx.core.util.isEmpty
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TIMETABLES
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
|
||||
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.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class LibrusApiTimetables(override val data: DataLibrus,
|
||||
val onSuccess: () -> Unit) : LibrusApi(data) {
|
||||
companion object {
|
||||
const val TAG = "LibrusApiTimetables"
|
||||
}
|
||||
|
||||
init {
|
||||
if (data.classrooms.isEmpty()) {
|
||||
data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id }
|
||||
}
|
||||
|
||||
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
|
||||
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
|
||||
apiGet(TAG, "Timetables?weekStart=$getDate") { json ->
|
||||
val days = json.getJsonObject("Timetable")
|
||||
|
||||
days?.entrySet()?.forEach { (dateString, dayEl) ->
|
||||
val day = dayEl?.asJsonArray
|
||||
|
||||
val lessonDate = dateString?.let { Date.fromY_m_d(it) } ?: return@forEach
|
||||
|
||||
var lessonsFound = false
|
||||
day?.forEach { lessonRangeEl ->
|
||||
val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList()
|
||||
if (lessonRange?.isNullOrEmpty() == false)
|
||||
lessonsFound = true
|
||||
lessonRange?.forEachIndexed { index, lesson ->
|
||||
parseLesson(lessonDate, lesson)
|
||||
}
|
||||
}
|
||||
|
||||
if (day.isNullOrEmpty() || !lessonsFound) {
|
||||
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply {
|
||||
type = Lesson.TYPE_NO_LESSONS
|
||||
date = lessonDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val weekStart = Date.fromY_m_d(getDate)
|
||||
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
|
||||
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
|
||||
|
||||
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
|
||||
data.setSyncNext(ENDPOINT_LIBRUS_API_TIMETABLES, SYNC_ALWAYS)
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseLesson(lessonDate: Date, lesson: JsonObject) {
|
||||
val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false
|
||||
val isCancelled = lesson.getBoolean("IsCanceled") ?: false
|
||||
|
||||
val lessonNo = lesson.getInt("LessonNo") ?: return
|
||||
val startTime = lesson.getString("HourFrom")?.let { Time.fromH_m(it) } ?: return
|
||||
val endTime = lesson.getString("HourTo")?.let { Time.fromH_m(it) } ?: return
|
||||
val subjectId = lesson.getJsonObject("Subject")?.getLong("Id")
|
||||
val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id")
|
||||
val classroomId = lesson.getJsonObject("Classroom")?.getLong("Id") ?: -1
|
||||
val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id")
|
||||
val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId
|
||||
|
||||
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
|
||||
val lessonObject = Lesson(profileId, id)
|
||||
|
||||
if (isSubstitution && isCancelled) {
|
||||
// shifted lesson - source
|
||||
val newDate = lesson.getString("NewDate")?.let { Date.fromY_m_d(it) } ?: return
|
||||
val newLessonNo = lesson.getInt("NewLessonNo") ?: return
|
||||
val newStartTime = lesson.getString("NewHourFrom")?.let { Time.fromH_m(it) } ?: return
|
||||
val newEndTime = lesson.getString("NewHourTo")?.let { Time.fromH_m(it) } ?: return
|
||||
val newSubjectId = lesson.getJsonObject("NewSubject")?.getLong("Id")
|
||||
val newTeacherId = lesson.getJsonObject("NewTeacher")?.getLong("Id")
|
||||
val newClassroomId = lesson.getJsonObject("NewClassroom")?.getLong("Id") ?: -1
|
||||
val newVirtualClassId = lesson.getJsonObject("NewVirtualClass")?.getLong("Id")
|
||||
val newTeamId = lesson.getJsonObject("NewClass")?.getLong("Id") ?: newVirtualClassId
|
||||
|
||||
lessonObject.let {
|
||||
it.type = Lesson.TYPE_SHIFTED_SOURCE
|
||||
it.oldDate = lessonDate
|
||||
it.oldLessonNumber = lessonNo
|
||||
it.oldStartTime = startTime
|
||||
it.oldEndTime = endTime
|
||||
it.oldSubjectId = subjectId
|
||||
it.oldTeacherId = teacherId
|
||||
it.oldTeamId = teamId
|
||||
it.oldClassroom = data.classrooms[classroomId]?.name
|
||||
|
||||
it.date = newDate
|
||||
it.lessonNumber = newLessonNo
|
||||
it.startTime = newStartTime
|
||||
it.endTime = newEndTime
|
||||
it.subjectId = newSubjectId
|
||||
it.teacherId = newTeacherId
|
||||
it.teamId = newTeamId
|
||||
it.classroom = data.classrooms[newClassroomId]?.name
|
||||
}
|
||||
}
|
||||
else if (isSubstitution) {
|
||||
// lesson change OR shifted lesson - target
|
||||
val oldDate = lesson.getString("OrgDate")?.let { Date.fromY_m_d(it) } ?: return
|
||||
val oldLessonNo = lesson.getInt("OrgLessonNo") ?: return
|
||||
val oldStartTime = lesson.getString("OrgHourFrom")?.let { Time.fromH_m(it) } ?: return
|
||||
val oldEndTime = lesson.getString("OrgHourTo")?.let { Time.fromH_m(it) } ?: return
|
||||
val oldSubjectId = lesson.getJsonObject("OrgSubject")?.getLong("Id")
|
||||
val oldTeacherId = lesson.getJsonObject("OrgTeacher")?.getLong("Id")
|
||||
val oldClassroomId = lesson.getJsonObject("OrgClassroom")?.getLong("Id") ?: -1
|
||||
val oldVirtualClassId = lesson.getJsonObject("OrgVirtualClass")?.getLong("Id")
|
||||
val oldTeamId = lesson.getJsonObject("OrgClass")?.getLong("Id") ?: oldVirtualClassId
|
||||
|
||||
lessonObject.let {
|
||||
it.type = if (lessonDate == oldDate && lessonNo == oldLessonNo) Lesson.TYPE_CHANGE else Lesson.TYPE_SHIFTED_TARGET
|
||||
it.oldDate = oldDate
|
||||
it.oldLessonNumber = oldLessonNo
|
||||
it.oldStartTime = oldStartTime
|
||||
it.oldEndTime = oldEndTime
|
||||
it.oldSubjectId = oldSubjectId
|
||||
it.oldTeacherId = oldTeacherId
|
||||
it.oldTeamId = oldTeamId
|
||||
it.oldClassroom = data.classrooms[oldClassroomId]?.name
|
||||
|
||||
it.date = lessonDate
|
||||
it.lessonNumber = lessonNo
|
||||
it.startTime = startTime
|
||||
it.endTime = endTime
|
||||
it.subjectId = subjectId
|
||||
it.teacherId = teacherId
|
||||
it.teamId = teamId
|
||||
it.classroom = data.classrooms[classroomId]?.name
|
||||
}
|
||||
}
|
||||
else if (isCancelled) {
|
||||
lessonObject.let {
|
||||
it.type = Lesson.TYPE_CANCELLED
|
||||
it.oldDate = lessonDate
|
||||
it.oldLessonNumber = lessonNo
|
||||
it.oldStartTime = startTime
|
||||
it.oldEndTime = endTime
|
||||
it.oldSubjectId = subjectId
|
||||
it.oldTeacherId = teacherId
|
||||
it.oldTeamId = teamId
|
||||
it.oldClassroom = data.classrooms[classroomId]?.name
|
||||
}
|
||||
}
|
||||
else {
|
||||
lessonObject.let {
|
||||
it.type = Lesson.TYPE_NORMAL
|
||||
it.date = lessonDate
|
||||
it.lessonNumber = lessonNo
|
||||
it.startTime = startTime
|
||||
it.endTime = endTime
|
||||
it.subjectId = subjectId
|
||||
it.teacherId = teacherId
|
||||
it.teamId = teamId
|
||||
it.classroom = data.classrooms[classroomId]?.name
|
||||
}
|
||||
}
|
||||
|
||||
if (lessonObject.type != Lesson.TYPE_NORMAL) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
lessonObject.id,
|
||||
data.profile?.empty ?: false,
|
||||
data.profile?.empty ?: false,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
data.lessonNewList += lessonObject
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package pl.szczodrzynski.edziennik.api.v2.librus.firstlogin
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.ERROR_NO_STUDENTS_IN_ACCOUNT
|
||||
import pl.szczodrzynski.edziennik.api.v2.FAKE_LIBRUS_ACCOUNTS
|
||||
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNTS_URL
|
||||
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL
|
||||
import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent
|
||||
@ -29,7 +30,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) {
|
||||
// email login: use Portal for account list
|
||||
LibrusLoginPortal(data) {
|
||||
portal.portalGet(TAG, LIBRUS_ACCOUNTS_URL) { json, response ->
|
||||
portal.portalGet(TAG, if (data.fakeLogin) FAKE_LIBRUS_ACCOUNTS else LIBRUS_ACCOUNTS_URL) { json, response ->
|
||||
val accounts = json.getJsonArray("accounts")
|
||||
|
||||
if (accounts == null || accounts.size() < 1) {
|
||||
|
@ -7,14 +7,15 @@ import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.body.MediaTypeUtils
|
||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.*
|
||||
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.getInt
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.getUnixDate
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
|
||||
import java.util.ArrayList
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
@ -42,7 +43,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
}
|
||||
else {
|
||||
data.app.cookieJar.clearForDomain("portal.librus.pl")
|
||||
authorize(LIBRUS_AUTHORIZE_URL)
|
||||
authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
|
||||
}
|
||||
}}
|
||||
|
||||
@ -86,10 +87,10 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
}
|
||||
|
||||
private fun login(csrfToken: String) {
|
||||
d(TAG, "Request: Librus/Login/Portal - $LIBRUS_LOGIN_URL")
|
||||
d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}")
|
||||
|
||||
Request.builder()
|
||||
.url(LIBRUS_LOGIN_URL)
|
||||
.url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL)
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.addParameter("email", data.portalEmail)
|
||||
.addParameter("password", data.portalPassword)
|
||||
@ -135,7 +136,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
|
||||
private var refreshTokenFailed = false
|
||||
private fun accessToken(code: String?, refreshToken: String?) {
|
||||
d(TAG, "Request: Librus/Login/Portal - $LIBRUS_TOKEN_URL")
|
||||
d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL}")
|
||||
|
||||
val onSuccess = { json: JsonObject, response: Response? ->
|
||||
data.portalAccessToken = json.getString("access_token")
|
||||
@ -204,7 +205,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
}
|
||||
|
||||
Request.builder()
|
||||
.url(LIBRUS_TOKEN_URL)
|
||||
.url(if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL)
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.addParams(params)
|
||||
.post()
|
||||
|
@ -43,7 +43,7 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () ->
|
||||
val accountLogin = data.apiLogin ?: return false
|
||||
data.portalAccessToken ?: return false
|
||||
|
||||
d(TAG, "Request: Librus/SynergiaTokenExtractor - $LIBRUS_ACCOUNT_URL$accountLogin")
|
||||
d(TAG, "Request: Librus/SynergiaTokenExtractor - ${if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL}$accountLogin")
|
||||
|
||||
val onSuccess = { json: JsonObject, response: Response? ->
|
||||
// synergiaAccount is executed when a synergia token needs a refresh
|
||||
@ -67,7 +67,7 @@ class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () ->
|
||||
}
|
||||
}
|
||||
|
||||
portalGet(TAG, LIBRUS_ACCOUNT_URL+accountLogin, onSuccess = onSuccess)
|
||||
portalGet(TAG, (if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL)+accountLogin, onSuccess = onSuccess)
|
||||
return true
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.mobidziennik
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
@ -48,7 +49,8 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId)
|
||||
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
|
||||
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
|
||||
|
@ -21,10 +21,10 @@ const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
|
||||
|
||||
val MobidziennikFeatures = listOf(
|
||||
// always synced
|
||||
/*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf(
|
||||
Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf(
|
||||
ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB,
|
||||
ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB
|
||||
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)),*/
|
||||
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), // TODO divide features into separate view IDs (all with API_MAIN)
|
||||
|
||||
// push config
|
||||
/*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf(
|
||||
|
@ -4,16 +4,102 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
|
||||
|
||||
import pl.szczodrzynski.edziennik.App.profileId
|
||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange
|
||||
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||
import pl.szczodrzynski.edziennik.fixName
|
||||
import pl.szczodrzynski.edziennik.singleOrNull
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
init {
|
||||
for (lessonStr in rows) {
|
||||
val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") }
|
||||
|
||||
val dataStart = Date.getToday()
|
||||
val dataEnd = dataStart.clone().stepForward(0, 0, 7 + (6 - dataStart.weekDay))
|
||||
|
||||
data.toRemove.add(DataRemoveModel.Timetable.between(dataStart.clone(), dataEnd))
|
||||
|
||||
val dataDays = mutableListOf<Int>()
|
||||
while (dataStart <= dataEnd) {
|
||||
dataDays += dataStart.value
|
||||
dataStart.stepForward(0, 0, 1)
|
||||
}
|
||||
|
||||
for (lesson in lessons) {
|
||||
val date = Date.fromYmd(lesson[2])
|
||||
val startTime = Time.fromYmdHm(lesson[3])
|
||||
val endTime = Time.fromYmdHm(lesson[4])
|
||||
val id = date.combineWith(startTime) / 6L * 10L + (lesson.joinToString("|").hashCode() and 0xFFFF)
|
||||
|
||||
dataDays.remove(date.value)
|
||||
|
||||
val subjectId = data.subjectList.singleOrNull { it.longName == lesson[5] }?.id ?: -1
|
||||
val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.id ?: -1
|
||||
val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1
|
||||
val classroom = lesson[11]
|
||||
|
||||
Lesson(data.profileId, id).also {
|
||||
when (lesson[1]) {
|
||||
"plan_lekcji", "lekcja" -> {
|
||||
it.type = Lesson.TYPE_NORMAL
|
||||
it.date = date
|
||||
it.startTime = startTime
|
||||
it.endTime = endTime
|
||||
it.subjectId = subjectId
|
||||
it.teacherId = teacherId
|
||||
it.teamId = teamId
|
||||
it.classroom = classroom
|
||||
}
|
||||
"lekcja_odwolana" -> {
|
||||
it.type = Lesson.TYPE_CANCELLED
|
||||
it.date = date
|
||||
it.startTime = startTime
|
||||
it.endTime = endTime
|
||||
it.oldSubjectId = subjectId
|
||||
//it.oldTeacherId = teacherId
|
||||
it.oldTeamId = teamId
|
||||
//it.oldClassroom = classroom
|
||||
}
|
||||
"zastepstwo" -> {
|
||||
it.type = Lesson.TYPE_CHANGE
|
||||
it.date = date
|
||||
it.startTime = startTime
|
||||
it.endTime = endTime
|
||||
it.subjectId = subjectId
|
||||
it.teacherId = teacherId
|
||||
it.teamId = teamId
|
||||
it.classroom = classroom
|
||||
}
|
||||
}
|
||||
|
||||
if (it.type != Lesson.TYPE_NORMAL) {
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
data.profileId,
|
||||
Metadata.TYPE_LESSON_CHANGE,
|
||||
it.id,
|
||||
data.profile?.empty ?: false,
|
||||
data.profile?.empty ?: false,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
data.lessonNewList += it
|
||||
}
|
||||
}
|
||||
|
||||
for (day in dataDays) {
|
||||
val lessonDate = Date.fromValue(day)
|
||||
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply {
|
||||
type = Lesson.TYPE_NO_LESSONS
|
||||
date = lessonDate
|
||||
}
|
||||
}
|
||||
|
||||
/*for (lessonStr in rows) {
|
||||
if (lessonStr.isNotEmpty()) {
|
||||
val lesson = lessonStr.split("|")
|
||||
|
||||
@ -76,9 +162,9 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
if (originalLesson == null) {
|
||||
// original lesson doesn't exist, save a new addition
|
||||
// TODO
|
||||
/*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
|
||||
*//*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
|
||||
app.profile.timetable.addLessonAddition(registerLessonChange);
|
||||
}*/
|
||||
}*//*
|
||||
} else {
|
||||
// original lesson exists, so we need to compare them
|
||||
if (!lessonChange.matches(originalLesson)) {
|
||||
@ -108,6 +194,6 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
@ -60,6 +60,8 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
val profileId
|
||||
get() = profile?.id ?: -1
|
||||
|
||||
var arguments: JsonObject? = null
|
||||
|
||||
/**
|
||||
* A callback passed to all [Feature]s and [LoginMethod]s
|
||||
*/
|
||||
@ -133,23 +135,20 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
mTeamClass = value
|
||||
}
|
||||
|
||||
var lessonsToRemove: DataRemoveModel? = null
|
||||
var toRemove = mutableListOf<DataRemoveModel>()
|
||||
|
||||
val lessonList = mutableListOf<Lesson>()
|
||||
val lessonChangeList = mutableListOf<LessonChange>()
|
||||
val lessonNewList = mutableListOf<pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson>()
|
||||
|
||||
var gradesToRemove: DataRemoveModel? = null
|
||||
val gradeList = mutableListOf<Grade>()
|
||||
|
||||
var eventsToRemove: DataRemoveModel? = null
|
||||
val eventList = mutableListOf<Event>()
|
||||
|
||||
var noticesToRemove: DataRemoveModel? = null
|
||||
val noticeList = mutableListOf<Notice>()
|
||||
|
||||
var attendancesToRemove: DataRemoveModel? = null
|
||||
val attendanceList = mutableListOf<Attendance>()
|
||||
|
||||
var announcementsToRemove: DataRemoveModel? = null
|
||||
val announcementList = mutableListOf<Announcement>()
|
||||
|
||||
val luckyNumberList = mutableListOf<LuckyNumber>()
|
||||
@ -166,6 +165,9 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
val db: AppDb by lazy { app.db }
|
||||
|
||||
init {
|
||||
if (App.devMode) {
|
||||
fakeLogin = loginStore.hasLoginData("fakeLogin")
|
||||
}
|
||||
clear()
|
||||
if (profile != null) {
|
||||
endpointTimers = db.endpointTimerDao().getAllNow(profile.id).toMutableList()
|
||||
@ -180,6 +182,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
fun clear() {
|
||||
loginMethods.clear()
|
||||
|
||||
toRemove.clear()
|
||||
endpointTimers.clear()
|
||||
teacherList.clear()
|
||||
subjectList.clear()
|
||||
@ -195,6 +198,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
|
||||
lessonList.clear()
|
||||
lessonChangeList.clear()
|
||||
lessonNewList.clear()
|
||||
gradeList.clear()
|
||||
noticeList.clear()
|
||||
attendanceList.clear()
|
||||
@ -248,6 +252,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
app.profile.loginStoreData = loginStore.data
|
||||
}
|
||||
|
||||
// always present and not empty, during every sync
|
||||
db.endpointTimerDao().addAll(endpointTimers)
|
||||
db.teacherDao().clear(profileId)
|
||||
db.teacherDao().addAll(teacherList.values())
|
||||
@ -260,6 +265,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
db.gradeCategoryDao().clear(profileId)
|
||||
db.gradeCategoryDao().addAll(gradeCategories.values())
|
||||
|
||||
// may be empty - extracted from DB on demand, by an endpoint
|
||||
if (classrooms.size > 0)
|
||||
db.classroomDao().addAll(classrooms.values())
|
||||
if (attendanceTypes.size > 0)
|
||||
@ -271,17 +277,24 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
||||
if (teacherAbsenceTypes.size > 0)
|
||||
db.teacherAbsenceTypeDao().addAll(teacherAbsenceTypes.values())
|
||||
|
||||
gradesToRemove?.let { it ->
|
||||
it.removeAll?.let { _ -> db.gradeDao().clear(profileId) }
|
||||
it.removeSemester?.let { semester -> db.gradeDao().clearForSemester(profileId, semester) }
|
||||
// clear DB with DataRemoveModels added by endpoints
|
||||
for (model in toRemove) {
|
||||
when (model) {
|
||||
is DataRemoveModel.Timetable -> model.commit(profileId, db.timetableDao())
|
||||
is DataRemoveModel.Grades -> model.commit(profileId, db.gradeDao())
|
||||
}
|
||||
}
|
||||
|
||||
// not extracted from DB - always new data
|
||||
if (lessonList.isNotEmpty()) {
|
||||
db.lessonDao().clear(profile.id)
|
||||
db.lessonDao().addAll(lessonList)
|
||||
}
|
||||
if (lessonChangeList.isNotEmpty())
|
||||
db.lessonChangeDao().addAll(lessonChangeList)
|
||||
if (lessonNewList.isNotEmpty()) {
|
||||
db.timetableDao() += lessonNewList
|
||||
}
|
||||
if (gradeList.isNotEmpty()) {
|
||||
db.gradeDao().addAll(gradeList)
|
||||
}
|
||||
|
@ -4,28 +4,37 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.models
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeDao
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class DataRemoveModel {
|
||||
var removeAll: Boolean? = null
|
||||
var removeSemester: Int? = null
|
||||
var removeDateFrom: Date? = null
|
||||
var removeDateTo: Date? = null
|
||||
|
||||
constructor() {
|
||||
this.removeAll = true
|
||||
open class DataRemoveModel {
|
||||
class Timetable(private val dateFrom: Date?, private val dateTo: Date?) : DataRemoveModel() {
|
||||
companion object {
|
||||
fun from(dateFrom: Date) = Timetable(dateFrom, null)
|
||||
fun to(dateTo: Date) = Timetable(null, dateTo)
|
||||
fun between(dateFrom: Date, dateTo: Date) = Timetable(dateFrom, dateTo)
|
||||
}
|
||||
fun commit(profileId: Int, dao: TimetableDao) {
|
||||
if (dateFrom != null && dateTo != null) {
|
||||
dao.clearBetweenDates(profileId, dateFrom, dateTo)
|
||||
}
|
||||
else {
|
||||
dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) }
|
||||
dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(semester: Int) {
|
||||
this.removeSemester = semester
|
||||
}
|
||||
|
||||
constructor(dateFrom: Date?, dateTo: Date) {
|
||||
this.removeDateFrom = dateFrom
|
||||
this.removeDateTo = dateTo
|
||||
}
|
||||
|
||||
constructor(dateFrom: Date) {
|
||||
this.removeDateFrom = dateFrom
|
||||
class Grades(val all: Boolean, val semester: Int?) : DataRemoveModel() {
|
||||
companion object {
|
||||
fun all() = Grades(true, null)
|
||||
fun semester(semester: Int) = Grades(false, semester)
|
||||
}
|
||||
fun commit(profileId: Int, dao: GradeDao) {
|
||||
if (all) {
|
||||
dao.clear(profileId)
|
||||
}
|
||||
semester?.let { dao.clearForSemester(profileId, it) }
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.template
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
@ -48,7 +49,8 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(templateLoginMethods, TemplateFeatures, featureIds, viewId)
|
||||
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
|
||||
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.api.v2.vulcan
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
|
||||
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
|
||||
@ -48,7 +49,8 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?) {
|
||||
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
|
||||
data.arguments = arguments
|
||||
data.prepare(vulcanLoginMethods, VulcanFeatures, featureIds, viewId)
|
||||
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
|
||||
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
|
||||
|
@ -72,6 +72,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceTypeDao
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherDao;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.TeamDao;
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao;
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||
|
||||
@Database(entities = {
|
||||
@ -103,7 +104,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||
Classroom.class,
|
||||
NoticeType.class,
|
||||
AttendanceType.class,
|
||||
Metadata.class}, version = 63)
|
||||
pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
|
||||
Metadata.class}, version = 64)
|
||||
@TypeConverters({
|
||||
ConverterTime.class,
|
||||
ConverterDate.class,
|
||||
@ -141,6 +143,7 @@ public abstract class AppDb extends RoomDatabase {
|
||||
public abstract ClassroomDao classroomDao();
|
||||
public abstract NoticeTypeDao noticeTypeDao();
|
||||
public abstract AttendanceTypeDao attendanceTypeDao();
|
||||
public abstract TimetableDao timetableDao();
|
||||
public abstract MetadataDao metadataDao();
|
||||
|
||||
private static volatile AppDb INSTANCE;
|
||||
@ -729,6 +732,37 @@ public abstract class AppDb extends RoomDatabase {
|
||||
database.execSQL("ALTER TABLE profiles ADD COLUMN studentSchoolYear TEXT DEFAULT NULL");
|
||||
}
|
||||
};
|
||||
private static final Migration MIGRATION_63_64 = new Migration(63, 64) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
//database.execSQL("ALTER TABLE lessons RENAME TO lessonsOld;");
|
||||
database.execSQL("CREATE TABLE timetable (" +
|
||||
"profileId INTEGER NOT NULL," +
|
||||
"id INTEGER NOT NULL," +
|
||||
"type INTEGER NOT NULL," +
|
||||
|
||||
"date TEXT DEFAULT NULL," +
|
||||
"lessonNumber INTEGER DEFAULT NULL," +
|
||||
"startTime TEXT DEFAULT NULL," +
|
||||
"endTime TEXT DEFAULT NULL," +
|
||||
"subjectId INTEGER DEFAULT NULL," +
|
||||
"teacherId INTEGER DEFAULT NULL," +
|
||||
"teamId INTEGER DEFAULT NULL," +
|
||||
"classroom TEXT DEFAULT NULL," +
|
||||
|
||||
"oldDate TEXT DEFAULT NULL," +
|
||||
"oldLessonNumber INTEGER DEFAULT NULL," +
|
||||
"oldStartTime TEXT DEFAULT NULL," +
|
||||
"oldEndTime TEXT DEFAULT NULL," +
|
||||
"oldSubjectId INTEGER DEFAULT NULL," +
|
||||
"oldTeacherId INTEGER DEFAULT NULL," +
|
||||
"oldTeamId INTEGER DEFAULT NULL," +
|
||||
"oldClassroom TEXT DEFAULT NULL," +
|
||||
"PRIMARY KEY(id));");
|
||||
database.execSQL("CREATE INDEX index_lessons_profileId_type_date ON timetable (profileId, type, date);");
|
||||
database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static AppDb getDatabase(final Context context) {
|
||||
@ -789,7 +823,8 @@ public abstract class AppDb extends RoomDatabase {
|
||||
MIGRATION_59_60,
|
||||
MIGRATION_60_61,
|
||||
MIGRATION_61_62,
|
||||
MIGRATION_62_63
|
||||
MIGRATION_62_63,
|
||||
MIGRATION_63_64
|
||||
)
|
||||
.allowMainThreadQueries()
|
||||
//.fallbackToDestructiveMigration()
|
||||
|
@ -4,12 +4,20 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.db.modules.timetable
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
open class Lesson(val profileId: Int) {
|
||||
@Entity(tableName = "timetable",
|
||||
indices = [
|
||||
Index(value = ["profileId", "type", "date"]),
|
||||
Index(value = ["profileId", "type", "oldDate"])
|
||||
])
|
||||
open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
|
||||
companion object {
|
||||
const val TYPE_NO_LESSONS = -1
|
||||
const val TYPE_NORMAL = 0
|
||||
const val TYPE_CANCELLED = 1
|
||||
const val TYPE_CHANGE = 2
|
||||
@ -17,15 +25,14 @@ open class Lesson(val profileId: Int) {
|
||||
const val TYPE_SHIFTED_TARGET = 4 /* target lesson */
|
||||
}
|
||||
|
||||
@ColumnInfo(name = "lessonType")
|
||||
var type: Int = TYPE_NORMAL
|
||||
|
||||
var date: Date? = null
|
||||
var lessonNumber: Int? = null
|
||||
var startTime: Time? = null
|
||||
var endTime: Time? = null
|
||||
var teacherId: Long? = null
|
||||
var subjectId: Long? = null
|
||||
var teacherId: Long? = null
|
||||
var teamId: Long? = null
|
||||
var classroom: String? = null
|
||||
|
||||
@ -33,8 +40,58 @@ open class Lesson(val profileId: Int) {
|
||||
var oldLessonNumber: Int? = null
|
||||
var oldStartTime: Time? = null
|
||||
var oldEndTime: Time? = null
|
||||
var oldTeacherId: Long? = null
|
||||
var oldSubjectId: Long? = null
|
||||
var oldTeacherId: Long? = null
|
||||
var oldTeamId: Long? = null
|
||||
var oldClassroom: String? = null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Lesson(profileId=$profileId, " +
|
||||
"id=$id, " +
|
||||
"type=$type, " +
|
||||
"date=$date, " +
|
||||
"lessonNumber=$lessonNumber, " +
|
||||
"startTime=$startTime, " +
|
||||
"endTime=$endTime, " +
|
||||
"subjectId=$subjectId, " +
|
||||
"teacherId=$teacherId, " +
|
||||
"teamId=$teamId, " +
|
||||
"classroom=$classroom, " +
|
||||
"oldDate=$oldDate, " +
|
||||
"oldLessonNumber=$oldLessonNumber, " +
|
||||
"oldStartTime=$oldStartTime, " +
|
||||
"oldEndTime=$oldEndTime, " +
|
||||
"oldSubjectId=$oldSubjectId, " +
|
||||
"oldTeacherId=$oldTeacherId, " +
|
||||
"oldTeamId=$oldTeamId, " +
|
||||
"oldClassroom=$oldClassroom)"
|
||||
}
|
||||
}
|
||||
/*
|
||||
DROP TABLE lessons;
|
||||
DROP TABLE lessonChanges;
|
||||
CREATE TABLE lessons (
|
||||
profileId INTEGER NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
|
||||
date TEXT DEFAULT NULL,
|
||||
lessonNumber INTEGER DEFAULT NULL,
|
||||
startTime TEXT DEFAULT NULL,
|
||||
endTime TEXT DEFAULT NULL,
|
||||
teacherId INTEGER DEFAULT NULL,
|
||||
subjectId INTEGER DEFAULT NULL,
|
||||
teamId INTEGER DEFAULT NULL,
|
||||
classroom TEXT DEFAULT NULL,
|
||||
|
||||
oldDate TEXT DEFAULT NULL,
|
||||
oldLessonNumber INTEGER DEFAULT NULL,
|
||||
oldStartTime TEXT DEFAULT NULL,
|
||||
oldEndTime TEXT DEFAULT NULL,
|
||||
oldTeacherId INTEGER DEFAULT NULL,
|
||||
oldSubjectId INTEGER DEFAULT NULL,
|
||||
oldTeamId INTEGER DEFAULT NULL,
|
||||
oldClassroom TEXT DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY(profileId)
|
||||
);
|
||||
*/
|
@ -0,0 +1,69 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.modules.timetable
|
||||
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
|
||||
var subjectName: String? = null
|
||||
var teacherName: String? = null
|
||||
var teamName: String? = null
|
||||
var oldSubjectName: String? = null
|
||||
var oldTeacherName: String? = null
|
||||
var oldTeamName: String? = null
|
||||
|
||||
val displayDate: Date?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldDate
|
||||
return date ?: oldDate
|
||||
}
|
||||
val displayLessonNumber: Int?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldLessonNumber
|
||||
return lessonNumber ?: oldLessonNumber
|
||||
}
|
||||
val displayStartTime: Time?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldStartTime
|
||||
return startTime ?: oldStartTime
|
||||
}
|
||||
val displayEndTime: Time?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldEndTime
|
||||
return endTime ?: oldEndTime
|
||||
}
|
||||
|
||||
val displaySubjectName: String?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldSubjectName
|
||||
return subjectName ?: oldSubjectName
|
||||
}
|
||||
val displayTeacherName: String?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldTeacherName
|
||||
return teacherName ?: oldTeacherName
|
||||
}
|
||||
val displayTeamName: String?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldTeamName
|
||||
return teamName ?: oldTeamName
|
||||
}
|
||||
|
||||
val displayClassroom: String?
|
||||
get() {
|
||||
if (type == TYPE_SHIFTED_SOURCE)
|
||||
return oldClassroom
|
||||
return classroom ?: oldClassroom
|
||||
}
|
||||
|
||||
// metadata
|
||||
var seen: Boolean = false
|
||||
var notified: Boolean = false
|
||||
var addedDate: Long = 0
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package pl.szczodrzynski.edziennik.data.db.modules.timetable
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
@Dao
|
||||
interface TimetableDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
operator fun plusAssign(lessonList: List<Lesson>)
|
||||
|
||||
@Query("DELETE FROM timetable WHERE profileId = :profileId")
|
||||
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)")
|
||||
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)")
|
||||
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)")
|
||||
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
timetable.*,
|
||||
subjects.subjectLongName AS subjectName,
|
||||
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
|
||||
teams.teamName AS teamName,
|
||||
oldS.subjectLongName AS oldSubjectName,
|
||||
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
|
||||
oldG.teamName AS oldTeamName,
|
||||
metadata.seen, metadata.notified, metadata.addedDate
|
||||
FROM timetable
|
||||
LEFT JOIN subjects USING(profileId, subjectId)
|
||||
LEFT JOIN teachers USING(profileId, teacherId)
|
||||
LEFT JOIN teams USING(profileId, teamId)
|
||||
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
|
||||
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
|
||||
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
|
||||
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
|
||||
WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)
|
||||
ORDER BY type
|
||||
""")
|
||||
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
|
||||
}
|
@ -112,7 +112,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
||||
app.notifier.postAll(profile);
|
||||
app.saveConfig("notifications");*/
|
||||
d(TAG, "Syncing profile " + profile.getId());
|
||||
EdziennikTask.Companion.syncProfile(profile.getId(), null).enqueue(app);
|
||||
EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
|
||||
} else {
|
||||
/*app.notifier.add(new Notification(app.getContext(), remoteMessage.getData().get("message"))
|
||||
.withProfileData(profile.id, profile.name)
|
||||
@ -123,7 +123,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
||||
app.notifier.postAll(profile);
|
||||
app.saveConfig("notifications");*/
|
||||
d(TAG, "Syncing profile " + profile.getId());
|
||||
EdziennikTask.Companion.syncProfile(profile.getId(), null).enqueue(app);
|
||||
EdziennikTask.Companion.syncProfile(profile.getId(), null, null).enqueue(app);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -26,6 +26,7 @@ public class LoginChooserFragment extends Fragment {
|
||||
private NavController nav;
|
||||
private FragmentLoginChooserBinding b;
|
||||
private static final String TAG = "LoginTemplate";
|
||||
public static boolean fakeLogin = false;
|
||||
|
||||
public LoginChooserFragment() { }
|
||||
|
||||
@ -71,6 +72,10 @@ public class LoginChooserFragment extends Fragment {
|
||||
b.cancelButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
b.fakeLogin.setVisibility(App.devMode ? View.VISIBLE : View.GONE);
|
||||
b.fakeLogin.setChecked(fakeLogin);
|
||||
b.fakeLogin.setOnCheckedChangeListener((v, isChecked) -> fakeLogin = isChecked);
|
||||
|
||||
b.helpButton.setOnClickListener((v -> {
|
||||
startActivity(new Intent(getActivity(), FeedbackActivity.class));
|
||||
}));
|
||||
|
@ -89,6 +89,10 @@ public class LoginProgressFragment extends Fragment {
|
||||
LoginStore loginStore = new LoginStore(-1, loginType, new JsonObject());
|
||||
loginStore.copyFrom(args);
|
||||
|
||||
if (App.devMode && LoginChooserFragment.fakeLogin) {
|
||||
loginStore.putLoginData("fakeLogin", true);
|
||||
}
|
||||
|
||||
EdziennikTask.Companion.firstLogin(loginStore).enqueue(getContext());
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,108 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.timetable.v2
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
|
||||
class TimetableFragment : Fragment() {
|
||||
companion object {
|
||||
private const val TAG = "TimetableFragment"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: FragmentTimetableV2Binding
|
||||
private var fabShown = false
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
if (context == null)
|
||||
return null
|
||||
app = activity.application as App
|
||||
context!!.theme.applyStyle(Themes.appTheme, true)
|
||||
if (app.profile == null)
|
||||
return inflater.inflate(R.layout.fragment_loading, container, false)
|
||||
// activity, context and profile is valid
|
||||
b = FragmentTimetableV2Binding.inflate(inflater)
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// TODO check if app, activity, b can be null
|
||||
if (app.profile == null || !isAdded)
|
||||
return
|
||||
|
||||
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
|
||||
b.timetableLayout.visibility = View.GONE
|
||||
b.timetableNotPublicLayout.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
b.timetableLayout.visibility = View.VISIBLE
|
||||
b.timetableNotPublicLayout.visibility = View.GONE
|
||||
|
||||
val items = mutableListOf<Date>()
|
||||
|
||||
val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||
|
||||
val today = Date.getToday().value
|
||||
val yearStart = app.profile.dateSemester1Start?.clone() ?: return
|
||||
val yearEnd = app.profile.dateYearEnd ?: return
|
||||
while (yearStart.value <= yearEnd.value) {
|
||||
items += yearStart.clone()
|
||||
var maxDays = monthDayCount[yearStart.month-1]
|
||||
if (yearStart.month == 2 && yearStart.isLeap)
|
||||
maxDays++
|
||||
yearStart.day++
|
||||
if (yearStart.day > maxDays) {
|
||||
yearStart.day = 1
|
||||
yearStart.month++
|
||||
}
|
||||
if (yearStart.month > 12) {
|
||||
yearStart.month = 1
|
||||
yearStart.year++
|
||||
}
|
||||
}
|
||||
|
||||
val pagerAdapter = TimetablePagerAdapter(fragmentManager ?: return, items)
|
||||
b.viewPager.offscreenPageLimit = 2
|
||||
b.viewPager.adapter = pagerAdapter
|
||||
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
activity.navView.bottomBar.fabEnable = items[position].value != today
|
||||
if (activity.navView.bottomBar.fabEnable && !fabShown) {
|
||||
activity.gainAttentionFAB()
|
||||
fabShown = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.tabLayout.setUpWithViewPager(b.viewPager)
|
||||
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false)
|
||||
|
||||
//activity.navView.bottomBar.fabEnable = true
|
||||
activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today)
|
||||
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today
|
||||
activity.navView.setFabOnClickListener(View.OnClickListener {
|
||||
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.timetable.v2
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: List<Date>) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
companion object {
|
||||
private const val TAG = "TimetablePagerAdapter"
|
||||
}
|
||||
|
||||
private val today by lazy { Date.getToday() }
|
||||
private val weekStart by lazy { today.clone().stepForward(0, 0, -today.weekDay) }
|
||||
private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) }
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
return pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragment(items[position])
|
||||
/*return TimetableDayFragment().apply {
|
||||
arguments = Bundle().also {
|
||||
it.putLong("date", items[position].value.toLong())
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence? {
|
||||
val date = items[position]
|
||||
val pageTitle = StringBuilder(Week.getFullDayName(date.weekDay))
|
||||
if (date > weekEnd || date < weekStart) {
|
||||
pageTitle.append(", ").append(date.stringDm)
|
||||
}
|
||||
return pageTitle
|
||||
}
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import com.linkedin.android.tachyon.DayView
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
|
||||
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
|
||||
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.LessonFull
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2DayBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
class TimetableDayFragment(val date: Date) : Fragment() {
|
||||
companion object {
|
||||
private const val TAG = "TimetableDayFragment"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: FragmentTimetableV2DayBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
if (context == null)
|
||||
return null
|
||||
app = activity.application as App
|
||||
b = FragmentTimetableV2DayBinding.inflate(inflater)
|
||||
Log.d(TAG, "onCreateView, date=$date")
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// TODO check if app, activity, b can be null
|
||||
if (app.profile == null || !isAdded)
|
||||
return
|
||||
|
||||
Log.d(TAG, "onViewCreated, date=$date")
|
||||
|
||||
// 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 ->
|
||||
buildLessonViews(lessons)
|
||||
})
|
||||
}
|
||||
|
||||
private fun buildLessonViews(lessons: List<LessonFull>) {
|
||||
if (lessons.isEmpty()) {
|
||||
b.dayScroll.visibility = View.GONE
|
||||
b.noTimetableLayout.visibility = View.VISIBLE
|
||||
b.noLessonsLayout.visibility = View.GONE
|
||||
val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d
|
||||
b.noTimetableSync.setOnClickListener {
|
||||
EdziennikTask.syncProfile(
|
||||
profileId = App.profileId,
|
||||
viewIds = listOf(
|
||||
DRAWER_ITEM_TIMETABLE to 0
|
||||
),
|
||||
arguments = JsonObject(
|
||||
"weekStart" to weekStart
|
||||
)
|
||||
).enqueue(activity)
|
||||
}
|
||||
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
|
||||
return
|
||||
}
|
||||
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
||||
b.dayScroll.visibility = View.GONE
|
||||
b.noTimetableLayout.visibility = View.GONE
|
||||
b.noLessonsLayout.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
// reload the fragment when: no lessons, user wants to sync the week, the timetable is not public, pager gets removed
|
||||
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
|
||||
activity.reloadTarget()
|
||||
// TODO fix for (not really)possible infinite loops
|
||||
return
|
||||
}
|
||||
|
||||
b.dayScroll.visibility = View.VISIBLE
|
||||
b.noTimetableLayout.visibility = View.GONE
|
||||
b.noLessonsLayout.visibility = View.GONE
|
||||
|
||||
var firstEventMinute = 24*60
|
||||
|
||||
val eventViews = mutableListOf<View>()
|
||||
val eventTimeRanges = mutableListOf<DayView.EventTimeRange>()
|
||||
|
||||
// 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
|
||||
val recycled = b.day.removeEventViews()
|
||||
var remaining = recycled?.size ?: 0
|
||||
|
||||
val arrowRight = " → "
|
||||
val bullet = " • "
|
||||
val colorSecondary = getColorFromAttr(activity, android.R.attr.textColorSecondary)
|
||||
|
||||
for (lesson in lessons) {
|
||||
val startTime = lesson.displayStartTime ?: continue
|
||||
val endTime = lesson.displayEndTime ?: continue
|
||||
|
||||
firstEventMinute = min(firstEventMinute, startTime.hour*60 + startTime.minute)
|
||||
|
||||
// Try to recycle an existing event view if there are enough left, otherwise inflate
|
||||
// a new one
|
||||
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, b.day, false))
|
||||
?: continue
|
||||
val lb = TimetableLessonBinding.bind(eventView)
|
||||
eventViews += eventView
|
||||
|
||||
eventView.tag = lesson
|
||||
|
||||
eventView.setOnClickListener {
|
||||
Log.d(TAG, "Clicked ${it.tag}")
|
||||
}
|
||||
|
||||
|
||||
val timeRange = "${startTime.stringHM} - ${endTime.stringHM}".asColoredSpannable(colorSecondary)
|
||||
|
||||
// teacher
|
||||
val teacherInfo = if (lesson.teacherId != null && lesson.teacherId == lesson.oldTeacherId)
|
||||
lesson.teacherName ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.teacherName?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
|
||||
// team
|
||||
val teamInfo = if (lesson.teamId != null && lesson.teamId == lesson.oldTeamId)
|
||||
lesson.teamName ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.teamName?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
|
||||
// classroom
|
||||
val classroomInfo = if (lesson.classroom != null && lesson.classroom == lesson.oldClassroom)
|
||||
lesson.classroom ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.classroom?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
|
||||
|
||||
lb.lessonNumber = lesson.displayLessonNumber
|
||||
lb.subjectName.text = lesson.displaySubjectName?.let {
|
||||
if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE)
|
||||
it.asStrikethroughSpannable().asColoredSpannable(colorSecondary)
|
||||
else
|
||||
it
|
||||
}
|
||||
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
|
||||
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
|
||||
|
||||
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
|
||||
when (lesson.type) {
|
||||
Lesson.TYPE_NORMAL -> {
|
||||
lb.annotationVisible = false
|
||||
}
|
||||
Lesson.TYPE_CANCELLED -> {
|
||||
lb.annotationVisible = true
|
||||
lb.annotation.setText(R.string.timetable_lesson_cancelled)
|
||||
lb.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(activity, R.attr.timetable_lesson_cancelled_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
//lb.subjectName.typeface = Typeface.DEFAULT
|
||||
}
|
||||
Lesson.TYPE_CHANGE -> {
|
||||
lb.annotationVisible = true
|
||||
if (lesson.subjectId != lesson.oldSubjectId && lesson.teacherId != lesson.oldTeacherId) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
"${lesson.oldSubjectName ?: "?"}, ${lesson.oldTeacherName ?: "?"}"
|
||||
)
|
||||
}
|
||||
else if (lesson.subjectId != lesson.oldSubjectId) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
lesson.oldSubjectName ?: "?"
|
||||
)
|
||||
}
|
||||
else if (lesson.teacherId != lesson.oldTeacherId) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
lesson.oldTeacherName ?: "?"
|
||||
)
|
||||
}
|
||||
else {
|
||||
lb.annotation.setText(R.string.timetable_lesson_change)
|
||||
}
|
||||
|
||||
lb.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(activity, R.attr.timetable_lesson_change_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
Lesson.TYPE_SHIFTED_SOURCE -> {
|
||||
lb.annotationVisible = true
|
||||
if (lesson.date != lesson.oldDate) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_shifted_other_day,
|
||||
lesson.date?.stringY_m_d ?: "?",
|
||||
lesson.startTime?.stringHM ?: "?"
|
||||
)
|
||||
}
|
||||
else if (lesson.startTime != lesson.oldStartTime) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_shifted_same_day,
|
||||
lesson.startTime?.stringHM ?: "?"
|
||||
)
|
||||
}
|
||||
else {
|
||||
lb.annotation.setText(R.string.timetable_lesson_shifted)
|
||||
}
|
||||
|
||||
lb.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(activity, R.attr.timetable_lesson_shifted_source_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
Lesson.TYPE_SHIFTED_TARGET -> {
|
||||
lb.annotationVisible = true
|
||||
if (lesson.date != lesson.oldDate) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_shifted_from_other_day,
|
||||
lesson.oldDate?.stringY_m_d ?: "?",
|
||||
lesson.oldStartTime?.stringHM ?: "?"
|
||||
)
|
||||
}
|
||||
else if (lesson.startTime != lesson.oldStartTime) {
|
||||
lb.annotation.setText(
|
||||
R.string.timetable_lesson_shifted_from_same_day,
|
||||
lesson.oldStartTime?.stringHM ?: "?"
|
||||
)
|
||||
}
|
||||
else {
|
||||
lb.annotation.setText(R.string.timetable_lesson_shifted_from)
|
||||
}
|
||||
|
||||
lb.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(activity, R.attr.timetable_lesson_shifted_target_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The day view needs the event time ranges in the start minute/end minute format,
|
||||
// so calculate those here
|
||||
val startMinute = 60 * (lesson.displayStartTime?.hour ?: 0) + (lesson.displayStartTime?.minute ?: 0)
|
||||
val endMinute = startMinute + 45
|
||||
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
|
||||
}
|
||||
|
||||
val minuteHeight = (b.day.getHourTop(1) - b.day.getHourTop(0)).toFloat() / 60f
|
||||
val firstEventTop = (firstEventMinute - b.day.startHour * 60) * minuteHeight
|
||||
b.day.setEventViews(eventViews, eventTimeRanges)
|
||||
b.dayScroll.scrollTo(0, firstEventTop.toInt())
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package pl.szczodrzynski.edziennik.utils.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
@ -131,6 +132,13 @@ public class Date implements Comparable<Date> {
|
||||
return year * 10000 + month * 100 + day;
|
||||
}
|
||||
|
||||
public static Date fromValue(int value) {
|
||||
int year = value / 10000;
|
||||
int month = (value-year*10000) / 100;
|
||||
int day = (value-year*10000-month*100);
|
||||
return new Date(year, month, day);
|
||||
}
|
||||
|
||||
public String getStringValue()
|
||||
{
|
||||
return Integer.toString(getValue());
|
||||
@ -182,6 +190,10 @@ public class Date implements Comparable<Date> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLeap() {
|
||||
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
|
||||
}
|
||||
|
||||
public static Date getToday()
|
||||
{
|
||||
Calendar cal = Calendar.getInstance();
|
||||
@ -214,6 +226,11 @@ public class Date implements Comparable<Date> {
|
||||
return this.getValue() - o.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return obj instanceof Date && this.getValue() == ((Date) obj).getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Date{" +
|
||||
|
@ -1,5 +1,7 @@
|
||||
package pl.szczodrzynski.edziennik.utils.models;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class Time {
|
||||
@ -173,6 +175,11 @@ public class Time {
|
||||
return (currentTime.getValue() >= startTime.getValue() && currentTime.getValue() <= endTime.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return obj instanceof Time && this.getValue() == ((Time) obj).getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Time{" +
|
||||
|
10
app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml
Normal file
10
app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#000000" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
6
app/src/main/res/drawable/bg_rounded_ripple_4dp.xml
Normal file
6
app/src/main/res/drawable/bg_rounded_ripple_4dp.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/bg_rounded_edittext_pressed" />
|
||||
<item android:state_focused="true" android:drawable="@drawable/bg_rounded_edittext_pressed" />
|
||||
<item android:state_selected="true" android:drawable="@drawable/bg_rounded_edittext_pressed" />
|
||||
</selector>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="4dp" />
|
||||
<stroke android:color="@color/dividerColor" android:width="1dp" />
|
||||
<solid android:color="#DCDCDC" />
|
||||
</shape>
|
61
app/src/main/res/drawable/ic_no_timetable.xml
Normal file
61
app/src/main/res/drawable/ic_no_timetable.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-11.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<path
|
||||
android:pathData="m117,76h4c3.852,0 7,-3.148 7,-7 0,-3.852 -3.148,-7 -7,-7h-13c-2.199,0 -4,-1.801 -4,-4s1.801,-4 4,-4h11c3.852,0 7,-3.148 7,-7 0,-3.852 -3.148,-7 -7,-7h-1c-2.211,0 -4,-1.789 -4,-4 0,-2.211 1.789,-4 4,-4h4c3.695,0 6.637,-3.387 5.879,-7.211 -0.566,-2.844 -3.238,-4.789 -6.141,-4.789h-15.738c-1.656,0 -3,-1.344 -3,-3s1.344,-3 3,-3h6.824c2.277,0 4.402,-1.441 4.992,-3.641 0.895,-3.328 -1.625,-6.359 -4.816,-6.359h-96c-3.852,0 -7,3.148 -7,7 0,3.852 3.148,7 7,7h3c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-10.77c-3.34,0 -6.391,2.242 -7.074,5.516 -0.941,4.488 2.508,8.484 6.844,8.484h15l-6,24h-10.77c-3.34,0 -6.391,2.242 -7.074,5.516 -0.941,4.488 2.508,8.484 6.844,8.484h1c2.211,0 4,1.789 4,4 0,2.211 -1.789,4 -4,4h-1c-4.336,0 -7.785,3.996 -6.844,8.484 0.684,3.273 3.734,5.516 7.074,5.516h10.77c2.211,0 4,1.789 4,4s-1.789,4 -4,4h-2.77c-3.34,0 -6.391,2.242 -7.074,5.516 -0.941,4.488 2.508,8.484 6.844,8.484h97c3.313,0 6,-2.688 6,-6s-2.688,-6 -6,-6h-1c-2.762,0 -5,-2.238 -5,-5s2.238,-5 5,-5h6c3.852,0 7,-3.148 7,-7 0,-3.852 -3.148,-7 -7,-7 -2.75,0 -5,-2.25 -5,-5s2.25,-5 5,-5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="100.522"
|
||||
android:centerX="63.746"
|
||||
android:centerY="71.138"
|
||||
android:type="radial"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#1F0FCCFF"/>
|
||||
<item android:offset="0.1927" android:color="#1F0FCEFF"/>
|
||||
<item android:offset="0.7025" android:color="#1F0FD5FF"/>
|
||||
<item android:offset="1" android:color="#1F0FD7FF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m103.24,15.168 l-7.883,7.871c-8.746,-7.063 -19.719,-11.039 -31.355,-11.039 -27.57,0 -50,22.43 -50,50 0,27.57 22.43,50 50,50 17.758,0 34.348,-9.367 43.301,-24.449 1.41,-2.375 0.625,-5.441 -1.75,-6.852 -2.367,-1.41 -5.438,-0.629 -6.852,1.746 -7.156,12.062 -20.453,19.555 -34.699,19.555 -22.055,0 -40,-17.945 -40,-40 0,-22.055 17.945,-40 40,-40 8.934,0 17.371,2.938 24.223,8.16l-9.063,9.047c-2.48,2.5 -0.719,6.762 2.801,6.762h24.039c2.199,0 4,-1.801 4,-4v-24c0,-3.52 -4.262,-5.301 -6.762,-2.801z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="125.452"
|
||||
android:startX="62"
|
||||
android:endY="6.4762"
|
||||
android:endX="62"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFEAA53"/>
|
||||
<item android:offset="0.6124" android:color="#FFFFCD49"/>
|
||||
<item android:offset="1" android:color="#FFFFDE44"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m68,85c0,2.762 -2.238,5 -5,5s-5,-2.238 -5,-5 2.238,-5 5,-5 5,2.238 5,5zM70,41c0,-3.867 -3.133,-7 -7,-7 -3.867,0 -7,3.133 -7,7 0,0.047 0.016,0.094 0.016,0.141h-0.016l2.438,28.59c0.203,2.414 2.188,4.269 4.563,4.269s4.359,-1.855 4.563,-4.269l2.438,-28.59h-0.016c0,-0.047 0.016,-0.094 0.016,-0.141z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="90"
|
||||
android:startX="63"
|
||||
android:endY="11.7176"
|
||||
android:endX="63"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFF634D"/>
|
||||
<item android:offset="0.2043" android:color="#FFFE6464"/>
|
||||
<item android:offset="0.5209" android:color="#FFFC6581"/>
|
||||
<item android:offset="0.7936" android:color="#FFFA6694"/>
|
||||
<item android:offset="0.9892" android:color="#FFFA669A"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
45
app/src/main/res/drawable/ic_sync.xml
Normal file
45
app/src/main/res/drawable/ic_sync.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-11.
|
||||
-->
|
||||
|
||||
<vector android:height="128dp" android:viewportHeight="64"
|
||||
android:viewportWidth="64" android:width="128dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:pathData="m59,16h2c1.848,0 3.319,-1.693 2.94,-3.605 -0.283,-1.423 -1.62,-2.395 -3.071,-2.395h-10.369c-0.828,0 -1.5,-0.672 -1.5,-1.5s0.672,-1.5 1.5,-1.5h5.912c1.139,0 2.202,-0.721 2.497,-1.821 0.446,-1.663 -0.813,-3.179 -2.409,-3.179h-48c-1.925,0 -3.5,1.575 -3.5,3.5s1.575,3.5 3.5,3.5h1.5c1.105,0 2,0.895 2,2s-0.895,2 -2,2h-5.385c-1.67,0 -3.195,1.122 -3.537,2.757 -0.47,2.245 1.254,4.243 3.422,4.243h7.5l-3,12h-5.385c-1.67,0 -3.195,1.122 -3.537,2.757 -0.47,2.245 1.254,4.243 3.422,4.243h0.5c1.105,0 2,0.895 2,2s-0.895,2 -2,2h-0.5c-2.168,0 -3.892,1.998 -3.422,4.243 0.342,1.635 1.867,2.757 3.537,2.757h5.385c1.105,0 2,0.895 2,2s-0.895,2 -2,2h-1.385c-1.67,0 -3.195,1.122 -3.537,2.757 -0.47,2.245 1.254,4.243 3.422,4.243h48.5c1.657,0 3,-1.343 3,-3s-1.343,-3 -3,-3h-0.5c-1.381,0 -2.5,-1.119 -2.5,-2.5s1.119,-2.5 2.5,-2.5h4.5c1.657,0 3,-1.343 3,-3s-1.343,-3 -3,-3h-8.377c2.141,-3.494 3.377,-7.602 3.377,-12 0,-2.441 -0.384,-4.792 -1.088,-7h6.501c1.139,0 2.202,-0.721 2.497,-1.821 0.445,-1.663 -0.814,-3.179 -2.41,-3.179h-1.5c-1.105,0 -2,-0.895 -2,-2s0.895,-2 2,-2z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:centerX="32" android:centerY="31.500021"
|
||||
android:gradientRadius="32" android:tileMode="mirror" android:type="radial">
|
||||
<item android:color="#1F0FCCFF" android:offset="0"/>
|
||||
<item android:color="#1F0FCEFF" android:offset="0.193"/>
|
||||
<item android:color="#1F0FD5FF" android:offset="0.703"/>
|
||||
<item android:color="#1F0ECEFF" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M51.483,15.326l3.937,-3.93c1.24,-1.25 0.36,-3.38 -1.4,-3.38H42c-1.1,0 -2,0.9 -2,2v12c0,1.76 2.13,2.65 3.38,1.4l4.537,-4.529C50.532,22.313 52,26.533 52,31c0,11.215 -8.565,20 -19.5,20c-1.381,0 -2.5,1.119 -2.5,2.5s1.119,2.5 2.5,2.5C46.238,56 57,45.019 57,31C57,25.183 55.012,19.699 51.483,15.326z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="43.5" android:endY="-21.15"
|
||||
android:startX="43.5" android:startY="59.034"
|
||||
android:tileMode="mirror" android:type="linear">
|
||||
<item android:color="#FFFF634D" android:offset="0"/>
|
||||
<item android:color="#FFFE6464" android:offset="0.204"/>
|
||||
<item android:color="#FFFC6581" android:offset="0.521"/>
|
||||
<item android:color="#FFFA6694" android:offset="0.794"/>
|
||||
<item android:color="#FFFA669A" android:offset="0.989"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M20.604,38.58l-4.523,4.53C13.468,39.683 12,35.463 12,31c0,-11.215 8.565,-20 19.5,-20c1.381,0 2.5,-1.119 2.5,-2.5S32.881,6 31.5,6C17.762,6 7,16.981 7,31c0,5.815 1.989,11.303 5.52,15.677L8.584,50.62c-1.25,1.25 -0.36,3.38 1.4,3.38h12c1.1,0 2,-0.9 2,-2V39.98C23.984,38.22 21.854,37.34 20.604,38.58z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="20.5" android:endY="-24.844"
|
||||
android:startX="20.5" android:startY="55.227"
|
||||
android:tileMode="mirror" android:type="linear">
|
||||
<item android:color="#FFFF634D" android:offset="0"/>
|
||||
<item android:color="#FFFE6464" android:offset="0.204"/>
|
||||
<item android:color="#FFFC6581" android:offset="0.521"/>
|
||||
<item android:color="#FFFA6694" android:offset="0.794"/>
|
||||
<item android:color="#FFFA669A" android:offset="0.989"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
202
app/src/main/res/drawable/ic_timetable.xml
Normal file
202
app/src/main/res/drawable/ic_timetable.xml
Normal file
@ -0,0 +1,202 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-11.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<path
|
||||
android:pathData="m118,62.129h2.605c3.789,0 7.188,-2.84 7.383,-6.625 0.211,-4.035 -3,-7.375 -6.988,-7.375h-4.805c-1.965,0 -3.785,-1.328 -4.129,-3.262 -0.043,-0.25 -0.066,-0.5 -0.066,-0.746 0.004,-2.168 1.734,-3.93 3.887,-3.988 3.023,-0.082 5.731,-2.293 6.07,-5.297 0.027,-0.242 0.039,-0.477 0.039,-0.711 0,-3.313 -2.688,-5.996 -6,-5.996h-4.602c-0.434,0 -4.863,-0.035 -5.281,-0.105 -0.035,-0.008 -0.074,-0.016 -0.113,-0.023v18h-42v-18h25.715c0.438,-1.688 0.539,-3.512 -0.945,-5.719 -1.836,-2.742 -5.023,-4.281 -8.324,-4.281h-9.445c-1.656,0 -3,-1.344 -3,-3s1.344,-3 3,-3h0.66c3.25,0 6.16,-2.434 6.332,-5.68 0.18,-3.461 -2.57,-6.32 -5.992,-6.32h-57.66c-3.25,0 -6.16,2.434 -6.332,5.68 -0.18,3.457 2.57,6.32 5.992,6.32h15c1.656,0 3,1.344 3,3s-1.344,3 -3,3h-17c-4.418,0 -8,3.582 -8,8s3.582,8 8,8h26v12h-14l2,21.109c-1.07,0.801 -1.836,1.98 -1.973,3.375 -0.184,1.84 0.633,3.5 1.973,4.504v5.047c-0.027,0.148 -0.043,0.297 -0.027,0.449 0.301,2.992 -2.039,5.516 -4.973,5.516h-14.66c-2.984,0 -5.762,2.023 -6.25,4.965 -0.246,1.465 0.043,2.848 0.684,4.012 1.074,1.938 3.234,3.023 5.449,3.023h3.164c2.375,0 4.207,1.328 4.551,3.27 0.039,0.246 0.063,0.488 0.063,0.723 0.004,2.215 -1.789,4.008 -3.996,4.008h-0.004c-2.27,0 -4.473,1.203 -5.398,3.277 -1.98,4.426 1.207,8.723 5.398,8.723h62c3.313,0 6,-2.688 6,-6s-2.688,-6 -6,-6h-10v-20l46,0.129h9.66c3.141,0 6.168,-2.539 6.328,-5.676 0.184,-3.461 -2.57,-6.324 -5.992,-6.324h-0.023,-0.035c-1.555,0 -3.078,-0.508 -4.156,-1.535 -0.34,-0.324 -0.637,-0.699 -0.875,-1.129 -2.574,-4.637 0.711,-9.336 5.094,-9.336z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="80.322"
|
||||
android:centerX="60.334"
|
||||
android:centerY="65.146"
|
||||
android:type="radial"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#1F0FCCFF"/>
|
||||
<item android:offset="0.1927" android:color="#1F0FCEFF"/>
|
||||
<item android:offset="0.7025" android:color="#1F0FD5FF"/>
|
||||
<item android:offset="1" android:color="#1F0FD7FF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m18,100v-60h92v60c0,5.523 -4.477,10 -10,10h-72c-5.523,0 -10,-4.477 -10,-10z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="110"
|
||||
android:startX="64"
|
||||
android:endY="40"
|
||||
android:endX="64"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFFC662"/>
|
||||
<item android:offset="0.0036" android:color="#FFFFC662"/>
|
||||
<item android:offset="0.6085" android:color="#FFFFC582"/>
|
||||
<item android:offset="1" android:color="#FFFFC491"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m110,29.602v16.398h-92v-16.398c0,-5.309 4.332,-9.602 9.684,-9.602h72.633c5.352,0 9.684,4.293 9.684,9.602">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="46"
|
||||
android:startX="64"
|
||||
android:endY="20"
|
||||
android:endX="64"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFF634D"/>
|
||||
<item android:offset="0.2083" android:color="#FFFD6464"/>
|
||||
<item android:offset="0.5223" android:color="#FFFC6582"/>
|
||||
<item android:offset="0.7935" android:color="#FFFA6694"/>
|
||||
<item android:offset="0.9892" android:color="#FFFA669A"/>
|
||||
<item android:offset="1" android:color="#FFFA669A"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m49.309,20h-12.621c-2.832,1.988 -4.688,5.277 -4.688,9 0,6.07 4.93,11 11,11 6.07,0 11,-4.93 11,-11 0,-3.723 -1.855,-7.012 -4.691,-9z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="40"
|
||||
android:startX="43"
|
||||
android:endY="20"
|
||||
android:endX="43"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFF5840"/>
|
||||
<item android:offset="0.0072" android:color="#FFFF5840"/>
|
||||
<item android:offset="0.9892" android:color="#FFFA528C"/>
|
||||
<item android:offset="1" android:color="#FFFA528C"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m43,34c-2.75,0 -5,-2.25 -5,-5v-12c0,-2.75 2.25,-5 5,-5s5,2.25 5,5v12c0,2.75 -2.25,5 -5,5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="12"
|
||||
android:startX="43"
|
||||
android:endY="34"
|
||||
android:endX="43"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFA4A4A4"/>
|
||||
<item android:offset="0.6301" android:color="#FF7F7F7F"/>
|
||||
<item android:offset="1" android:color="#FF6F6F6F"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m91.309,20h-12.621c-2.832,1.988 -4.688,5.277 -4.688,9 0,6.07 4.93,11 11,11 6.07,0 11,-4.93 11,-11 0,-3.723 -1.855,-7.012 -4.691,-9z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="40"
|
||||
android:startX="85"
|
||||
android:endY="20"
|
||||
android:endX="85"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFF5840"/>
|
||||
<item android:offset="0.0072" android:color="#FFFF5840"/>
|
||||
<item android:offset="0.9892" android:color="#FFFA528C"/>
|
||||
<item android:offset="1" android:color="#FFFA528C"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m85,34c-2.75,0 -5,-2.25 -5,-5v-12c0,-2.75 2.25,-5 5,-5s5,2.25 5,5v12c0,2.75 -2.25,5 -5,5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="12"
|
||||
android:startX="85"
|
||||
android:endY="34"
|
||||
android:endX="85"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFA4A4A4"/>
|
||||
<item android:offset="0.6301" android:color="#FF7F7F7F"/>
|
||||
<item android:offset="1" android:color="#FF6F6F6F"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m78,81.008c0,2.762 2.238,5 5,4.996h2.992c3.316,-0.008 6.008,2.684 6.008,5.996s-2.688,6 -6,6h-8c-3.313,0 -6,2.688 -6,6s2.688,6 6,6h22c5.523,0 10,-4.477 10,-10v-24l-27.008,0.012c-2.758,-0 -4.992,2.234 -4.992,4.996z"
|
||||
android:fillColor="#ffb86a"/>
|
||||
<path
|
||||
android:pathData="m18,68h17.785c1.992,0 3.84,-1.363 4.16,-3.328 0.406,-2.508 -1.516,-4.672 -3.945,-4.672h-3c-1.656,0 -3,-1.344 -3,-3s1.344,-3 3,-3h14.785c1.992,0 3.84,-1.363 4.16,-3.328 0.406,-2.508 -1.516,-4.672 -3.945,-4.672h-30z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="108.25"
|
||||
android:startX="35"
|
||||
android:endY="43.742"
|
||||
android:endX="35"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFFCE76"/>
|
||||
<item android:offset="0.0036" android:color="#FFFFCE76"/>
|
||||
<item android:offset="0.6054" android:color="#FFFFCD92"/>
|
||||
<item android:offset="1" android:color="#FFFFCCA0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m98,62c0,3.313 -2.688,6 -6,6s-6,-2.688 -6,-6 2.688,-6 6,-6 6,2.688 6,6z"
|
||||
android:fillColor="#ffb977"/>
|
||||
<path
|
||||
android:pathData="m48,74h-8c-2.211,0 -4,-1.789 -4,-4v-8c0,-2.211 1.789,-4 4,-4h8c2.211,0 4,1.789 4,4v8c0,2.211 -1.789,4 -4,4zM72,70v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM92,70v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM52,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM72,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4zM92,90v-8c0,-2.211 -1.789,-4 -4,-4h-8c-2.211,0 -4,1.789 -4,4v8c0,2.211 1.789,4 4,4h8c2.211,0 4,-1.789 4,-4z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="94"
|
||||
android:startX="64"
|
||||
android:endY="58"
|
||||
android:endX="64"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FFFFE79F"/>
|
||||
<item android:offset="0.1186" android:color="#FFFFE9A6"/>
|
||||
<item android:offset="1" android:color="#FFFFF5D5"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m124,104c0,11.047 -8.953,20 -20,20 -11.047,0 -20,-8.953 -20,-20 0,-11.047 8.953,-20 20,-20 11.047,0 20,8.953 20,20z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="m104,80c-13.254,0 -24,10.746 -24,24 0,13.254 10.746,24 24,24 13.254,0 24,-10.746 24,-24 0,-13.254 -10.746,-24 -24,-24zM104,120c-8.836,0 -16,-7.164 -16,-16 0,-8.836 7.164,-16 16,-16 8.836,0 16,7.164 16,16 0,8.836 -7.164,16 -16,16z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="128"
|
||||
android:startX="104"
|
||||
android:endY="80"
|
||||
android:endX="104"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FF155CDE"/>
|
||||
<item android:offset="0.6248" android:color="#FF2289E7"/>
|
||||
<item android:offset="1" android:color="#FF289FEC"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="m113.41,110.59 l-5.563,-5.563c0.086,-0.328 0.148,-0.668 0.148,-1.023 0,-1.477 -0.809,-2.754 -2,-3.445v-6.555c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2v6.555c-1.191,0.691 -2,1.969 -2,3.445 0,2.211 1.789,4 4,4 0.355,0 0.695,-0.063 1.023,-0.148l5.563,5.563c0.391,0.391 0.902,0.586 1.414,0.586s1.023,-0.195 1.414,-0.586c0.781,-0.781 0.781,-2.047 0,-2.828z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="92"
|
||||
android:startX="107"
|
||||
android:endY="114"
|
||||
android:endX="107"
|
||||
android:type="linear"
|
||||
android:tileMode="mirror">
|
||||
<item android:offset="0" android:color="#FF919191"/>
|
||||
<item android:offset="1" android:color="#FF6F6F6F"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
11
app/src/main/res/drawable/timetable_lesson_annotation.xml
Normal file
11
app/src/main/res/drawable/timetable_lesson_annotation.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-10.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:shape="rectangle">
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" />
|
||||
<solid android:color="#2196f3" tools:color="?timetable_lesson_change_color" />
|
||||
</shape>
|
7
app/src/main/res/drawable/timetable_lesson_bg_dark.xml
Normal file
7
app/src/main/res/drawable/timetable_lesson_bg_dark.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="@color/colorSurface_4dp" />
|
||||
</shape>
|
7
app/src/main/res/drawable/timetable_lesson_bg_light.xml
Normal file
7
app/src/main/res/drawable/timetable_lesson_bg_light.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners android:radius="4dp" />
|
||||
<stroke android:width="1dp" android:color="#1e000000" />
|
||||
</shape>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-10.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" />
|
||||
<solid android:color="#e91e63" />
|
||||
</shape>
|
@ -151,6 +151,16 @@
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/fakeLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:text="Fake login" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
90
app/src/main/res/layout/fragment_timetable_v2.xml
Normal file
90
app/src/main/res/layout/fragment_timetable_v2.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/timetableLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
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_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/timetableNotPublicLayout"
|
||||
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_no_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_not_public_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_not_public_text"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<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_not_public_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</layout>
|
153
app/src/main/res/layout/fragment_timetable_v2_day.xml
Normal file
153
app/src/main/res/layout/fragment_timetable_v2_day.xml
Normal file
@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/dayScroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.linkedin.android.tachyon.DayView
|
||||
android:id="@+id/day"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
app:dividerHeight="1dp"
|
||||
app:endHour="18"
|
||||
app:eventMargin="2dp"
|
||||
app:halfHourDividerColor="#e0e0e0"
|
||||
app:halfHourHeight="60dp"
|
||||
app:hourDividerColor="#b0b0b0"
|
||||
app:hourLabelMarginEnd="10dp"
|
||||
app:hourLabelWidth="40dp"
|
||||
app:startHour="5"
|
||||
tools:visibility="gone"/>
|
||||
</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>
|
||||
</layout>
|
16
app/src/main/res/layout/timetable_hour_label.xml
Normal file
16
app/src/main/res/layout/timetable_hour_label.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2019 LinkedIn Corporation -->
|
||||
<!-- All Rights Reserved. -->
|
||||
<!-- -->
|
||||
<!-- Licensed under the BSD 2-Clause License (the "License"). See License in the project root -->
|
||||
<!-- for license information. -->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Base.TextAppearance.AppCompat.Small"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="viewEnd"
|
||||
tools:text="1 PM" />
|
139
app/src/main/res/layout/timetable_lesson.xml
Normal file
139
app/src/main/res/layout/timetable_lesson.xml
Normal file
@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<data>
|
||||
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="annotationVisible"
|
||||
type="boolean"/>
|
||||
<variable
|
||||
name="lessonNumber"
|
||||
type="Integer" />
|
||||
</data>
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:colorBackground"
|
||||
android:foreground="@drawable/bg_rounded_ripple_4dp"
|
||||
tools:padding="32dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_height="90dp"
|
||||
android:background="?timetable_lesson_bg"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/annotation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/timetable_lesson_annotation"
|
||||
android:fontFamily="sans-serif-condensed"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/timetable_lesson_cancelled"
|
||||
android:textColor="#000"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="italic"
|
||||
android:visibility="@{annotationVisible ? View.VISIBLE : View.GONE}"
|
||||
tools:text="Zastępstwo: zamiast lekcji język polski z Adam Dodatkowy"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
<!--tools:background="@drawable/timetable_subject_color_rounded"-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subjectName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:maxLines="@{annotationVisible ? 1 : 2}"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:autoSizeMaxTextSize="16sp"
|
||||
app:autoSizeMinTextSize="12sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
tools:maxLines="2"
|
||||
tools:text="pracownia urządzeń techniki komputerowej" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/attendanceIcon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
tools:srcCompat="@sample/check"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView4"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_weight="0"
|
||||
app:srcCompat="@drawable/bg_circle"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView6"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:fontFamily="sans-serif-condensed-light"
|
||||
android:includeFontPadding="false"
|
||||
android:layout_marginBottom="-4dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:text="@{Integer.toString(lessonNumber)}"
|
||||
android:textSize="28sp"
|
||||
android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}"
|
||||
tools:text="3"/>
|
||||
<!--android:layout_marginTop="@{annotationVisible ? `-4dp` : `4dp`}"
|
||||
android:layout_marginBottom="@{annotationVisible ? `-4dp` : `0dp`}"-->
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detailsFirst"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
tools:text="8:10 - 8:55 • 015 językowa → 016 językowa" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detailsSecond"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/NavView.TextView.Helper"
|
||||
android:textSize="12sp"
|
||||
tools:text="Paweł Informatyczny • 2b3T n1" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</layout>
|
@ -3,4 +3,9 @@
|
||||
<attr name="colorSection" format="color" />
|
||||
<attr name="cardBackgroundDimmed" format="color" />
|
||||
<attr name="cardBackgroundHighlight" format="color" />
|
||||
<attr name="timetable_lesson_bg" format="reference" />
|
||||
<attr name="timetable_lesson_cancelled_color" format="color" />
|
||||
<attr name="timetable_lesson_change_color" format="color" />
|
||||
<attr name="timetable_lesson_shifted_source_color" format="color" />
|
||||
<attr name="timetable_lesson_shifted_target_color" format="color" />
|
||||
</resources>
|
@ -985,4 +985,25 @@
|
||||
<string name="app_manager_open_failed">Nie udało się otworzyć ustawień</string>
|
||||
<string name="edziennik_notification_api_notify_title">Tworzenie powiadomień</string>
|
||||
<string name="login_librus_captcha_title">Librus - logowanie</string>
|
||||
<string name="timetable_today">Dzisiaj</string>
|
||||
<string name="timetable_lesson_cancelled">Lekcja odwołana</string>
|
||||
<string name="timetable_lesson_change">Zastępstwo</string>
|
||||
<string name="timetable_lesson_change_format">Zastępstwo: zamiast %s</string>
|
||||
<string name="timetable_lesson_shifted_same_day">Lekcja przeniesiona na godz. %s</string>
|
||||
<string name="timetable_lesson_shifted_other_day">Lekcja przeniesiona na %s, godz. %s</string>
|
||||
<string name="timetable_lesson_shifted_from_same_day">Lekcja przeniesiona z godz. %s</string>
|
||||
<string name="timetable_lesson_shifted_from_other_day">Lekcja przeniesiona z dnia %s, godz. %s</string>
|
||||
<string name="timetable_lesson_shifted">Lekcja przeniesiona na inny termin</string>
|
||||
<string name="timetable_lesson_shifted_from">Lekcja przeniesiona z innego terminu</string>
|
||||
<string name="timetable_not_public_title">Brak planu lekcji</string>
|
||||
<string name="timetable_not_public_text">Plan lekcji nie został opublikowany przez szkołę.</string>
|
||||
<string name="timetable_not_public_hint">Skontaktuj się z wychowawcą w celu udostępnienia planu lekcji.</string>
|
||||
<string name="timetable_free_day_title">Dzień wolny</string>
|
||||
<string name="timetable_free_day_text">W tym dniu nie ma lekcji:</string>
|
||||
<string name="timetable_free_day_show">Pokaż plan lekcji</string>
|
||||
<string name="timetable_no_lessons_title">Brak lekcji tego dnia</string>
|
||||
<string name="timetable_no_timetable_title">Brak planu lekcji</string>
|
||||
<string name="timetable_no_timetable_text">Nie pobrano planu lekcji na ten tydzień.</string>
|
||||
<string name="timetable_no_timetable_sync">Pobierz plan lekcji</string>
|
||||
<string name="timetable_no_timetable_week">na tydzień %s</string>
|
||||
</resources>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources>
|
||||
|
||||
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
@ -95,6 +95,12 @@
|
||||
<item name="mal_color_secondary">?android:textColorSecondary</item>
|
||||
<item name="mal_card_background">?colorSurface</item>
|
||||
<item name="mal_divider_color">@color/dividerColor</item>
|
||||
|
||||
<item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_light</item>
|
||||
<item name="timetable_lesson_cancelled_color">#9f9f9f</item>
|
||||
<item name="timetable_lesson_change_color">#ffb300</item>
|
||||
<item name="timetable_lesson_shifted_source_color">#A1887F</item>
|
||||
<item name="timetable_lesson_shifted_target_color">#4caf50</item>
|
||||
</style>
|
||||
<style name="AppTheme.Dark" parent="NavView.Dark">
|
||||
<item name="colorPrimary">#64b5f6</item>
|
||||
@ -119,6 +125,12 @@
|
||||
<item name="mal_color_secondary">@color/secondaryTextDark</item>
|
||||
<item name="mal_card_background">?colorSurface</item>
|
||||
<item name="mal_divider_color">@color/dividerColor</item>
|
||||
|
||||
<item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_dark</item>
|
||||
<item name="timetable_lesson_cancelled_color">#838383</item>
|
||||
<item name="timetable_lesson_change_color">#ffb300</item>
|
||||
<item name="timetable_lesson_shifted_source_color">#A1887F</item>
|
||||
<item name="timetable_lesson_shifted_target_color">#4caf50</item>
|
||||
</style>
|
||||
|
||||
|
||||
|
@ -5,8 +5,8 @@ buildscript {
|
||||
kotlin_version = '1.3.50'
|
||||
|
||||
release = [
|
||||
versionName: "3.9.3-dev",
|
||||
versionCode: 3090300
|
||||
versionName: "3.9.4-dev",
|
||||
versionCode: 3090400
|
||||
]
|
||||
|
||||
setup = [
|
||||
|
Reference in New Issue
Block a user