cutout voyager

This commit is contained in:
2025-02-22 16:48:26 +03:00
parent 11cafec847
commit 7d313e1c64
29 changed files with 78 additions and 280 deletions
+1 -2
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
@@ -10,8 +11,6 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/app/data" />
<option value="$PROJECT_DIR$/app/domain" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
-9
View File
@@ -63,15 +63,6 @@ dependencies {
ksp(libs.hilt.android.compiler)
implementation(libs.voyager.navigator)
implementation(libs.voyager.screenmodel)
implementation(libs.voyager.bottom.sheet.navigator)
implementation(libs.voyager.transitions)
implementation(libs.voyager.hilt)
implementation(libs.jsoup)
}
-1
View File
@@ -1 +0,0 @@
/build
-45
View File
@@ -1,45 +0,0 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "ru.megboyzz.lks.data"
compileSdk = 34
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(libs.jsoup)
}
-21
View File
@@ -1,21 +0,0 @@
# 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
@@ -1,24 +0,0 @@
package ru.megboyzz.lks.data
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.data.test", appContext.packageName)
}
}
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -1,17 +0,0 @@
package ru.megboyzz.lks.data
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)
}
}
-1
View File
@@ -1 +0,0 @@
/build
-15
View File
@@ -1,15 +0,0 @@
plugins {
id("java-library")
alias(libs.plugins.jetbrains.kotlin.jvm)
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
dependencies {
testImplementation(libs.junit)
}
-69
View File
@@ -1,69 +0,0 @@
import org.junit.Test
import ru.megboyzz.lks.domain.entities.Student
class EntityTest {
@Test
fun testStudent() {
val student = Student(
surname = "Киц",
name = "Михаил",
patronymic = "Андреевич"
)
assert(student.toString() == "Киц Михаил Андреевич")
}
@Test
fun testStudent2() {
val student = Student(
surname = null,
name = "Михаил",
patronymic = "Андреевич"
)
assert(student.toString() == "Михаил Андреевич")
}
@Test
fun testStudent3() {
val student = Student(
surname = "Киц",
name = null,
patronymic = "Андреевич"
)
println(student)
assert(student.toString() == "Киц Андреевич")
}
@Test
fun testStudent4() {
val student = Student(
surname = "Киц",
name = "Михаил",
patronymic = null
)
assert(student.toString() == "Киц Михаил")
}
@Test
fun testStudent5() {
val student = Student(
surname = null,
name = null,
patronymic = null
)
assert(student.toString().isEmpty())
}
}
@@ -15,9 +15,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
import dagger.hilt.android.AndroidEntryPoint
import ru.megboyzz.lks.profile.ui.components.MainNavigator
import ru.megboyzz.lks.profile.ui.screens.SearchScreen
@@ -1,15 +1,13 @@
package ru.megboyzz.lks.profile.data
import android.util.Log
import ru.megboyzz.lks.profile.domain.entities.LksRequest
import ru.megboyzz.lks.profile.domain.entities.LksResponse
import ru.megboyzz.lks.profile.domain.entities.LksStudent
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>
@@ -50,23 +48,15 @@ fun getProfilePage(payload: LksRequest, cookies: Cookies, page: Int): Document {
val sesid = cookies["BITRIX_SM_UIDH"]
//Log.i("TEXT", "Its begin! 1")
fakeConnect(cookies)
//Log.i("TEXT", "Its end! 1")
//Log.i("TEXT", "Its begin! 2")
val conn = Jsoup
.connect("https://lk.mirea.ru/staff/students/?${payload.getSubmitUrlPath(sesid ?: "")}&PAGEN_1=$page")
//.userAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
.cookies(cookies)
.referrer("https://lk.mirea.ru/")
.execute()
//Log.i("TEXT", "Its end! 2")
val doc = conn.parse()
//Log.i("TEXT", "Its end! 3")
println(doc.body())
return doc
}
@@ -75,9 +65,9 @@ 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 findStudentsInPage(document: Document): LksResponse {
fun findStudentsInPage(document: Document): LksStudent {
val table = document.select("body > div.container > table > tbody") ?: return LksResponse()
val table = document.select("body > div.container > table > tbody") ?: return LksStudent()
val students = table.select("tr").map {
@@ -102,7 +92,7 @@ fun findStudentsInPage(document: Document): LksResponse {
}
return LksResponse(
return LksStudent(
students = students,
availablePages = document.getTotalPages()
)
@@ -8,13 +8,14 @@ import ru.megboyzz.lks.profile.data.getProfilePage
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.entities.LksStudent
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
import javax.inject.Inject
class LksRepositoryImpl @Inject constructor(
@CookiesPrefs
private val sharedPreferences: SharedPreferences
private val sharedPreferences: SharedPreferences,
) : LksRepository {
private val cookieNamesKey = "cookie_names"
@@ -30,8 +31,6 @@ class LksRepositoryImpl @Inject constructor(
val document = getProfilePage(LksRequest(), cookie, 1)
println(document.body())
val pages =
document.select("body > div.container > div.bx-pagination > div > ul > li:nth-child(6) > a > span")
.text()
@@ -58,7 +57,7 @@ class LksRepositoryImpl @Inject constructor(
}
}
override suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse> {
override suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksStudent> {
Log.i("TEXT", "Its begin doc!")
val document = getProfilePage(lksRequest, cookie, pageNumber)
@@ -68,7 +67,7 @@ class LksRepositoryImpl @Inject constructor(
val page = findStudentsInPage(document)
Log.i("TEXT", "Its end!")
return if(page == LksResponse())
return if(page == LksStudent())
Result.failure(Exception())
else
Result.success(page)
@@ -1,18 +1,18 @@
package ru.megboyzz.lks.profile.data.repositories
import ru.megboyzz.lks.profile.domain.entities.LksResponse
import ru.megboyzz.lks.profile.domain.entities.LksStudent
import ru.megboyzz.lks.profile.domain.repositories.StudentRepository
class StudentRepositoryImpl : StudentRepository {
override fun getAllFavStudents(): List<LksResponse> {
override fun getAllFavStudents(): List<LksStudent> {
TODO("Not yet implemented")
}
override fun addStudent(lksResponse: LksResponse) {
override fun likeStudent(lksResponse: LksStudent) {
TODO("Not yet implemented")
}
override fun deleteFormFav(lksResponse: LksResponse) {
override fun deleteFormFav(lksResponse: LksStudent) {
TODO("Not yet implemented")
}
}
@@ -1,6 +1,6 @@
package ru.megboyzz.lks.profile.domain.entities
data class LksResponse(
data class LksStudent(
val students: List<LksStudent> = emptyList(),
val availablePages: Int = 0
)
@@ -1,15 +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
import ru.megboyzz.lks.profile.domain.entities.LksStudent
import java.io.Serializable
interface LksRepository {
interface LksRepository: Serializable {
suspend fun signInAndGetCookies(username: String, password: String): Map<String, String>
suspend fun cookiesIsValid(cookie: Map<String, String>): Boolean
suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse>
suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksStudent>
fun saveCookie(cookie: Map<String, String>)
@@ -1,13 +1,13 @@
package ru.megboyzz.lks.profile.domain.repositories
import ru.megboyzz.lks.profile.domain.entities.LksResponse
import ru.megboyzz.lks.profile.domain.entities.LksStudent
interface StudentRepository {
fun getAllFavStudents(): List<LksResponse>
fun getAllFavStudents(): List<LksStudent>
fun addStudent(lksResponse: LksResponse)
fun likeStudent(lksResponse: LksStudent)
fun deleteFormFav(lksResponse: LksResponse)
fun deleteFormFav(lksResponse: LksStudent)
}
@@ -117,7 +117,7 @@ fun SearchTextField(
Icons.Filled.KeyboardArrowDown
Column() {
Column {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
@@ -1,16 +1,12 @@
package ru.megboyzz.lks.profile.ui.components
import android.provider.ContactsContract.Profile
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -20,9 +16,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
@@ -39,22 +33,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import ru.megboyzz.lks.profile.MainActivity
import ru.megboyzz.lks.profile.domain.entities.LksResponse
import ru.megboyzz.lks.profile.domain.entities.LksStudent
import ru.megboyzz.lks.profile.ui.clickableWithNoEffect
import ru.megboyzz.lks.profile.ui.clickableWithNormalRipple
import ru.megboyzz.lks.profile.ui.screens.sub.LongPressMenu
import ru.megboyzz.lks.profile.ui.theme.MainTheme
@Composable
@@ -1,25 +1,25 @@
package ru.megboyzz.lks.profile.ui.model
import android.util.Log
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import ru.megboyzz.lks.profile.domain.DefaultCookies
import ru.megboyzz.lks.profile.domain.entities.LksRequest
import ru.megboyzz.lks.profile.domain.entities.LksResponse
import ru.megboyzz.lks.profile.domain.entities.LksStudent
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
import java.io.Serializable
import javax.inject.Inject
class SearchScreenModel @Inject constructor(
private val lksRepository: LksRepository,
@DefaultCookies
private val cookies: Map<String, String>
) : ScreenModel {
) : ScreenModel, Serializable {
init {
Log.i(this::class.simpleName, "its new init!")
}
private val _request = MutableStateFlow(LksRequest())
val request = _request.asStateFlow()
@@ -27,7 +27,7 @@ class SearchScreenModel @Inject constructor(
private val _curPageNumber = MutableStateFlow(1)
val curPageNumber = _curPageNumber.asStateFlow()
private val _responseList = MutableStateFlow(LksResponse())
private val _responseList = MutableStateFlow(LksStudent())
val responseList = _responseList.asStateFlow()
sealed class UiState(val status: String? = null) {
@@ -48,7 +48,7 @@ class SearchScreenModel @Inject constructor(
suspend fun resetStudents() {
_curPageNumber.value = 1
_responseList.emit(LksResponse())
_responseList.emit(LksStudent())
}
fun newSearch(request: LksRequest) {
@@ -70,9 +70,9 @@ class SearchScreenModel @Inject constructor(
if(result.isFailure)
return UiState.Error("Ошибка :-(")
val list = result.getOrElse { LksResponse() }
val list = result.getOrElse { LksStudent() }
val updatedStudents = LksResponse(
val updatedStudents = LksStudent(
students = _responseList.value.students + list.students,
availablePages = list.availablePages
)
@@ -1,5 +1,7 @@
package ru.megboyzz.lks.profile.ui.model
import android.content.Context
import android.widget.Toast
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import cafe.adriel.voyager.hilt.ScreenModelFactory
@@ -7,19 +9,26 @@ import cafe.adriel.voyager.navigator.Navigator
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import ru.megboyzz.lks.profile.domain.DefaultCookies
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
import ru.megboyzz.lks.profile.ui.screens.SearchScreen
class SignInScreenModel @AssistedInject constructor(
@ApplicationContext val context: Context,
private val lksRepository: LksRepository,
@Assisted private val navigator: Navigator
@Assisted private val navigator: Navigator,
@DefaultCookies
private val defaultCookies: Map<String, String>
) : ScreenModel {
private val _username = MutableStateFlow("")
@@ -42,13 +51,22 @@ class SignInScreenModel @AssistedInject constructor(
private var cookie: Map<String, String> = mapOf()
private fun makeSuspendToast(text: String) = screenModelScope.launch(Dispatchers.Main) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}
init {
screenModelScope.launch(Dispatchers.IO) {
_loadingScreen.emit(true)
cookie = lksRepository.restoreCooke()
if(lksRepository.cookiesIsValid(cookie))
//if(cookie.isEmpty()) cookie = defaultCookies
if(lksRepository.cookiesIsValid(cookie)) {
navigator.push(SearchScreen())
makeSuspendToast("Успех!")
}else {
makeSuspendToast("Не успех!")
}
_loadingScreen.emit(false)
}
@@ -21,11 +21,12 @@ import cafe.adriel.voyager.hilt.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import ru.megboyzz.lks.profile.ui.components.LoadingShield
import ru.megboyzz.lks.profile.ui.model.SignInScreenModel
import ru.megboyzz.lks.profile.ui.theme.MainTheme
class SignInScreen : Screen {
@Composable
override fun Content() {
override fun Content() = MainTheme {
val navigator = LocalNavigator.current!!
@@ -9,9 +9,10 @@ import cafe.adriel.voyager.core.screen.Screen
import ru.megboyzz.lks.profile.domain.entities.LksStudent
import ru.megboyzz.lks.profile.ui.components.LongPressCardContextMenu
import ru.megboyzz.lks.profile.ui.copyToClipboard
import java.io.Serializable
class LongPressMenu(private val lksStudent: LksStudent) : Screen {
class LongPressMenu(private val lksStudent: LksStudent) : Screen, Serializable {
@Composable
override fun Content() {
@@ -24,7 +25,7 @@ class LongPressMenu(private val lksStudent: LksStudent) : Screen {
context.copyToClipboard(lksStudent.studentFullName.toString())
},
onCopyGroup = {
context.copyToClipboard(lksStudent.studentFullName.toString())
context.copyToClipboard(lksStudent.group.toString())
},
onGoToVk = {
Toast.makeText(context, "Переход на VK", Toast.LENGTH_SHORT).show()
@@ -3,16 +3,20 @@ package ru.megboyzz.lks.profile.ui.screens.sub
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.hilt.getScreenModel
import ru.megboyzz.lks.profile.ui.components.SearchContextMenu
import ru.megboyzz.lks.profile.ui.model.SearchScreenModel
import ru.megboyzz.lks.profile.ui.theme.MainTheme
import java.io.Serializable
class SearchBottomScreen(
private val searchScreenModel: SearchScreenModel
) : Screen {
) : Screen, Serializable {
@Composable
override fun Content() {
override fun Content() = MainTheme {
//val screenModel = getScreenModel<SearchScreenModel>("my_tag")
val screenModel = rememberScreenModel { searchScreenModel }
SearchContextMenu { lksRequest -> screenModel.newSearch(lksRequest) }
@@ -26,7 +26,7 @@ private val DarkColorScheme = darkColorScheme(
)
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF90CAF9), // Голубой с пастельным оттенком
primary = Color(0xFF207AEE), // Голубой с пастельным оттенком
secondary = Color(0xFFB3E5FC), // Мягкий голубой
tertiary = Color(0xFFC5CAE9), // Светлый фиолетовый
@@ -45,7 +45,7 @@ fun MainTheme(
dynamicColor: Boolean = false, // Отключаем динамические цвета для четкости палитры
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
val colorScheme = LightColorScheme
MaterialTheme(
colorScheme = colorScheme,
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M440,840v-240h80v80h320v80L520,760v80h-80ZM120,760v-80h240v80L120,760ZM280,600v-80L120,520v-80h160v-80h80v240h-80ZM440,520v-80h400v80L440,520ZM600,360v-240h80v80h160v80L680,280v80h-80ZM120,280v-80h400v80L120,280Z"
android:fillColor="#e8eaed"/>
</vector>
-2
View File
@@ -21,5 +21,3 @@ dependencyResolutionManagement {
rootProject.name = "LKS PROFILE"
include(":app")
include(":app:domain")
include(":app:data")