mirror of
https://github.com/n08i40k/polytechnic-android.git
synced 2025-12-06 09:47:48 +03:00
2.3.0
This commit is contained in:
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PolytecnicNext
|
||||||
3
.idea/dictionaries/n08i40k.xml
generated
Normal file
3
.idea/dictionaries/n08i40k.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="n08i40k" />
|
||||||
|
</component>
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user