13 Commits

Author SHA1 Message Date
dependabot[bot]
bc1daf8e30 Bump com.google.dagger.hilt.android from 2.55 to 2.57.2
Bumps [com.google.dagger.hilt.android](https://github.com/google/dagger) from 2.55 to 2.57.2.
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.55...dagger-2.57.2)

---
updated-dependencies:
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 22:33:35 +00:00
Nikita
09871faaca Merge pull request #20 from n08i40k/dependabot/gradle/com.google.firebase-firebase-messaging-24.1.1
Bump com.google.firebase:firebase-messaging from 24.1.0 to 24.1.1
2025-03-25 02:18:17 +04:00
dependabot[bot]
d6fd1ae5ff Merge pull request #22 from n08i40k/dependabot/gradle/com.google.devtools.ksp-2.1.20-1.0.31 2025-03-24 22:15:15 +00:00
dependabot[bot]
e7eda47e13 Merge pull request #21 from n08i40k/dependabot/gradle/com.google.dagger-hilt-android-compiler-2.56 2025-03-24 22:15:07 +00:00
dependabot[bot]
6d9f6c6b4e Bump com.google.devtools.ksp from 2.1.10-1.0.30 to 2.1.20-1.0.31
Bumps [com.google.devtools.ksp](https://github.com/google/ksp) from 2.1.10-1.0.30 to 2.1.20-1.0.31.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.10-1.0.30...2.1.20-1.0.31)

---
updated-dependencies:
- dependency-name: com.google.devtools.ksp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 22:13:41 +00:00
dependabot[bot]
b9cf49023a Merge pull request #23 from n08i40k/dependabot/gradle/plugin.serialization-2.1.20 2025-03-24 22:12:48 +00:00
dependabot[bot]
f8dee292dd Bump plugin.serialization from 2.1.10 to 2.1.20
Bumps [plugin.serialization](https://github.com/JetBrains/kotlin) from 2.1.10 to 2.1.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.1.10...v2.1.20)

---
updated-dependencies:
- dependency-name: plugin.serialization
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 20:45:15 +00:00
dependabot[bot]
c6fc268c19 Bump com.google.dagger:hilt-android-compiler from 2.55 to 2.56
Bumps [com.google.dagger:hilt-android-compiler](https://github.com/google/dagger) from 2.55 to 2.56.
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.55...dagger-2.56)

---
updated-dependencies:
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 20:42:23 +00:00
dependabot[bot]
3400015895 Bump com.google.firebase:firebase-messaging from 24.1.0 to 24.1.1
Bumps [com.google.firebase:firebase-messaging](https://github.com/firebase/firebase-android-sdk) from 24.1.0 to 24.1.1.
- [Commits](https://github.com/firebase/firebase-android-sdk/commits)

---
updated-dependencies:
- dependency-name: com.google.firebase:firebase-messaging
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 20:41:16 +00:00
dbc1afcf28 3.2.0
Изменённый дизайн.
2025-03-20 07:56:33 +04:00
96f84b9f54 3.1.1
Кешированные ответы от сервера теперь находятся в специальном файле, а не в настройках.

Возвращён просмотр подробностей о паре.
2025-03-20 04:40:57 +04:00
Nikita
15894b290a Merge pull request #19 from n08i40k/dependabot/gradle/androidx.navigation-navigation-compose-2.8.9
Bump androidx.navigation:navigation-compose from 2.8.7 to 2.8.9
2025-03-18 00:30:28 +04:00
dependabot[bot]
9f41cef263 Bump androidx.navigation:navigation-compose from 2.8.7 to 2.8.9
Bumps androidx.navigation:navigation-compose from 2.8.7 to 2.8.9.

---
updated-dependencies:
- dependency-name: androidx.navigation:navigation-compose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 19:56:52 +00:00
43 changed files with 837 additions and 343 deletions

6
.idea/kotlinc.xml generated
View File

@@ -7,10 +7,10 @@
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.0" />
<option name="languageVersion" value="2.0" />
<option name="apiVersion" value="2.1" />
<option name="languageVersion" value="2.1" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.10" />
<option name="version" value="2.1.10" />
</component>
</project>

View File

@@ -7,7 +7,7 @@ plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.1.10"
kotlin("plugin.serialization") version "2.1.20"
id("kotlin-parcelize")
id("com.google.devtools.ksp")
@@ -46,8 +46,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26
targetSdk = 35
versionCode = 27
versionName = "3.1.0"
versionCode = 29
versionName = "3.2.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -136,6 +136,7 @@ dependencies {
implementation(libs.kotlinx.datetime)
// default
implementation(libs.androidx.runtime.tracing)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
@@ -158,7 +159,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

@@ -2,9 +2,8 @@ package ru.n08i40k.polytechnic.next.ui.screen.schedule
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -24,6 +23,7 @@ import ru.n08i40k.polytechnic.next.ui.model.GroupUiState
import ru.n08i40k.polytechnic.next.ui.model.GroupViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.SchedulePager
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.UpdateInfo
import ru.n08i40k.polytechnic.next.utils.rememberUpdatedLifecycleOwner
@Composable
@@ -62,11 +62,10 @@ fun GroupScheduleScreen(viewModel: GroupViewModel) {
) {
when (uiState) {
is GroupUiState.HasData -> {
Column {
Column(Modifier.padding(20.dp), Arrangement.spacedBy(10.dp)) {
val data = uiState as GroupUiState.HasData
UpdateInfo(data.lastUpdateAt, data.updateDates)
Spacer(Modifier.height(10.dp))
UpdateInfo(data.lastUpdateAt, data.cacheDate)
SchedulePager(data.group)
}
}

View File

@@ -2,9 +2,8 @@ package ru.n08i40k.polytechnic.next.ui.screen.schedule
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -24,6 +23,7 @@ import ru.n08i40k.polytechnic.next.ui.model.TeacherUiState
import ru.n08i40k.polytechnic.next.ui.model.TeacherViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.SchedulePager
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.UpdateInfo
import ru.n08i40k.polytechnic.next.utils.rememberUpdatedLifecycleOwner
@Composable
@@ -62,11 +62,10 @@ fun TeacherScheduleScreen(viewModel: TeacherViewModel) {
) {
when (uiState) {
is TeacherUiState.HasData -> {
Column {
Column(Modifier.padding(20.dp), Arrangement.spacedBy(10.dp)) {
val data = uiState as TeacherUiState.HasData
UpdateInfo(data.lastUpdateAt, data.updateDates)
Spacer(Modifier.height(10.dp))
UpdateInfo(data.lastUpdateAt, data.cacheDate)
SchedulePager(data.teacher)
}
}

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -28,6 +29,7 @@ import ru.n08i40k.polytechnic.next.ui.model.SearchUiState
import ru.n08i40k.polytechnic.next.ui.model.SearchViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.SchedulePager
import ru.n08i40k.polytechnic.next.ui.widgets.schedule.UpdateInfo
import ru.n08i40k.polytechnic.next.ui.widgets.selector.TeacherNameSelector
import ru.n08i40k.polytechnic.next.utils.rememberUpdatedLifecycleOwner
@@ -82,11 +84,10 @@ fun TeacherSearchScreen(viewModel: SearchViewModel) {
when (uiState) {
is SearchUiState.HasData -> {
Column {
Column(Modifier.padding(20.dp), Arrangement.spacedBy(10.dp)) {
val data = uiState as SearchUiState.HasData
UpdateInfo(data.lastUpdateAt, data.updateDates)
Spacer(Modifier.height(10.dp))
UpdateInfo(data.lastUpdateAt, data.cacheDate)
SchedulePager(data.teacher)
}
}

View File

@@ -10,7 +10,6 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -32,17 +31,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun ExpandableCard(
modifier: Modifier = Modifier,
colors: CardColors = CardDefaults.cardColors(),
border: BorderStroke = BorderStroke(
Dp.Hairline,
MaterialTheme.colorScheme.inverseSurface
),
expanded: Boolean = false,
onExpandedChange: () -> Unit,
title: @Composable () -> Unit,
@@ -61,8 +55,7 @@ fun ExpandableCard(
onExpandedChange()
transitionState.targetState = expanded
},
colors = colors,
border = border
colors = colors
) {
Column {
ExpandableCardHeader(title, transition)
@@ -76,15 +69,10 @@ fun ExpandableCard(
modifier: Modifier = Modifier,
title: @Composable () -> Unit,
colors: CardColors = CardDefaults.cardColors(),
border: BorderStroke = BorderStroke(
Dp.Hairline,
MaterialTheme.colorScheme.inverseSurface
),
) {
Card(
modifier = modifier,
colors = colors,
border = border
colors = colors
) {
ExpandableCardHeader(title, null)
}

View File

@@ -1,31 +1,31 @@
package ru.n08i40k.polytechnic.next.ui.widgets.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.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
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
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
@@ -34,8 +34,9 @@ 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.LessonType
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.utils.dateTime
import ru.n08i40k.polytechnic.next.utils.now
@@ -76,81 +77,42 @@ private fun getCurrentLessonIdx(day: Day?): Flow<Int> {
return value
}
@Preview(showBackground = true)
@PreviewLightDark
@Composable
private fun DayCardPreview() {
AppTheme {
Surface(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
) {
DayCard(Modifier, MockScheduleRepository.exampleTeacher.days[0]) {}
}
}
}
@Composable
fun DayCard(
modifier: Modifier = Modifier,
day: Day = MockScheduleRepository.exampleTeacher.days[0]
day: Day,
onLessonClick: (Lesson) -> Unit,
) {
val offset = remember(day) { getDayOffset(day) }
val defaultCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
)
val customCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
)
val noneCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
val examCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer,
)
Card(
modifier,
colors = CardDefaults.cardColors(
containerColor = when (offset) {
DayOffset.TODAY -> MaterialTheme.colorScheme.primaryContainer
else -> MaterialTheme.colorScheme.secondaryContainer
}
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.inverseSurface)
) {
Text(
day.name,
Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
containerColor = MaterialTheme.colorScheme.surfaceContainerLowest
)
if (day.street != null) {
Text(
day.street,
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
)
}
if (offset != DayOffset.OTHER) {
Text(
stringResource(
when (offset) {
DayOffset.YESTERDAY -> R.string.yesterday
DayOffset.TODAY -> R.string.today
DayOffset.TOMORROW -> R.string.tomorrow
DayOffset.OTHER -> throw RuntimeException()
}
),
Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
)
}
) {
val currentLessonIndex by getCurrentLessonIdx(if (offset == DayOffset.TODAY) day else null)
.collectAsStateWithLifecycle(0)
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
.verticalScroll(rememberScrollState())
.padding(10.dp),
Arrangement.spacedBy(0.5.dp)
) {
if (day.lessons.isEmpty()) {
@@ -159,39 +121,10 @@ fun DayCard(
}
for (lessonIndex in day.lessons.indices) {
HorizontalDivider(
thickness = 1.dp,
color = MaterialTheme.colorScheme.inversePrimary
)
val lesson = day.lessons[lessonIndex]
val cardColors = when (lesson.type) {
LessonType.DEFAULT -> defaultCardColors
LessonType.ADDITIONAL -> noneCardColors
LessonType.BREAK -> noneCardColors
LessonType.CONSULTATION -> customCardColors
LessonType.INDEPENDENT_WORK -> customCardColors
LessonType.EXAM -> examCardColors
LessonType.EXAM_WITH_GRADE -> examCardColors
LessonType.EXAM_DEFAULT -> examCardColors
}
// TODO: Вернуть ExtraInfo
var extraInfo by remember { mutableStateOf(false) }
Box(
Modifier
.clickable { extraInfo = true }
.background(cardColors.containerColor)
) {
val modifier =
if (lessonIndex == currentLessonIndex)
Modifier.border(BorderStroke(1.dp, MaterialTheme.colorScheme.error))
else
Modifier
LessonRow(modifier, lesson, cardColors)
Box(Modifier.clickable { onLessonClick(lesson) }) {
LessonRow(modifier, lesson, lessonIndex == currentLessonIndex)
}
}
}

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

@@ -1,34 +1,47 @@
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
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.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
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.rememberTextMeasurer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
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.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
@@ -42,31 +55,51 @@ private fun fmtTime(start: Int, end: Int, format: TimeFormat): ArrayList<String>
return when (format) {
TimeFormat.CLOCK -> arrayListOf(start.fmtAsClock(), end.fmtAsClock())
TimeFormat.DURATION -> arrayListOf(
"${end - start} ${stringResource(R.string.minutes)}"
"${end - start} ${stringResource(R.string.minutes_full)}"
)
}
}
@Preview(showBackground = true, showSystemUi = true)
@PreviewLightDark
@Composable
private fun LessonRowPreview() {
AppTheme {
Surface(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
) {
LessonRow(Modifier, MockScheduleRepository.exampleGroup.days[0].lessons[6], true)
}
}
}
@Composable
fun LessonRow(
modifier: Modifier = Modifier,
lesson: Lesson = MockScheduleRepository.exampleGroup.days[0].lessons[0],
colors: CardColors = CardDefaults.cardColors()
lesson: Lesson,
current: Boolean
) {
val verticalPadding = when (lesson.type) {
LessonType.BREAK -> 2.5.dp
else -> 5.dp
}
var time = fmtTime(
lesson.time.start.dayMinutes,
lesson.time.end.dayMinutes,
if (lesson.type == LessonType.BREAK) TimeFormat.DURATION else TimeFormat.CLOCK
)
val timeFormat = when (lesson.type) {
LessonType.BREAK -> TimeFormat.DURATION
else -> TimeFormat.CLOCK
}
if (lesson.type == LessonType.BREAK) {
Box(Modifier.fillMaxWidth(), Alignment.Center) {
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHighest)
Text(
time[0],
Modifier
.background(MaterialTheme.colorScheme.surfaceContainerLowest)
.padding(5.dp, 0.dp),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.surfaceContainerHighest,
)
}
val contentColor = when (lesson.type) {
LessonType.BREAK -> colors.disabledContentColor
else -> colors.contentColor
return
}
// магические вычисления))
@@ -78,120 +111,184 @@ fun LessonRow(
Box(modifier) {
Row(
Modifier.padding(10.dp, verticalPadding * rangeSize),
verticalAlignment = Alignment.CenterVertically,
Modifier.padding(10.dp, 5.dp * rangeSize),
Arrangement.spacedBy(15.dp),
Alignment.CenterVertically,
) {
Text(
when (range) {
null -> " "
else -> {
if (range[0] == range[1])
" ${range[0]} "
else
"${range[0]}-${range[1]}"
}
},
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
color = contentColor
)
Spacer(Modifier.width(5.dp))
val textMeasurer = rememberTextMeasurer()
val timeWidth = textMeasurer.measure(
text = "00:00 ",
text = "00:00",
style = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)
)
Column(
Modifier.width(with(LocalDensity.current) { timeWidth.size.width.toDp() }),
horizontalAlignment = Alignment.CenterHorizontally
Modifier
.width(with(LocalDensity.current) { timeWidth.size.width.toDp() + 1.dp }),
Arrangement.spacedBy(5.dp),
Alignment.CenterHorizontally
) {
var time = fmtTime(
lesson.time.start.dayMinutes,
lesson.time.end.dayMinutes,
timeFormat
)
Text(time[0], color = contentColor, fontFamily = FontFamily.Monospace, maxLines = 1)
if (lesson.type != LessonType.BREAK)
Column {
Text(
time[0],
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleSmall
)
Text(
time[1],
color = contentColor,
fontFamily = FontFamily.Monospace,
maxLines = 1
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleSmall
)
}
}
Spacer(Modifier.width(5.dp))
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
Column(Modifier.weight(1f)) {
// FIXME: Очень странный метод отсеивания, может что-нибудь на замену сделать?
if (lesson.type.value > LessonType.BREAK.value) {
Text(
when (lesson.type) {
LessonType.CONSULTATION -> stringResource(R.string.lesson_type_consultation)
LessonType.INDEPENDENT_WORK -> stringResource(R.string.lesson_type_independent_work)
LessonType.EXAM -> stringResource(R.string.lesson_type_exam)
LessonType.EXAM_WITH_GRADE -> stringResource(R.string.lesson_type_exam_with_grade)
LessonType.EXAM_DEFAULT -> stringResource(R.string.lesson_type_exam_default)
else -> throw RuntimeException("Unknown lesson type!")
},
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
)
}
if (range != null) {
HorizontalDivider(
Modifier.width(32.dp),
1.dp,
MaterialTheme.colorScheme.inverseSurface
)
Text(
lesson.name ?: stringResource(R.string.lesson_type_break),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = contentColor
if (range[0] == range[1])
" ${range[0]} "
else
"${range[0]}-${range[1]}",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodySmall
)
if (lesson.group != null) {
Text(
lesson.group,
color = contentColor,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
for (subGroup in lesson.subGroups) {
Text(
subGroup.teacher,
color = contentColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Column(Modifier.wrapContentWidth()) {
if (lesson.subGroups.size != 1) {
BasicText("")
if (lesson.group != null)
BasicText("")
}
for (subGroup in lesson.subGroups) {
Text(
subGroup.cabinet,
color = contentColor,
maxLines = 1,
fontFamily = FontFamily.Monospace
)
}
}
}
VerticalDivider(
Modifier.height(42.dp),
1.dp,
if (current)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.inverseSurface
)
Column(Modifier.weight(1f)) {
Text(
lesson.name!!,
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleMedium
)
// FIXME: Очень странный метод отсеивания, может что-нибудь на замену сделать?
if (lesson.type.value > LessonType.BREAK.value) {
Text(
when (lesson.type) {
LessonType.CONSULTATION -> stringResource(R.string.lesson_type_consultation)
LessonType.INDEPENDENT_WORK -> stringResource(R.string.lesson_type_independent_work)
LessonType.EXAM -> stringResource(R.string.lesson_type_exam)
LessonType.EXAM_WITH_GRADE -> stringResource(R.string.lesson_type_exam_with_grade)
LessonType.EXAM_DEFAULT -> stringResource(R.string.lesson_type_exam_default)
else -> throw RuntimeException("Unknown lesson type!")
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.W600,
style = MaterialTheme.typography.titleMedium
)
}
@Composable
fun SmallTextWithIcon(
@DrawableRes iconId: Int,
contentDescription: String,
text: String
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painterResource(iconId),
contentDescription,
Modifier.size(12.dp)
)
Text(
text,
fontWeight = FontWeight.W400,
style = MaterialTheme.typography.titleSmall
)
}
}
if (lesson.group != null) {
Row {
Spacer(Modifier.size(5.dp))
SmallTextWithIcon(
R.drawable.ic_group,
"Group",
lesson.group
)
}
}
Row(
Modifier
.fillMaxWidth()
.padding(5.dp),
Arrangement.SpaceBetween,
Alignment.CenterVertically
) {
lesson
.subGroups
.sortedBy { it.number }
.forEachIndexed { subGroupIdx, subGroup ->
if (subGroupIdx > 0) {
VerticalDivider(
Modifier.height(25.dp), 1.dp,
MaterialTheme.colorScheme.inverseSurface
)
}
// FIXME: тупая проверка
if (subGroup.teacher == "Только у другой") {
Text(
stringResource(
if (subGroup.number == 1)
R.string.only_for_second
else
R.string.only_for_first
),
fontWeight = FontWeight.W400,
style = MaterialTheme.typography.titleSmall
)
return@forEachIndexed
}
Column {
val cabinet =
if (subGroup.cabinet.toIntOrNull() == null)
subGroup.cabinet
else
"${subGroup.cabinet}"
SmallTextWithIcon(
R.drawable.ic_cabinet,
"Cabinet",
cabinet
)
SmallTextWithIcon(
R.drawable.ic_teacher,
"Teacher",
subGroup.teacher
)
}
}
}
}
}
}
}

View File

@@ -1,26 +1,57 @@
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.compose.animation.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import kotlinx.coroutines.launch
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.theme.AppTheme
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
@@ -32,24 +63,120 @@ private fun isScheduleOutdated(schedule: GroupOrTeacher): Boolean {
return nowDateTime > lastLesson.time.end.dateTime
}
@Preview(showSystemUi = true)
@PreviewLightDark()
@Composable
fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeacher) {
private fun SchedulePagerPreview() {
AppTheme {
Surface(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
) {
SchedulePager(MockScheduleRepository.exampleTeacher)
}
}
}
val weekList = listOf(
R.string.week_bar_monday,
R.string.week_bar_tuesday,
R.string.week_bar_wednesday,
R.string.week_bar_thursday,
R.string.week_bar_friday,
R.string.week_bar_saturday,
)
@Composable
fun SchedulePager(schedule: GroupOrTeacher) {
val pagerState = rememberPagerState(
initialPage = (schedule.currentIdx ?: (schedule.days.size - 1)).coerceAtLeast(0),
pageCount = { schedule.days.size }
)
Column {
var dialogLesson by remember { mutableStateOf<WeakReference<Lesson>?>(null) }
Column(
Modifier
.fillMaxWidth(),
Arrangement.spacedBy(20.dp)
) {
if (isScheduleOutdated(schedule))
NotificationCard(Level.WARNING, stringResource(R.string.outdated_schedule))
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLowest
)
) {
Row(Modifier.fillMaxWidth(), Arrangement.spacedBy(5.dp)) {
val coroutineScope = rememberCoroutineScope()
for (i in 0..5) {
val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer
val onPrimaryContainerColor = MaterialTheme.colorScheme.onPrimaryContainer
val onSurfaceColor = MaterialTheme.colorScheme.onSurface
val containerColor = remember {
Animatable(
if (pagerState.currentPage == i) primaryContainerColor
else Color.Transparent
)
}
val contentColor = remember {
Animatable(
if (pagerState.currentPage == i) onPrimaryContainerColor
else onSurfaceColor
)
}
LaunchedEffect(pagerState, pagerState.currentPage) {
containerColor.animateTo(
if (pagerState.currentPage == i) primaryContainerColor
else Color.Transparent
)
contentColor.animateTo(
if (pagerState.currentPage == i) onPrimaryContainerColor
else onSurfaceColor
)
}
Column(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(20))
.background(containerColor.value)
.clickable { coroutineScope.launch { pagerState.animateScrollToPage(i) } },
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
Modifier
.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
stringResource(weekList[i]),
style = MaterialTheme.typography.bodyMedium,
color = contentColor.value
)
Text(
schedule.days[i].date.dateTime.date.dayOfMonth.toString(),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.W600,
color = contentColor.value
)
}
}
}
}
}
HorizontalPager(
pagerState,
Modifier
.height(600.dp)
.padding(top = 5.dp),
PaddingValues(horizontal = 7.dp),
.height(600.dp),
verticalAlignment = Alignment.Top
) { page ->
DayCard(
@@ -59,7 +186,7 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
).absoluteValue
lerp(
start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f)
start = 1f, stop = 0.95f, fraction = offset.coerceIn(0f, 1f)
).also { scale ->
scaleX = scale
scaleY = scale
@@ -69,7 +196,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

@@ -1,4 +1,4 @@
package ru.n08i40k.polytechnic.next.ui.screen.schedule
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -19,31 +21,31 @@ 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.screen.schedule.PaskhalkoDialog
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCard
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCardTitle
import ru.n08i40k.polytechnic.next.utils.*
import java.util.Date
val expanded = mutableStateOf(false)
@Preview(showBackground = true)
@Composable
fun UpdateInfo(
lastUpdateAt: Long = 0,
updateDates: UpdateDates = UpdateDates.newBuilder().build()
cacheDate: CacheDate = CacheDate.newBuilder().build()
) {
var expanded by remember { expanded }
var expanded by remember { mutableStateOf(false) }
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,
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest),
onExpandedChange = { expanded = !expanded },
title = { ExpandableCardTitle(stringResource(R.string.update_info_header)) }
) {

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

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M120,840v-80h80v-640h400v40h160v600h80v80L680,840v-600h-80v600L120,840ZM280,200v560,-560ZM440,520q17,0 28.5,-11.5T480,480q0,-17 -11.5,-28.5T440,440q-17,0 -28.5,11.5T400,480q0,17 11.5,28.5T440,520ZM280,760h240v-560L280,200v560Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M96,768v-92q0,-25.78 12.5,-47.39T143,594q54,-32 114.5,-49T384,528q66,0 126.5,17T625,594q22,13 34.5,34.61T672,676v92L96,768ZM744,768v-92q0,-42 -19.5,-78T672,539q39,8 75.5,21.5T817,594q22,13 34.5,34.67Q864,650.35 864,676v92L744,768ZM384,480q-60,0 -102,-42t-42,-102q0,-60 42,-102t102,-42q60,0 102,42t42,102q0,60 -42,102t-102,42ZM720,336q0,60 -42,102t-102,42q-8,0 -15,-0.5t-15,-2.5q25,-29 39.5,-64.5T600,336q0,-41 -14.5,-76.5T546,195q8,-2 15,-2.5t15,-0.5q60,0 102,42t42,102ZM168,696h432v-20q0,-6.47 -3.03,-11.76 -3.02,-5.3 -7.97,-8.24 -47,-27 -99,-41.5T384,600q-54,0 -106,14t-99,42q-4.95,2.83 -7.98,7.91 -3.02,5.09 -3.02,12L168,696ZM384.21,408Q414,408 435,386.79t21,-51Q456,306 434.79,285t-51,-21Q354,264 333,285.21t-21,51Q312,366 333.21,387t51,21ZM384,696ZM384,336Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,840 L200,688v-240L40,360l440,-240 440,240v320h-80v-276l-80,44v240L480,840ZM480,508 L754,360 480,212 206,360 480,508ZM480,749 L680,641v-151L480,600 280,490v151l200,108ZM480,508ZM480,598ZM480,598Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@@ -67,7 +67,7 @@
<string name="auth_error_disallowed_role">Пожалуйста, используйте другую роль.</string>
<string name="sign_in_manual">По имени пользователя</string>
<string name="updater_support_end">Поддержка версий ниже %1$s прекращена!</string>
<string name="last_server_schedule_update">Последнее обновление расписания</string>
<string name="last_server_schedule_update">Расписание</string>
<string name="teacher_name">ФИО преподавателя</string>
<string name="updater_body">Желаете ли вы обновиться до последней версии?</string>
<string name="updater_new_version">Вышла новая версия приложения!</string>
@@ -76,8 +76,8 @@
<string name="updater_update">ОБНОВИТЬ</string>
<string name="updater_suppress">ЗАГЛУШИТЬ</string>
<string name="update_info_header">Дополнительная информация</string>
<string name="last_local_update">Последнее локальное обновление</string>
<string name="last_server_cache_update">Последнее обновление кеша</string>
<string name="last_local_update">Локально</string>
<string name="last_server_cache_update">Кеш</string>
<string name="download_update">Скачать обновление</string>
<string name="telegram_channel">Телеграм канал</string>
<string name="schedule_update_title">Расписание обновлено!</string>
@@ -100,4 +100,20 @@
<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>
<string name="minutes_full">минут</string>
<string name="only_for_second">Только у второй</string>
<string name="only_for_first">Только у первой</string>
<string name="week_bar_monday">Пн</string>
<string name="week_bar_tuesday">Вт</string>
<string name="week_bar_wednesday">Ср</string>
<string name="week_bar_thursday">Чт</string>
<string name="week_bar_friday">Пт</string>
<string name="week_bar_saturday">Сб</string>
</resources>

View File

@@ -100,4 +100,20 @@
<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>
<string name="minutes_full">minutes</string>
<string name="only_for_second">Only for second</string>
<string name="only_for_first">Only for first</string>
<string name="week_bar_tuesday">Tue</string>
<string name="week_bar_wednesday">Wed</string>
<string name="week_bar_thursday">Thu</string>
<string name="week_bar_friday">Fri</string>
<string name="week_bar_saturday">Sat</string>
<string name="week_bar_monday">Mon</string>
</resources>

View File

@@ -4,14 +4,14 @@ plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
id("com.google.devtools.ksp") version "2.1.10-1.0.30" apply false
id("com.google.devtools.ksp") version "2.1.20-1.0.31" apply false
id("com.google.protobuf") version "0.9.4" apply false
id("com.google.gms.google-services") version "4.4.2" apply false
alias(libs.plugins.google.firebase.crashlytics) apply false
id("com.google.dagger.hilt.android") version "2.55" apply false
id("com.google.dagger.hilt.android") version "2.57.2" apply false
id("vkid.manifest.placeholders") version "1.1.0" apply true
}

View File

@@ -1,30 +1,33 @@
[versions]
agp = "8.8.1"
desugar_jdk_libs = "2.1.4"
agp = "8.8.2"
desugar_jdk_libs = "2.1.5"
kotlin = "2.1.10"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0"
composeBom = "2025.02.00"
activityCompose = "1.10.1"
composeBom = "2025.03.00"
accompanistSwiperefresh = "0.36.0"
firebaseBom = "33.9.0"
firebaseBom = "33.10.0"
hiltAndroid = "2.55"
hiltAndroidCompiler = "2.55"
hiltAndroidCompiler = "2.56"
hiltNavigationCompose = "1.2.0"
kotlinxSerializationJson = "1.8.0"
protobufLite = "3.0.1"
runtimeTracing = "1.7.8"
volley = "1.2.1"
datastore = "1.1.2"
navigationCompose = "2.8.7"
datastore = "1.1.3"
navigationCompose = "2.8.9"
googleFirebaseCrashlytics = "3.0.3"
workRuntime = "2.10.0"
vkid = "2.3.1"
#noinspection GradleDependency
vkid = "2.2.2"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "runtimeTracing" }
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@@ -59,9 +62,9 @@ volley = { group = "com.android.volley", name = "volley", version.ref = "volley"
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics", version = "22.2.0" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version = "19.4.0" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version = "24.1.0" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics", version = "22.3.0" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version = "19.4.1" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version = "24.1.1" }
firebase-config = { group = "com.google.firebase", name = "firebase-config", version = "22.1.0" }
vk-vkid = {group = "com.vk.id", name = "vkid", version.ref = "vkid" }
vk-onetap-compose = {group = "com.vk.id", name = "onetap-compose", version.ref = "vkid" }