commit 983249091a3b8ef9fd1c029fd206bf5c328e63da Author: sweetbread Date: Tue Mar 19 00:27:57 2024 +0300 Init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..7643783 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..39bf74d --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..dc563e8 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,88 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) +} + +android { + namespace = "ru.sweetbread.unn" + compileSdk = 34 + + defaultConfig { + applicationId = "ru.sweetbread.unn" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + viewBinding = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + coreLibraryDesugaring(libs.desugar.jdk.libs) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.annotation) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.lifecycle.livedata.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.activity) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + implementation(libs.androidx.core.splashscreen) + + + implementation(libs.androidx.navigation.compose) + + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.logging) + + implementation(libs.androidx.datastore.preferences) + implementation(libs.splitties.funpack.android.base.with.views.dsl) + + implementation(libs.compose) + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/ru/sweetbread/unn/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ru/sweetbread/unn/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..a5be119 --- /dev/null +++ b/app/src/androidTest/java/ru/sweetbread/unn/ExampleInstrumentedTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8c8e329 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..7686ad5 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/ru/sweetbread/unn/ui/API.kt b/app/src/main/java/ru/sweetbread/unn/ui/API.kt new file mode 100644 index 0000000..2ba7ed0 --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/API.kt @@ -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, + 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 { + 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() + for (i in 0 until json.length()) { + val unit = json.getJSONObject(i) + val lecturesJson = unit.getJSONArray("listOfLecturers") + val lecturers = arrayListOf() + + 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 +} diff --git a/app/src/main/java/ru/sweetbread/unn/ui/composes/Schedule.kt b/app/src/main/java/ru/sweetbread/unn/ui/composes/Schedule.kt new file mode 100644 index 0000000..23a549a --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/composes/Schedule.kt @@ -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() } + + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/sweetbread/unn/ui/layout/LoginActivity.kt b/app/src/main/java/ru/sweetbread/unn/ui/layout/LoginActivity.kt new file mode 100644 index 0000000..e8915bf --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/layout/LoginActivity.kt @@ -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() + }, { + 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 = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/sweetbread/unn/ui/layout/MainActivity.kt b/app/src/main/java/ru/sweetbread/unn/ui/layout/MainActivity.kt new file mode 100644 index 0000000..f09c5bb --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/layout/MainActivity.kt @@ -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() + runBlocking { + if (!auth()) start() + } + + 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() + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/sweetbread/unn/ui/theme/Color.kt b/app/src/main/java/ru/sweetbread/unn/ui/theme/Color.kt new file mode 100644 index 0000000..1c48e39 --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/theme/Color.kt @@ -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) diff --git a/app/src/main/java/ru/sweetbread/unn/ui/theme/Theme.kt b/app/src/main/java/ru/sweetbread/unn/ui/theme/Theme.kt new file mode 100644 index 0000000..b57743a --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/theme/Theme.kt @@ -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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/ru/sweetbread/unn/ui/theme/Type.kt b/app/src/main/java/ru/sweetbread/unn/ui/theme/Type.kt new file mode 100644 index 0000000..ae3988f --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/theme/Type.kt @@ -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 + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..65291b9 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..65291b9 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..0a0496c Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..7011e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..d67e9e8 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..5f07e41 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..96b0555 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..19d045c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..d60c71b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..5a99495 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..305a8f7 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..6ccccc3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..f7ea60a Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..4df0125 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..3861cc0 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..bdefa3f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..305e63d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..5f681ae --- /dev/null +++ b/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..d720e84 --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,6 @@ + + + Пароль + Логин + Войти + \ No newline at end of file diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 0000000..7e06511 --- /dev/null +++ b/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ + + 200dp + \ No newline at end of file diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml new file mode 100644 index 0000000..5f681ae --- /dev/null +++ b/app/src/main/res/values-w600dp/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..e00c2dd --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..38e41d3 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #1565AA + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..01d78ad --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,9 @@ + + UNN + + Email + Login + Password + Sign in + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..446fbfe --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +