init commit
@@ -0,0 +1,24 @@
|
||||
package ru.megboyzz.lks.profile
|
||||
|
||||
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.megboyzz.lks.profile", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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.LKSPROFILE"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.LKSPROFILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.megboyzz.lks.profile
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import ru.megboyzz.lks.profile.ui.screens.SignInScreen
|
||||
import ru.megboyzz.lks.profile.ui.theme.MainTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
Navigator(SignInScreen())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package ru.megboyzz.lks.profile.data
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Connection
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
|
||||
private typealias Cookies = Map<String, String>
|
||||
|
||||
private fun Connection.dataMap(vararg pairs: Pair<String, String>) = this.data(mapOf(*pairs))
|
||||
|
||||
suspend fun signInLks(username: String, password: String) = withContext(Dispatchers.IO) {
|
||||
val cookies = Jsoup
|
||||
.connect("https://lk.mirea.ru/auth.php?login=yes")
|
||||
.dataMap(
|
||||
"AUTH_FORM" to "Y",
|
||||
"TYPE" to "AUTH",
|
||||
"USER_LOGIN" to username,
|
||||
"USER_PASSWORD" to password,
|
||||
"USER_REMEMBER" to "Y"
|
||||
)
|
||||
.header("Content-Type", "multipart/form-data")
|
||||
.method(Connection.Method.POST)
|
||||
.ignoreContentType(true)
|
||||
.execute().cookies()
|
||||
|
||||
if(cookies["PHPSESSID"] == "deleted")
|
||||
return@withContext cookies as Cookies
|
||||
else
|
||||
return@withContext mapOf<String, String>()
|
||||
}
|
||||
|
||||
suspend fun fakeConnect(cookies: Cookies) = withContext(Dispatchers.IO) {
|
||||
Jsoup
|
||||
.connect("https://lk.mirea.ru/")
|
||||
.cookies(cookies)
|
||||
.referrer("https://lk.mirea.ru/")
|
||||
.execute()
|
||||
}
|
||||
|
||||
suspend fun getPage(payload: LksRequest, cookies: Cookies, page: Int) = withContext(Dispatchers.IO) {
|
||||
|
||||
val sesid = cookies["BITRIX_SM_UIDH"]
|
||||
|
||||
fakeConnect(cookies)
|
||||
Jsoup
|
||||
.connect("https://lk.mirea.ru/staff/students/?${payload.getSubmitUrlPath(sesid ?: "")}&PAGEN_1=$page")
|
||||
.cookies(cookies)
|
||||
.referrer("https://lk.mirea.ru/")
|
||||
.execute().parse()
|
||||
}
|
||||
|
||||
fun Document.getTotalPages() = kotlin.runCatching {
|
||||
this.select("body > div.container > div.bx-pagination > div > ul > li:nth-child(6) > a > span").text().toInt()
|
||||
}.getOrNull() ?: 0
|
||||
|
||||
fun Document.findStudentsInPage(): LksResponse {
|
||||
|
||||
val table = this.select("body > div.container > table > tbody") ?: return LksResponse()
|
||||
|
||||
val students = table.select("tr").map {
|
||||
|
||||
var name: String? = null
|
||||
var group: String? = null
|
||||
var department: String? = null
|
||||
|
||||
it.select("td").forEachIndexed { row, element ->
|
||||
when(row) {
|
||||
// Парсим имя
|
||||
0 -> name = element.select("td").text()
|
||||
1 -> department = element.select("td").text()
|
||||
2 -> group = element.select("td").text()
|
||||
}
|
||||
}
|
||||
|
||||
LksStudent(
|
||||
studentFullName = name,
|
||||
group = group,
|
||||
department = department
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return LksResponse(
|
||||
students = students,
|
||||
availablePages = this.getTotalPages()
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ru.megboyzz.lks.profile.data.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.plus
|
||||
import ru.megboyzz.lks.profile.domain.LocalDispatcherScope
|
||||
import ru.megboyzz.lks.profile.domain.NetworkDispatcherScope
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class CoroutineModule {
|
||||
|
||||
@Provides
|
||||
@LocalDispatcherScope
|
||||
fun providesLocalDispatcher() = CoroutineScope(Dispatchers.IO) + CoroutineName("Local")
|
||||
|
||||
@Provides
|
||||
@NetworkDispatcherScope
|
||||
fun providesNetworkDispatcher() = CoroutineScope(Dispatchers.IO) + CoroutineName("Net")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ru.megboyzz.lks.profile.data.repositories
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import ru.megboyzz.lks.profile.data.findStudentsInPage
|
||||
import ru.megboyzz.lks.profile.data.signInLks
|
||||
import ru.megboyzz.lks.profile.domain.CookiesPrefs
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class LksRepositoryImpl @Inject constructor(
|
||||
@CookiesPrefs
|
||||
private val sharedPreferences: SharedPreferences
|
||||
) : LksRepository {
|
||||
|
||||
private val cookieNamesKey = "cookie_names"
|
||||
|
||||
override suspend fun signInAndGetCookies(
|
||||
username: String,
|
||||
password: String
|
||||
): Map<String, String> = signInLks(username, password)
|
||||
|
||||
|
||||
override fun saveCookie(cookie: Map<String, String>) {
|
||||
sharedPreferences.edit {
|
||||
cookie.forEach {
|
||||
this.putString(it.key, it.value)
|
||||
}
|
||||
this.putStringSet(cookieNamesKey, cookie.keys)
|
||||
this.commit()
|
||||
}
|
||||
}
|
||||
|
||||
override fun restoreCooke(): Map<String, String> {
|
||||
val keys = sharedPreferences.getStringSet(cookieNamesKey, setOf())
|
||||
if(keys!!.isEmpty()) return mapOf()
|
||||
return keys.associateWith {
|
||||
sharedPreferences.getString(it, "") ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse> {
|
||||
|
||||
val document = ru.megboyzz.lks.profile.data.getPage(lksRequest, cookie, pageNumber)
|
||||
|
||||
val page = document.findStudentsInPage()
|
||||
|
||||
return if(page == LksResponse())
|
||||
Result.failure(Exception())
|
||||
else
|
||||
Result.success(page)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ru.megboyzz.lks.profile.data.repositories
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.repositories.StudentRepository
|
||||
|
||||
class StudentRepositoryImpl : StudentRepository {
|
||||
override fun getAllFavStudents(): List<LksResponse> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addStudent(lksResponse: LksResponse) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun deleteFormFav(lksResponse: LksResponse) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.megboyzz.lks.profile.domain
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
annotation class NetworkDispatcherScope
|
||||
|
||||
@Scope
|
||||
annotation class LocalDispatcherScope
|
||||
|
||||
@Scope
|
||||
annotation class CookiesPrefs
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.megboyzz.lks.profile.domain.entities
|
||||
|
||||
/**
|
||||
* Класс описывающий запрос/ответ к/от лкс
|
||||
*/
|
||||
data class LksRequest(
|
||||
var student: Student,
|
||||
var group: String?,
|
||||
var department: String?
|
||||
){
|
||||
/**
|
||||
* Метод для получения пути после https://lk.mirea.ru/staff/students? ...
|
||||
*/
|
||||
fun getSubmitUrlPath(sesid: String) =
|
||||
"sessid=$sesid&findname=${student}&findgroup=${group ?: ""}&finddepartment=${department ?: ""}&submit-filter=Найти"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.megboyzz.lks.profile.domain.entities
|
||||
|
||||
data class LksResponse(
|
||||
val students: List<LksStudent> = emptyList(),
|
||||
val availablePages: Int = 0
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.megboyzz.lks.profile.domain.entities
|
||||
|
||||
data class LksStudent(
|
||||
val studentFullName: String? = null,
|
||||
val department: String? = null,
|
||||
val group: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.megboyzz.lks.profile.domain.entities
|
||||
|
||||
sealed class Resource {
|
||||
|
||||
data object Idle : Resource()
|
||||
|
||||
data object Loading : Resource()
|
||||
data class Success(val data: String) : Resource()
|
||||
data class Error(val message: String) : Resource()
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.megboyzz.lks.profile.domain.entities
|
||||
|
||||
|
||||
data class Student(
|
||||
/**
|
||||
* Имя студента
|
||||
*/
|
||||
var name: String?,
|
||||
/**
|
||||
* Фамилия Студента
|
||||
*/
|
||||
var surname: String?,
|
||||
|
||||
/**
|
||||
* Отчество студента
|
||||
*/
|
||||
var patronymic: String?
|
||||
) {
|
||||
override fun toString() = listOfNotNull(surname, name, patronymic).joinToString(" ")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.megboyzz.lks.profile.domain.repositories
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
|
||||
interface LksRepository {
|
||||
|
||||
suspend fun signInAndGetCookies(username: String, password: String): Map<String, String>
|
||||
|
||||
suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse>
|
||||
|
||||
fun saveCookie(cookie: Map<String, String>)
|
||||
|
||||
fun restoreCooke(): Map<String, String>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ru.megboyzz.lks.profile.domain.repositories
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
|
||||
interface StudentRepository {
|
||||
|
||||
fun getAllFavStudents(): List<LksResponse>
|
||||
|
||||
fun addStudent(lksResponse: LksResponse)
|
||||
|
||||
fun deleteFormFav(lksResponse: LksResponse)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.data.signInLks
|
||||
import ru.megboyzz.lks.profile.domain.NetworkDispatcherScope
|
||||
import ru.megboyzz.lks.profile.domain.entities.Resource
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class SignInUseCase @Inject constructor(
|
||||
private val lksRepository: LksRepository,
|
||||
@NetworkDispatcherScope
|
||||
private val coroutineDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(username: String, password: String): Resource = withContext(coroutineDispatcher) {
|
||||
|
||||
val cookies = signInLks(username, password)
|
||||
|
||||
return@withContext if(cookies.containsKey("BITRIX_SM_UIDH")) {
|
||||
lksRepository.saveCookie(cookies)
|
||||
Resource.Success("SignIn success")
|
||||
}else
|
||||
Resource.Error(message = "Wrong creds")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases.fav
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.domain.LocalDispatcherScope
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.repositories.StudentRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class AddFavoriteUseCase @Inject constructor(
|
||||
private val studentRepository: StudentRepository,
|
||||
@LocalDispatcherScope
|
||||
private val coroutineDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(lksResponse: LksResponse) = withContext(coroutineDispatcher) {
|
||||
studentRepository.addStudent(lksResponse)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases.fav
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.domain.LocalDispatcherScope
|
||||
import ru.megboyzz.lks.profile.domain.repositories.StudentRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetAllFavoritesUseCase @Inject constructor(
|
||||
private val studentRepository: StudentRepository,
|
||||
@LocalDispatcherScope
|
||||
private val coroutineDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
|
||||
suspend operator fun invoke() = withContext(coroutineDispatcher) { studentRepository.getAllFavStudents() }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases.fav
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.domain.LocalDispatcherScope
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.repositories.StudentRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoveFavoriteUseCase @Inject constructor(
|
||||
private val studentRepository: StudentRepository,
|
||||
@LocalDispatcherScope
|
||||
private val coroutineDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(lksResponse: LksResponse) = withContext(coroutineDispatcher) {
|
||||
studentRepository.deleteFormFav(lksResponse)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases.pages
|
||||
|
||||
class AddLatestDataSearchUseCase {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases.pages
|
||||
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.domain.NetworkDispatcherScope
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class GetDataPage @Inject constructor(
|
||||
private val lksRepository: LksRepository,
|
||||
@NetworkDispatcherScope
|
||||
private val coroutineContext: CoroutineContext
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(lksRequest: LksRequest, pageNumber: Int) = withContext(coroutineContext) {
|
||||
//lksRepository.getPage(lksRequest, pageNumber)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package ru.megboyzz.lks.profile.domain.usecases.pages
|
||||
|
||||
class GetLatestDataSearchUseCase {
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package ru.megboyzz.lks.profile.ui.components
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.megboyzz.lks.profile.ui.model
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
|
||||
class SearchScreenModel : ScreenModel {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package ru.megboyzz.lks.profile.ui.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import ru.megboyzz.lks.profile.ui.screens.SearchScreen
|
||||
|
||||
class SignInScreenModel(
|
||||
private val lksRepository: LksRepository,
|
||||
private val navigator: Navigator
|
||||
) : ScreenModel {
|
||||
|
||||
private val _username = MutableStateFlow("")
|
||||
val username = _username.asStateFlow()
|
||||
|
||||
private val _password = MutableStateFlow("")
|
||||
val password = _password.asStateFlow()
|
||||
|
||||
sealed class SignInState(val status: String? = null) {
|
||||
data object Loading: SignInState()
|
||||
data object Success: SignInState("Успешный вход")
|
||||
data class Error(val _status: String) : SignInState(_status)
|
||||
data object Idle: SignInState()
|
||||
}
|
||||
|
||||
val state = MutableStateFlow<SignInState>(SignInState.Idle)
|
||||
|
||||
private var cookie: Map<String, String> = mapOf()
|
||||
|
||||
init {
|
||||
|
||||
cookie = lksRepository.restoreCooke()
|
||||
|
||||
if(cookie.isNotEmpty()) {
|
||||
navigator.push(SearchScreen())
|
||||
}
|
||||
|
||||
state.onEach {
|
||||
if(it == SignInState.Success)
|
||||
navigator.push(SearchScreen())
|
||||
}.launchIn(screenModelScope)
|
||||
|
||||
}
|
||||
|
||||
fun setUsername(username: String) {
|
||||
_username.value = username
|
||||
}
|
||||
|
||||
fun setPassword(password: String) {
|
||||
_password.value = password
|
||||
}
|
||||
|
||||
fun signIn() {
|
||||
screenModelScope.launch(Dispatchers.IO) {
|
||||
state.emit(SignInState.Loading)
|
||||
cookie = lksRepository.signInAndGetCookies(_username.value, _password.value)
|
||||
if(cookie.isEmpty())
|
||||
state.emit(SignInState.Error("Неправильный логин или пароль"))
|
||||
else {
|
||||
state.emit(SignInState.Success)
|
||||
lksRepository.saveCookie(cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ru.megboyzz.lks.profile.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.PointerIcon.Companion.Text
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
|
||||
class SearchScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
|
||||
val navigator = LocalNavigator.current!!
|
||||
|
||||
Text("Успех!")
|
||||
OutlinedButton(
|
||||
onClick = navigator::popUntilRoot
|
||||
) {
|
||||
Text("Вернуться")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package ru.megboyzz.lks.profile.ui.screens
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import ru.megboyzz.lks.profile.data.repositories.LksRepositoryImpl
|
||||
import ru.megboyzz.lks.profile.ui.model.SignInScreenModel
|
||||
import kotlin.math.log
|
||||
|
||||
class SignInScreen : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val navigator = LocalNavigator.current!!
|
||||
|
||||
val viewModel = rememberScreenModel {
|
||||
val preferences = context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
|
||||
SignInScreenModel(
|
||||
lksRepository = LksRepositoryImpl(preferences),
|
||||
navigator = navigator
|
||||
)
|
||||
}
|
||||
|
||||
val username by viewModel.username.collectAsState()
|
||||
val password by viewModel.password.collectAsState()
|
||||
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
OutlinedTextField(
|
||||
value = username,
|
||||
onValueChange = { viewModel.setUsername(it) }
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { viewModel.setPassword(it) }
|
||||
)
|
||||
|
||||
OutlinedButton(
|
||||
onClick = viewModel::signIn
|
||||
) {
|
||||
Text("Войти")
|
||||
}
|
||||
|
||||
when(state) {
|
||||
is SignInScreenModel.SignInState.Idle -> Text("")
|
||||
is SignInScreenModel.SignInState.Success -> Text(state.status ?: "")
|
||||
is SignInScreenModel.SignInState.Loading -> CircularProgressIndicator()
|
||||
is SignInScreenModel.SignInState.Error -> Text(state.status ?: "Ошибка")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.megboyzz.lks.profile.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -0,0 +1,57 @@
|
||||
package ru.megboyzz.lks.profile.ui.theme
|
||||
|
||||
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 = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* 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 MainTheme(
|
||||
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
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ru.megboyzz.lks.profile.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">LKS PROFILE</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.LKSPROFILE" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.megboyzz.lks.profile
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||