Compare commits

..

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

35 changed files with 419 additions and 766 deletions

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,29 +1,24 @@
package ru.sweetbread.unn.ui package ru.sweetbread.unn.ui
import android.util.Log
import io.ktor.client.request.forms.submitForm import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter 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 org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import ru.sweetbread.unn.R
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 java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
private lateinit var PHPSESSID: String private lateinit var PHPSESSID: String
private lateinit var CSRF: String
lateinit var ME: User lateinit var ME: User
const val portalURL = "https://portal.unn.ru" const val portalURL = "https://portal.unn.ru"
const val ruzapiURL = "$portalURL/ruzapi" const val ruzapiURL = "$portalURL/ruzapi"
const val vuzapiURL = "$portalURL/bitrix/vuz/api"
const val restURL = "$portalURL/rest"
enum class Type(val s: String) { enum class Type(val s: String) {
Student("student"), Student("student"),
@ -32,14 +27,12 @@ enum class Type(val s: String) {
Auditorium("auditorium") Auditorium("auditorium")
} }
enum class LecturerRank(val id: Int) { enum class LecturerRank(val s: String) {
Assistant(R.string.assistant), Lecturer("Lecturer"),
Lecturer(R.string.lecturer), SLecturer("Senior Lecturer")
SLecturer(R.string.slecturer),
AProfessor(R.string.aprofessor)
} }
class ScheduleUnit(val oid: Int, class ScheduleUnit(
val auditorium: Auditorium, val auditorium: Auditorium,
val date: LocalDate, val date: LocalDate,
val discipline: Discipline, val discipline: Discipline,
@ -69,36 +62,20 @@ class KindOfWork( val name: String,
class Lecturer (val name: String, class Lecturer (val name: String,
val rank: LecturerRank, val rank: LecturerRank,
val email: String, val email: String,
val unnId: Int, val oid: Int,
val uid: String) val uid: String)
class User (val unnId: Int?, class User (val id: String,
val bitrixId: Int, val uns: String,
val userId: Int,
val type: Type, val type: Type,
val email: String, val email: String,
val nameRu: String, val name: String,
val nameEn: String, val info: 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] * Authorize user by [login] and [password]
* *
* Also defines local vars [PHPSESSID] and [ME] * Also defines local vars [PHPSESSID] and [ME.id]
*/ */
suspend fun auth(login: String = LoginData.login, password: String = LoginData.password, forced: Boolean = false): Boolean { suspend fun auth(login: String = LoginData.login, password: String = LoginData.password, forced: Boolean = false): Boolean {
if (!forced) { if (!forced) {
@ -117,67 +94,36 @@ suspend fun auth(login: String = LoginData.login, password: String = LoginData.p
if (r.status.value == 302) { if (r.status.value == 302) {
PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1] PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
getMyself(login) getMyself(login)
getCSRF()
return true return true
} }
return false return false
} }
/**
* Save info about current [User] in memory
*/
private suspend fun getMyself(login: String) { private suspend fun getMyself(login: String) {
val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo") { val r = client.get("$ruzapiURL/studentinfo") {
parameter("uns", login.substring(1)) parameter("uns", login.substring(1))
}.bodyAsText()) }
val json = JSONObject(r.bodyAsText())
val user = JSONObject(
client.get("$vuzapiURL/user") {
header("Cookie", "PHPSESSID=${PHPSESSID}")
}.bodyAsText()
)
ME = User( ME = User(
unnId = studentinfo.getString("id").toInt(), id = json.getString("id"),
bitrixId = user.getInt("bitrix_id"), uns = json.getString("uns"),
userId = user.getInt("id"), type = when(json.getString("type")) {
type = when(studentinfo.getString("type")) {
"lecturer" -> Type.Lecturer // ig,,, "lecturer" -> Type.Lecturer // ig,,,
else -> Type.Student else -> Type.Student
}, },
email = user.getString("email"), email = json.getString("email"),
nameRu = user.getString("fullname"), name = json.getString("fio"),
nameEn = user.getString("fullname_en"), info = json.getString("info")
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( suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: LocalDate, finish: LocalDate): ArrayList<ScheduleUnit> {
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") { val r = client.get("$ruzapiURL/schedule/${type.s}/$id") {
parameter("start", start.format(unnDatePattern)) parameter("start", start.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")))
parameter("finish", finish.format(unnDatePattern)) parameter("finish", finish.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")))
parameter("lng", "1") parameter("lng", "1")
} }
val json = JSONArray(r.bodyAsText()) val json = JSONArray(r.bodyAsText())
val out = arrayListOf<ScheduleUnit>() val out = arrayListOf<ScheduleUnit>()
for (i in 0 until json.length()) { for (i in 0 until json.length()) {
val unit = json.getJSONObject(i) val unit = json.getJSONObject(i)
@ -190,12 +136,10 @@ suspend fun getSchedule(
Lecturer( Lecturer(
name = lecturer.getString("lecturer"), name = lecturer.getString("lecturer"),
email = lecturer.getString("lecturerEmail"), email = lecturer.getString("lecturerEmail"),
unnId = lecturer.getInt("lecturerOid"), oid = lecturer.getInt("lecturerOid"),
uid = lecturer.getString("lecturerUID"), uid = lecturer.getString("lecturerUID"),
rank = when (lecturer.getString("lecturer_rank")) { rank = when (lecturer.getString("lecturer_rank")) {
"АССИСТ" -> LecturerRank.Assistant
"СТПРЕП" -> LecturerRank.SLecturer "СТПРЕП" -> LecturerRank.SLecturer
"ДОЦЕНТ" -> LecturerRank.AProfessor
else -> LecturerRank.Lecturer else -> LecturerRank.Lecturer
} }
) )
@ -204,7 +148,6 @@ suspend fun getSchedule(
out.add( out.add(
ScheduleUnit( ScheduleUnit(
oid = unit.getInt("lessonOid"),
auditorium = Auditorium( auditorium = Auditorium(
name = unit.getString("auditorium"), name = unit.getString("auditorium"),
oid = unit.getInt("auditoriumOid"), oid = unit.getInt("auditoriumOid"),
@ -215,7 +158,7 @@ suspend fun getSchedule(
oid = unit.getInt("buildingOid") oid = unit.getInt("buildingOid")
) )
), ),
date = LocalDate.parse(unit.getString("date"), unnDatePattern), date = LocalDate.parse(unit.getString("date"), DateTimeFormatter.ofPattern("yyyy.MM.dd")),
discipline = Discipline( discipline = Discipline(
name = unit.getString("discipline"), name = unit.getString("discipline"),
oid = unit.getInt("disciplineOid"), oid = unit.getInt("disciplineOid"),
@ -236,79 +179,3 @@ suspend fun getSchedule(
} }
return out 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

@ -1,26 +0,0 @@
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

@ -1,261 +0,0 @@
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,10 +1,8 @@
package ru.sweetbread.unn.ui.composes package ru.sweetbread.unn.ui.composes
import android.util.Log import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -15,7 +13,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@ -23,7 +20,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -31,7 +27,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment 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.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -41,7 +36,6 @@ import com.kizitonwose.calendar.compose.WeekCalendar
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.Auditorium import ru.sweetbread.unn.ui.Auditorium
import ru.sweetbread.unn.ui.Building import ru.sweetbread.unn.ui.Building
import ru.sweetbread.unn.ui.Discipline import ru.sweetbread.unn.ui.Discipline
@ -53,14 +47,13 @@ import ru.sweetbread.unn.ui.getSchedule
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@Composable @Composable
fun Schedule() { fun Schedule() {
val state = rememberWeekCalendarState( val state = rememberWeekCalendarState(
firstDayOfWeek = DayOfWeek.MONDAY // TODO: set start and end weeks to September and July of current year firstDayOfWeek = DayOfWeek.MONDAY
) )
Column { Column {
@ -73,10 +66,14 @@ fun Schedule() {
.padding(vertical = 16.dp) .padding(vertical = 16.dp)
.aspectRatio(1f) // This is important for square sizing! .aspectRatio(1f) // This is important for square sizing!
.offset(2.dp) .offset(2.dp)
.background(if (it.date == curDate) MaterialTheme.colorScheme.inversePrimary else MaterialTheme.colorScheme.surfaceContainer) .background(if (it.date == curDate) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.secondaryContainer)
.clickable( .clickable(
onClick = { curDate = it.date }, onClick = {
enabled = curDate != it.date curDate = it.date
Log.d("Here bug (olClick)",
curDate.format(DateTimeFormatter.ISO_DATE)
)
}
), ),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
@ -96,16 +93,12 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var loadedDate by remember { mutableStateOf(LocalDate.MIN) } var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
val lessons = remember { mutableListOf<ScheduleUnit>() } val lessons = remember { mutableListOf<ScheduleUnit>() }
var expanded by remember { mutableIntStateOf(0) }
if (loadedDate == date) { if (loadedDate == date) {
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}") Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
LazyColumn (modifier) { LazyColumn (modifier) {
items(lessons) { // TODO: Add empty list notification items(lessons) {
ScheduleItem(unit = it, modifier = Modifier.clickable { ScheduleItem(unit = it)
expanded = if (it.oid == expanded) 0
else it.oid
}, expanded = expanded == it.oid)
} }
} }
} else { } else {
@ -121,143 +114,51 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
lessons.clear() lessons.clear()
lessons.addAll(getSchedule(start = date, finish = date)) lessons.addAll(getSchedule(start = date, finish = date))
loadedDate = 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 @Composable
fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit, expanded: Boolean = false) { fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit) {
val begin = unit.begin.format(DateTimeFormatter.ofPattern("HH:mm"))
val end = unit.end.format(DateTimeFormatter.ofPattern("HH:mm"))
Row ( Row (
modifier modifier
.fillMaxWidth() .fillMaxWidth()
.padding(4.dp) .padding(4.dp)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background( .background(MaterialTheme.colorScheme.primaryContainer)
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) .padding(8.dp)
){ ){
Column (Modifier.weight(1f)) { Column (Modifier.weight(1f)) {
Column {
Text( Text(
text = unit.discipline.name, text = unit.discipline.name,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.zIndex(1f), 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, maxLines = 1,
overflow = TextOverflow.Ellipsis 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)
} }
} }
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 { Column {
Text(text = unit.lecturers[0].name, fontWeight = FontWeight.Bold) val begin = unit.begin.format(DateTimeFormatter.ofPattern("HH:mm"))
Text(text = stringResource(unit.lecturers[0].rank.id)) val end = unit.end.format(DateTimeFormatter.ofPattern("HH:mm"))
}
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(begin.toString(), fontWeight = FontWeight.Bold)
Text(end.toString()) Text(end.toString())
} }
} }
} }
}
AnimatedVisibility (!expanded) {
Column {
Text(begin.toString(), fontWeight = FontWeight.Bold)
Text(end.toString())
}
}
}
}
@Preview @Preview
@Composable @Composable
fun ScheduleItemPreview() { fun ScheduleItemPreview() {
val unit = ScheduleUnit( val unit = ScheduleUnit(
oid = 1,
Auditorium( Auditorium(
name = "с/з 1(110)", name = "с/з 1(110)",
oid = 3752, oid = 3752,
@ -285,8 +186,8 @@ fun ScheduleItemPreview() {
name = "Фамилия Имя Отчество", name = "Фамилия Имя Отчество",
rank = LecturerRank.SLecturer, rank = LecturerRank.SLecturer,
email = "", email = "",
unnId = 28000, oid = 28407,
uid = "51000" uid = "51769"
) )
), ),
stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP", stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP",
@ -295,56 +196,9 @@ fun ScheduleItemPreview() {
) )
UNNTheme { UNNTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
ScheduleItem(unit = unit) 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,30 +36,14 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import ru.sweetbread.unn.R import ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.auth import ru.sweetbread.unn.ui.auth
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.activities.start 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() { class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if ((LoginData.login != "") and (LoginData.password != ""))
runBlocking {
if (auth()) {
start<MainActivity>()
finish()
}
}
setContent { setContent {
UNNTheme { UNNTheme {
Surface( Surface(
@ -78,14 +62,13 @@ class LoginActivity : ComponentActivity() {
LoginData.login = login LoginData.login = login
LoginData.password = password LoginData.password = password
start<MainActivity>() start<MainActivity>()
finish()
}, { }, {
scope.launch { scope.launch {
snackbarHostState snackbarHostState
.showSnackbar( .showSnackbar(
message = "Error", message = "Error",
duration = SnackbarDuration.Short duration = SnackbarDuration.Long
) )
} }
}) })

View File

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

View File

@ -0,0 +1,170 @@
<?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

@ -0,0 +1,30 @@
<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.

After

Width:  |  Height:  |  Size: 76 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -3,11 +3,4 @@
<string name="prompt_password">Пароль</string> <string name="prompt_password">Пароль</string>
<string name="prompt_login">Логин</string> <string name="prompt_login">Логин</string>
<string name="sign_in">Войти</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> </resources>

View File

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

View File

@ -5,13 +5,5 @@
<string name="prompt_login">Login</string> <string name="prompt_login">Login</string>
<string name="prompt_password">Password</string> <string name="prompt_password">Password</string>
<string name="sign_in">Sign in</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>--> <!-- <string name="login_failed">"Login failed"</string>-->
</resources> </resources>

View File

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

View File

@ -0,0 +1,36 @@
<?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,9 +3,3 @@ plugins {
alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false alias(libs.plugins.jetbrainsKotlinAndroid) apply false
} }
buildscript {
dependencies {
classpath(libs.secrets.gradle.plugin)
}
}

View File

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