Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1077be1897 | ||
|
|
96db4fa3a6 | ||
|
|
830b4664fb | ||
|
|
dd4fb90dc4 | ||
|
|
a55fb0461c | ||
|
|
e789729c76 | ||
|
|
7e23895f0d | ||
|
|
708ad11dca | ||
|
|
b1418f79da | ||
|
|
d17c70e871 | ||
|
|
6cd211f744 |
@@ -74,9 +74,9 @@ jobs:
|
||||
SUFFIX=$((SUFFIX + 1))
|
||||
TAG="v${BASE}.${SUFFIX}"
|
||||
done
|
||||
echo "version_name=$BASE" >> "$GITHUB_OUTPUT"
|
||||
echo "version_name=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "version_tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "Generated version: $BASE (tag: $TAG)"
|
||||
echo "Generated version: $BASE"
|
||||
|
||||
- name: Restore keystore from secret
|
||||
# Ключ подписи передаётся как base64-секрет и декодируется в файл.
|
||||
|
||||
@@ -40,7 +40,8 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
|
||||
Vendored
+19
@@ -19,3 +19,22 @@
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
# Игнорировать отсутствие класса декодера JP2 в PdfBox-Android
|
||||
-dontwarn com.gemalto.jp2.JP2Decoder
|
||||
# На всякий случай, если PdfBox ссылается на другие отсутствующие библиотеки (например, ICU для текста)
|
||||
-dontwarn com.tom_roush.pdfbox.**
|
||||
|
||||
# Запретить R8 удалять и переименовывать внутренности PdfBox-Android
|
||||
-keep class com.tom_roush.pdfbox.** { *; }
|
||||
-keep interface com.tom_roush.pdfbox.** { *; }
|
||||
|
||||
# Moshi удаляется proguard
|
||||
-keepnames @com.squareup.moshi.JsonClass class *
|
||||
-keep class **JsonAdapter {
|
||||
public <init>(...);
|
||||
<fields>;
|
||||
}
|
||||
-keepclassmembers @com.squareup.moshi.JsonClass class * {
|
||||
synthetic <init>(...);
|
||||
}
|
||||
@@ -108,9 +108,13 @@ object PdfScheduleParser {
|
||||
|
||||
for (dayCell in daysColumn.cells) {
|
||||
val day = content(dayCell).split(' ')
|
||||
if(day.size == 1) {
|
||||
Log.v(TAG, "day.first(): ${day.first()}")
|
||||
}
|
||||
if(day.size == 1 && day.first() == "День") {
|
||||
val groupCell = select(lessonsColumn, dayCell).firstOrNull() ?: continue
|
||||
group = content(groupCell)
|
||||
Log.v(TAG, "Group: $group")
|
||||
continue
|
||||
}
|
||||
if(day.size != 2) {
|
||||
@@ -164,7 +168,7 @@ object PdfScheduleParser {
|
||||
val teacher = raw.substring(typeMatch.last().range.last+1, raw.length - room.length).trim()
|
||||
var subgroup: String? = null
|
||||
Log.d(TAG, "$subject $group")
|
||||
if(group != null && subject.startsWith("$group,")) {
|
||||
if(group != null && (subject.startsWith("$group,") || subject.startsWith("$group "))) {
|
||||
subject = subject.substring(group.length + 1).trim()
|
||||
val subgroupMatch = SUBGROUP_REGEX.find(subject)
|
||||
if(subgroupMatch != null) {
|
||||
@@ -197,6 +201,7 @@ object PdfScheduleParser {
|
||||
|
||||
/** Вывод таблички из PDF в SVG формате + отладочные жлементы из [Pdf2Table.asTable] **/
|
||||
Log.v(SVG_TAG, "====")
|
||||
Log.v(SVG_TAG+ "-" + (pageIndex+1), "====")
|
||||
for(line in svg.asSvg().split("\n")) {
|
||||
if(line.length > 250) {
|
||||
Log.v(SVG_TAG, "length: " + line.length)
|
||||
|
||||
@@ -54,17 +54,5 @@ fun BuildingsScreen() {
|
||||
}
|
||||
HorizontalDivider(Modifier.padding(horizontal = 16.dp))
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Text(
|
||||
"Версия ${BuildConfig.VERSION_NAME}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,15 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -48,20 +54,27 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import java.time.DayOfWeek
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
val hPadding = 16.dp
|
||||
@@ -128,6 +141,32 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
|
||||
if (day == null) {
|
||||
Text("Занятий нет", style = MaterialTheme.typography.bodyLarge)
|
||||
} else {
|
||||
|
||||
val schedule = state.schedule!!
|
||||
val weekDays = state.weekStart
|
||||
?.let { ws -> schedule.periods.firstOrNull { it.weekStart == ws } }
|
||||
?.let { schedule.daysOf(it) }
|
||||
.orEmpty()
|
||||
|
||||
val selectedDayIndex = weekDays.indexOfFirst { it.date == state.dayDate }.coerceAtLeast(0)
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = selectedDayIndex,
|
||||
pageCount = { weekDays.size }
|
||||
)
|
||||
LaunchedEffect(state.dayDate) {
|
||||
val targetPage = weekDays.indexOfFirst { it.date == state.dayDate }
|
||||
if (targetPage != -1 && targetPage != pagerState.currentPage) {
|
||||
pagerState.scrollToPage(targetPage) // Мгновенное переключение без анимации
|
||||
}
|
||||
}
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||
if (pagerState.isScrollInProgress && page in weekDays.indices) {
|
||||
viewModel.selectDay(weekDays[page].date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isTodayDay = day.date == state.todayDate
|
||||
val dayLabel = if (isTodayDay) "Сегодня " else ""
|
||||
|
||||
@@ -153,20 +192,31 @@ fun ScheduleScreen(viewModel: ScheduleViewModel, onSwitchGroup: () -> Unit) {
|
||||
days = weekDays,
|
||||
selectedDate = state.dayDate,
|
||||
todayDate = state.todayDate,
|
||||
onSelect = viewModel::selectDay
|
||||
onSelect = viewModel::selectDay,
|
||||
pagerState = pagerState
|
||||
)
|
||||
|
||||
HorizontalDivider(Modifier.padding(top=0.dp, start = hPadding, end = hPadding))
|
||||
if (day.intervals.all { it.lessons.isEmpty() }) {
|
||||
Text("Занятий нет", style = MaterialTheme.typography.bodyLarge)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
contentPadding = PaddingValues(vertical = 8.dp)
|
||||
) {
|
||||
itemsIndexed(day.intervals) { slotIndex, slot ->
|
||||
LessonSlot(slotIndex = slotIndex, slot = slot)
|
||||
|
||||
HorizontalPager(state = pagerState, modifier = Modifier.weight(1f)) { page ->
|
||||
val day = weekDays.getOrNull(page)
|
||||
|
||||
if (day == null || day.intervals.all { it.lessons.isEmpty() }) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("Занятий нет", style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
contentPadding = PaddingValues(vertical = 8.dp)
|
||||
) {
|
||||
itemsIndexed(day.intervals) { slotIndex, slot ->
|
||||
LessonSlot(slotIndex = slotIndex, slot = slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,14 +292,70 @@ private fun WeekSelector(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getVisiblePercentage(item: LazyListItemInfo, layoutInfo: LazyListLayoutInfo): Float {
|
||||
val viewportStart = layoutInfo.viewportStartOffset
|
||||
val viewportEnd = layoutInfo.viewportEndOffset
|
||||
|
||||
val itemStart = item.offset
|
||||
val itemEnd = item.offset + item.size
|
||||
|
||||
val visibleStart = max(itemStart, viewportStart)
|
||||
val visibleEnd = min(itemEnd, viewportEnd)
|
||||
val visibleSize = visibleEnd - visibleStart
|
||||
|
||||
return if (visibleSize <= 0) 0f else visibleSize.toFloat() / item.size
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DayChips(
|
||||
days: List<Day>,
|
||||
selectedDate: LocalDate?,
|
||||
todayDate: LocalDate?,
|
||||
pagerState: PagerState,
|
||||
onSelect: (LocalDate) -> Unit
|
||||
) {
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp), contentPadding = PaddingValues(horizontal = hPadding, vertical = 2.dp),) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val density = LocalDensity.current
|
||||
|
||||
val paddingPx = with(density) { hPadding.toPx() }
|
||||
|
||||
|
||||
LaunchedEffect(pagerState, days) {
|
||||
snapshotFlow {
|
||||
pagerState.currentPage
|
||||
}.collectLatest { targetPage ->
|
||||
|
||||
if (targetPage in days.indices) {
|
||||
val layoutInfo = listState.layoutInfo
|
||||
val visibleItems = layoutInfo.visibleItemsInfo
|
||||
val targetItem = visibleItems.firstOrNull { it.index == targetPage }
|
||||
|
||||
if (targetItem != null) {
|
||||
val visiblePercentage = getVisiblePercentage(targetItem, layoutInfo)
|
||||
val minVisibleThreshold = 0.9f
|
||||
|
||||
if (visiblePercentage < minVisibleThreshold) {
|
||||
val viewPortWidth = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset
|
||||
val halfViewport = viewPortWidth / 2f
|
||||
val centerOffset = (halfViewport - (targetItem.size / 2f) - paddingPx).toInt()
|
||||
|
||||
listState.animateScrollToItem(index = targetPage, scrollOffset = -centerOffset)
|
||||
}
|
||||
} else {
|
||||
val viewPortWidth = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset
|
||||
val halfViewport = viewPortWidth / 2f
|
||||
|
||||
val averageSize = visibleItems.map { it.size }.average().toInt().coerceAtLeast(150)
|
||||
val centerOffset = (halfViewport - (averageSize / 2f) - paddingPx).toInt()
|
||||
|
||||
listState.animateScrollToItem(index = targetPage, scrollOffset = -centerOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyRow(state = listState, horizontalArrangement = Arrangement.spacedBy(6.dp), contentPadding = PaddingValues(horizontal = hPadding, vertical = 2.dp),) {
|
||||
items(days, key = { it.date.toEpochDay() }) { day ->
|
||||
val selected = selectedDate == day.date
|
||||
val isTodayChip = todayDate == day.date
|
||||
|
||||
@@ -118,7 +118,7 @@ class AppUpdater(
|
||||
}
|
||||
|
||||
val remoteVersion = release.tagName?.trimStart('v', 'V')
|
||||
if (remoteVersion == null || !isNewerVersion(remoteVersion, BuildConfig.VERSION_NAME)) {
|
||||
if (remoteVersion == null || !isNewerVersion(remoteVersion, BuildConfig.VERSION_NAME.trimStart('v', 'V'))) {
|
||||
_state.value = UpdateState.UpToDate
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user