Initial commit
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
85
app/build.gradle.kts
Normal 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
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
29
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
205
app/src/main/java/com/example/ssau_schedule/AuthActivity.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/src/main/java/com/example/ssau_schedule/Utils.kt
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
87
app/src/main/java/com/example/ssau_schedule/api/Auth.kt
Normal 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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
54
app/src/main/java/com/example/ssau_schedule/api/Http.kt
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
34
app/src/main/java/com/example/ssau_schedule/ui/theme/Type.kt
Normal 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
|
||||
)
|
||||
*/
|
||||
)
|
||||
9
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
14
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/ssau_logo_01.xml
Normal file
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
13
app/src/main/res/values-en/strings.xml
Normal 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>
|
||||
10
app/src/main/res/values/colors.xml
Normal 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>
|
||||
4
app/src/main/res/values/endpoints.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="base_url" translatable="false">Расписание СамГУ</string>
|
||||
</resources>
|
||||
12
app/src/main/res/values/strings.xml
Normal 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>
|
||||
5
app/src/main/res/values/themes.xml
Normal 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>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal 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>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal 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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||