This commit is contained in:
2024-12-20 23:44:19 +04:00
parent f4d3759d47
commit 44c1f01541
16 changed files with 323 additions and 44 deletions

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
PolytecnicNext

3
.idea/dictionaries/n08i40k.xml generated Normal file
View File

@@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="n08i40k" />
</component>

View File

@@ -33,8 +33,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next" applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 22 versionCode = 23
versionName = "2.2.1" versionName = "2.3.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@@ -29,6 +29,7 @@ import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.UserRole import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.ui.widgets.GroupSelector import ru.n08i40k.polytechnic.next.ui.widgets.GroupSelector
import ru.n08i40k.polytechnic.next.ui.widgets.RoleSelector import ru.n08i40k.polytechnic.next.ui.widgets.RoleSelector
import ru.n08i40k.polytechnic.next.ui.widgets.TeacherNameSelector
@Preview(showBackground = true) @Preview(showBackground = true)
@@ -105,17 +106,26 @@ internal fun RegisterForm(
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
OutlinedTextField( if (role != UserRole.TEACHER) {
value = username, OutlinedTextField(
singleLine = true, value = username,
onValueChange = { singleLine = true,
username = it onValueChange = {
usernameError = false username = it
}, usernameError = false
label = { Text(stringResource(R.string.username)) }, },
isError = usernameError, label = { Text(stringResource(R.string.username)) },
readOnly = loading isError = usernameError,
) readOnly = loading
)
} else {
TeacherNameSelector(
value = username,
isError = usernameError,
readOnly = loading,
onValueChange = { username = it ?: "" }
)
}
OutlinedTextField( OutlinedTextField(
value = password, value = password,
@@ -135,7 +145,8 @@ internal fun RegisterForm(
GroupSelector( GroupSelector(
value = group, value = group,
isError = groupError, isError = groupError,
readOnly = loading readOnly = loading,
teacher = role == UserRole.TEACHER
) { ) {
groupError = false groupError = false
group = it group = it

View File

@@ -8,19 +8,31 @@ import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import ru.n08i40k.polytechnic.next.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.UserRole
data class BottomNavItem( data class BottomNavItem(
@StringRes val label: Int, @StringRes val label: Int,
val icon: ImageVector, val icon: ImageVector,
val route: String, val route: String,
val isAdmin: Boolean = false val requiredRole: UserRole? = null
) )
object Constants { object Constants {
val bottomNavItem = listOf( val bottomNavItem = listOf(
BottomNavItem(R.string.profile, Icons.Filled.AccountCircle, "profile"), BottomNavItem(R.string.profile, Icons.Filled.AccountCircle, "profile"),
BottomNavItem(R.string.replacer, Icons.Filled.Create, "replacer", true), BottomNavItem(R.string.replacer, Icons.Filled.Create, "replacer", UserRole.ADMIN),
BottomNavItem(
R.string.teacher_schedule,
Icons.Filled.Person,
"teacher-main-schedule",
UserRole.TEACHER
),
BottomNavItem(R.string.schedule, Icons.Filled.DateRange, "schedule"), BottomNavItem(R.string.schedule, Icons.Filled.DateRange, "schedule"),
BottomNavItem(R.string.teachers, Icons.Filled.Person, "teacher-schedule") BottomNavItem(
R.string.teachers,
Icons.Filled.Person,
"teacher-user-schedule",
UserRole.STUDENT
)
) )
} }

View File

@@ -61,6 +61,7 @@ import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.MainViewModel import ru.n08i40k.polytechnic.next.MainViewModel
import ru.n08i40k.polytechnic.next.PolytechnicApplication import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.model.UserRole import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.settings.settingsDataStore import ru.n08i40k.polytechnic.next.settings.settingsDataStore
import ru.n08i40k.polytechnic.next.ui.icons.AppIcons import ru.n08i40k.polytechnic.next.ui.icons.AppIcons
@@ -70,7 +71,8 @@ import ru.n08i40k.polytechnic.next.ui.icons.appicons.filled.Telegram
import ru.n08i40k.polytechnic.next.ui.main.profile.ProfileScreen import ru.n08i40k.polytechnic.next.ui.main.profile.ProfileScreen
import ru.n08i40k.polytechnic.next.ui.main.replacer.ReplacerScreen import ru.n08i40k.polytechnic.next.ui.main.replacer.ReplacerScreen
import ru.n08i40k.polytechnic.next.ui.main.schedule.group.GroupScheduleScreen import ru.n08i40k.polytechnic.next.ui.main.schedule.group.GroupScheduleScreen
import ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.TeacherScheduleScreen import ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.main.TeacherMainScheduleScreen
import ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.user.TeacherUserScheduleScreen
import ru.n08i40k.polytechnic.next.ui.model.GroupScheduleViewModel import ru.n08i40k.polytechnic.next.ui.model.GroupScheduleViewModel
import ru.n08i40k.polytechnic.next.ui.model.ProfileUiState import ru.n08i40k.polytechnic.next.ui.model.ProfileUiState
import ru.n08i40k.polytechnic.next.ui.model.ProfileViewModel import ru.n08i40k.polytechnic.next.ui.model.ProfileViewModel
@@ -84,15 +86,27 @@ import ru.n08i40k.polytechnic.next.ui.model.profileViewModel
private fun NavHostContainer( private fun NavHostContainer(
navController: NavHostController, navController: NavHostController,
padding: PaddingValues, padding: PaddingValues,
profileViewModel: ProfileViewModel,
groupScheduleViewModel: GroupScheduleViewModel, groupScheduleViewModel: GroupScheduleViewModel,
teacherScheduleViewModel: TeacherScheduleViewModel, teacherScheduleViewModel: TeacherScheduleViewModel,
scheduleReplacerViewModel: ScheduleReplacerViewModel? scheduleReplacerViewModel: ScheduleReplacerViewModel?
) { ) {
val context = LocalContext.current val context = LocalContext.current
val profileUiState by profileViewModel.uiState.collectAsStateWithLifecycle()
val profile: Profile? = when (profileUiState) {
is ProfileUiState.NoProfile -> null
is ProfileUiState.HasProfile ->
(profileUiState as ProfileUiState.HasProfile).profile
}
if (profile == null)
return
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = "schedule", startDestination = if (profile.role == UserRole.TEACHER) "teacher-main-schedule" else "schedule",
modifier = Modifier.padding(paddingValues = padding), modifier = Modifier.padding(paddingValues = padding),
enterTransition = { enterTransition = {
slideIn( slideIn(
@@ -126,8 +140,16 @@ private fun NavHostContainer(
GroupScheduleScreen(groupScheduleViewModel) { groupScheduleViewModel.refresh() } GroupScheduleScreen(groupScheduleViewModel) { groupScheduleViewModel.refresh() }
} }
composable("teacher-schedule") { composable("teacher-user-schedule") {
TeacherScheduleScreen(teacherScheduleViewModel) { TeacherUserScheduleScreen(teacherScheduleViewModel) {
if (it.isNotEmpty()) teacherScheduleViewModel.fetch(
it
)
}
}
composable("teacher-main-schedule") {
TeacherMainScheduleScreen(teacherScheduleViewModel) {
if (it.isNotEmpty()) teacherScheduleViewModel.fetch( if (it.isNotEmpty()) teacherScheduleViewModel.fetch(
it it
) )
@@ -231,14 +253,14 @@ private fun TopNavBar(
} }
@Composable @Composable
private fun BottomNavBar(navController: NavHostController, isAdmin: Boolean) { private fun BottomNavBar(navController: NavHostController, userRole: UserRole) {
NavigationBar { NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
Constants.bottomNavItem.forEach { Constants.bottomNavItem.forEach {
if (it.isAdmin && !isAdmin) if (it.requiredRole != null && it.requiredRole != userRole && userRole != UserRole.ADMIN)
return@forEach return@forEach
NavigationBarItem( NavigationBarItem(
@@ -303,17 +325,17 @@ fun MainScreen(
// schedule replacer view model // schedule replacer view model
val profileUiState by profileViewModel.uiState.collectAsStateWithLifecycle() val profileUiState by profileViewModel.uiState.collectAsStateWithLifecycle()
val isAdmin = when (profileUiState) { val profile: Profile? = when (profileUiState) {
is ProfileUiState.NoProfile -> false is ProfileUiState.NoProfile -> null
is ProfileUiState.HasProfile -> { is ProfileUiState.HasProfile ->
val profile = (profileUiState as ProfileUiState.HasProfile).profile (profileUiState as ProfileUiState.HasProfile).profile
profile.role == UserRole.ADMIN
}
} }
if (profile == null)
return
val scheduleReplacerViewModel: ScheduleReplacerViewModel? = val scheduleReplacerViewModel: ScheduleReplacerViewModel? =
if (isAdmin) hiltViewModel(LocalContext.current as ComponentActivity) if (profile.role == UserRole.ADMIN) hiltViewModel(LocalContext.current as ComponentActivity)
else null else null
// nav controller // nav controller
@@ -321,11 +343,12 @@ fun MainScreen(
val navController = rememberNavController() val navController = rememberNavController()
Scaffold( Scaffold(
topBar = { TopNavBar(remoteConfigViewModel) }, topBar = { TopNavBar(remoteConfigViewModel) },
bottomBar = { BottomNavBar(navController, isAdmin) } bottomBar = { BottomNavBar(navController, profile.role) }
) { paddingValues -> ) { paddingValues ->
NavHostContainer( NavHostContainer(
navController, navController,
paddingValues, paddingValues,
profileViewModel,
groupScheduleViewModel, groupScheduleViewModel,
teacherScheduleViewModel, teacherScheduleViewModel,
scheduleReplacerViewModel scheduleReplacerViewModel

View File

@@ -23,6 +23,7 @@ import com.android.volley.ClientError
import ru.n08i40k.polytechnic.next.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.users.impl.FakeProfileRepository import ru.n08i40k.polytechnic.next.data.users.impl.FakeProfileRepository
import ru.n08i40k.polytechnic.next.model.Profile import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.network.request.profile.ProfileChangeGroup import ru.n08i40k.polytechnic.next.network.request.profile.ProfileChangeGroup
import ru.n08i40k.polytechnic.next.ui.widgets.GroupSelector import ru.n08i40k.polytechnic.next.ui.widgets.GroupSelector
@@ -65,10 +66,10 @@ internal fun ChangeGroupDialog(
GroupSelector( GroupSelector(
value = group, value = group,
onValueChange = { group = it },
isError = groupError, isError = groupError,
readOnly = processing readOnly = processing,
) teacher = profile.role == UserRole.TEACHER
) { group = it }
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
Button( Button(

View File

@@ -61,7 +61,9 @@ fun DayPager(groupOrTeacher: GroupOrTeacher = FakeScheduleRepository.exampleGrou
) { page -> ) { page ->
DayCard( DayCard(
modifier = Modifier.graphicsLayer { modifier = Modifier.graphicsLayer {
val offset = pagerState.getOffsetDistanceInPages(page).absoluteValue val offset = pagerState.getOffsetDistanceInPages(
page.coerceIn(0, pagerState.pageCount - 1)
).absoluteValue
lerp( lerp(
start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f) start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f)

View File

@@ -0,0 +1,112 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.main
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.MockAppContainer
import ru.n08i40k.polytechnic.next.ui.main.schedule.DayPager
import ru.n08i40k.polytechnic.next.ui.model.ProfileUiState
import ru.n08i40k.polytechnic.next.ui.model.TeacherScheduleUiState
import ru.n08i40k.polytechnic.next.ui.model.TeacherScheduleViewModel
import ru.n08i40k.polytechnic.next.ui.model.profileViewModel
import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
@Composable
private fun rememberUpdatedLifecycleOwner(): LifecycleOwner {
val lifecycleOwner = LocalLifecycleOwner.current
return remember { lifecycleOwner }
}
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun TeacherMainScheduleScreen(
teacherScheduleViewModel: TeacherScheduleViewModel = TeacherScheduleViewModel(
MockAppContainer(
LocalContext.current
)
),
fetch: (String) -> Unit = {}
) {
val profileViewModel = LocalContext.current.profileViewModel!!
val profileUiState by profileViewModel.uiState.collectAsStateWithLifecycle()
if (profileUiState is ProfileUiState.NoProfile)
return
val profile = (profileUiState as ProfileUiState.HasProfile).profile
var teacherName = profile.username
val uiState by teacherScheduleViewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(uiState) {
delay(120_000)
fetch(teacherName)
}
val lifecycleOwner = rememberUpdatedLifecycleOwner()
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> {
fetch(teacherName)
}
else -> Unit
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Column(Modifier.fillMaxSize()) {
LoadingContent(
empty = when (uiState) {
is TeacherScheduleUiState.NoData -> uiState.isLoading
is TeacherScheduleUiState.HasData -> false
},
loading = uiState.isLoading,
) {
when (uiState) {
is TeacherScheduleUiState.HasData -> {
Column {
val hasData = uiState as TeacherScheduleUiState.HasData
DayPager(hasData.teacher)
}
}
is TeacherScheduleUiState.NoData -> {
if (!uiState.isLoading) {
Text(
modifier = Modifier.fillMaxSize(),
text = stringResource(R.string.teacher_not_selected),
textAlign = TextAlign.Center
)
}
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.user
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth

View File

@@ -1,4 +1,4 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.user
import android.content.Context import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable

View File

@@ -1,4 +1,4 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher package ru.n08i40k.polytechnic.next.ui.main.schedule.teacher.user
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -39,7 +39,7 @@ private fun rememberUpdatedLifecycleOwner(): LifecycleOwner {
@Preview(showBackground = true, showSystemUi = true) @Preview(showBackground = true, showSystemUi = true)
@Composable @Composable
fun TeacherScheduleScreen( fun TeacherUserScheduleScreen(
teacherScheduleViewModel: TeacherScheduleViewModel = TeacherScheduleViewModel( teacherScheduleViewModel: TeacherScheduleViewModel = TeacherScheduleViewModel(
MockAppContainer( MockAppContainer(
LocalContext.current LocalContext.current

View File

@@ -27,7 +27,7 @@ import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleGetGroupNames import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleGetGroupNames
@Composable @Composable
private fun getGroups(context: Context, onUpdated: (String?) -> Unit): ArrayList<String?> { private fun getTeacherNames(context: Context, onUpdated: (String?) -> Unit): ArrayList<String?> {
val groupPlaceholder = stringResource(R.string.loading) val groupPlaceholder = stringResource(R.string.loading)
val groups = remember { arrayListOf(null, groupPlaceholder) } val groups = remember { arrayListOf(null, groupPlaceholder) }
@@ -55,6 +55,7 @@ fun GroupSelector(
value: String? = "ИС-214/24", value: String? = "ИС-214/24",
isError: Boolean = false, isError: Boolean = false,
readOnly: Boolean = false, readOnly: Boolean = false,
teacher: Boolean = false,
onValueChange: (String?) -> Unit = {}, onValueChange: (String?) -> Unit = {},
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@@ -68,10 +69,10 @@ fun GroupSelector(
expanded = !readOnly && !expanded expanded = !readOnly && !expanded
} }
) { ) {
val groups = getGroups(LocalContext.current, onValueChange) val groups = getTeacherNames(LocalContext.current, onValueChange)
TextField( TextField(
label = { Text(stringResource(R.string.group)) }, label = { Text(stringResource(if (teacher) R.string.supervised_group else R.string.group)) },
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable), modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
value = value ?: groups.getOrElse(1) { "TODO" }!!, value = value ?: groups.getOrElse(1) { "TODO" }!!,
leadingIcon = { leadingIcon = {
@@ -97,7 +98,7 @@ fun GroupSelector(
DropdownMenuItem( DropdownMenuItem(
text = { Text(it) }, text = { Text(it) },
onClick = { onClick = {
if (groups.size > 0 && groups[0] != null) if (groups.isNotEmpty() && groups[0] != null)
onValueChange(it) onValueChange(it)
expanded = false expanded = false
} }

View File

@@ -0,0 +1,109 @@
package ru.n08i40k.polytechnic.next.ui.widgets
import android.content.Context
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
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.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleGetTeacherNames
@Composable
private fun getTeacherNames(context: Context, onUpdated: (String?) -> Unit): ArrayList<String?> {
val groupPlaceholder = stringResource(R.string.loading)
val names = remember { arrayListOf(null, groupPlaceholder) }
LaunchedEffect(names) {
ScheduleGetTeacherNames(context, {
names.clear()
names.addAll(it.names)
onUpdated(names.getOrElse(0) { "TODO" }!!)
}, {
names.clear()
names.add(null)
names.add(context.getString(R.string.failed_to_fetch_teacher_names))
onUpdated(names[1]!!)
}).send()
}
return names
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun TeacherNameSelector(
value: String? = "Фамилия И.О.",
isError: Boolean = false,
readOnly: Boolean = false,
onValueChange: (String?) -> Unit = {},
) {
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier.wrapContentSize()
) {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !readOnly && !expanded
}
) {
val names = getTeacherNames(LocalContext.current, onValueChange)
TextField(
label = { Text(stringResource(R.string.username)) },
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
value = value ?: names.getOrElse(1) { "TODO" }!!,
leadingIcon = {
Icon(
Icons.Filled.Person,
contentDescription = "username"
)
},
onValueChange = {},
isError = isError,
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
names.forEach {
if (it == null)
return@forEach
DropdownMenuItem(
text = { Text(it) },
onClick = {
if (names.isNotEmpty() && names[0] != null)
onValueChange(it)
expanded = false
}
)
}
}
}
}
}

View File

@@ -85,4 +85,6 @@
<string name="lesson_type_exam">ЗАЧЁТ</string> <string name="lesson_type_exam">ЗАЧЁТ</string>
<string name="lesson_type_exam_with_grade">ЗАЧЁТ С ОЦЕНКОЙ</string> <string name="lesson_type_exam_with_grade">ЗАЧЁТ С ОЦЕНКОЙ</string>
<string name="lesson_type_exam_default">ЭКЗАМЕН</string> <string name="lesson_type_exam_default">ЭКЗАМЕН</string>
<string name="supervised_group">Курируемая группа</string>
<string name="teacher_schedule">Преподаватель</string>
</resources> </resources>

View File

@@ -85,4 +85,6 @@
<string name="lesson_type_exam">EXAM*</string> <string name="lesson_type_exam">EXAM*</string>
<string name="lesson_type_exam_with_grade">EXAM* WITH GRADE</string> <string name="lesson_type_exam_with_grade">EXAM* WITH GRADE</string>
<string name="lesson_type_exam_default">EXAM</string> <string name="lesson_type_exam_default">EXAM</string>
<string name="supervised_group">Supervised group</string>
<string name="teacher_schedule">Teacher</string>
</resources> </resources>