mirror of
https://github.com/n08i40k/polytechnic-android.git
synced 2025-12-06 17:57:46 +03:00
2.0.0
Переход на API v2 Переработано отображение расписания. - Один предмет теперь может занимать несколько пар. - В заголовке дня теперь может писаться "Сегодня", "Завтра", "Вчера". Обновление расписания теперь происходит без отгрузки целой страницы политехникума на сервер. Приложение теперь само находит ссылку с помощью регулярных выражений, что влечёт за собой малый прирост к скорости отправки запроса и его обработки сервером. Пасхалко.
This commit is contained in:
@@ -12,7 +12,6 @@ import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class PolytechnicApplication : Application() {
|
||||
@Suppress("unused")
|
||||
@Inject
|
||||
lateinit var container: AppContainer
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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/"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,7 @@ class AuthChangePassword(
|
||||
) : AuthorizedRequest(
|
||||
context,
|
||||
Method.POST,
|
||||
"auth/change-password",
|
||||
"v1/auth/change-password",
|
||||
{ listener.onResponse(null) },
|
||||
errorListener,
|
||||
canBeUnauthorized = true
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,7 @@ class ProfileChangeGroup(
|
||||
) : AuthorizedRequest(
|
||||
context,
|
||||
Method.POST,
|
||||
"users/change-group",
|
||||
"v1/users/change-group",
|
||||
{ listener.onResponse(null) },
|
||||
errorListener
|
||||
) {
|
||||
|
||||
@@ -15,7 +15,7 @@ class ProfileChangeUsername(
|
||||
) : AuthorizedRequest(
|
||||
context,
|
||||
Method.POST,
|
||||
"users/change-username",
|
||||
"v1/users/change-username",
|
||||
{ listener.onResponse(null) },
|
||||
errorListener
|
||||
) {
|
||||
|
||||
@@ -13,7 +13,7 @@ class ProfileMe(
|
||||
) : AuthorizedRequest(
|
||||
context,
|
||||
Method.GET,
|
||||
"users/me",
|
||||
"v2/users/me",
|
||||
{ listener.onResponse(Json.decodeFromString(it)) },
|
||||
errorListener
|
||||
)
|
||||
@@ -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>,
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class ScheduleGetGroupNames(
|
||||
) : RequestBase(
|
||||
context,
|
||||
Method.GET,
|
||||
"schedule/get-group-names",
|
||||
"v2/schedule/group-names",
|
||||
{ listener.onResponse(Json.decodeFromString(it)) },
|
||||
errorListener
|
||||
) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class ScheduleReplacerClear(
|
||||
) : AuthorizedRequest(
|
||||
context,
|
||||
Method.POST,
|
||||
"schedule-replacer/clear",
|
||||
"v1/schedule-replacer/clear",
|
||||
{ listener.onResponse(Json.decodeFromString(it)) },
|
||||
errorListener
|
||||
) {
|
||||
|
||||
@@ -13,7 +13,7 @@ class ScheduleReplacerGet(
|
||||
) : AuthorizedRequest(
|
||||
context,
|
||||
Method.GET,
|
||||
"schedule-replacer/get",
|
||||
"v1/schedule-replacer/get",
|
||||
{ listener.onResponse(Json.decodeFromString(it)) },
|
||||
errorListener
|
||||
)
|
||||
@@ -14,7 +14,7 @@ class ScheduleReplacerSet(
|
||||
) : AuthorizedMultipartRequest(
|
||||
context,
|
||||
Method.POST,
|
||||
"schedule-replacer/set",
|
||||
"v1/schedule-replacer/set",
|
||||
{ listener.onResponse(null) },
|
||||
errorListener
|
||||
) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]))
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
BIN
app/src/main/res/drawable-mdpi/paskhalko.jpg
Normal file
BIN
app/src/main/res/drawable-mdpi/paskhalko.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user