Compare commits

..

No commits in common. "master" and "dev" have entirely different histories.
master ... dev

35 changed files with 767 additions and 420 deletions

View File

@ -1,6 +1,12 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}
secrets {
propertiesFileName = "secrets.properties"
}
android {
@ -36,6 +42,7 @@ android {
buildFeatures {
compose = true
viewBinding = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
@ -48,9 +55,10 @@ android {
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.5.0")
coreLibraryDesugaring(libs.desugar.jdk.libs)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
@ -71,16 +79,19 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.navigation.compose)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.logging)
implementation(libs.coil.compose)
implementation(libs.androidx.datastore.preferences)
implementation("com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl:3.0.0")
implementation(libs.splitties.funpack.android.base.with.views.dsl)
implementation("com.kizitonwose.calendar:compose:2.5.0")
implementation(libs.compose)
implementation(libs.kefirbb)
implementation(libs.acra.http)
}

View File

@ -5,9 +5,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".ui.UNNApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.UNN"
@ -15,6 +16,10 @@
<activity
android:name=".ui.layout.MainActivity"
android:exported="true"
android:theme="@style/Theme.UNN" />
<activity
android:name=".ui.layout.LoginActivity"
android:exported="true"
android:theme="@style/Theme.UNN">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -22,10 +27,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.layout.LoginActivity"
android:exported="true"
android:theme="@style/Theme.UNN"/>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,24 +1,29 @@
package ru.sweetbread.unn.ui
import android.util.Log
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.statement.bodyAsText
import io.ktor.http.parameters
import org.json.JSONArray
import org.json.JSONObject
import ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.layout.LoginData
import ru.sweetbread.unn.ui.layout.client
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
private lateinit var PHPSESSID: String
private lateinit var CSRF: String
lateinit var ME: User
const val portalURL = "https://portal.unn.ru"
const val ruzapiURL = "$portalURL/ruzapi"
const val vuzapiURL = "$portalURL/bitrix/vuz/api"
const val restURL = "$portalURL/rest"
enum class Type(val s: String) {
Student("student"),
@ -27,55 +32,73 @@ enum class Type(val s: String) {
Auditorium("auditorium")
}
enum class LecturerRank(val s: String) {
Lecturer("Lecturer"),
SLecturer("Senior Lecturer")
enum class LecturerRank(val id: Int) {
Assistant(R.string.assistant),
Lecturer(R.string.lecturer),
SLecturer(R.string.slecturer),
AProfessor(R.string.aprofessor)
}
class ScheduleUnit(
val auditorium: Auditorium,
val date: LocalDate,
val discipline: Discipline,
val kindOfWork: KindOfWork,
val lecturers: ArrayList<Lecturer>,
val stream: String,
val begin: LocalTime,
val end: LocalTime)
class ScheduleUnit(val oid: Int,
val auditorium: Auditorium,
val date: LocalDate,
val discipline: Discipline,
val kindOfWork: KindOfWork,
val lecturers: ArrayList<Lecturer>,
val stream: String,
val begin: LocalTime,
val end: LocalTime)
class Auditorium (val name: String,
val oid: Int,
val floor: Int,
val building: Building)
class Building(val name: String,
val gid: Int,
val oid: Int)
class Auditorium( val name: String,
val oid: Int,
val floor: Int,
val building: Building)
class Building( val name: String,
val gid: Int,
val oid: Int)
class Discipline (val name: String,
val oid: Int,
val type: Int)
class Discipline( val name: String,
val oid: Int,
val type: Int)
class KindOfWork (val name: String,
val oid: Int,
val uid: String,
val complexity: Int)
class KindOfWork( val name: String,
val oid: Int,
val uid: String,
val complexity: Int)
class Lecturer (val name: String,
val rank: LecturerRank,
val email: String,
val oid: Int,
val uid: String)
class Lecturer( val name: String,
val rank: LecturerRank,
val email: String,
val unnId: Int,
val uid: String)
class User (val id: String,
val uns: String,
class User (val unnId: Int?,
val bitrixId: Int,
val userId: Int,
val type: Type,
val email: String,
val name: String,
val info: String)
val nameRu: String,
val nameEn: String,
val isMale: Boolean,
val birthday: LocalDate,
val avatar: ImageSet)
class Post(
val id: Int,
val authorId: Int,
val enableComments: Boolean,
val numComments: Int,
val date: LocalDateTime,
val content: String)
class ImageSet(val original: String,
val thumbnail: String,
val small: String)
/**
* Authorize user by [login] and [password]
*
* Also defines local vars [PHPSESSID] and [ME.id]
* Also defines local vars [PHPSESSID] and [ME]
*/
suspend fun auth(login: String = LoginData.login, password: String = LoginData.password, forced: Boolean = false): Boolean {
if (!forced) {
@ -94,36 +117,67 @@ suspend fun auth(login: String = LoginData.login, password: String = LoginData.p
if (r.status.value == 302) {
PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
getMyself(login)
getCSRF()
return true
}
return false
}
/**
* Save info about current [User] in memory
*/
private suspend fun getMyself(login: String) {
val r = client.get("$ruzapiURL/studentinfo") {
val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo") {
parameter("uns", login.substring(1))
}
val json = JSONObject(r.bodyAsText())
}.bodyAsText())
val user = JSONObject(
client.get("$vuzapiURL/user") {
header("Cookie", "PHPSESSID=${PHPSESSID}")
}.bodyAsText()
)
ME = User(
id = json.getString("id"),
uns = json.getString("uns"),
type = when(json.getString("type")) {
unnId = studentinfo.getString("id").toInt(),
bitrixId = user.getInt("bitrix_id"),
userId = user.getInt("id"),
type = when(studentinfo.getString("type")) {
"lecturer" -> Type.Lecturer // ig,,,
else -> Type.Student
},
email = json.getString("email"),
name = json.getString("fio"),
info = json.getString("info")
email = user.getString("email"),
nameRu = user.getString("fullname"),
nameEn = user.getString("fullname_en"),
isMale = user.getString("sex") == "M",
birthday = LocalDate.parse(
user.getString("birthdate"),
DateTimeFormatter.ofPattern("yyyy-MM-dd")
),
avatar = user.getJSONObject("photo").let {
ImageSet(
it.getString("orig"),
it.getString("thumbnail"),
it.getString("small"),
)
}
)
}
suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: LocalDate, finish: LocalDate): ArrayList<ScheduleUnit> {
suspend fun getSchedule(
type: Type = ME.type,
id: Int = ME.unnId!!,
start: LocalDate,
finish: LocalDate
): ArrayList<ScheduleUnit> {
val unnDatePattern = DateTimeFormatter.ofPattern("yyyy.MM.dd")
val r = client.get("$ruzapiURL/schedule/${type.s}/$id") {
parameter("start", start.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")))
parameter("finish", finish.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")))
parameter("start", start.format(unnDatePattern))
parameter("finish", finish.format(unnDatePattern))
parameter("lng", "1")
}
val json = JSONArray(r.bodyAsText())
val out = arrayListOf<ScheduleUnit>()
for (i in 0 until json.length()) {
val unit = json.getJSONObject(i)
@ -136,10 +190,12 @@ suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: Lo
Lecturer(
name = lecturer.getString("lecturer"),
email = lecturer.getString("lecturerEmail"),
oid = lecturer.getInt("lecturerOid"),
unnId = lecturer.getInt("lecturerOid"),
uid = lecturer.getString("lecturerUID"),
rank = when (lecturer.getString("lecturer_rank")) {
"АССИСТ" -> LecturerRank.Assistant
"СТПРЕП" -> LecturerRank.SLecturer
"ДОЦЕНТ" -> LecturerRank.AProfessor
else -> LecturerRank.Lecturer
}
)
@ -148,6 +204,7 @@ suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: Lo
out.add(
ScheduleUnit(
oid = unit.getInt("lessonOid"),
auditorium = Auditorium(
name = unit.getString("auditorium"),
oid = unit.getInt("auditoriumOid"),
@ -158,7 +215,7 @@ suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: Lo
oid = unit.getInt("buildingOid")
)
),
date = LocalDate.parse(unit.getString("date"), DateTimeFormatter.ofPattern("yyyy.MM.dd")),
date = LocalDate.parse(unit.getString("date"), unnDatePattern),
discipline = Discipline(
name = unit.getString("discipline"),
oid = unit.getInt("disciplineOid"),
@ -179,3 +236,79 @@ suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: Lo
}
return out
}
suspend fun getCSRF() {
val r = client.get("$restURL/log.blogpost.get") {
header("Cookie", "PHPSESSID=${PHPSESSID}")
parameter("sessid", "")
}
CSRF = JSONObject(r.bodyAsText()).getString("sessid")
}
suspend fun getBlogposts(): ArrayList<Post> {
val r = client.get("$restURL/log.blogpost.get") {
header("Cookie", "PHPSESSID=${PHPSESSID}")
parameter("sessid", CSRF)
}
val json = JSONObject(r.bodyAsText())
val result = json.getJSONArray("result")
val out = arrayListOf<Post>()
for (i in 0 until result.length()) {
val el = result.getJSONObject(i)
out.add(
Post(
id = el.getString("ID").toInt(),
authorId = el.getString("AUTHOR_ID").toInt(),
enableComments = el.getString("ENABLE_COMMENTS") == "Y",
numComments = el.getString("NUM_COMMENTS").toInt(),
date = LocalDateTime.parse(
el.getString("DATE_PUBLISH"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+03:00'")
),
content = el.getString("DETAIL_TEXT")
)
)
}
return out
}
suspend fun getUserByBitrixId(id: Int): User {
val userId = JSONObject(client.get("$vuzapiURL/user/bx/$id") {
header("Cookie", "PHPSESSID=${PHPSESSID}")
}.bodyAsText()).getInt("id")
return getUser(userId)
}
suspend fun getUser(id: Int): User {
val json = JSONObject(
client.get("$vuzapiURL/user/$id") {
header("Cookie", "PHPSESSID=${PHPSESSID}")
}.bodyAsText()
)
return User(
unnId = null,
bitrixId = json.getInt("bitrix_id"),
userId = json.getInt("id"),
type = when (json.getJSONArray("profiles").getJSONObject(0).getString("type")) {
"lecturer" -> Type.Lecturer // ig,,,
else -> Type.Student
},
email = json.getString("email"),
nameRu = json.getString("fullname"),
nameEn = json.getString("fullname_en"),
isMale = json.getString("sex") == "M",
birthday = LocalDate.parse(
json.getString("birthdate"),
DateTimeFormatter.ofPattern("yyyy-MM-dd")
),
avatar = json.getJSONObject("photo").let {
ImageSet(
it.getString("orig"),
it.getString("thumbnail"),
it.getString("small"),
)
}
)
}

View File

@ -0,0 +1,26 @@
package ru.sweetbread.unn.ui
import android.app.Application
import android.content.Context
import org.acra.config.httpSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import ru.sweetbread.unn.BuildConfig
class UNNApp : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
initAcra {
buildConfigClass = BuildConfig::class.java
reportFormat = StringFormat.JSON
httpSender {
uri = BuildConfig.ACRA_URL
basicAuthLogin = BuildConfig.ACRA_LOGIN
basicAuthPassword = BuildConfig.ACRA_PASS
httpMethod = HttpSender.Method.POST
}
}
}
}

View File

@ -0,0 +1,261 @@
package ru.sweetbread.unn.ui.composes
import android.text.util.Linkify
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.HtmlCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.google.android.material.textview.MaterialTextView
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.kefirsf.bb.BBProcessorFactory
import org.kefirsf.bb.TextProcessor
import ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.ImageSet
import ru.sweetbread.unn.ui.Post
import ru.sweetbread.unn.ui.Type
import ru.sweetbread.unn.ui.User
import ru.sweetbread.unn.ui.getBlogposts
import ru.sweetbread.unn.ui.getUserByBitrixId
import ru.sweetbread.unn.ui.portalURL
import ru.sweetbread.unn.ui.theme.UNNTheme
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
val defUser = User(
null,
123,
123,
Type.Student,
"cool.email@domain.com",
"Джон Сигма Омегович",
"Jon Sigma Omega",
true,
LocalDate.now(),
ImageSet(
"https://upload.wikimedia.org/wikipedia/ru/thumb/9/94/%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg/500px-%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg",
"https://upload.wikimedia.org/wikipedia/ru/thumb/9/94/%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg/500px-%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg",
"https://upload.wikimedia.org/wikipedia/ru/thumb/9/94/%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg/500px-%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg"
)
)
@Composable
fun Blogposts(viewModel: PostViewModel = viewModel()) {
val posts by viewModel.posts.collectAsState()
LaunchedEffect(Unit) {
viewModel.loadPosts()
}
if (posts.isNotEmpty()) {
Log.d("Another fuck", posts.size.toString())
LazyColumn {
items(posts) {
PostItem(
Modifier
.padding(8.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.secondaryContainer),
post = it
)
}
}
} else {
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
trackColor = MaterialTheme.colorScheme.secondary,
)
}
}
class PostRepository {
suspend fun loadPosts(): List<Post> {
return getBlogposts()
}
}
class PostViewModel : ViewModel() {
private val repository = PostRepository()
private val _posts = MutableStateFlow<List<Post>>(emptyList())
val posts: StateFlow<List<Post>> = _posts.asStateFlow()
suspend fun loadPosts() {
_posts.value = repository.loadPosts()
}
}
@Composable
@NonRestartableComposable
fun UserItem(modifier: Modifier = Modifier, user: User, info: String? = null) {
Row(
modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
AsyncImage(
modifier = Modifier
.padding(end = 8.dp)
.size(48.dp)
.clip(RoundedCornerShape(50)),
model = portalURL + user.avatar.thumbnail,
contentDescription = user.nameEn
)
Column {
Text(user.nameRu, fontWeight = FontWeight.Bold)
if (!info.isNullOrBlank())
Text(
text = info,
fontStyle = FontStyle.Italic,
fontSize = MaterialTheme.typography.labelLarge.fontSize
)
}
}
}
@Composable
@NonRestartableComposable
fun PostItem(modifier: Modifier = Modifier, post: Post) {
var user: User? by remember { mutableStateOf(null) }
val processor = remember { BBProcessorFactory.getInstance().create() }
var html: String by remember { mutableStateOf("") }
LaunchedEffect(post) {
html = toHtml(processor, post)
user = getUserByBitrixId(post.authorId)
}
Column(modifier.padding(16.dp)) {
if (user != null)
UserItem(user = user!!)
else
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
trackColor = MaterialTheme.colorScheme.secondary,
)
AndroidView(
factory = {
MaterialTextView(it).apply {
autoLinkMask = Linkify.WEB_URLS
linksClickable = true
setLinkTextColor(Color.White.toArgb())
}
},
update = {
it.maxLines = 25
it.text = HtmlCompat.fromHtml(html, 0)
}
)
HorizontalDivider(
modifier = Modifier.padding(vertical = 16.dp),
thickness = 1.dp,
color = MaterialTheme.colorScheme.onBackground
)
Text(text = post.date.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)))
}
}
private fun toHtml(
processor: TextProcessor,
post: Post
): String {
val html = processor.process(post.content)
return html.replace("""\[URL=(.+)](.+)\[/URL]""".toRegex()) {
Log.d("replace", it.groups.toString())
"<a href='${it.groups[1]?.value}'>${it.groups[2]?.value}</a>"
}.replace("""(\[FONT=.+]|\[CENTER])(.+)(\[/FONT]|\[/CENTER])""".toRegex()) {
it.groups[2]?.value.toString()
}.replace("""\[IMG .+].+\[/IMG]""".toRegex(), "")
}
@Preview
@Composable
fun UserItemPreview() {
UNNTheme {
Surface {
UserItem(
Modifier
.width(300.dp)
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.primaryContainer), defUser, Type.Student.s
)
}
}
}
@Preview
@Composable
fun PostItemPreview() {
val post = Post(
id = 154923,
authorId = 165945,
enableComments = true,
numComments = 0,
date = LocalDateTime.of(2024, 3, 20, 18, 55, 20),
content = stringResource(id = R.string.lorem)
)
UNNTheme {
Surface {
PostItem(
Modifier
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.primaryContainer), post
)
}
}
}

View File

@ -1,8 +1,10 @@
package ru.sweetbread.unn.ui.composes
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -13,6 +15,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -20,6 +23,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -27,6 +31,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -36,6 +41,7 @@ import com.kizitonwose.calendar.compose.WeekCalendar
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.Auditorium
import ru.sweetbread.unn.ui.Building
import ru.sweetbread.unn.ui.Discipline
@ -47,13 +53,14 @@ import ru.sweetbread.unn.ui.getSchedule
import ru.sweetbread.unn.ui.theme.UNNTheme
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@Composable
fun Schedule() {
val state = rememberWeekCalendarState(
firstDayOfWeek = DayOfWeek.MONDAY
firstDayOfWeek = DayOfWeek.MONDAY // TODO: set start and end weeks to September and July of current year
)
Column {
@ -66,14 +73,10 @@ fun Schedule() {
.padding(vertical = 16.dp)
.aspectRatio(1f) // This is important for square sizing!
.offset(2.dp)
.background(if (it.date == curDate) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.secondaryContainer)
.background(if (it.date == curDate) MaterialTheme.colorScheme.inversePrimary else MaterialTheme.colorScheme.surfaceContainer)
.clickable(
onClick = {
curDate = it.date
Log.d("Here bug (olClick)",
curDate.format(DateTimeFormatter.ISO_DATE)
)
}
onClick = { curDate = it.date },
enabled = curDate != it.date
),
contentAlignment = Alignment.Center,
) {
@ -93,12 +96,16 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
val scope = rememberCoroutineScope()
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
val lessons = remember { mutableListOf<ScheduleUnit>() }
var expanded by remember { mutableIntStateOf(0) }
if (loadedDate == date) {
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
LazyColumn (modifier) {
items(lessons) {
ScheduleItem(unit = it)
items(lessons) { // TODO: Add empty list notification
ScheduleItem(unit = it, modifier = Modifier.clickable {
expanded = if (it.oid == expanded) 0
else it.oid
}, expanded = expanded == it.oid)
}
}
} else {
@ -114,43 +121,134 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
lessons.clear()
lessons.addAll(getSchedule(start = date, finish = date))
loadedDate = date
Log.d("Loading", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
Log.d("Here bug", "${loadedDate.format(DateTimeFormatter.ISO_DATE)} ${date.format(DateTimeFormatter.ISO_DATE)}")
}
}
}
}
@Composable
fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit) {
fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit, expanded: Boolean = false) {
val begin = unit.begin.format(DateTimeFormatter.ofPattern("HH:mm"))
val end = unit.end.format(DateTimeFormatter.ofPattern("HH:mm"))
Row (
modifier
.fillMaxWidth()
.padding(4.dp)
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.primaryContainer)
.background(
if ((LocalDateTime.of(
unit.date,
unit.begin
) < LocalDateTime.now()) and (LocalDateTime.now() < LocalDateTime.of(
unit.date,
unit.end
))
)
MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.secondaryContainer
)
.padding(8.dp)
){
Column (Modifier.weight(1f)) {
Text(
text = unit.discipline.name,
fontWeight = FontWeight.Bold,
modifier = Modifier.zIndex(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(text = unit.kindOfWork.name, maxLines = 1, overflow = TextOverflow.Ellipsis)
Row (Modifier) {
Text(text = unit.auditorium.name, fontWeight = FontWeight.Bold, modifier = Modifier.padding(end = 4.dp))
Text(text = unit.auditorium.building.name, maxLines = 1, overflow = TextOverflow.Ellipsis)
Column {
Text(
text = unit.discipline.name,
fontWeight = FontWeight.Bold,
modifier = Modifier.zIndex(1f),
maxLines = if (expanded) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis
)
}
AnimatedVisibility (expanded) {
HorizontalDivider(
modifier = Modifier.padding(vertical = 16.dp),
thickness = 1.dp,
color = MaterialTheme.colorScheme.onBackground
)
}
Column {
Text(
text = unit.kindOfWork.name,
overflow = TextOverflow.Ellipsis
)
AnimatedVisibility (expanded) {
Text(text = unit.stream)
}
}
AnimatedVisibility (expanded) {
HorizontalDivider(
modifier = Modifier.padding(vertical = 16.dp),
thickness = 1.dp,
color = MaterialTheme.colorScheme.onBackground
)
}
AnimatedVisibility (!expanded) {
Row(Modifier) {
Text(
text = unit.auditorium.name,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(end = 4.dp)
)
Text(
text = unit.auditorium.building.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
AnimatedVisibility (expanded) {
Column {
Text(
text = "${stringResource(R.string.auditorium)}: ${unit.auditorium.name}",
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(end = 4.dp)
)
Text(
text = "${stringResource(R.string.building)}: ${unit.auditorium.building.name}",
overflow = TextOverflow.Ellipsis
)
if (unit.auditorium.floor != 0) {
Text(
text = "${stringResource(R.string.floor)}: ${unit.auditorium.floor}",
overflow = TextOverflow.Ellipsis
)
}
HorizontalDivider(
modifier = Modifier.padding(vertical = 16.dp),
thickness = 1.dp,
color = MaterialTheme.colorScheme.onBackground
)
Column {
Text(text = unit.lecturers[0].name, fontWeight = FontWeight.Bold)
Text(text = stringResource(unit.lecturers[0].rank.id))
}
HorizontalDivider(
modifier = Modifier.padding(vertical = 16.dp),
thickness = 1.dp,
color = MaterialTheme.colorScheme.onBackground
)
Row (Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(begin.toString(), fontWeight = FontWeight.Bold)
Text(end.toString())
}
}
}
}
Column {
val begin = unit.begin.format(DateTimeFormatter.ofPattern("HH:mm"))
val end = unit.end.format(DateTimeFormatter.ofPattern("HH:mm"))
Text(begin.toString(), fontWeight = FontWeight.Bold)
Text(end.toString())
AnimatedVisibility (!expanded) {
Column {
Text(begin.toString(), fontWeight = FontWeight.Bold)
Text(end.toString())
}
}
}
}
@ -159,6 +257,7 @@ fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit) {
@Composable
fun ScheduleItemPreview() {
val unit = ScheduleUnit(
oid = 1,
Auditorium(
name = "с/з 1(110)",
oid = 3752,
@ -186,8 +285,8 @@ fun ScheduleItemPreview() {
name = "Фамилия Имя Отчество",
rank = LecturerRank.SLecturer,
email = "",
oid = 28407,
uid = "51769"
unnId = 28000,
uid = "51000"
)
),
stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP",
@ -196,9 +295,56 @@ fun ScheduleItemPreview() {
)
UNNTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colorScheme.background) {
ScheduleItem(unit = unit)
}
}
}
@Preview
@Composable
fun ScheduleExpandedItemPreview() {
val unit = ScheduleUnit(
oid = 1,
Auditorium(
name = "с/з 1(110)",
oid = 3752,
floor = 0,
building = Building(
name = "Корпус 6",
gid = 30,
oid = 155
),
),
date = LocalDate.of(2024, 3, 11),
discipline = Discipline(
name = "Физическая культура и спорт (элективная дисциплина)",
oid = 67895,
type = 0
),
kindOfWork = KindOfWork(
name = "Практика (семинарские занятия)",
oid = 261,
uid = "281474976710661",
complexity = 1
),
lecturers = arrayListOf(
Lecturer(
name = "Фамилия Имя Отчество",
rank = LecturerRank.SLecturer,
email = "",
unnId = 28000,
uid = "51000"
)
),
stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP",
begin = LocalTime.of(10, 50),
end = LocalTime.of(12, 20)
)
UNNTheme {
Surface(color = MaterialTheme.colorScheme.background) {
ScheduleItem(unit = unit, expanded = true)
}
}
}

View File

@ -36,14 +36,30 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.auth
import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.activities.start
import splitties.preferences.Preferences
object LoginData : Preferences("loginData") {
var login by stringPref("login", "")
var password by stringPref("password", "")
}
class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if ((LoginData.login != "") and (LoginData.password != ""))
runBlocking {
if (auth()) {
start<MainActivity>()
finish()
}
}
setContent {
UNNTheme {
Surface(
@ -62,13 +78,14 @@ class LoginActivity : ComponentActivity() {
LoginData.login = login
LoginData.password = password
start<MainActivity>()
finish()
}, {
scope.launch {
snackbarHostState
.showSnackbar(
message = "Error",
duration = SnackbarDuration.Long
duration = SnackbarDuration.Short
)
}
})

View File

@ -4,85 +4,39 @@ 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.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
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.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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.kizitonwose.calendar.compose.WeekCalendar
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
import io.ktor.client.HttpClient
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.cache.HttpCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import ru.sweetbread.unn.ui.Auditorium
import ru.sweetbread.unn.ui.Building
import ru.sweetbread.unn.ui.Discipline
import ru.sweetbread.unn.ui.KindOfWork
import ru.sweetbread.unn.ui.Lecturer
import ru.sweetbread.unn.ui.LecturerRank
import ru.sweetbread.unn.ui.ScheduleUnit
import ru.sweetbread.unn.ui.auth
import ru.sweetbread.unn.ui.getSchedule
import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.activities.start
import splitties.preferences.Preferences
import splitties.toast.toast
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import io.ktor.client.plugins.logging.*
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import ru.sweetbread.unn.ui.composes.Blogposts
import ru.sweetbread.unn.ui.composes.Schedule
import ru.sweetbread.unn.ui.composes.ScheduleDay
object LoginData : Preferences("loginData") {
var login by stringPref("login", "")
var password by stringPref("password", "")
}
import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.toast.toast
import java.io.File
val client = HttpClient {
install(HttpCache)
@ -106,46 +60,50 @@ val client = HttpClient {
}
}
val cacheDir = File("/data/data/ru.sweetbread.unn/files/cache")
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (LoginData.login.isEmpty() or LoginData.password.isEmpty()) start<LoginActivity>()
runBlocking {
if (!auth()) start<LoginActivity>()
}
Log.d("mkdir", cacheDir.mkdir().toString())
setContent {
UNNTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
val navController = rememberNavController()
var route by remember { mutableStateOf("portal/blogposts") }
Scaffold(
bottomBar = {
NavigationBar {
NavigationBarItem(
onClick = { toast("Not implemented") },
onClick = {
route = "portal/blogposts"
navController.navigate(route)
},
icon = {
Icon(
Icons.Filled.Home,
contentDescription = "Home"
)
},
selected = navController.currentDestination?.route?.startsWith("home") ?: false
selected = route.startsWith("portal/")
)
NavigationBarItem(
onClick = { navController.navigate("schedule/student/me/today")
Log.d("route", navController.currentDestination?.route.toString())},
onClick = {
route = "journal/schedule"
navController.navigate(route)
},
icon = {
Icon(
Icons.Filled.DateRange,
contentDescription = "Schedule"
)
},
selected = navController.currentDestination?.route?.startsWith("schedule") ?: false
selected = route.startsWith("journal/")
)
NavigationBarItem(
onClick = { toast("Not implemented") },
icon = {
@ -158,19 +116,13 @@ class MainActivity : ComponentActivity() {
)
}
}
) {innerPadding ->
Box(Modifier.padding(innerPadding)) {
NavHost(navController, startDestination = "home/blogposts") {
composable("home/blogposts") {
Text("Not implemented")
NavHost(navController, startDestination = "portal/blogposts") {
composable("portal/blogposts") {
Blogposts()
}
composable("schedule/{type}/{who}/{when}",
arguments = listOf(
navArgument("type") { type = NavType.StringType },
navArgument("who") { type = NavType.StringType },
navArgument("when") { type = NavType.StringType },)
) {
composable("journal/schedule") {
Schedule()
}
}

View File

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -3,4 +3,11 @@
<string name="prompt_password">Пароль</string>
<string name="prompt_login">Логин</string>
<string name="sign_in">Войти</string>
<string name="assistant">Ассистент</string>
<string name="lecturer">Преподаватель</string>
<string name="slecturer">Старший Преподаватель</string>
<string name="aprofessor">Доцент</string>
<string name="auditorium">Аудитория</string>
<string name="building">Здание</string>
<string name="floor">Этаж</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="ic_launcher_background">#1565AA</color>
</resources>

View File

@ -5,5 +5,13 @@
<string name="prompt_login">Login</string>
<string name="prompt_password">Password</string>
<string name="sign_in">Sign in</string>
<string name="assistant">Assistant</string>
<string name="lecturer">Lecturer</string>
<string name="slecturer">Senior Lecturer</string>
<string name="aprofessor">Assistant professor</string>
<string name="auditorium">Auditorium</string>
<string name="building">Building</string>
<string name="floor">Floor</string>
<string name="lorem" translatable="false">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vel iaculis elit. Aliquam varius urna ut nisl rhoncus ullamcorper. Maecenas et nisl at dui mollis maximus nec in libero. Ut eu nulla id felis hendrerit lobortis. Maecenas vel facilisis lectus. Morbi eleifend massa a ante consequat, eu aliquam elit euismod. Aenean quis erat tincidunt, egestas ligula id, convallis tortor. Vivamus volutpat condimentum nisl sed eleifend. Aenean dapibus dolor ut orci lobortis, placerat lobortis tortor pretium. Nam eros lectus, convallis sed ultricies sit amet, lacinia sed sem. In mi odio, porta non malesuada et, cursus a metus. Morbi quis odio sed quam commodo gravida id sit amet dolor. Donec ac iaculis massa. Nulla mauris sapien, auctor consequat est in, tempus accumsan ipsum. Donec semper volutpat nisi. Quisque dignissim tellus ipsum, sed malesuada libero aliquam sed. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam eleifend pharetra orci eu scelerisque. In hac habitasse platea dictumst. Sed non neque vitae metus porttitor vestibulum ut eget felis. Aliquam venenatis a magna eu mattis. Proin rutrum, sapien id viverra finibus, nisi quam aliquam eros, et dignissim lectus sem sit amet purus. Donec et semper enim, sed pretium lacus. Nullam venenatis ullamcorper maximus. Mauris pellentesque velit non sem sollicitudin molestie. Duis hendrerit consequat enim eget euismod.</string>
<!-- <string name="login_failed">"Login failed"</string>-->
</resources>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.UNN" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.UNN" parent="android:Theme.Material.NoActionBar" />
</resources>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!--
TODO: Use <include> and <exclude> to control what is backed up.
The domain can be file, database, sharedpref, external or root.
Examples:
<include domain="file" path="file_to_include"/>
<exclude domain="file" path="file_to_exclude"/>
<include domain="file" path="include_folder"/>
<exclude domain="file" path="include_folder/file_to_exclude"/>
<exclude domain="file" path="exclude_folder"/>
<include domain="file" path="exclude_folder/file_to_include"/>
<include domain="sharedpref" path="include_shared_pref1.xml"/>
<include domain="database" path="db_name/file_to_include"/>
<exclude domain="database" path="db_name/include_folder/file_to_exclude"/>
<include domain="external" path="file_to_include"/>
<exclude domain="external" path="file_to_exclude"/>
<include domain="root" path="file_to_include"/>
<exclude domain="root" path="file_to_exclude"/>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -3,3 +3,9 @@ plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
}
buildscript {
dependencies {
classpath(libs.secrets.gradle.plugin)
}
}

View File

@ -1,32 +1,43 @@
[versions]
agp = "8.3.0"
acraHttp = "5.11.3"
agp = "8.3.1"
coilCompose = "2.6.0"
compose = "2.5.0"
coreSplashscreen = "1.0.1"
datastorePreferences = "1.0.0"
desugar_jdk_libs = "2.0.4"
kotlin = "1.9.0"
coreKtx = "1.10.1"
junit = "4.13.2"
coreKtx = "1.12.0"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
ktorClientCio = "2.3.9"
ktorClientCore = "2.3.9"
ktorClientLogging = "2.3.9"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.7.0"
composeBom = "2023.08.00"
lifecycleRuntimeKtx = "2.7.0"
activityCompose = "1.8.2"
composeBom = "2024.02.02"
appcompat = "1.6.1"
material = "1.10.0"
annotation = "1.6.0"
material = "1.11.0"
annotation = "1.7.1"
constraintlayout = "2.1.4"
lifecycleLivedataKtx = "2.6.1"
lifecycleViewmodelKtx = "2.6.1"
activity = "1.8.0"
lifecycleLivedataKtx = "2.7.0"
lifecycleViewmodelKtx = "2.7.0"
activity = "1.8.2"
navigationCompose = "2.7.7"
richeditorCompose = "1.0.0-rc01"
secretsGradlePlugin = "2.0.1"
splittiesFunPackAndroidBaseWithViewsDsl = "3.0.0"
kefirbb = "1.5"
[libraries]
acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
compose = { module = "com.kizitonwose.calendar:compose", version.ref = "compose" }
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
@ -49,7 +60,10 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
splitties-fun-pack-android-base-with-views-dsl = { module = "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl", version.ref = "splittiesFunPackAndroidBaseWithViewsDsl" }
richeditor-compose = { module = "com.mohamedrejeb.richeditor:richeditor-compose", version.ref = "richeditorCompose" }
secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }
splitties-funpack-android-base-with-views-dsl = { module = "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl", version.ref = "splittiesFunPackAndroidBaseWithViewsDsl" }
kefirbb = { group = "org.kefirsf", name = "kefirbb", version.ref = "kefirbb" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }