feat: add side menu
This commit is contained in:
parent
0cad16ac74
commit
654acf1b77
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Created by sweetbread
|
||||
* Copyright (c) 2025. All rights reserved.
|
||||
*/
|
||||
|
||||
package ru.sweetbread.unn
|
||||
|
||||
import android.util.Log
|
||||
@ -8,8 +13,6 @@ import io.ktor.client.request.parameter
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.parameters
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
@ -19,6 +22,7 @@ import ru.sweetbread.unn.db.loadSchedule
|
||||
import ru.sweetbread.unn.db.loadUserByBitrixId
|
||||
import ru.sweetbread.unn.ui.layout.LoginData
|
||||
import ru.sweetbread.unn.ui.layout.client
|
||||
import splitties.resources.appStr
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -39,11 +43,11 @@ const val restURL = "$portalURL/rest"
|
||||
|
||||
|
||||
enum class Type(val s: String) {
|
||||
Student("student"),
|
||||
Student(appStr(R.string.student)),
|
||||
Group("group"),
|
||||
Lecturer("lecturer"),
|
||||
Lecturer(appStr(R.string.lecturer)),
|
||||
Auditorium("auditorium"),
|
||||
Employee("employee")
|
||||
Employee(appStr(R.string.employee))
|
||||
}
|
||||
|
||||
enum class LecturerRank(val id: Int) {
|
||||
@ -166,6 +170,7 @@ suspend fun auth(
|
||||
*/
|
||||
private suspend fun getMyself(login: String) {
|
||||
// WARNING: trailing / is important, 'cuz API devs are eating shit
|
||||
// TODO: make up another way to get unnId: this is not useful for lectures
|
||||
val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") {
|
||||
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||
parameter("uns", login.drop(1))
|
||||
@ -181,7 +186,7 @@ private suspend fun getMyself(login: String) {
|
||||
|
||||
ME = User(
|
||||
unnId = studentinfo.getString("id").toInt(),
|
||||
bitrixId = user.getInt("bitrix_id"),
|
||||
bitrixId = user.getInt("bitrix_id"), // TODO: remove
|
||||
userId = user.getInt("id"),
|
||||
type = when (studentinfo.getString("type")) {
|
||||
"lecturer" -> Type.Lecturer // ig,,,
|
||||
|
@ -9,29 +9,57 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
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.fillMaxHeight
|
||||
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.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountBox
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import coil.compose.AsyncImage
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.android.Android
|
||||
import io.ktor.client.plugins.HttpRequestRetry
|
||||
@ -40,8 +68,11 @@ import io.ktor.client.plugins.cache.HttpCache
|
||||
import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
import io.ktor.client.plugins.logging.Logging
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.sweetbread.unn.ME
|
||||
import ru.sweetbread.unn.R
|
||||
import ru.sweetbread.unn.db.AppDatabase
|
||||
import ru.sweetbread.unn.portalURL
|
||||
import ru.sweetbread.unn.ui.composes.Blogposts
|
||||
import ru.sweetbread.unn.ui.composes.Schedule
|
||||
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||
@ -85,76 +116,106 @@ class MainActivity : ComponentActivity() {
|
||||
UNNTheme {
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar (
|
||||
title = {
|
||||
Text(
|
||||
when {
|
||||
currentRoute?.startsWith("portal/") == true -> appStr(R.string.news)
|
||||
currentRoute?.startsWith("journal/") == true -> appStr(R.string.schedule)
|
||||
else -> appStr(R.string.app_name)
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
DrawerContent(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(.75f)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.systemBarsPadding(),
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
) {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
when {
|
||||
currentRoute?.startsWith("portal/") == true ->
|
||||
appStr(R.string.news)
|
||||
|
||||
currentRoute?.startsWith("journal/") == true ->
|
||||
appStr(R.string.schedule)
|
||||
|
||||
else -> appStr(R.string.app_name)
|
||||
},
|
||||
Modifier.padding()
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch { drawerState.open() }
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Filled.Menu, "Меню")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
NavigationBarItem(
|
||||
onClick = {
|
||||
navController.navigate("portal/blogposts") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
Modifier.padding()
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.Home,
|
||||
contentDescription = "Home"
|
||||
)
|
||||
},
|
||||
selected = currentRoute?.startsWith("portal/") == true
|
||||
)
|
||||
|
||||
NavigationBarItem(
|
||||
onClick = {
|
||||
navController.navigate("journal/schedule") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.DateRange,
|
||||
contentDescription = "Schedule"
|
||||
)
|
||||
},
|
||||
selected = currentRoute?.startsWith("journal/") == true
|
||||
)
|
||||
|
||||
NavigationBarItem(
|
||||
onClick = { toast("Not implemented") },
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.AccountBox,
|
||||
contentDescription = "Account"
|
||||
)
|
||||
},
|
||||
selected = false
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
NavigationBarItem(
|
||||
onClick = {
|
||||
navController.navigate("portal/blogposts") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.Home,
|
||||
contentDescription = "Home"
|
||||
)
|
||||
},
|
||||
selected = currentRoute?.startsWith("portal/") == true
|
||||
)
|
||||
|
||||
NavigationBarItem(
|
||||
onClick = {
|
||||
navController.navigate("journal/schedule") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.DateRange,
|
||||
contentDescription = "Schedule"
|
||||
)
|
||||
},
|
||||
selected = currentRoute?.startsWith("journal/") == true
|
||||
)
|
||||
|
||||
NavigationBarItem(
|
||||
onClick = { toast("Not implemented") },
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.AccountBox,
|
||||
contentDescription = "Account"
|
||||
)
|
||||
},
|
||||
selected = false
|
||||
)
|
||||
}
|
||||
}
|
||||
) {innerPadding ->
|
||||
Box(Modifier.padding(innerPadding)) {
|
||||
NavHost(navController, startDestination = "portal/blogposts") {
|
||||
composable("portal/blogposts") {
|
||||
Blogposts()
|
||||
}
|
||||
composable("journal/schedule") {
|
||||
Schedule()
|
||||
) { innerPadding ->
|
||||
Box(Modifier.padding(innerPadding)) {
|
||||
NavHost(navController, startDestination = "portal/blogposts") {
|
||||
composable("portal/blogposts") {
|
||||
Blogposts()
|
||||
}
|
||||
composable("journal/schedule") {
|
||||
Schedule()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,4 +224,82 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawerContent(modifier: Modifier = Modifier, navController: NavController) {
|
||||
Column(modifier = modifier.padding(0.dp)) {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(16.dp))
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier.size(64.dp).clip(CircleShape),
|
||||
model = portalURL + ME.avatar.thumbnail,
|
||||
contentDescription = ME.nameEn
|
||||
)
|
||||
|
||||
Column (Modifier.padding(horizontal = 8.dp)) {
|
||||
Text(
|
||||
ME.nameRu.split(" ").dropLast(1).joinToString(" "),
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(ME.type.s)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
HorizontalDivider()
|
||||
|
||||
TextButton(
|
||||
{},
|
||||
Modifier.fillMaxWidth(),
|
||||
shape = RectangleShape
|
||||
) {
|
||||
Text(
|
||||
appStr(R.string.record_book),
|
||||
Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
{},
|
||||
Modifier.fillMaxWidth(),
|
||||
shape = RectangleShape
|
||||
) {
|
||||
Text(
|
||||
appStr(R.string.documents),
|
||||
Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
{},
|
||||
Modifier.fillMaxWidth(),
|
||||
shape = RectangleShape
|
||||
) {
|
||||
Text(
|
||||
appStr(R.string.materials),
|
||||
Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
HorizontalDivider()
|
||||
TextButton(
|
||||
{},
|
||||
Modifier.fillMaxWidth(),
|
||||
shape = RectangleShape
|
||||
) {
|
||||
Text(appStr(R.string.about_app))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,10 @@
|
||||
<string name="noData">Нет данных</string>
|
||||
<string name="news">Новости</string>
|
||||
<string name="schedule">Расписание</string>
|
||||
<string name="record_book">Зачётная книга</string>
|
||||
<string name="about_app">О приложении</string>
|
||||
<string name="documents">Документы</string>
|
||||
<string name="materials">Материалы</string>
|
||||
<string-array name="short_weekdays">
|
||||
<item>Пн</item>
|
||||
<item>Вт</item>
|
||||
@ -29,4 +33,6 @@
|
||||
<item>Пт</item>
|
||||
<item>Сб</item>
|
||||
</string-array>
|
||||
<string name="student">Студент</string>
|
||||
<string name="employee">Сотрудник</string>
|
||||
</resources>
|
@ -7,7 +7,6 @@
|
||||
<string name="app_name_reg">UNN</string>
|
||||
<string name="app_name_dev">UNN Dev</string>
|
||||
<string name="app_name_beta">UNN Beta</string>
|
||||
<!-- <string name="title_activity_login">LoginActivity</string>-->
|
||||
<string name="prompt_email" translatable="false">Email</string>
|
||||
<string name="prompt_login">Login</string>
|
||||
<string name="prompt_password">Password</string>
|
||||
@ -23,6 +22,12 @@
|
||||
<string name="noData">No Data</string>
|
||||
<string name="news">News</string>
|
||||
<string name="schedule">Schedule</string>
|
||||
<string name="student">Student</string>
|
||||
<string name="employee">Employee</string>
|
||||
<string name="record_book">Record book</string>
|
||||
<string name="about_app">About app</string>
|
||||
<string name="documents">Documents</string>
|
||||
<string name="materials">Materials</string>
|
||||
<string-array name="short_weekdays">
|
||||
<item>Mn</item>
|
||||
<item>Tu</item>
|
||||
@ -31,5 +36,4 @@
|
||||
<item>Fr</item>
|
||||
<item>Sa</item>
|
||||
</string-array>
|
||||
<!-- <string name="login_failed">"Login failed"</string>-->
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user