feat: add side menu

This commit is contained in:
Sweetbread 2025-04-25 22:15:38 +03:00
parent 0cad16ac74
commit 654acf1b77
Signed by: Sweetbread
GPG Key ID: 17A5CB9A7DD85319
4 changed files with 227 additions and 73 deletions

View File

@ -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,,,

View File

@ -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))
}
}
}
}

View File

@ -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>

View File

@ -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>