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
|
package ru.sweetbread.unn
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -8,8 +13,6 @@ import io.ktor.client.request.parameter
|
|||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.parameters
|
import io.ktor.http.parameters
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -19,6 +22,7 @@ import ru.sweetbread.unn.db.loadSchedule
|
|||||||
import ru.sweetbread.unn.db.loadUserByBitrixId
|
import ru.sweetbread.unn.db.loadUserByBitrixId
|
||||||
import ru.sweetbread.unn.ui.layout.LoginData
|
import ru.sweetbread.unn.ui.layout.LoginData
|
||||||
import ru.sweetbread.unn.ui.layout.client
|
import ru.sweetbread.unn.ui.layout.client
|
||||||
|
import splitties.resources.appStr
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@ -39,11 +43,11 @@ const val restURL = "$portalURL/rest"
|
|||||||
|
|
||||||
|
|
||||||
enum class Type(val s: String) {
|
enum class Type(val s: String) {
|
||||||
Student("student"),
|
Student(appStr(R.string.student)),
|
||||||
Group("group"),
|
Group("group"),
|
||||||
Lecturer("lecturer"),
|
Lecturer(appStr(R.string.lecturer)),
|
||||||
Auditorium("auditorium"),
|
Auditorium("auditorium"),
|
||||||
Employee("employee")
|
Employee(appStr(R.string.employee))
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LecturerRank(val id: Int) {
|
enum class LecturerRank(val id: Int) {
|
||||||
@ -166,6 +170,7 @@ suspend fun auth(
|
|||||||
*/
|
*/
|
||||||
private suspend fun getMyself(login: String) {
|
private suspend fun getMyself(login: String) {
|
||||||
// WARNING: trailing / is important, 'cuz API devs are eating shit
|
// 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/") {
|
val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") {
|
||||||
header("Cookie", "PHPSESSID=$PHPSESSID")
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
parameter("uns", login.drop(1))
|
parameter("uns", login.drop(1))
|
||||||
@ -181,7 +186,7 @@ private suspend fun getMyself(login: String) {
|
|||||||
|
|
||||||
ME = User(
|
ME = User(
|
||||||
unnId = studentinfo.getString("id").toInt(),
|
unnId = studentinfo.getString("id").toInt(),
|
||||||
bitrixId = user.getInt("bitrix_id"),
|
bitrixId = user.getInt("bitrix_id"), // TODO: remove
|
||||||
userId = user.getInt("id"),
|
userId = user.getInt("id"),
|
||||||
type = when (studentinfo.getString("type")) {
|
type = when (studentinfo.getString("type")) {
|
||||||
"lecturer" -> Type.Lecturer // ig,,,
|
"lecturer" -> Type.Lecturer // ig,,,
|
||||||
|
@ -9,29 +9,57 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
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.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.AccountBox
|
import androidx.compose.material.icons.filled.AccountBox
|
||||||
import androidx.compose.material.icons.filled.DateRange
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.DrawerValue
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
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.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.core.view.WindowCompat
|
||||||
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import coil.compose.AsyncImage
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.android.Android
|
import io.ktor.client.engine.android.Android
|
||||||
import io.ktor.client.plugins.HttpRequestRetry
|
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.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
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.R
|
||||||
import ru.sweetbread.unn.db.AppDatabase
|
import ru.sweetbread.unn.db.AppDatabase
|
||||||
|
import ru.sweetbread.unn.portalURL
|
||||||
import ru.sweetbread.unn.ui.composes.Blogposts
|
import ru.sweetbread.unn.ui.composes.Blogposts
|
||||||
import ru.sweetbread.unn.ui.composes.Schedule
|
import ru.sweetbread.unn.ui.composes.Schedule
|
||||||
import ru.sweetbread.unn.ui.theme.UNNTheme
|
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||||
@ -85,76 +116,106 @@ class MainActivity : ComponentActivity() {
|
|||||||
UNNTheme {
|
UNNTheme {
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Scaffold(
|
ModalNavigationDrawer(
|
||||||
topBar = {
|
drawerState = drawerState,
|
||||||
CenterAlignedTopAppBar (
|
drawerContent = {
|
||||||
title = {
|
DrawerContent(
|
||||||
Text(
|
Modifier
|
||||||
when {
|
.fillMaxHeight()
|
||||||
currentRoute?.startsWith("portal/") == true -> appStr(R.string.news)
|
.fillMaxWidth(.75f)
|
||||||
currentRoute?.startsWith("journal/") == true -> appStr(R.string.schedule)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
else -> appStr(R.string.app_name)
|
.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 ->
|
||||||
) {innerPadding ->
|
Box(Modifier.padding(innerPadding)) {
|
||||||
Box(Modifier.padding(innerPadding)) {
|
NavHost(navController, startDestination = "portal/blogposts") {
|
||||||
NavHost(navController, startDestination = "portal/blogposts") {
|
composable("portal/blogposts") {
|
||||||
composable("portal/blogposts") {
|
Blogposts()
|
||||||
Blogposts()
|
}
|
||||||
}
|
composable("journal/schedule") {
|
||||||
composable("journal/schedule") {
|
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="noData">Нет данных</string>
|
||||||
<string name="news">Новости</string>
|
<string name="news">Новости</string>
|
||||||
<string name="schedule">Расписание</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">
|
<string-array name="short_weekdays">
|
||||||
<item>Пн</item>
|
<item>Пн</item>
|
||||||
<item>Вт</item>
|
<item>Вт</item>
|
||||||
@ -29,4 +33,6 @@
|
|||||||
<item>Пт</item>
|
<item>Пт</item>
|
||||||
<item>Сб</item>
|
<item>Сб</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="student">Студент</string>
|
||||||
|
<string name="employee">Сотрудник</string>
|
||||||
</resources>
|
</resources>
|
@ -7,7 +7,6 @@
|
|||||||
<string name="app_name_reg">UNN</string>
|
<string name="app_name_reg">UNN</string>
|
||||||
<string name="app_name_dev">UNN Dev</string>
|
<string name="app_name_dev">UNN Dev</string>
|
||||||
<string name="app_name_beta">UNN Beta</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_email" translatable="false">Email</string>
|
||||||
<string name="prompt_login">Login</string>
|
<string name="prompt_login">Login</string>
|
||||||
<string name="prompt_password">Password</string>
|
<string name="prompt_password">Password</string>
|
||||||
@ -23,6 +22,12 @@
|
|||||||
<string name="noData">No Data</string>
|
<string name="noData">No Data</string>
|
||||||
<string name="news">News</string>
|
<string name="news">News</string>
|
||||||
<string name="schedule">Schedule</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">
|
<string-array name="short_weekdays">
|
||||||
<item>Mn</item>
|
<item>Mn</item>
|
||||||
<item>Tu</item>
|
<item>Tu</item>
|
||||||
@ -31,5 +36,4 @@
|
|||||||
<item>Fr</item>
|
<item>Fr</item>
|
||||||
<item>Sa</item>
|
<item>Sa</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<!-- <string name="login_failed">"Login failed"</string>-->
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user