Переход на API v2

Переработано отображение расписания.
- Один предмет теперь может занимать несколько пар.
- В заголовке дня теперь может писаться "Сегодня", "Завтра", "Вчера".

Обновление расписания теперь происходит без отгрузки целой страницы политехникума на сервер.
Приложение теперь само находит ссылку с помощью регулярных выражений, что влечёт за собой малый прирост к скорости отправки запроса и его обработки сервером.

Пасхалко.
This commit is contained in:
2024-10-19 01:31:44 +04:00
parent 8ed9ce17e7
commit c81fd2540b
44 changed files with 486 additions and 418 deletions

View File

@@ -15,27 +15,8 @@
<option name="projectNumber" value="946974192625" />
</ConnectionSetting>
</option>
<option name="devices">
<list>
<DeviceSetting>
<option name="deviceType" value="Phone" />
<option name="displayName" value="Xiaomi (2311DRK48G)" />
<option name="manufacturer" value="Xiaomi" />
<option name="model" value="2311DRK48G" />
</DeviceSetting>
</list>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="versions">
<list>
<VersionSetting>
<option name="buildVersion" value="13" />
<option name="displayName" value="1.7.1 (13)" />
<option name="displayVersion" value="1.7.1" />
</VersionSetting>
</list>
</option>
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>

View File

@@ -33,13 +33,14 @@ android {
applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26
targetSdk = 35
versionCode = 15
versionName = "1.8.0"
versionCode = 16
versionName = "2.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
versionNameSuffix = "prod"
}
buildTypes {
@@ -112,7 +113,10 @@ dependencies {
implementation(libs.accompanist.swiperefresh)
// json
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)

View File

@@ -12,7 +12,6 @@ import javax.inject.Inject
@HiltAndroidApp
class PolytechnicApplication : Application() {
@Suppress("unused")
@Inject
lateinit var container: AppContainer

View File

@@ -4,6 +4,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.data.schedule.ScheduleRepository
import ru.n08i40k.polytechnic.next.model.Day
@@ -11,6 +15,25 @@ import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonTime
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.model.SubGroup
import ru.n08i40k.polytechnic.next.utils.now
private fun genLocalDateTime(hour: Int, minute: Int): Instant {
return LocalDateTime(2024, 1, 1, hour, minute, 0, 0).toInstant(TimeZone.currentSystemDefault())
}
private fun genBreak(start: Instant, end: Instant): Lesson {
return Lesson(
type = LessonType.BREAK,
defaultRange = null,
name = null,
time = LessonTime(
start,
end
),
subGroups = listOf()
)
}
class FakeScheduleRepository : ScheduleRepository {
@Suppress("SpellCheckingInspection")
@@ -19,132 +42,102 @@ class FakeScheduleRepository : ScheduleRepository {
name = "ИС-214/23", days = arrayListOf(
Day(
name = "Понедельник",
nonNullIndices = arrayListOf(0, 1, 2, 3, 4, 5),
defaultIndices = arrayListOf(2, 3, 4, 5),
customIndices = arrayListOf(0, 1),
lessons = arrayListOf(
date = LocalDateTime.now().toInstant(TimeZone.currentSystemDefault()),
lessons = listOf(
Lesson(
type = LessonType.CUSTOM,
defaultIndex = -1,
type = LessonType.ADDITIONAL,
defaultRange = null,
name = "Линейка",
time = LessonTime(510, 520),
cabinets = arrayListOf(),
teacherNames = arrayListOf(),
time = LessonTime(
genLocalDateTime(8, 30),
genLocalDateTime(8, 40),
),
subGroups = listOf()
),
genBreak(
genLocalDateTime(8, 40),
genLocalDateTime(8, 45),
),
Lesson(
type = LessonType.CUSTOM,
defaultIndex = -1,
type = LessonType.ADDITIONAL,
defaultRange = null,
name = "Разговор о важном",
time = LessonTime(525, 555),
cabinets = arrayListOf(),
teacherNames = arrayListOf(),
time = LessonTime(
genLocalDateTime(8, 45),
genLocalDateTime(9, 15),
),
subGroups = listOf()
),
genBreak(
genLocalDateTime(9, 15),
genLocalDateTime(9, 25),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 1,
name = "Элементы высшей математики",
time = LessonTime(565, 645),
cabinets = arrayListOf("31", "12"),
teacherNames = arrayListOf("Цацаева Т.Н."),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 2,
name = "Операционные системы и среды",
time = LessonTime(655, 735),
cabinets = arrayListOf("42", "52"),
teacherNames = arrayListOf("Сергачева А.О.", "Не Сергачева А.О."),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 3,
name = "Физическая культура",
time = LessonTime(755, 835),
cabinets = arrayListOf("c/3"),
teacherNames = arrayListOf("Васюнин В.Г.", "Не Васюнин В.Г."),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 4,
defaultRange = listOf(1, 1),
name = "МДК.05.01 Проектирование и дизайн информационных систем",
time = LessonTime(845, 925),
cabinets = arrayListOf("43"),
teacherNames = arrayListOf("Ивашова А.Н."),
time = LessonTime(
genLocalDateTime(9, 25),
genLocalDateTime(10, 45),
),
subGroups = listOf(
SubGroup(
teacher = "Ивашова А.Н.",
number = 1,
cabinet = "43"
)
)
),
null,
null,
)
), Day(
name = "Вторник",
nonNullIndices = arrayListOf(0, 1, 2),
defaultIndices = arrayListOf(0, 1, 2),
customIndices = arrayListOf(),
lessons = arrayListOf(
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 1,
name = "Стандартизация, сертификация и техническое документоведение",
time = LessonTime(525, 605),
cabinets = arrayListOf("42"),
teacherNames = arrayListOf("Сергачева А.О."),
genBreak(
genLocalDateTime(10, 45),
genLocalDateTime(10, 55),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 2,
name = "Элементы высшей математики",
time = LessonTime(620, 700),
cabinets = arrayListOf("31"),
teacherNames = arrayListOf("Цацаева Т.Н."),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 3,
defaultRange = listOf(2, 2),
name = "Основы проектирования баз данных",
time = LessonTime(720, 800),
cabinets = arrayListOf("21"),
teacherNames = arrayListOf("Чинарева Е.А."),
time = LessonTime(
genLocalDateTime(10, 55),
genLocalDateTime(12, 15),
),
subGroups = listOf(
SubGroup(
teacher = "Чинарева Е.А.",
number = 1,
cabinet = "21"
),
SubGroup(
teacher = "Ивашова А.Н.",
number = 2,
cabinet = "44"
),
)
),
genBreak(
genLocalDateTime(12, 15),
genLocalDateTime(12, 35),
),
null,
null,
null,
null,
null,
)
), Day(
name = "Среда",
nonNullIndices = arrayListOf(0, 1, 2),
defaultIndices = arrayListOf(0, 1, 2),
customIndices = arrayListOf(),
lessons = arrayListOf(
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 1,
defaultRange = listOf(3, 3),
name = "Операционные системы и среды",
time = LessonTime(525, 605),
cabinets = arrayListOf("42"),
teacherNames = arrayListOf("Сергачева А.О."),
time = LessonTime(
genLocalDateTime(12, 35),
genLocalDateTime(13, 55),
),
subGroups = listOf(
SubGroup(
teacher = "Сергачева А.О.",
number = 1,
cabinet = "42"
),
SubGroup(
teacher = "Воронцева Н.В.",
number = 2,
cabinet = "41"
),
)
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 2,
name = "Элементы высшей математики",
time = LessonTime(620, 700),
cabinets = arrayListOf("31"),
teacherNames = arrayListOf("Цацаева Т.Н."),
),
Lesson(
type = LessonType.DEFAULT,
defaultIndex = 3,
name = "МДК.05.01 Проектирование и дизайн информационных систем",
time = LessonTime(720, 800),
cabinets = arrayListOf("43"),
teacherNames = arrayListOf("Ивашова А.Н."),
),
null,
null,
null,
null,
null,
)
)
)

View File

@@ -13,7 +13,13 @@ class FakeProfileRepository : ProfileRepository {
companion object {
val exampleProfile =
Profile("66db32d24030a07e02d974c5", "n08i40k", "ИС-214/23", UserRole.STUDENT)
Profile(
"66db32d24030a07e02d974c5",
"128735612876",
"n08i40k",
"ИС-214/23",
UserRole.STUDENT
)
}
override suspend fun getProfile(): MyResult<Profile> {

View File

@@ -1,30 +1,33 @@
package ru.n08i40k.polytechnic.next.model
import android.os.Parcelable
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
import kotlinx.serialization.Serializable
import ru.n08i40k.polytechnic.next.utils.getDayMinutes
import java.util.Calendar
import ru.n08i40k.polytechnic.next.utils.dateTime
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.now
@Parcelize
@Suppress("unused", "MemberVisibilityCanBePrivate")
@Serializable
class Day(
val name: String,
val nonNullIndices: ArrayList<Int>,
val defaultIndices: ArrayList<Int>,
val customIndices: ArrayList<Int>,
val lessons: ArrayList<Lesson?>
val date: @RawValue Instant,
val lessons: List<Lesson>
) : Parcelable {
fun distanceToNextByMinutes(from: Int): Pair<Int, Int>? {
fun distanceToNextByLocalDateTime(from: LocalDateTime): Pair<Int, Int>? {
val toIdx = lessons
.map { if (it?.time == null) null else it.time.start }
.indexOfFirst { if (it == null) false else it > from }
.map { it.time.start }
.indexOfFirst { it.dateTime > from }
if (toIdx == -1)
return null
return Pair(toIdx, lessons[toIdx]!!.time.start - from)
return Pair(toIdx, lessons[toIdx].time.start.dayMinutes - from.dayMinutes)
}
fun distanceToNextByIdx(from: Int? = null): Pair<Int, Int>? {
@@ -35,24 +38,22 @@ class Day(
val fromTime =
if (from != null)
fromLesson!!.time.end
fromLesson!!.time.end.dateTime
else
Calendar.getInstance()
.get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance()
.get(Calendar.MINUTE)
LocalDateTime.now()
return distanceToNextByMinutes(fromTime)
return distanceToNextByLocalDateTime(fromTime)
}
// current
val currentIdx: Int?
get() {
val minutes = Calendar.getInstance().getDayMinutes()
val now = LocalDateTime.now()
for (lessonIdx in nonNullIndices) {
val lesson = lessons[lessonIdx]!!
for (lessonIdx in lessons.indices) {
val lesson = lessons[lessonIdx]
if (lesson.time.start <= minutes && minutes < lesson.time.end)
if (lesson.time.start.dateTime <= now && now < lesson.time.end.dateTime)
return lessonIdx
}
@@ -67,36 +68,36 @@ class Day(
val currentKV: Pair<Int, Lesson>?
get() {
val idx = currentIdx ?: return null
return Pair(idx, lessons[idx]!!)
return Pair(idx, lessons[idx])
}
// first
val firstIdx: Int?
get() = nonNullIndices.getOrNull(0)
get() = if (lessons.isEmpty()) null else 0
val first: Lesson?
get() {
return lessons[firstIdx ?: return null]!!
return lessons[firstIdx ?: return null]
}
val firstKV: Pair<Int, Lesson>?
get() {
val idx = firstIdx ?: return null
return Pair(idx, lessons[idx]!!)
return Pair(idx, lessons[idx])
}
// last
val lastIdx: Int?
get() = nonNullIndices.getOrNull(nonNullIndices.size - 1)
get() = if (lessons.isEmpty()) null else lessons.size - 1
val last: Lesson?
get() {
return lessons[lastIdx ?: return null]!!
return lessons[lastIdx ?: return null]
}
val lastKV: Pair<Int, Lesson>?
get() {
val idx = lastIdx ?: return null
return Pair(idx, lessons[idx]!!)
return Pair(idx, lessons[idx])
}
}
}

View File

@@ -10,7 +10,7 @@ import java.util.Calendar
@Serializable
data class Group(
val name: String,
val days: ArrayList<Day?>
val days: List<Day>
) : Parcelable {
val currentIdx: Int?
get() {
@@ -27,7 +27,7 @@ data class Group(
return days.getOrNull(currentIdx ?: return null)
}
val currentKV: Pair<Int, Day?>?
val currentKV: Pair<Int, Day>?
get() {
val idx = currentIdx ?: return null
return Pair(idx, days[idx])

View File

@@ -5,26 +5,30 @@ import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.limit
@Parcelize
@Serializable
data class Lesson(
val type: LessonType,
val defaultIndex: Int,
val name: String,
val defaultRange: List<Int>?,
val name: String?,
val time: LessonTime,
val cabinets: ArrayList<String>,
val teacherNames: ArrayList<String>
val subGroups: List<SubGroup>
) : Parcelable {
val duration: Int
get() {
return time.end - time.start
val startMinutes = time.start.dayMinutes
val endMinutes = time.end.dayMinutes
return endMinutes - startMinutes
}
fun getNameAndCabinetsShort(context: Context): String {
val limitedName = name limit 15
val limitedName = name!! limit 15
val cabinets = subGroups.map { it.cabinet }
if (cabinets.isEmpty())
return limitedName

View File

@@ -1,9 +1,24 @@
package ru.n08i40k.polytechnic.next.model
import android.os.Parcelable
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
data class LessonTime(val start: Int, val end: Int) : Parcelable
data class LessonTime(
val start: @RawValue Instant,
val end: @RawValue Instant
) : Parcelable {
companion object {
fun fromLocalDateTime(start: LocalDateTime, end: LocalDateTime): LessonTime {
val timeZone = TimeZone.currentSystemDefault()
return LessonTime(start.toInstant(timeZone), end.toInstant(timeZone))
}
}
}

View File

@@ -11,5 +11,7 @@ private class LessonTypeIntSerializer : EnumAsIntSerializer<LessonType>(
@Serializable(with = LessonTypeIntSerializer::class)
enum class LessonType(val value: Int) {
DEFAULT(0), CUSTOM(1)
DEFAULT(0),
ADDITIONAL(1),
BREAK(2)
}

View File

@@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class Profile(
val id: String,
val accessToken: String,
val username: String,
val group: String,
val role: UserRole

View File

@@ -0,0 +1,13 @@
package ru.n08i40k.polytechnic.next.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
data class SubGroup(
val number: Int,
val cabinet: String,
val teacher: String
) : Parcelable

View File

@@ -1,5 +1,5 @@
package ru.n08i40k.polytechnic.next.network
object NetworkValues {
const val API_HOST = "https://polytechnic.n08i40k.ru:5050/api/v1/"
const val API_HOST = "https://192.168.0.103:5050/api/"
}

View File

@@ -16,8 +16,7 @@ import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleUpdate
import ru.n08i40k.polytechnic.next.network.tryFuture
import ru.n08i40k.polytechnic.next.network.tryGet
import java.util.logging.Logger
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import java.util.regex.Pattern
open class CachedRequest(
context: Context,
@@ -34,36 +33,43 @@ open class CachedRequest(
}, errorListener) {
private val appContainer: AppContainer = (context as PolytechnicApplication).container
@OptIn(ExperimentalEncodingApi::class)
suspend fun getMainPage(): MyResult<String> {
return withContext(Dispatchers.IO) {
val mainPageFuture = RequestFuture.newFuture<String>()
val request = StringRequest(
Method.GET,
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
mainPageFuture,
mainPageFuture
)
NetworkConnection.getInstance(context).addToRequestQueue(request)
when (val response = tryGet(mainPageFuture)) {
is MyResult.Failure -> response
is MyResult.Success -> {
val encodedMainPage = Base64.Default.encode(response.data.encodeToByteArray())
MyResult.Success(encodedMainPage)
}
}
}
companion object {
private const val REGEX: String = "<a href=\"(/\\d{4}/[\\w\\-_]+\\.xls)\">"
val pattern: Pattern = Pattern.compile(REGEX, Pattern.MULTILINE)
}
private suspend fun getXlsUrl(): MyResult<String> = withContext(Dispatchers.IO) {
val mainPageFuture = RequestFuture.newFuture<String>()
val request = StringRequest(
Method.GET,
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
mainPageFuture,
mainPageFuture
)
NetworkConnection.getInstance(context).addToRequestQueue(request)
val response = tryGet(mainPageFuture)
if (response is MyResult.Failure)
return@withContext response
val pageData = (response as MyResult.Success).data
val matcher = pattern.matcher(pageData)
if (!matcher.find())
return@withContext MyResult.Failure(RuntimeException("Required url not found!"))
MyResult.Success("https://politehnikum-eng.ru" + matcher.group(1))
}
private suspend fun updateMainPage(): MyResult<ScheduleGetCacheStatus.ResponseDto> {
return withContext(Dispatchers.IO) {
when (val mainPage = getMainPage()) {
is MyResult.Failure -> mainPage
when (val xlsUrl = getXlsUrl()) {
is MyResult.Failure -> xlsUrl
is MyResult.Success -> {
tryFuture {
ScheduleUpdate(
ScheduleUpdate.RequestDto(mainPage.data),
ScheduleUpdate.RequestDto(xlsUrl.data),
context,
it,
it

View File

@@ -15,7 +15,7 @@ class AuthChangePassword(
) : AuthorizedRequest(
context,
Method.POST,
"auth/change-password",
"v1/auth/change-password",
{ listener.onResponse(null) },
errorListener,
canBeUnauthorized = true

View File

@@ -5,34 +5,25 @@ import com.android.volley.Response
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.network.RequestBase
class AuthSignIn(
private val data: RequestDto,
context: Context,
listener: Response.Listener<ResponseDto>,
listener: Response.Listener<Profile>,
errorListener: Response.ErrorListener?
) : RequestBase(
context,
Method.POST,
"auth/sign-in",
"v2/auth/sign-in",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
@Serializable
data class RequestDto(val username: String, val password: String)
@Serializable
data class ResponseDto(val id: String, val accessToken: String, val group: String)
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
}
override fun getHeaders(): MutableMap<String, String> {
val headers = super.getHeaders()
headers["version"] = "2"
return headers
}
}

View File

@@ -5,18 +5,19 @@ import com.android.volley.Response
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.network.RequestBase
class AuthSignUp(
private val data: RequestDto,
context: Context,
listener: Response.Listener<ResponseDto>,
listener: Response.Listener<Profile>,
errorListener: Response.ErrorListener?
) : RequestBase(
context,
Method.POST,
"auth/sign-up",
"v2/auth/sign-up",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
@@ -28,9 +29,6 @@ class AuthSignUp(
val role: UserRole
)
@Serializable
data class ResponseDto(val id: String, val accessToken: String)
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
}

View File

@@ -11,7 +11,7 @@ class FcmSetToken(
errorListener: Response.ErrorListener?,
) : AuthorizedRequest(
context, Method.POST,
"fcm/set-token/$token",
"v1/fcm/set-token/$token",
{ listener.onResponse(Unit) },
errorListener,
true

View File

@@ -11,7 +11,7 @@ class FcmUpdateCallback(
errorListener: Response.ErrorListener?,
) : AuthorizedRequest(
context, Method.POST,
"fcm/update-callback/$version",
"v1/fcm/update-callback/$version",
{ listener.onResponse(Unit) },
errorListener,
true

View File

@@ -15,7 +15,7 @@ class ProfileChangeGroup(
) : AuthorizedRequest(
context,
Method.POST,
"users/change-group",
"v1/users/change-group",
{ listener.onResponse(null) },
errorListener
) {

View File

@@ -15,7 +15,7 @@ class ProfileChangeUsername(
) : AuthorizedRequest(
context,
Method.POST,
"users/change-username",
"v1/users/change-username",
{ listener.onResponse(null) },
errorListener
) {

View File

@@ -13,7 +13,7 @@ class ProfileMe(
) : AuthorizedRequest(
context,
Method.GET,
"users/me",
"v2/users/me",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
)

View File

@@ -13,8 +13,8 @@ class ScheduleGet(
errorListener: Response.ErrorListener? = null
) : CachedRequest(
context,
Method.POST,
"schedule/get-group",
Method.GET,
"v2/schedule/group",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
@@ -25,6 +25,6 @@ class ScheduleGet(
data class ResponseDto(
val updatedAt: String,
val group: Group,
val lastChangedDays: ArrayList<Int>,
val updated: ArrayList<Int>,
)
}

View File

@@ -13,7 +13,7 @@ class ScheduleGetCacheStatus(
) : AuthorizedRequest(
context,
Method.GET,
"schedule/cache-status",
"v2/schedule/cache-status",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
@@ -24,11 +24,4 @@ class ScheduleGetCacheStatus(
val lastCacheUpdate: Long,
val lastScheduleUpdate: Long,
)
override fun getHeaders(): MutableMap<String, String> {
val headers = super.getHeaders()
headers["version"] = "1"
return headers
}
}

View File

@@ -13,7 +13,7 @@ class ScheduleGetGroupNames(
) : RequestBase(
context,
Method.GET,
"schedule/get-group-names",
"v2/schedule/group-names",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {

View File

@@ -14,22 +14,13 @@ class ScheduleUpdate(
errorListener: Response.ErrorListener? = null
) : AuthorizedRequest(
context,
Method.POST,
"schedule/update-site-main-page",
Method.PATCH,
"v2/schedule/update-download-url",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
@Serializable
data class RequestDto(val mainPage: String)
data class RequestDto(val url: String)
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
}
override fun getHeaders(): MutableMap<String, String> {
val headers = super.getHeaders()
headers["version"] = "1"
return headers
}
}
override fun getBody(): ByteArray = Json.encodeToString(data).toByteArray()
}

View File

@@ -13,7 +13,7 @@ class ScheduleReplacerClear(
) : AuthorizedRequest(
context,
Method.POST,
"schedule-replacer/clear",
"v1/schedule-replacer/clear",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {

View File

@@ -13,7 +13,7 @@ class ScheduleReplacerGet(
) : AuthorizedRequest(
context,
Method.GET,
"schedule-replacer/get",
"v1/schedule-replacer/get",
{ listener.onResponse(Json.decodeFromString(it)) },
errorListener
)

View File

@@ -14,7 +14,7 @@ class ScheduleReplacerSet(
) : AuthorizedMultipartRequest(
context,
Method.POST,
"schedule-replacer/set",
"v1/schedule-replacer/set",
{ listener.onResponse(null) },
errorListener
) {

View File

@@ -10,6 +10,7 @@ import android.os.IBinder
import android.os.Looper
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat.startForegroundService
import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.NotificationChannels
import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R
@@ -17,6 +18,7 @@ import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
import ru.n08i40k.polytechnic.next.utils.getDayMinutes
import java.util.Calendar
@@ -64,7 +66,7 @@ class CurrentLessonViewService : Service() {
override fun run() {
val logger = Logger.getLogger("CLV.updateRunnable")
if (day == null || day!!.nonNullIndices.isEmpty()) {
if (day == null || day!!.lessons.isEmpty()) {
logger.warning("Stopping, because day is null or empty!")
stopSelf()
return
@@ -99,15 +101,16 @@ class CurrentLessonViewService : Service() {
return
}
val firstLessonIdx = day!!.distanceToNextByMinutes(0)?.first
?: throw NullPointerException("Is this even real?")
val distanceToFirst = day!!.lessons[firstLessonIdx]!!.time!!.start - currentMinutes
val firstLessonIdx =
day!!.distanceToNextByLocalDateTime(LocalDateTime(0, 0, 0, 0, 0))?.first
?: throw NullPointerException("Is this even real?")
val distanceToFirst = day!!.lessons[firstLessonIdx]!!.time!!.start.dayMinutes - currentMinutes
val currentLessonDelay =
if (currentLesson == null) // Если эта пара - перемена, то конец перемены через (результат getDistanceToNext)
nextLessonEntry!!.second
else // Если эта пара - обычная пара, то конец пары через (конец этой пары - текущее кол-во минут)
currentLesson.time!!.end - currentMinutes
currentLesson.time!!.end.dayMinutes - currentMinutes
val currentLessonName =
currentLesson?.getNameAndCabinetsShort(this@CurrentLessonViewService)
@@ -144,7 +147,7 @@ class CurrentLessonViewService : Service() {
getString(
R.string.lesson_going_notification_description,
currentLessonName,
nextLessonTotal.fmtAsClock(),
nextLessonTotal.dayMinutes.fmtAsClock(),
nextLessonName,
)
)
@@ -183,7 +186,7 @@ class CurrentLessonViewService : Service() {
}
val currentDay = group.current
if (currentDay == null || currentDay.nonNullIndices.isEmpty()) {
if (currentDay == null || currentDay.lessons.isEmpty()) {
logger.warning("Stopping, because current day is null or empty")
stopSelf()
return
@@ -191,7 +194,7 @@ class CurrentLessonViewService : Service() {
val nowMinutes = Calendar.getInstance().getDayMinutes()
if (nowMinutes < ((5 * 60) + 30)
|| currentDay.last!!.time.end < nowMinutes
|| currentDay.last!!.time.end.dayMinutes < nowMinutes
) {
logger.warning("Stopping, because service started outside of acceptable time range!")
stopSelf()

View File

@@ -28,7 +28,7 @@ import ru.n08i40k.polytechnic.next.work.FcmSetTokenWorker
import java.time.Duration
class MyFirebaseMessagingService : FirebaseMessagingService() {
val scope = CoroutineScope(Job() + Dispatchers.Main)
private val scope = CoroutineScope(Job() + Dispatchers.Main)
override fun onNewToken(token: String) {
super.onNewToken(token)
@@ -53,7 +53,6 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
@DrawableRes iconId: Int,
title: String,
contentText: String,
priority: Int,
id: Any?,
intent: Intent? = null
) {
@@ -70,7 +69,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
.setSmallIcon(iconId)
.setContentTitle(title)
.setContentText(contentText)
.setPriority(priority)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()
@@ -103,7 +102,6 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
else
R.string.schedule_update_default
),
NotificationCompat.PRIORITY_DEFAULT,
message.data["etag"]
)
}
@@ -121,7 +119,6 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
R.drawable.download,
getString(R.string.app_update_title, message.data["version"]),
getString(R.string.app_update_description),
NotificationCompat.PRIORITY_DEFAULT,
message.data["version"],
Intent(Intent.ACTION_VIEW, Uri.parse(message.data["downloadLink"]))
)

View File

@@ -29,7 +29,12 @@ val FilledGroup.Download: ImageVector
) {
moveTo(3.0f, 12.3f)
verticalLineToRelative(7.0f)
arcToRelative(2.0f, 2.0f, 0.0f, false, false, 2.0f, 2.0f)
arcToRelative(2.0f, 2.0f, 0.0f,
isMoreThanHalf = false,
isPositiveArc = false,
dx1 = 2.0f,
dy1 = 2.0f
)
horizontalLineTo(19.0f)
arcToRelative(2.0f, 2.0f, 0.0f, false, false, 2.0f, -2.0f)
verticalLineToRelative(-7.0f)

View File

@@ -2,14 +2,11 @@ package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
@@ -20,7 +17,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -56,7 +52,7 @@ private fun getCurrentLessonIdx(day: Day?): Flow<Int> {
fun DayCard(
modifier: Modifier = Modifier,
day: Day? = FakeScheduleRepository.exampleGroup.days[0],
current: Boolean = true
distance: Int = 0
) {
val defaultCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
@@ -75,7 +71,7 @@ fun DayCard(
modifier = modifier,
colors = CardDefaults.cardColors(
containerColor =
if (current) MaterialTheme.colorScheme.primaryContainer
if (distance == 0) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.secondaryContainer
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.inverseSurface)
@@ -85,6 +81,7 @@ fun DayCard(
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge,
text = stringResource(R.string.day_null)
)
return@Card
@@ -96,66 +93,55 @@ fun DayCard(
text = day.name,
)
val currentLessonIdx by getCurrentLessonIdx(if (current) day else null)
if (distance >= -1 && distance <= 1) {
Text(
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
text = stringResource(when (distance) {
-1 -> R.string.yesterday
0 -> R.string.today
1 -> R.string.tommorow
else -> throw RuntimeException()
}),
)
}
val currentLessonIdx by getCurrentLessonIdx(if (distance == 0) day else null)
.collectAsStateWithLifecycle(0)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(0.5.dp)
) {
if (day.nonNullIndices.isEmpty()) {
if (day.lessons.isEmpty()) {
Text("Can't get schedule!")
return@Column
}
for (i in day.nonNullIndices.first()..day.nonNullIndices.last()) {
val lesson = day.lessons[i]!!
for (lessonIdx in day.lessons.indices) {
val lesson = day.lessons[lessonIdx]
val cardColors = when (lesson.type) {
LessonType.DEFAULT -> defaultCardColors
LessonType.CUSTOM -> customCardColors
LessonType.ADDITIONAL -> customCardColors
LessonType.BREAK -> noneCardColors
}
val mutableExpanded = remember { mutableStateOf(false) }
val lessonBoxModifier = remember {
Box(
Modifier
.padding(PaddingValues(2.5.dp, 0.dp))
.clickable { mutableExpanded.value = true }
.background(cardColors.containerColor)
}
Box(
modifier =
if (i == currentLessonIdx) lessonBoxModifier.border(
border = BorderStroke(
3.5.dp,
Color(
cardColors.containerColor.red * 0.5F,
cardColors.containerColor.green * 0.5F,
cardColors.containerColor.blue * 0.5F,
1F
)
)
)
else lessonBoxModifier
) {
LessonRow(
day, lesson, cardColors
)
}
if (i != day.nonNullIndices.last()) {
Box(
modifier = Modifier
.padding(PaddingValues(2.5.dp, 0.dp))
.background(noneCardColors.containerColor)
) {
FreeLessonRow(
lesson,
day.lessons[day.nonNullIndices[day.nonNullIndices.indexOf(i) + 1]]!!,
noneCardColors
)
}
val now = lessonIdx == currentLessonIdx
if (lesson.type === LessonType.BREAK)
FreeLessonRow(lesson, lesson, cardColors, now)
else
LessonRow(day, lesson, cardColors, now)
}
if (mutableExpanded.value)

View File

@@ -27,10 +27,10 @@ import java.util.logging.Level
import kotlin.math.absoluteValue
private fun isCurrentWeek(group: Group): Boolean {
if (group.days.size == 0 || group.days[0] == null)
if (group.days.isEmpty())
return true
val dateString = group.days[0]!!.name
val dateString = group.days[0].name
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale("ru"))
val datePart = dateString.split(" ").getOrNull(1) ?: return true
@@ -49,10 +49,12 @@ private fun isCurrentWeek(group: Group): Boolean {
@Composable
fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
val currentDay = (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 2)
val calendarDay = currentDay
.coerceAtLeast(0)
.coerceAtMost(group.days.size - 1)
val pagerState = rememberPagerState(initialPage = calendarDay, pageCount = { group.days.size })
val calendarDay = if (currentDay == -1) 6 else currentDay
val pagerState = rememberPagerState(
initialPage = calendarDay
.coerceAtMost(group.days.size - 1),
pageCount = { group.days.size })
Column {
if (!isCurrentWeek(group)) {
@@ -65,7 +67,9 @@ fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
state = pagerState,
contentPadding = PaddingValues(horizontal = 20.dp),
verticalAlignment = Alignment.Top,
modifier = Modifier.height(600.dp).padding(top = 5.dp)
modifier = Modifier
.height(600.dp)
.padding(top = 5.dp)
) { page ->
DayCard(
modifier = Modifier.graphicsLayer {
@@ -82,7 +86,7 @@ fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
)
},
day = group.days[page],
current = currentDay == page
distance = page - currentDay
)
}
}

View File

@@ -1,12 +1,13 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
@@ -26,11 +27,15 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleRepository
import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonTime
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.model.SubGroup
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
private enum class LessonTimeFormat {
@@ -58,28 +63,36 @@ private fun fmtTime(start: Int, end: Int, format: LessonTimeFormat): ArrayList<S
@Preview(showBackground = true)
@Composable
fun LessonExtraInfo(
lesson: Lesson = FakeScheduleRepository.exampleGroup.days[0]!!.lessons[0]!!,
lesson: Lesson = FakeScheduleRepository.exampleGroup.days[0].lessons[0],
mutableExpanded: MutableState<Boolean> = mutableStateOf(true)
) {
Dialog(onDismissRequest = { mutableExpanded.value = false }) {
if (lesson.type === LessonType.BREAK) {
mutableExpanded.value = false
return@Dialog
}
Card {
Column(Modifier.padding(10.dp)) {
Text(lesson.name)
Text(lesson.name!!)
if (lesson.teacherNames.isNotEmpty()) {
val teachers = buildString {
append(stringResource(if (lesson.teacherNames.count() > 1) R.string.lesson_teachers else R.string.lesson_teacher))
for (subGroup in lesson.subGroups) {
val subGroups = buildString {
append("[")
append(subGroup.number)
append("] ")
append(subGroup.teacher)
append(" - ")
append(lesson.teacherNames.joinToString(", "))
append(subGroup.cabinet)
}
Text(teachers)
Text(subGroups)
}
val duration = buildString {
append(stringResource(R.string.lesson_duration))
append(" - ")
val duration =
lesson.time.end - lesson.time.start
lesson.time.end.dayMinutes - lesson.time.start.dayMinutes
append(duration / 60)
append(stringResource(R.string.hours))
@@ -88,15 +101,6 @@ fun LessonExtraInfo(
append(stringResource(R.string.minutes))
}
Text(duration)
if (lesson.cabinets.isNotEmpty()) {
val cabinets = buildString {
append(stringResource(R.string.cabinets))
append(" - ")
append(lesson.cabinets.joinToString(", "))
}
Text(cabinets)
}
}
}
}
@@ -105,47 +109,74 @@ fun LessonExtraInfo(
@Preview(showBackground = true)
@Composable
private fun LessonViewRow(
idx: Int = 1,
time: LessonTime? = LessonTime(0, 60),
range: List<Int>? = listOf(1, 3),
time: LessonTime = LessonTime.fromLocalDateTime(
LocalDateTime(2024, 1, 1, 0, 0),
LocalDateTime(2024, 1, 1, 1, 0),
),
timeFormat: LessonTimeFormat = LessonTimeFormat.FROM_TO,
name: String = "Test",
teacherNames: ArrayList<String> = arrayListOf(
"Хомченко Н.Е. (1 подggggggggggggggggggggggggggggggggggggggгруппа)",
"Хомченко Н.Е. (2 подгруппа)"
),
cabinets: ArrayList<String> = arrayListOf("14", "31"),
subGroups: List<SubGroup> = listOf(),
cardColors: CardColors = CardDefaults.cardColors(),
verticalPadding: Dp = 10.dp
verticalPadding: Dp = 10.dp,
now: Boolean = true,
) {
val contentColor =
if (timeFormat == LessonTimeFormat.FROM_TO) cardColors.contentColor else cardColors.disabledContentColor
if (timeFormat == LessonTimeFormat.FROM_TO) cardColors.contentColor
else cardColors.disabledContentColor
val teacherNamesRepl = teacherNames.map { it.replace("подгруппа", "подгр.") }
val rangeSize = if (range == null) 1 else (range[1] - range[0] + 1) * 2
Row(
modifier = Modifier.padding(10.dp, verticalPadding),
verticalAlignment = Alignment.CenterVertically,
Box(
if (now) Modifier.border(
BorderStroke(
3.5.dp,
Color(
cardColors.containerColor.red * 0.5F,
cardColors.containerColor.green * 0.5F,
cardColors.containerColor.blue * 0.5F,
1F
)
)
) else Modifier
) {
Text(
text = if (idx == -1) "1" else idx.toString(),
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
color = if (idx == -1) Color(0) else contentColor
)
Row(
modifier = Modifier.padding(10.dp, verticalPadding * rangeSize),
verticalAlignment = Alignment.CenterVertically,
) {
val rangeString = run {
if (range == null)
" "
else
buildString {
val same = range[0] == range[1]
Spacer(Modifier.width(7.5.dp))
if (time != null) {
val formattedTime: ArrayList<String> = fmtTime(time.start, time.end, timeFormat)
append(if (same) " " else range[0])
append(if (same) range[0] else "-")
append(if (same) " " else range[1])
}
}
Text(
text = rangeString,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
color = contentColor
)
Column(
modifier = Modifier.fillMaxWidth(0.25f),
modifier = Modifier.fillMaxWidth(0.20f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
val formattedTime: ArrayList<String> =
fmtTime(time.start.dayMinutes, time.end.dayMinutes, timeFormat)
Text(
text = formattedTime[0], fontFamily = FontFamily.Monospace, color = contentColor
text = formattedTime[0],
fontFamily = FontFamily.Monospace,
color = contentColor
)
if (formattedTime.count() > 1) {
Text(
text = formattedTime[1],
@@ -154,54 +185,47 @@ private fun LessonViewRow(
)
}
}
}
Spacer(Modifier.width(7.5.dp))
Column(
verticalArrangement = Arrangement.Center
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = name,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
)
for (teacherName in teacherNamesRepl) {
Column(verticalArrangement = Arrangement.Center) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = teacherName,
text = name,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
)
for (subGroup in subGroups) {
Text(
text = subGroup.teacher,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
)
}
}
Column(modifier = Modifier.wrapContentWidth()) {
if (subGroups.size != 1)
Text(text = "")
for (subGroup in subGroups) {
Text(
text = subGroup.cabinet,
maxLines = 1,
fontFamily = FontFamily.Monospace,
color = contentColor
)
}
}
}
Column(modifier = Modifier.wrapContentWidth()) {
if (cabinets.size <= teacherNamesRepl.size) {
Text(
text = "",
maxLines = 1
)
}
for (listIdx: Int in 0..<cabinets.size) {
Text(
text = cabinets[listIdx],
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
)
}
}
}
}
}
}
@@ -209,37 +233,39 @@ private fun LessonViewRow(
@Preview(showBackground = true)
@Composable
fun FreeLessonRow(
lesson: Lesson = FakeScheduleRepository.exampleGroup.days[0]!!.lessons[0]!!,
nextLesson: Lesson = FakeScheduleRepository.exampleGroup.days[0]!!.lessons[1]!!,
cardColors: CardColors = CardDefaults.cardColors()
lesson: Lesson = FakeScheduleRepository.exampleGroup.days[0].lessons[0],
nextLesson: Lesson = FakeScheduleRepository.exampleGroup.days[0].lessons[1],
cardColors: CardColors = CardDefaults.cardColors(),
now: Boolean = true
) {
LessonViewRow(
-1,
LessonTime(lesson.time.end, nextLesson.time.start),
lesson.defaultRange,
LessonTime(lesson.time.start, nextLesson.time.end),
LessonTimeFormat.ONLY_MINUTES_DURATION,
stringResource(R.string.lesson_break),
arrayListOf(),
arrayListOf(),
lesson.subGroups,
cardColors,
2.5.dp
2.5.dp,
now
)
}
@Preview(showBackground = true)
@Composable
fun LessonRow(
day: Day = FakeScheduleRepository.exampleGroup.days[0]!!,
lesson: Lesson = FakeScheduleRepository.exampleGroup.days[0]!!.lessons[0]!!,
cardColors: CardColors = CardDefaults.cardColors()
day: Day = FakeScheduleRepository.exampleGroup.days[0],
lesson: Lesson = FakeScheduleRepository.exampleGroup.days[0].lessons[0],
cardColors: CardColors = CardDefaults.cardColors(),
now: Boolean = true,
) {
LessonViewRow(
lesson.defaultIndex,
lesson.defaultRange,
lesson.time,
LessonTimeFormat.FROM_TO,
lesson.name,
lesson.teacherNames,
lesson.cabinets,
lesson.name!!,
lesson.subGroups,
cardColors,
5.dp
5.dp,
now
)
}

View File

@@ -0,0 +1,16 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.window.Dialog
import ru.n08i40k.polytechnic.next.R
@Preview(showSystemUi = true, showBackground = true)
@Composable
internal fun PaskhalkoDialog() {
Dialog(onDismissRequest = {}) {
Image(painterResource(R.drawable.paskhalko), contentDescription = "Paskhalko")
}
}

View File

@@ -1,5 +1,6 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -52,10 +54,16 @@ fun UpdateInfo(
onExpandedChange = { expanded = !expanded },
title = { ExpandableCardTitle(stringResource(R.string.update_info_header)) }
) {
var paskhalkoCounter by remember { mutableIntStateOf(0) }
if (paskhalkoCounter >= 10)
PaskhalkoDialog()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clickable { ++paskhalkoCounter }
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,

View File

@@ -323,7 +323,7 @@ fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable() () -> Unit
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {

View File

@@ -1,5 +1,10 @@
package ru.n08i40k.polytechnic.next.utils
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import java.util.Calendar
infix fun <T> T?.or(data: T): T {
@@ -30,4 +35,18 @@ infix fun String.limit(count: Int): String {
}
fun Calendar.getDayMinutes(): Int =
this.get(Calendar.HOUR_OF_DAY) * 60 + this.get(Calendar.MINUTE)
this.get(Calendar.HOUR_OF_DAY) * 60 + this.get(Calendar.MINUTE)
val Instant.dayMinutes: Int
get() = this.toLocalDateTime(TimeZone.currentSystemDefault()).dayMinutes
val LocalDateTime.dayMinutes: Int
get() = this.hour * 60 + this.minute
val Instant.dateTime: LocalDateTime
get() = this.toLocalDateTime(TimeZone.currentSystemDefault())
fun LocalDateTime.Companion.now(): LocalDateTime {
val clock = Clock.System.now()
return clock.toLocalDateTime(TimeZone.currentSystemDefault())
}

View File

@@ -5,7 +5,6 @@ import androidx.work.Worker
import androidx.work.WorkerParameters
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.service.CurrentLessonViewService
class LinkUpdateWorker(context: Context, params: WorkerParameters) :
Worker(context, params) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -73,4 +73,7 @@
<string name="already_exists">Пользователь с таким именем уже зарегистрирован!</string>
<string name="group_does_not_exists">Группа с таким названием не существует!</string>
<string name="no_connection">Нет подключения к интернету!</string>
<string name="today">Сегодня</string>
<string name="yesterday">Вчера</string>
<string name="tommorow">Завтра</string>
</resources>

View File

@@ -73,4 +73,7 @@
<string name="already_exists">A user with this name is already registered!</string>
<string name="group_does_not_exists">A group with this name does not exist!</string>
<string name="no_connection">No internet connection!</string>
<string name="today">Today</string>
<string name="yesterday">Yesterday</string>
<string name="tommorow">Tommorow</string>
</resources>

View File

@@ -1,6 +1,6 @@
[versions]
accompanistSwiperefresh = "0.36.0"
agp = "8.7.0"
agp = "8.7.1"
firebaseBom = "33.4.0"
hiltAndroid = "2.52"
hiltAndroidCompiler = "2.52"
@@ -12,12 +12,12 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1"
kotlinxSerializationJson = "1.7.3"
lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.9.2"
composeBom = "2024.09.03"
activityCompose = "1.9.3"
composeBom = "2024.10.00"
protobufLite = "3.0.1"
volley = "1.2.1"
datastore = "1.1.1"
navigationCompose = "2.8.2"
navigationCompose = "2.8.3"
googleFirebaseCrashlytics = "3.0.2"
workRuntime = "2.9.1"
@@ -40,9 +40,10 @@ androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version = "1.7.3" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version = "1.7.4" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
protobuf-lite = { module = "com.google.protobuf:protobuf-lite", version.ref = "protobufLite" }
volley = { group = "com.android.volley", name = "volley", version.ref = "volley" }