Init commit

This commit is contained in:
sweetbread
2024-03-19 00:27:57 +03:00
commit 983249091a
58 changed files with 1747 additions and 0 deletions
@@ -0,0 +1,24 @@
package ru.sweetbread.unn
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("ru.sweetbread.unn", appContext.packageName)
}
}
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.UNN"
tools:targetApi="31">
<activity
android:name=".ui.layout.MainActivity"
android:exported="true"
android:theme="@style/Theme.UNN">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<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.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,187 @@
package ru.sweetbread.unn.ui
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
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.ui.layout.LoginData
import ru.sweetbread.unn.ui.layout.client
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
private lateinit var PHPSESSID: String
lateinit var ME: User
const val portalURL = "https://portal.unn.ru"
const val ruzapiURL = "$portalURL/ruzapi"
enum class Type(val s: String) {
Student("student"),
Group("group"),
Lecturer("lecturer"),
Auditorium("auditorium")
}
enum class LecturerRank(val s: String) {
Lecturer("Lecturer"),
SLecturer("Senior Lecturer")
}
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 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 Lecturer( val name: String,
val rank: LecturerRank,
val email: String,
val oid: Int,
val uid: String)
class User (val id: String,
val uns: String,
val type: Type,
val email: String,
val name: String,
val info: String)
/**
* Authorize user by [login] and [password]
*
* Also defines local vars [PHPSESSID] and [ME]
*/
suspend fun auth(login: String = LoginData.login, password: String = LoginData.password, forced: Boolean = false): Boolean {
if (!forced) {
if (::PHPSESSID.isInitialized and ::ME.isInitialized)
return true
}
val r = client.submitForm("$portalURL/auth/?login=yes",
formParameters = parameters {
append("AUTH_FORM", "Y")
append("TYPE", "AUTH")
append("backurl", "/")
append("USER_LOGIN", login)
append("USER_PASSWORD", password)
}
)
if (r.status.value == 302) {
PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
getMyself(login)
return true
}
return false
}
/**
* Save info about current [User] in memory
*/
private suspend fun getMyself(login: String) {
val r = client.get("$ruzapiURL/studentinfo") {
parameter("uns", login.substring(1))
}
val json = JSONObject(r.bodyAsText())
ME = User(
id = json.getString("id"),
uns = json.getString("uns"),
type = when(json.getString("type")) {
"lecturer" -> Type.Lecturer // ig,,,
else -> Type.Student
},
email = json.getString("email"),
name = json.getString("fio"),
info = json.getString("info")
)
}
suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, 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(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)
val lecturesJson = unit.getJSONArray("listOfLecturers")
val lecturers = arrayListOf<Lecturer>()
for (j in 0 until lecturesJson.length()) {
val lecturer = lecturesJson.getJSONObject(j)
lecturers.add(
Lecturer(
name = lecturer.getString("lecturer"),
email = lecturer.getString("lecturerEmail"),
oid = lecturer.getInt("lecturerOid"),
uid = lecturer.getString("lecturerUID"),
rank = when (lecturer.getString("lecturer_rank")) {
"СТПРЕП" -> LecturerRank.SLecturer
else -> LecturerRank.Lecturer
}
)
)
}
out.add(
ScheduleUnit(
oid = unit.getInt("lessonOid"),
auditorium = Auditorium(
name = unit.getString("auditorium"),
oid = unit.getInt("auditoriumOid"),
floor = unit.getInt("auditoriumfloor"),
building = Building(
name = unit.getString("building"),
gid = unit.getInt("buildingGid"),
oid = unit.getInt("buildingOid")
)
),
date = LocalDate.parse(unit.getString("date"), unnDatePattern),
discipline = Discipline(
name = unit.getString("discipline"),
oid = unit.getInt("disciplineOid"),
type = unit.getInt("disciplinetypeload")
),
kindOfWork = KindOfWork(
name = unit.getString("kindOfWork"),
complexity = unit.getInt("kindOfWorkComplexity"),
oid = unit.getInt("kindOfWorkOid"),
uid = unit.getString("kindOfWorkUid")
),
lecturers = lecturers,
stream = unit.getString("stream"),
begin = LocalTime.parse(unit.getString("beginLesson"), DateTimeFormatter.ofPattern("HH:mm")),
end = LocalTime.parse(unit.getString("endLesson"), DateTimeFormatter.ofPattern("HH:mm"))
)
)
}
return out
}
@@ -0,0 +1,199 @@
package ru.sweetbread.unn.ui.composes
import android.util.Log
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.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.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.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 com.kizitonwose.calendar.compose.WeekCalendar
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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.getSchedule
import ru.sweetbread.unn.ui.theme.UNNTheme
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@Composable
fun Schedule() {
val state = rememberWeekCalendarState(
firstDayOfWeek = DayOfWeek.MONDAY
)
Column {
var curDate by remember { mutableStateOf(LocalDate.now()) }
WeekCalendar(
state = state,
dayContent = {
Box(
modifier = Modifier
.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)
.clickable(
onClick = {
curDate = it.date
}
),
contentAlignment = Alignment.Center,
) {
Text(
text = it.date.dayOfMonth.toString(),
fontWeight = if (it.date == LocalDate.now()) FontWeight.Bold else null
)
}
}
)
ScheduleDay(date = curDate)
}
}
@Composable
fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
val scope = rememberCoroutineScope()
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
val lessons = remember { mutableListOf<ScheduleUnit>() }
if (loadedDate == date) {
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
LazyColumn (modifier) {
items(lessons) {
ScheduleItem(unit = it)
}
}
} else {
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
trackColor = MaterialTheme.colorScheme.secondary,
)
LaunchedEffect(date != loadedDate) {
scope.launch(Dispatchers.IO) {
lessons.clear()
lessons.addAll(getSchedule(start = date, finish = date))
loadedDate = date
}
}
}
}
@Composable
fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit) {
Row (
modifier
.fillMaxWidth()
.padding(4.dp)
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.primaryContainer)
.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 {
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())
}
}
}
@Preview
@Composable
fun ScheduleItemPreview() {
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 = "",
oid = 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)
}
}
}
@@ -0,0 +1,145 @@
package ru.sweetbread.unn.ui.layout
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
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.input.KeyboardType
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 ru.sweetbread.unn.R
import ru.sweetbread.unn.ui.auth
import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.activities.start
class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
UNNTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
) { innerPadding ->
LoginPanel(Modifier.padding(innerPadding), { login, password ->
LoginData.login = login
LoginData.password = password
start<MainActivity>()
}, {
scope.launch {
snackbarHostState
.showSnackbar(
message = "Error",
duration = SnackbarDuration.Long
)
}
})
}
}
}
}
}
}
@Composable
fun LoginPanel(
modifier: Modifier = Modifier,
ok: (login: String, password: String) -> Unit,
error: () -> Unit
) {
var login by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var loading by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Box(Modifier.fillMaxSize(), Alignment.BottomCenter) {
Column(
modifier
.padding(32.dp, 0.dp)
.clip(RoundedCornerShape(10.dp, 10.dp))
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(16.dp)
) {
TextField(
modifier = Modifier.padding(8.dp),
value = login,
onValueChange = { login = it },
singleLine = true,
label = { Text(stringResource(R.string.prompt_login)) }
)
TextField(
modifier = Modifier.padding(8.dp),
value = password,
onValueChange = { password = it },
singleLine = true,
label = { Text(stringResource(R.string.prompt_password)) },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
Button(modifier = Modifier
.fillMaxWidth()
.padding(8.dp), onClick = {
loading = true
scope.launch {
if (auth(login, password)) {
ok(login, password)
} else {
error()
}
loading = false
}
}) {
Text(stringResource(R.string.sign_in))
}
}
}
}
@Preview(showBackground = true)
@Composable
fun LoginPreview() {
UNNTheme {
LoginPanel(ok = { _, _ -> }, error = {})
}
}
@@ -0,0 +1,181 @@
package ru.sweetbread.unn.ui.layout
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 ru.sweetbread.unn.ui.Type
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 {
install(HttpCache)
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Log.i("Ktor", message)
}
}
level = LogLevel.ALL
}
install(HttpTimeout) {
socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
}
install(HttpRequestRetry) {
retryOnServerErrors(maxRetries = 3)
exponentialDelay()
modifyRequest { request ->
request.headers.append("x-retry-count", retryCount.toString())
}
}
}
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>()
}
setContent {
UNNTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
val navController = rememberNavController()
var route by remember { mutableStateOf("home") }
Scaffold(
bottomBar = {
NavigationBar {
NavigationBarItem(
onClick = { toast("Not implemented") },
icon = {
Icon(
Icons.Filled.Home,
contentDescription = "Home"
)
},
selected = route.startsWith("home")
)
NavigationBarItem(
onClick = {
navController.navigate("journal/schedule")
route = "journal/schedule" },
icon = {
Icon(
Icons.Filled.DateRange,
contentDescription = "Schedule"
)
},
selected = route.startsWith("journal/")
)
NavigationBarItem(
onClick = { toast("Not implemented") },
icon = {
Icon(
Icons.Filled.AccountBox,
contentDescription = "Account"
)
},
selected = false
)
}
}
) {innerPadding ->
Box(Modifier.padding(innerPadding)) {
NavHost(navController, startDestination = "home/blogposts") {
composable("home/blogposts") {
Text("Not implemented")
}
composable("journal/schedule") {
Schedule()
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,67 @@
package ru.sweetbread.unn.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF345CA8)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFD9E2FF)
val md_theme_light_onPrimaryContainer = Color(0xFF001A43)
val md_theme_light_secondary = Color(0xFF575E71)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFDBE2F9)
val md_theme_light_onSecondaryContainer = Color(0xFF141B2C)
val md_theme_light_tertiary = Color(0xFF725573)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFFCD7FB)
val md_theme_light_onTertiaryContainer = Color(0xFF2A132D)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFEFBFF)
val md_theme_light_onBackground = Color(0xFF1B1B1F)
val md_theme_light_surface = Color(0xFFFEFBFF)
val md_theme_light_onSurface = Color(0xFF1B1B1F)
val md_theme_light_surfaceVariant = Color(0xFFE1E2EC)
val md_theme_light_onSurfaceVariant = Color(0xFF44464F)
val md_theme_light_outline = Color(0xFF757780)
val md_theme_light_inverseOnSurface = Color(0xFFF2F0F4)
val md_theme_light_inverseSurface = Color(0xFF303034)
val md_theme_light_inversePrimary = Color(0xFFAFC6FF)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF345CA8)
val md_theme_light_outlineVariant = Color(0xFFC5C6D0)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFAFC6FF)
val md_theme_dark_onPrimary = Color(0xFF002D6C)
val md_theme_dark_primaryContainer = Color(0xFF15448F)
val md_theme_dark_onPrimaryContainer = Color(0xFFD9E2FF)
val md_theme_dark_secondary = Color(0xFFBFC6DC)
val md_theme_dark_onSecondary = Color(0xFF293042)
val md_theme_dark_secondaryContainer = Color(0xFF3F4759)
val md_theme_dark_onSecondaryContainer = Color(0xFFDBE2F9)
val md_theme_dark_tertiary = Color(0xFFDFBBDE)
val md_theme_dark_onTertiary = Color(0xFF402743)
val md_theme_dark_tertiaryContainer = Color(0xFF583E5A)
val md_theme_dark_onTertiaryContainer = Color(0xFFFCD7FB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1B1B1F)
val md_theme_dark_onBackground = Color(0xFFE3E2E6)
val md_theme_dark_surface = Color(0xFF1B1B1F)
val md_theme_dark_onSurface = Color(0xFFE3E2E6)
val md_theme_dark_surfaceVariant = Color(0xFF44464F)
val md_theme_dark_onSurfaceVariant = Color(0xFFC5C6D0)
val md_theme_dark_outline = Color(0xFF8F9099)
val md_theme_dark_inverseOnSurface = Color(0xFF1B1B1F)
val md_theme_dark_inverseSurface = Color(0xFFE3E2E6)
val md_theme_dark_inversePrimary = Color(0xFF345CA8)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFAFC6FF)
val md_theme_dark_outlineVariant = Color(0xFF44464F)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF1C4893)
@@ -0,0 +1,96 @@
package ru.sweetbread.unn.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun UNNTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colors = when {
dynamicColor && useDarkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColor && !useDarkTheme -> dynamicLightColorScheme(LocalContext.current)
useDarkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
@@ -0,0 +1,34 @@
package ru.sweetbread.unn.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

+3
View File
@@ -0,0 +1,3 @@
<resources>
<dimen name="activity_horizontal_margin">48dp</dimen>
</resources>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="prompt_password">Пароль</string>
<string name="prompt_login">Логин</string>
<string name="sign_in">Войти</string>
</resources>
@@ -0,0 +1,3 @@
<resources>
<dimen name="activity_horizontal_margin">200dp</dimen>
</resources>
@@ -0,0 +1,3 @@
<resources>
<dimen name="activity_horizontal_margin">48dp</dimen>
</resources>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
+5
View File
@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1565AA</color>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<resources>
<string name="app_name" translatable="false">UNN</string>
<!-- <string name="title_activity_login">LoginActivity</string>-->
<string name="prompt_email" translatable="false">Email</string>
<string name="prompt_login">Login</string>
<string name="prompt_password">Password</string>
<string name="sign_in">Sign in</string>
<!-- <string name="login_failed">"Login failed"</string>-->
</resources>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.UNN" parent="android:Theme.Material.NoActionBar" />
</resources>