mirror of
https://github.com/n08i40k/polytechnic-android.git
synced 2025-12-06 17:57:46 +03:00
Compare commits
2 Commits
15894b290a
...
dbc1afcf28
| Author | SHA1 | Date | |
|---|---|---|---|
|
dbc1afcf28
|
|||
|
96f84b9f54
|
6
.idea/kotlinc.xml
generated
6
.idea/kotlinc.xml
generated
@@ -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>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLowest
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
day.name,
|
||||
Modifier.fillMaxWidth(),
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
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 timeFormat = when (lesson.type) {
|
||||
LessonType.BREAK -> TimeFormat.DURATION
|
||||
else -> TimeFormat.CLOCK
|
||||
}
|
||||
|
||||
val contentColor = when (lesson.type) {
|
||||
LessonType.BREAK -> colors.disabledContentColor
|
||||
else -> colors.contentColor
|
||||
return
|
||||
}
|
||||
|
||||
// магические вычисления))
|
||||
@@ -78,56 +111,73 @@ 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
|
||||
Column {
|
||||
Text(
|
||||
time[0],
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.W600,
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
|
||||
Text(time[0], color = contentColor, fontFamily = FontFamily.Monospace, maxLines = 1)
|
||||
if (lesson.type != LessonType.BREAK)
|
||||
Text(
|
||||
time[1],
|
||||
color = contentColor,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
maxLines = 1
|
||||
fontWeight = FontWeight.W600,
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(5.dp))
|
||||
if (range != null) {
|
||||
HorizontalDivider(
|
||||
Modifier.width(32.dp),
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.inverseSurface
|
||||
)
|
||||
|
||||
Text(
|
||||
if (range[0] == range[1])
|
||||
" ${range[0]} "
|
||||
else
|
||||
"${range[0]}-${range[1]}",
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
VerticalDivider(
|
||||
Modifier.height(42.dp),
|
||||
1.dp,
|
||||
if (current)
|
||||
MaterialTheme.colorScheme.primary
|
||||
else
|
||||
MaterialTheme.colorScheme.inverseSurface
|
||||
)
|
||||
|
||||
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
lesson.name!!,
|
||||
fontWeight = FontWeight.W600,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
|
||||
// FIXME: Очень странный метод отсеивания, может что-нибудь на замену сделать?
|
||||
if (lesson.type.value > LessonType.BREAK.value) {
|
||||
Text(
|
||||
@@ -139,59 +189,106 @@ fun LessonRow(
|
||||
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
|
||||
fontWeight = FontWeight.W600,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
lesson.name ?: stringResource(R.string.lesson_type_break),
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = contentColor
|
||||
@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) {
|
||||
Text(
|
||||
lesson.group,
|
||||
color = contentColor,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Row {
|
||||
Spacer(Modifier.size(5.dp))
|
||||
|
||||
for (subGroup in lesson.subGroups) {
|
||||
Text(
|
||||
subGroup.teacher,
|
||||
color = contentColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
SmallTextWithIcon(
|
||||
R.drawable.ic_group,
|
||||
"Group",
|
||||
lesson.group
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.wrapContentWidth()) {
|
||||
if (lesson.subGroups.size != 1) {
|
||||
BasicText("")
|
||||
|
||||
if (lesson.group != null)
|
||||
BasicText("")
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
for (subGroup in lesson.subGroups) {
|
||||
// FIXME: тупая проверка
|
||||
if (subGroup.teacher == "Только у другой") {
|
||||
Text(
|
||||
subGroup.cabinet,
|
||||
color = contentColor,
|
||||
maxLines = 1,
|
||||
fontFamily = FontFamily.Monospace
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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)) }
|
||||
) {
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
19
app/src/main/proto/cache.proto
Normal file
19
app/src/main/proto/cache.proto
Normal 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;
|
||||
}
|
||||
15
app/src/main/proto/settings-v2.proto
Normal file
15
app/src/main/proto/settings-v2.proto
Normal 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;
|
||||
}
|
||||
9
app/src/main/res/drawable/ic_cabinet.xml
Normal file
9
app/src/main/res/drawable/ic_cabinet.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_group.xml
Normal file
9
app/src/main/res/drawable/ic_group.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_teacher.xml
Normal file
9
app/src/main/res/drawable/ic_teacher.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
hiltNavigationCompose = "1.2.0"
|
||||
kotlinxSerializationJson = "1.8.0"
|
||||
protobufLite = "3.0.1"
|
||||
runtimeTracing = "1.7.8"
|
||||
volley = "1.2.1"
|
||||
datastore = "1.1.2"
|
||||
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,8 +62,8 @@ 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-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.0" }
|
||||
firebase-config = { group = "com.google.firebase", name = "firebase-config", version = "22.1.0" }
|
||||
vk-vkid = {group = "com.vk.id", name = "vkid", version.ref = "vkid" }
|
||||
|
||||
Reference in New Issue
Block a user