Кешированные ответы от сервера теперь находятся в специальном файле, а не в настройках.

Возвращён просмотр подробностей о паре.
This commit is contained in:
2025-03-20 04:40:57 +04:00
parent 15894b290a
commit 96f84b9f54
37 changed files with 396 additions and 90 deletions

View File

@@ -46,8 +46,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26
targetSdk = 35
versionCode = 27
versionName = "3.1.0"
versionCode = 28
versionName = "3.1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -158,7 +158,7 @@ dependencies {
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.29.3"
artifact = "com.google.protobuf:protoc:21.0-rc-1"
}
plugins {

View File

@@ -18,7 +18,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.app.AppContainer
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.proto.settings_v0
import ru.n08i40k.polytechnic.next.utils.Observable
import ru.n08i40k.polytechnic.next.worker.UpdateFCMTokenWorker
import ru.n08i40k.polytechnic.next.worker.UpdateLinkWorker
@@ -97,8 +98,35 @@ class Application : Application() {
override fun onCreate() {
super.onCreate()
runBlocking { fixupSettings() }
VKID.init(this)
setupFirebase()
}
private suspend fun fixupSettings() {
val accessToken = this.settings_v0.data.map { it.accessToken }.first()
if (accessToken.isEmpty())
return
val userId = this.settings_v0.data.map { it.userId }.first()
val group = this.settings_v0.data.map { it.group }.first()
val version = this.settings_v0.data.map { it.version }.first()
val fcmToken = this.settings_v0.data.map { it.fcmToken }.first()
this.settings.updateData {
it
.toBuilder()
.setUserId(userId)
.setAccessToken(accessToken)
.setGroup(group)
.setVersion(version)
.setFcmToken(fcmToken)
.build()
}
this.settings_v0.updateData { it.toBuilder().clear().build() }
}
}

View File

@@ -22,7 +22,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import ru.n08i40k.polytechnic.next.app.NotificationChannels
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.PolytechnicApp
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.utils.app

View File

@@ -5,6 +5,7 @@ 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
@@ -17,6 +18,8 @@ data class Lesson(
val group: String? = null,
val subGroups: List<SubGroup>
) : Parcelable {
val duration: Int get() = time.end.dayMinutes - time.start.dayMinutes
fun getShortName(context: Context): String {
val name =
if (type == LessonType.BREAK)

View File

@@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.app.AppContainer
import ru.n08i40k.polytechnic.next.network.RequestBase
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
open class AuthorizedRequest(
val appContainer: AppContainer,

View File

@@ -0,0 +1,29 @@
package ru.n08i40k.polytechnic.next.proto
import android.content.Context
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.DataStore
import androidx.datastore.core.Serializer
import androidx.datastore.dataStore
import com.google.protobuf.InvalidProtocolBufferException
import ru.n08i40k.polytechnic.next.Cache
import java.io.InputStream
import java.io.OutputStream
object CacheSerializer : Serializer<Cache> {
override val defaultValue: Cache = Cache.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Cache =
try {
Cache.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
override suspend fun writeTo(t: Cache, output: OutputStream) = t.writeTo(output)
}
val Context.cache: DataStore<Cache> by dataStore(
fileName = "cache.pb",
serializer = CacheSerializer
)

View File

@@ -1,4 +1,4 @@
package ru.n08i40k.polytechnic.next.settings
package ru.n08i40k.polytechnic.next.proto
import android.content.Context
import androidx.datastore.core.CorruptionException
@@ -23,7 +23,7 @@ object SettingsSerializer : Serializer<Settings> {
override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
}
val Context.settings: DataStore<Settings> by dataStore(
val Context.settings_v0: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)

View File

@@ -0,0 +1,29 @@
package ru.n08i40k.polytechnic.next.proto
import android.content.Context
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.DataStore
import androidx.datastore.core.Serializer
import androidx.datastore.dataStore
import com.google.protobuf.InvalidProtocolBufferException
import ru.n08i40k.polytechnic.next.SettingsV2
import java.io.InputStream
import java.io.OutputStream
object SettingsV2Serializer : Serializer<SettingsV2> {
override val defaultValue: SettingsV2 = SettingsV2.getDefaultInstance()
override suspend fun readFrom(input: InputStream): SettingsV2 =
try {
SettingsV2.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
override suspend fun writeTo(t: SettingsV2, output: OutputStream) = t.writeTo(output)
}
val Context.settings: DataStore<SettingsV2> by dataStore(
fileName = "settings-v2.pb",
serializer = SettingsV2Serializer
)

View File

@@ -1,12 +1,12 @@
package ru.n08i40k.polytechnic.next.repository.cache
import ru.n08i40k.polytechnic.next.CachedResponse
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.CacheResponse
interface NetworkCacheRepository {
suspend fun put(url: String, data: String)
suspend fun get(url: String): CachedResponse?
suspend fun get(url: String): CacheResponse?
suspend fun clear()
@@ -14,7 +14,7 @@ interface NetworkCacheRepository {
suspend fun setHash(hash: String)
suspend fun getUpdateDates(): UpdateDates
suspend fun getUpdateDates(): CacheDate
suspend fun setUpdateDates(cache: Long, schedule: Long)
}

View File

@@ -5,17 +5,18 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import ru.n08i40k.polytechnic.next.CachedResponse
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.CacheResponse
import ru.n08i40k.polytechnic.next.app.AppContainer
import ru.n08i40k.polytechnic.next.proto.cache
import ru.n08i40k.polytechnic.next.repository.cache.NetworkCacheRepository
import ru.n08i40k.polytechnic.next.settings.settings
import javax.inject.Inject
class LocalNetworkCacheRepository
@Inject constructor(private val appContainer: AppContainer) : NetworkCacheRepository {
private val cacheMap: MutableMap<String, CachedResponse> = mutableMapOf()
private var updateDates: UpdateDates = UpdateDates.newBuilder().build()
private val cacheMap: MutableMap<String, CacheResponse> = mutableMapOf()
private var cacheDate: CacheDate = CacheDate.newBuilder().build()
private var hash: String? = null
private val context get() = appContainer.context
@@ -26,14 +27,14 @@ class LocalNetworkCacheRepository
runBlocking {
cacheMap.putAll(
context
.settings
.cache
.data
.map { settings -> settings.cacheStorageMap }.first()
.map { it.storageMap }.first()
)
}
}
override suspend fun get(url: String): CachedResponse? {
override suspend fun get(url: String): CacheResponse? {
// Если кешированного ответа нет, то возвращаем null
// Если хеши не совпадают и локальный хеш присутствует, то возвращаем null
@@ -49,7 +50,7 @@ class LocalNetworkCacheRepository
if (hash == null)
throw IllegalStateException("Не установлен хеш!")
cacheMap[url] = CachedResponse
cacheMap[url] = CacheResponse
.newBuilder()
.setHash(this.hash)
.setData(data)
@@ -83,21 +84,21 @@ class LocalNetworkCacheRepository
}
}
override suspend fun getUpdateDates(): UpdateDates {
return this.updateDates
override suspend fun getUpdateDates(): CacheDate {
return this.cacheDate
}
override suspend fun setUpdateDates(cache: Long, schedule: Long) {
updateDates = UpdateDates
cacheDate = CacheDate
.newBuilder()
.setCache(cache)
.setSchedule(schedule).build()
withContext(Dispatchers.IO) {
context.settings.updateData {
context.cache.updateData {
it
.toBuilder()
.setUpdateDates(updateDates)
.setDate(cacheDate)
.build()
}
}
@@ -106,10 +107,10 @@ class LocalNetworkCacheRepository
private suspend fun save() {
withContext(Dispatchers.IO) {
context.settings.updateData {
context.cache.updateData {
it
.toBuilder()
.putAllCacheStorage(cacheMap)
.putAllStorage(cacheMap)
.build()
}
}

View File

@@ -1,11 +1,11 @@
package ru.n08i40k.polytechnic.next.repository.cache.impl
import ru.n08i40k.polytechnic.next.CachedResponse
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.CacheResponse
import ru.n08i40k.polytechnic.next.repository.cache.NetworkCacheRepository
class MockNetworkCacheRepository : NetworkCacheRepository {
override suspend fun get(url: String): CachedResponse? {
override suspend fun get(url: String): CacheResponse? {
return null
}
@@ -19,8 +19,8 @@ class MockNetworkCacheRepository : NetworkCacheRepository {
override suspend fun setHash(hash: String) {}
override suspend fun getUpdateDates(): UpdateDates {
return UpdateDates.newBuilder().build()
override suspend fun getUpdateDates(): CacheDate {
return CacheDate.newBuilder().build()
}
override suspend fun setUpdateDates(cache: Long, schedule: Long) {}

View File

@@ -7,8 +7,9 @@ import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.network.request.fcm.FcmSetToken
import ru.n08i40k.polytechnic.next.network.request.profile.ProfileMe
import ru.n08i40k.polytechnic.next.network.tryFuture
import ru.n08i40k.polytechnic.next.proto.cache
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.repository.profile.ProfileRepository
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.utils.MyResult
import ru.n08i40k.polytechnic.next.utils.app
@@ -40,7 +41,14 @@ class RemoteProfileRepository(private val container: AppContainer) : ProfileRepo
override suspend fun signOut() {
val context = container.context
container.context.settings.updateData {
context.settings.updateData {
it
.toBuilder()
.clear()
.build()
}
context.cache.updateData {
it
.toBuilder()
.clear()
@@ -48,12 +56,5 @@ class RemoteProfileRepository(private val container: AppContainer) : ProfileRepo
}
context.app.events.signOut.next(Unit)
// context.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
// val pm = context.packageManager
// val intent = pm.getLaunchIntentForPackage(context.packageName)
// val mainIntent = Intent.makeRestartActivityTask(intent?.component)
// context.startActivity(mainIntent)
// Runtime.getRuntime().exit(0)
}
}

View File

@@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.Application
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.screen.MainScreen
import ru.n08i40k.polytechnic.next.ui.screen.auth.AuthScreen
import ru.n08i40k.polytechnic.next.utils.app

View File

@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.app.AppContainer
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
import ru.n08i40k.polytechnic.next.utils.MyResult
@@ -25,7 +25,7 @@ sealed interface GroupUiState {
data class HasData(
val group: GroupOrTeacher,
val updateDates: UpdateDates,
val cacheDate: CacheDate,
val lastUpdateAt: Long,
override val isLoading: Boolean
) : GroupUiState
@@ -33,7 +33,7 @@ sealed interface GroupUiState {
private data class GroupViewModelState(
val group: GroupOrTeacher? = null,
val updateDates: UpdateDates? = null,
val updateDates: CacheDate? = null,
val lastUpdateAt: Long = 0,
val isLoading: Boolean = false
) {

View File

@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.app.AppContainer
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
import ru.n08i40k.polytechnic.next.utils.MyResult
@@ -25,7 +25,7 @@ sealed interface SearchUiState {
data class HasData(
val teacher: GroupOrTeacher,
val updateDates: UpdateDates,
val cacheDate: CacheDate,
val lastUpdateAt: Long,
override val isLoading: Boolean
) : SearchUiState
@@ -33,7 +33,7 @@ sealed interface SearchUiState {
private data class SearchViewModelState(
val teacher: GroupOrTeacher? = null,
val updateDates: UpdateDates? = null,
val updateDates: CacheDate? = null,
val lastUpdateAt: Long = 0,
val isLoading: Boolean = false
) {

View File

@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.app.AppContainer
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
import ru.n08i40k.polytechnic.next.utils.MyResult
@@ -24,7 +24,7 @@ sealed interface TeacherUiState {
data class HasData(
val teacher: GroupOrTeacher,
val updateDates: UpdateDates,
val cacheDate: CacheDate,
val lastUpdateAt: Long,
override val isLoading: Boolean
) : TeacherUiState
@@ -32,7 +32,7 @@ sealed interface TeacherUiState {
private data class TeacherViewModelState(
val teacher: GroupOrTeacher? = null,
val updateDates: UpdateDates? = null,
val updateDates: CacheDate? = null,
val lastUpdateAt: Long = 0,
val isLoading: Boolean = false
) {

View File

@@ -38,7 +38,7 @@ import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.Application
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.AppRoute
import ru.n08i40k.polytechnic.next.ui.icons.AppIcons
import ru.n08i40k.polytechnic.next.ui.icons.appicons.Filled

View File

@@ -35,6 +35,8 @@ import androidx.navigation.compose.rememberNavController
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.proto.cache
import ru.n08i40k.polytechnic.next.ui.AppRoute
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
import ru.n08i40k.polytechnic.next.ui.helper.SnackbarBox
@@ -61,8 +63,7 @@ private fun FormWrapper(
with(localDensity) {
onWidthChange(it.size.width.toDp())
}
}
/*.animateContentSize()*/,
},
content = content
)
}
@@ -84,6 +85,8 @@ private fun AuthForm(parentNavController: NavController, pushSnackbar: PushSnack
}
val finish: () -> Unit = {
runBlocking { context.cache.updateData { it.toBuilder().clear().build() } }
parentNavController.navigate(AppRoute.MAIN.route) {
popUpTo(AppRoute.AUTH.route) { inclusive = true }
}

View File

@@ -40,7 +40,7 @@ import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignIn
import ru.n08i40k.polytechnic.next.network.unwrapException
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
import ru.n08i40k.polytechnic.next.ui.helper.data.rememberInputValue
import java.util.logging.Logger

View File

@@ -8,7 +8,7 @@ import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignInVK
import ru.n08i40k.polytechnic.next.network.unwrapException
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.widgets.OneTapComplete
import java.util.logging.Logger

View File

@@ -42,7 +42,7 @@ import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignUp
import ru.n08i40k.polytechnic.next.network.unwrapException
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
import ru.n08i40k.polytechnic.next.ui.helper.data.rememberInputValue
import ru.n08i40k.polytechnic.next.ui.widgets.selector.GroupSelector

View File

@@ -41,7 +41,7 @@ import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignUpVK
import ru.n08i40k.polytechnic.next.network.unwrapException
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
import ru.n08i40k.polytechnic.next.ui.helper.data.rememberInputValue
import ru.n08i40k.polytechnic.next.ui.widgets.selector.GroupSelector

View File

@@ -65,7 +65,7 @@ fun GroupScheduleScreen(viewModel: GroupViewModel) {
Column {
val data = uiState as GroupUiState.HasData
UpdateInfo(data.lastUpdateAt, data.updateDates)
UpdateInfo(data.lastUpdateAt, data.cacheDate)
Spacer(Modifier.height(10.dp))
SchedulePager(data.group)
}

View File

@@ -65,7 +65,7 @@ fun TeacherScheduleScreen(viewModel: TeacherViewModel) {
Column {
val data = uiState as TeacherUiState.HasData
UpdateInfo(data.lastUpdateAt, data.updateDates)
UpdateInfo(data.lastUpdateAt, data.cacheDate)
Spacer(Modifier.height(10.dp))
SchedulePager(data.teacher)
}

View File

@@ -85,7 +85,7 @@ fun TeacherSearchScreen(viewModel: SearchViewModel) {
Column {
val data = uiState as SearchUiState.HasData
UpdateInfo(data.lastUpdateAt, data.updateDates)
UpdateInfo(data.lastUpdateAt, data.cacheDate)
Spacer(Modifier.height(10.dp))
SchedulePager(data.teacher)
}

View File

@@ -19,8 +19,8 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import ru.n08i40k.polytechnic.next.CacheDate
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCard
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCardTitle
import ru.n08i40k.polytechnic.next.utils.*
@@ -32,15 +32,15 @@ val expanded = mutableStateOf(false)
@Composable
fun UpdateInfo(
lastUpdateAt: Long = 0,
updateDates: UpdateDates = UpdateDates.newBuilder().build()
cacheDate: CacheDate = CacheDate.newBuilder().build()
) {
var expanded by remember { expanded }
val format = "HH:mm:ss dd.MM.yyyy"
val currentDate = Date(lastUpdateAt).toString(format)
val cacheUpdateDate = Date(updateDates.cache).toString(format)
val scheduleUpdateDate = Date(updateDates.schedule).toString(format)
val cacheUpdateDate = Date(cacheDate.cache).toString(format)
val scheduleUpdateDate = Date(cacheDate.schedule).toString(format)
ExpandableCard(
expanded = expanded,

View File

@@ -18,9 +18,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -34,6 +32,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.utils.dateTime
@@ -80,7 +79,8 @@ private fun getCurrentLessonIdx(day: Day?): Flow<Int> {
@Composable
fun DayCard(
modifier: Modifier = Modifier,
day: Day = MockScheduleRepository.exampleTeacher.days[0]
day: Day = MockScheduleRepository.exampleTeacher.days[0],
onLessonClick: (Lesson) -> Unit = {},
) {
val offset = remember(day) { getDayOffset(day) }
@@ -119,9 +119,9 @@ fun DayCard(
style = MaterialTheme.typography.titleLarge
)
if (day.street != null) {
day.street?.let {
Text(
day.street,
it,
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
)
@@ -177,12 +177,9 @@ fun DayCard(
LessonType.EXAM_DEFAULT -> examCardColors
}
// TODO: Вернуть ExtraInfo
var extraInfo by remember { mutableStateOf(false) }
Box(
Modifier
.clickable { extraInfo = true }
.clickable { onLessonClick(lesson) }
.background(cardColors.containerColor)
) {
val modifier =

View File

@@ -0,0 +1,150 @@
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.window.Dialog
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
class LessonPreviewParameterProvider : PreviewParameterProvider<Lesson> {
override val values: Sequence<Lesson>
get() {
val lessons = MockScheduleRepository.exampleGroup.days[0].lessons
return sequenceOf(
lessons[0],
lessons[2],
lessons[4],
lessons[6],
)
}
}
@Preview
@Composable
private fun ExtraInfoDialogPreview(
@PreviewParameter(LessonPreviewParameterProvider::class) lesson: Lesson
) {
ExtraInfoDialog(lesson) { }
}
@Composable
fun ExtraInfoDialog(
lesson: Lesson,
onDismiss: () -> Unit
) {
Dialog(onDismiss) {
Card {
Column(Modifier.padding(10.dp)) {
var minWidth by remember { mutableStateOf(Dp.Unspecified) }
val density = LocalDensity.current
@Composable
fun kvText(title: String, text: String) {
Row(
Modifier.alpha(if (minWidth == Dp.Unspecified) 0f else 1f),
verticalAlignment = Alignment.CenterVertically
) {
Text(
title,
Modifier
.onGloballyPositioned {
with(density) {
val dp = it.size.width.toDp()
minWidth =
if (minWidth == Dp.Unspecified)
dp
else
max(minWidth, dp)
}
}
.size(minWidth, Dp.Unspecified),
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
textAlign = TextAlign.Right,
)
Text(text)
}
}
kvText(stringResource(R.string.extra_info_lesson_name), lesson.name ?: "")
when (lesson.type) {
LessonType.BREAK -> throw IllegalArgumentException()
LessonType.DEFAULT -> null
LessonType.ADDITIONAL -> null
LessonType.CONSULTATION -> R.string.lesson_type_consultation
LessonType.INDEPENDENT_WORK -> R.string.lesson_type_independent_work
LessonType.EXAM -> R.string.lesson_type_exam
LessonType.EXAM_WITH_GRADE -> R.string.lesson_type_exam_with_grade
LessonType.EXAM_DEFAULT -> R.string.lesson_type_exam_default
}?.let {
kvText(stringResource(R.string.extra_info_type), stringResource(it))
}
if (lesson.subGroups.size == 1) {
kvText(
stringResource(R.string.extra_info_teacher),
stringResource(
R.string.extra_info_teacher_second,
lesson.subGroups[0].teacher,
lesson.subGroups[0].cabinet
)
)
} else {
for (subGroup in lesson.subGroups) {
kvText(
stringResource(R.string.extra_info_teacher),
stringResource(
R.string.extra_info_teacher_second_subgroup,
subGroup.teacher,
subGroup.cabinet,
subGroup.number
)
)
}
}
kvText(
stringResource(R.string.extra_info_duration),
stringResource(
R.string.extra_info_duration_second,
lesson.time.start.dayMinutes.fmtAsClock(),
lesson.time.end.dayMinutes.fmtAsClock(),
lesson.duration / 60,
lesson.duration % 60
)
)
}
}
}
}

View File

@@ -7,6 +7,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
@@ -17,10 +21,13 @@ import androidx.compose.ui.util.lerp
import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.ui.widgets.NotificationCard
import ru.n08i40k.polytechnic.next.utils.dateTime
import ru.n08i40k.polytechnic.next.utils.now
import java.lang.ref.WeakReference
import java.util.logging.Level
import kotlin.math.absoluteValue
@@ -40,6 +47,8 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
pageCount = { schedule.days.size }
)
var dialogLesson by remember { mutableStateOf<WeakReference<Lesson>?>(null) }
Column {
if (isScheduleOutdated(schedule))
NotificationCard(Level.WARNING, stringResource(R.string.outdated_schedule))
@@ -69,7 +78,14 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
)
},
schedule.days[page]
)
) { dialogLesson = WeakReference(it) }
}
}
dialogLesson?.get()?.let { lesson ->
if (lesson.type == LessonType.BREAK)
return@let
ExtraInfoDialog(lesson) { dialogLesson = null }
}
}

View File

@@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.app.appContainer
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import ru.n08i40k.polytechnic.next.utils.MyResult
import java.time.Duration
import java.util.logging.Logger

View File

@@ -8,7 +8,7 @@ import androidx.work.WorkerParameters
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import ru.n08i40k.polytechnic.next.app.appContainer
import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.proto.settings
import java.util.concurrent.TimeUnit
import java.util.logging.Logger

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
option java_package = "ru.n08i40k.polytechnic.next";
option java_multiple_files = true;
message CacheResponse {
string hash = 1;
string data = 2;
}
message CacheDate {
int64 cache = 1;
int64 schedule = 2;
}
message Cache {
map<string, CacheResponse> storage = 4;
CacheDate date = 5;
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
option java_package = "ru.n08i40k.polytechnic.next";
option java_multiple_files = true;
message SettingsV2 {
string user_id = 1;
string access_token = 2;
string group = 3;
string version = 5;
string suppressed_version = 6;
string fcm_token = 7;
}

View File

@@ -100,4 +100,11 @@
<string name="day_view_end_description">Ура, можно идти домой! Наверное :(</string>
<string name="day_view_channel_name">Текущая пара</string>
<string name="day_view_channel_description">Отображает текущую пару или перемену в уведомлении</string>
<string name="extra_info_lesson_name">"Название: "</string>
<string name="extra_info_teacher">"Преподаватель: "</string>
<string name="extra_info_teacher_second">%1$s в %2$s каб.</string>
<string name="extra_info_teacher_second_subgroup">%1$s в %2$s каб. [подгруппа - %3$d]</string>
<string name="extra_info_duration">"Длительность: "</string>
<string name="extra_info_duration_second">С %1$s до %2$s (%3$d ч. %4$d мин.)</string>
<string name="extra_info_type">"Тип: "</string>
</resources>

View File

@@ -100,4 +100,11 @@
<string name="day_view_end_description">ya ne budu eto perevidit\'</string>
<string name="day_view_channel_name">Current lesson</string>
<string name="day_view_channel_description">View the current lesson and breaks in notification</string>
<string name="extra_info_lesson_name">"Name: "</string>
<string name="extra_info_teacher">"Teacher: "</string>
<string name="extra_info_teacher_second">%1$s in %2$s cab.</string>
<string name="extra_info_teacher_second_subgroup">%1$s in %2$s cab. [subgroup - %3$d]</string>
<string name="extra_info_duration">"Duration: "</string>
<string name="extra_info_duration_second">From %1$s to %2$s (%3$d h. %4$d min.)</string>
<string name="extra_info_type">"Type: "</string>
</resources>