Initial commit

This commit is contained in:
2024-09-08 15:08:18 +04:00
commit fcc3f992db
54 changed files with 1316 additions and 0 deletions

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

85
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,85 @@
import com.android.build.api.variant.BuildConfigField
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.example.ssau_schedule"
compileSdk = 34
defaultConfig {
applicationId = "com.example.ssau_schedule"
minSdk = 24
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"
)
}
}
androidComponents {
onVariants {
it.buildConfigFields.putAll(mapOf(
Pair("BASE_URL", BuildConfigField("String", "\"https://lk.ssau.ru/\"", null)),
Pair("SIGN_IN_URL", BuildConfigField("String", "\"account/login\"", null))
))
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
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.media3.common)
testImplementation(libs.junit)
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.squareup.okhttp)
implementation(libs.androidx.datastore)
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,24 @@
package com.example.ssau_schedule
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("com.example.ssau_schedule", appContext.packageName)
}
}

View File

@@ -0,0 +1,29 @@
<?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:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SSAU_Schedule"
tools:targetApi="31">
<activity
android:name=".AuthActivity"
android:exported="true"
android:theme="@style/Theme.SSAU_Schedule"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,205 @@
package com.example.ssau_schedule
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import com.example.ssau_schedule.ui.theme.SSAU_ScheduleTheme
import kotlinx.coroutines.delay
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import com.example.ssau_schedule.api.AuthErrorMessage
import com.example.ssau_schedule.api.AuthorizationAPI
import com.example.ssau_schedule.api.Http
import kotlin.math.min
class AuthActivity : ComponentActivity() {
private val http = Http()
private var auth: AuthorizationAPI? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
auth = AuthorizationAPI.getInstance(http, applicationContext)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
SSAU_ScheduleTheme {
AuthPage()
}
}
}
@Composable
fun AuthPage() {
var loginOpen by remember { mutableStateOf(false) }
val keyboardOpen by Utils.keyboardState()
val logoHeight by animateFloatAsState(
if (keyboardOpen) 0f else min(LocalConfiguration.current.screenWidthDp, 500) / 101f * 48f,
label = "alpha",
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
))
LaunchedEffect(false) {
delay(1000)
loginOpen = true
}
Box(Modifier.background(MaterialTheme.colorScheme.primary)
.fillMaxSize().statusBarsPadding().navigationBarsPadding().imePadding(),
contentAlignment = BiasAlignment(0f, -0.25f),
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(Modifier.widthIn(0.dp, 500.dp)
.height(logoHeight.dp).padding(20.dp, 0.dp)
) {
Image(painterResource(R.drawable.ssau_logo_01),
contentDescription = stringResource(R.string.samara_university),
modifier = Modifier.fillMaxSize().padding(10.dp),
contentScale = ContentScale.FillWidth,
alignment = Alignment.TopCenter)
}
Box(Modifier.padding(20.dp, 0.dp).widthIn(0.dp, 400.dp)) {
Card(Modifier.fillMaxWidth().animateContentSize(animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = 50f
))
.height(if(loginOpen) 280.dp else 0.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.background,
),
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp)
) {
AuthForm()
}
}
}
}
}
@Composable
fun AuthForm() {
val authScope = rememberCoroutineScope()
var login by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var error by remember { mutableStateOf<AuthErrorMessage?>(null) }
Column(Modifier.fillMaxWidth().padding(30.dp, 20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
stringResource(R.string.sign_in),
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.displaySmall
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = login,
onValueChange = { login = it; error = null },
label = { Text(stringResource(R.string.login)) },
placeholder =
{ Text(stringResource(R.string.enter_your_login)) },
)
Spacer(Modifier.height(2.dp))
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = password,
onValueChange = { password = it; error = null },
label = { Text(stringResource(R.string.password)) },
placeholder =
{ Text(stringResource(R.string.enter_your_password)) },
)
Spacer(Modifier.height(2.dp))
Box(Modifier.fillMaxWidth().height(14.dp)) {
this@Column.AnimatedVisibility(
modifier = Modifier.align(Alignment.Center),
visible = error !== null,
enter = fadeIn(animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessVeryLow
)),
exit = fadeOut(animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessVeryLow
))
) {
Text(error?.getMessage(applicationContext) ?: "",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.labelSmall
)
}
}
Spacer(Modifier.height(4.dp))
FilledTonalButton(onClick = {
if(login.length < 5) error = AuthErrorMessage.LOGIN_IS_TOO_SHORT
else if(password.length < 5) error = AuthErrorMessage.PASSWORD_IS_TOO_SHORT
else {
auth?.signIn(login, password, authScope,
{ startActivity(Intent(applicationContext, AuthActivity::class.java)) },
{ _, _ ->
error = AuthErrorMessage.INCORRECT_LOGIN_OR_PASSWORD
}
)
}
},
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary)
) {
Text(stringResource(R.string.sign_in))
}
}
}
}

View File

@@ -0,0 +1,34 @@
package com.example.ssau_schedule
import android.graphics.Rect
import android.view.ViewTreeObserver
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
class Utils {
companion object {
@Composable
fun keyboardState(): State<Boolean> {
val keyboardOpen = remember { mutableStateOf(false) }
val view = LocalView.current
DisposableEffect(view) {
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
val rect = Rect()
view.getWindowVisibleDisplayFrame(rect)
val screenHeight = view.rootView.height
val keypadHeight = screenHeight - rect.bottom
keyboardOpen.value = keypadHeight > screenHeight * 0.15
}
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
onDispose { view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) }
}
return keyboardOpen
}
}
}

View File

@@ -0,0 +1,87 @@
package com.example.ssau_schedule.api
import android.annotation.SuppressLint
import android.content.Context
import androidx.datastore.preferences.core.edit
import com.example.ssau_schedule.BuildConfig
import com.example.ssau_schedule.R
import com.example.ssau_schedule.data.source.AuthStoreKeys
import com.example.ssau_schedule.data.source.authStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import okhttp3.Headers.Companion.toHeaders
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.IOException
import org.json.JSONArray
enum class AuthErrorMessage(private val resource: Int) {
LOGIN_IS_TOO_SHORT(R.string.login_is_too_short),
PASSWORD_IS_TOO_SHORT(R.string.password_is_too_short),
INCORRECT_LOGIN_OR_PASSWORD(R.string.incorrect_login_or_password);
fun getMessage(context: Context) =
context.getString(resource)
}
class AuthorizationAPI(private var http: Http, private var context: Context) {
companion object {
@SuppressLint("StaticFieldLeak")
@Volatile
private var INSTANCE: AuthorizationAPI? = null
fun getInstance(http: Http, context: Context) =
INSTANCE ?: synchronized(this) {
INSTANCE ?: AuthorizationAPI(http, context).also {
INSTANCE = it
}
}
}
private val responseHasAuthToken =
{response: Response? -> response?.headers?.toMap()?.containsKey("set-cookie") == true}
private fun safeAuthToken(response: Response,
authScope: CoroutineScope,
callback: HttpResponseCallback) {
authScope.launch {
context.authStore.edit { authStore ->
authStore[AuthStoreKeys.AUTH_TOKEN] =
response.headers("set-cookie").joinToString(", ")
}.run {
callback(response)
}
}
}
fun signIn(login: String, password: String, authScope: CoroutineScope,
callback: HttpResponseCallback,
exceptionCallback: HttpExceptionCallback? = null) {
http.request(
Method.POST,
BuildConfig.SIGN_IN_URL,
JSONArray(arrayOf(mapOf(
Pair("login", login),
Pair("password", password)
))).toString().toRequestBody("application/json".toMediaType()),
mapOf(
Pair("Next-Action", "b395d17834d8b7df06372cbf1f241170a272d540")
).toHeaders(),
fun(response) {
if(responseHasAuthToken(response))
safeAuthToken(response, authScope, callback)
else if(exceptionCallback != null)
exceptionCallback(IOException("Authorization token not found"), response)
},
fun (error, response): Boolean {
if(responseHasAuthToken(response)) return true
if(exceptionCallback !== null) exceptionCallback(error, response)
return false
}
)
}
}

View File

@@ -0,0 +1,54 @@
package com.example.ssau_schedule.api
import android.util.Log
import com.example.ssau_schedule.BuildConfig
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import java.io.IOException
enum class Method {
GET,
POST,
PUT,
DELETE
}
typealias HttpResponseCallback = (response: Response) -> Unit
typealias HttpExceptionVerifyCallback = (exception: IOException, response: Response?) -> Boolean
typealias HttpExceptionCallback = (exception: IOException, response: Response?) -> Unit
class Http {
val http = OkHttpClient()
fun request(
method: Method,
url: String,
body: RequestBody? = null,
headers: Headers? = null,
callback: HttpResponseCallback,
exceptionCallback: HttpExceptionVerifyCallback? = null
) {
val request = Request.Builder().url(BuildConfig.BASE_URL+url).
method(method.toString(), body)
if(headers !== null) request.headers(headers)
http.newCall(request.build()).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("Http request failed", e.toString())
if(exceptionCallback !== null) exceptionCallback(e, null)
}
override fun onResponse(call: Call, response: Response) {
var runCallback = false
if (!response.isSuccessful && exceptionCallback !== null)
runCallback = exceptionCallback(
IOException("Http response is not successful"), response)
if(runCallback || response.isSuccessful) callback(response)
}
})
}
}

View File

@@ -0,0 +1,11 @@
package com.example.ssau_schedule.data.source
import android.content.Context
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
val Context.authStore by preferencesDataStore(name = "auth")
object AuthStoreKeys {
val AUTH_TOKEN = stringPreferencesKey("auth_token")
}

View File

@@ -0,0 +1,18 @@
package com.example.ssau_schedule.ui.theme
import androidx.compose.ui.graphics.Color
val Primary01 = Color(0xFF0D47A1)
val Primary02 = Color(0xFF134BC5)
val Primary03 = Color(0xFF1D5DEB)
val Primary04 = Color(0xFF2780E3)
val Primary05 = Color(0xFF6B92E5)
val Primary06 = Color(0xFFA8C4EC)
val White = Color(0xFFFFFFFF)
val Gray01 = Color(0xFF2C2C2C)
val Gray02 = Color(0xFF383838)
val Gray03 = Color(0xFF66727F)
val Red = Color(0xFFEE3F58)

View File

@@ -0,0 +1,64 @@
package com.example.ssau_schedule.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Primary01,
secondary = Primary03,
tertiary = Primary04,
background = Gray01,
surface = Gray02,
error = Red,
)
private val LightColorScheme = lightColorScheme(
primary = Primary01,
secondary = Primary03,
tertiary = Primary04,
background = White,
surface = Primary06,
error = Red,
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun SSAU_ScheduleTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
//dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
// dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
// val context = LocalContext.current
// if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
// }
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,34 @@
package com.example.ssau_schedule.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.SansSerif,
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
)
*/
)

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="108"
android:viewportHeight="108"
android:width="108dp"
android:height="108dp">
<path
android:pathData="M-1 -1H129V129H-1V-1Z"
android:fillColor="#0D47A1" />
</vector>

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M41.5,28.38C42.54,28.38 43.38,29.21 43.38,30.25L43.38,32.16C45.03,32.12 46.85,32.12 48.86,32.13L59.14,32.13C61.15,32.12 62.97,32.12 64.63,32.16L64.63,30.25C64.63,29.21 65.46,28.38 66.5,28.38C67.54,28.38 68.38,29.21 68.38,30.25L68.38,32.32C69.02,32.37 69.64,32.43 70.22,32.51C73.15,32.9 75.53,33.73 77.4,35.6C79.27,37.47 80.1,39.85 80.49,42.78C80.62,43.71 80.7,44.74 80.76,45.85C80.83,46.05 80.88,46.27 80.88,46.5C80.88,46.67 80.85,46.84 80.81,47C80.88,49.01 80.88,51.28 80.88,53.86L80.88,59.14C80.88,63.74 80.88,67.37 80.49,70.22C80.1,73.15 79.27,75.53 77.4,77.4C75.53,79.27 73.15,80.1 70.22,80.49C67.37,80.88 63.74,80.88 59.14,80.88L48.86,80.88C44.26,80.88 40.63,80.88 37.78,80.49C34.85,80.1 32.47,79.27 30.6,77.4C28.73,75.53 27.9,73.15 27.51,70.22C27.12,67.37 27.12,63.74 27.13,59.14L27.13,53.86C27.12,51.28 27.12,49.01 27.19,47C27.15,46.84 27.13,46.67 27.13,46.5C27.13,46.27 27.17,46.05 27.24,45.85C27.3,44.74 27.38,43.71 27.51,42.78C27.9,39.85 28.73,37.47 30.6,35.6C32.47,33.73 34.85,32.9 37.78,32.51C38.36,32.43 38.98,32.37 39.63,32.32L39.63,30.25C39.63,29.21 40.46,28.38 41.5,28.38ZM30.91,48.38C30.88,50.01 30.88,51.86 30.88,54L30.88,59C30.88,63.77 30.88,67.15 31.22,69.72C31.56,72.24 32.2,73.69 33.25,74.75C34.31,75.8 35.76,76.44 38.28,76.78C40.85,77.12 44.23,77.13 49,77.13L59,77.13C63.77,77.13 67.15,77.12 69.72,76.78C72.24,76.44 73.69,75.8 74.75,74.75C75.8,73.69 76.44,72.24 76.78,69.72C77.12,67.15 77.13,63.77 77.13,59L77.13,54C77.13,51.86 77.12,50.01 77.09,48.38L30.91,48.38ZM76.92,44.63L31.08,44.63C31.12,44.15 31.17,43.7 31.22,43.28C31.56,40.76 32.2,39.31 33.25,38.25C34.31,37.2 35.76,36.56 38.28,36.22C40.85,35.88 44.23,35.88 49,35.88L59,35.88C63.77,35.88 67.15,35.88 69.72,36.22C72.24,36.56 73.69,37.2 74.75,38.25C75.8,39.31 76.44,40.76 76.78,43.28C76.83,43.7 76.88,44.15 76.92,44.63Z"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"/>
<path
android:pathData="M54.38,65.4L54.23,65.4C50.49,65.4 47.45,62.38 47.45,58.67C47.45,54.95 50.49,51.93 54.23,51.93L54.38,51.93L54.38,50L54.23,50C49.4,50 45.5,53.87 45.5,58.67C45.5,63.44 49.4,67.34 54.23,67.34L54.38,67.34L54.38,65.4ZM54.23,55.78L54.38,55.78L54.38,53.87C54.36,53.87 54.33,53.86 54.31,53.86C54.28,53.85 54.25,53.84 54.23,53.84C51.55,53.84 49.37,56.01 49.37,58.67C49.37,61.33 51.55,63.47 54.23,63.47L54.54,63.47C56.15,63.47 57.45,64.76 57.45,66.36C57.45,67.95 56.15,69.24 54.54,69.24C54.49,69.24 54.44,69.24 54.38,69.22L54.38,71.15L54.54,71.15C57.22,71.15 59.4,69.01 59.4,66.36C59.4,63.7 57.22,61.53 54.54,61.53C54.51,61.53 54.45,61.54 54.38,61.54C54.32,61.55 54.25,61.56 54.23,61.56C52.62,61.56 51.32,60.27 51.32,58.67C51.32,57.07 52.62,55.78 54.23,55.78ZM54.38,57.69L54.54,57.69C59.37,57.69 63.27,61.56 63.27,66.36C63.27,71.13 59.37,75 54.57,75.03L54.51,75.03L54.38,75.03L54.38,73.09L54.54,73.09C58.31,73.09 61.35,70.07 61.35,66.36C61.35,62.64 58.31,59.62 54.54,59.62L54.38,59.62L54.38,57.69Z"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"/>
</vector>

File diff suppressed because one or more lines are too long

View File

@@ -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="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -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="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">SSAU Schedule</string>
<string name="sign_in">Sign in</string>
<string name="login">Login</string>
<string name="enter_your_login">Enter your login</string>
<string name="enter_your_password">Enter your password</string>
<string name="password">Password</string>
<string name="samara_university">Samara university</string>
<string name="incorrect_login_or_password">Incorrect login or password</string>
<string name="login_is_too_short">Login is too short</string>
<string name="password_is_too_short">Password is too short</string>
</resources>

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>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="base_url" translatable="false">Расписание СамГУ</string>
</resources>

View File

@@ -0,0 +1,12 @@
<resources>
<string name="app_name">Расписание СамГУ</string>
<string name="sign_in">Войти</string>
<string name="login">Логин</string>
<string name="enter_your_login">Введите ваш логин</string>
<string name="enter_your_password">Введите ваш пароль</string>
<string name="password">Пароль</string>
<string name="samara_university">Самарский университет</string>
<string name="incorrect_login_or_password">Неверный логин или пароль</string>
<string name="login_is_too_short">Логин слишком короткий</string>
<string name="password_is_too_short">Пароль слишком короткий</string>
</resources>

View File

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

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@@ -0,0 +1,19 @@
<?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.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@@ -0,0 +1,17 @@
package com.example.ssau_schedule
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}