filany refactor code

This commit is contained in:
2025-02-22 19:34:07 +03:00
parent 7d313e1c64
commit 193ad2d07a
23 changed files with 392 additions and 337 deletions
+7 -2
View File
@@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
id("com.google.devtools.ksp") id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android") id("com.google.dagger.hilt.android")
} }
@@ -59,10 +60,14 @@ dependencies {
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.runtime)
implementation(libs.navigation.compose)
implementation(libs.hilt.android) implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler) ksp(libs.hilt.android.compiler)
ksp(libs.androidx.room.compiler)
implementation(libs.jsoup) implementation(libs.jsoup)
implementation(libs.kotlinx.serialization.json)
} }
@@ -4,22 +4,9 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
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 androidx.compose.ui.unit.dp
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import ru.megboyzz.lks.profile.ui.components.MainNavigator import ru.megboyzz.lks.profile.ui.components.MainNavHost
import ru.megboyzz.lks.profile.ui.screens.SearchScreen import ru.megboyzz.lks.profile.ui.provider.ActivityProvider
import ru.megboyzz.lks.profile.ui.screens.SignInScreen
import ru.megboyzz.lks.profile.ui.theme.MainTheme
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -29,7 +16,7 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
MainNavigator(SignInScreen()) MainNavHost()
} }
} }
} }
@@ -8,6 +8,7 @@ import kotlinx.coroutines.withContext
import org.jsoup.Connection import org.jsoup.Connection
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import ru.megboyzz.lks.profile.domain.entities.LksResponse
private typealias Cookies = Map<String, String> private typealias Cookies = Map<String, String>
@@ -65,9 +66,9 @@ fun Document.getTotalPages() = kotlin.runCatching {
this.select("body > div.container > div.bx-pagination > div > ul > li:nth-child(6) > a > span").text().toInt() this.select("body > div.container > div.bx-pagination > div > ul > li:nth-child(6) > a > span").text().toInt()
}.getOrNull() ?: 0 }.getOrNull() ?: 0
fun findStudentsInPage(document: Document): LksStudent { fun findStudentsInPage(document: Document): LksResponse {
val table = document.select("body > div.container > table > tbody") ?: return LksStudent() val table = document.select("body > div.container > table > tbody") ?: return LksResponse()
val students = table.select("tr").map { val students = table.select("tr").map {
@@ -92,7 +93,7 @@ fun findStudentsInPage(document: Document): LksStudent {
} }
return LksStudent( return LksResponse(
students = students, students = students,
availablePages = document.getTotalPages() availablePages = document.getTotalPages()
) )
@@ -8,6 +8,7 @@ import ru.megboyzz.lks.profile.data.getProfilePage
import ru.megboyzz.lks.profile.data.signInLks import ru.megboyzz.lks.profile.data.signInLks
import ru.megboyzz.lks.profile.domain.CookiesPrefs import ru.megboyzz.lks.profile.domain.CookiesPrefs
import ru.megboyzz.lks.profile.domain.entities.LksRequest 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.entities.LksStudent
import ru.megboyzz.lks.profile.domain.repositories.LksRepository import ru.megboyzz.lks.profile.domain.repositories.LksRepository
import javax.inject.Inject import javax.inject.Inject
@@ -57,7 +58,7 @@ class LksRepositoryImpl @Inject constructor(
} }
} }
override suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksStudent> { override suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse> {
Log.i("TEXT", "Its begin doc!") Log.i("TEXT", "Its begin doc!")
val document = getProfilePage(lksRequest, cookie, pageNumber) val document = getProfilePage(lksRequest, cookie, pageNumber)
@@ -67,7 +68,7 @@ class LksRepositoryImpl @Inject constructor(
val page = findStudentsInPage(document) val page = findStudentsInPage(document)
Log.i("TEXT", "Its end!") Log.i("TEXT", "Its end!")
return if(page == LksStudent()) return if(page == LksResponse())
Result.failure(Exception()) Result.failure(Exception())
else else
Result.success(page) Result.success(page)
@@ -1,6 +1,6 @@
package ru.megboyzz.lks.profile.domain.entities package ru.megboyzz.lks.profile.domain.entities
data class LksStudent( data class LksResponse(
val students: List<LksStudent> = emptyList(), val students: List<LksStudent> = emptyList(),
val availablePages: Int = 0 val availablePages: Int = 0
) )
@@ -1,6 +1,7 @@
package ru.megboyzz.lks.profile.domain.repositories package ru.megboyzz.lks.profile.domain.repositories
import ru.megboyzz.lks.profile.domain.entities.LksRequest 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.entities.LksStudent
import java.io.Serializable import java.io.Serializable
@@ -10,7 +11,7 @@ interface LksRepository: Serializable {
suspend fun cookiesIsValid(cookie: Map<String, String>): Boolean suspend fun cookiesIsValid(cookie: Map<String, String>): Boolean
suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksStudent> suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse>
fun saveCookie(cookie: Map<String, String>) fun saveCookie(cookie: Map<String, String>)
@@ -1,44 +0,0 @@
package ru.megboyzz.lks.profile.ui
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.hilt.ScreenModelFactory
import cafe.adriel.voyager.hilt.ScreenModelFactoryKey
import cafe.adriel.voyager.hilt.ScreenModelKey
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.multibindings.IntoMap
import ru.megboyzz.lks.profile.ui.model.SearchScreenModel
import ru.megboyzz.lks.profile.ui.model.SignInScreenModel
import javax.inject.Qualifier
@Module
@InstallIn(ActivityComponent::class)
interface HiltModule {
/*@Binds
@IntoMap
@ScreenModelKey(SignInScreenModel::class)
abstract fun bindSignInScreenModel(
signInScreenModel: SignInScreenModel
) : ScreenModel*/
@Qualifier
annotation class MyFactory
@Binds
@IntoMap
@ScreenModelFactoryKey(SignInScreenModel.Factory::class)
fun bindSignInScreenModelFactory(
factory: SignInScreenModel.Factory
): ScreenModelFactory
@Binds
@IntoMap
@ScreenModelKey(SearchScreenModel::class)
fun bindSearchScreenModelFactory(
factory: SearchScreenModel
): ScreenModel
}
@@ -3,6 +3,8 @@ package ru.megboyzz.lks.profile.ui
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -51,4 +53,12 @@ fun Context.copyToClipboard(content: String) {
clipboardManager.setPrimaryClip(clip) clipboardManager.setPrimaryClip(clip)
Toast.makeText(this, "Текст скопирован в буфер обмена", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Текст скопирован в буфер обмена", Toast.LENGTH_SHORT).show()
}
fun Context.goToVK(request: String) {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://vk.com/search?q=$request")
)
this.startActivity(browserIntent)
} }
@@ -0,0 +1,39 @@
package ru.megboyzz.lks.profile.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SimpleDropdownMenu(
expanded: Boolean,
onDismiss: () -> Unit,
content: @Composable ColumnScope.() -> Unit
) = DropdownMenu(
expanded = expanded,
onDismissRequest = onDismiss,
content = content
)
@Composable
fun SimpleDropdownMenuItem(
text: String,
onClick: () -> Unit
) = DropdownMenuItem(
text = { Text(text) },
onClick = onClick
)
@@ -0,0 +1,52 @@
package ru.megboyzz.lks.profile.ui.components
import androidx.activity.viewModels
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import ru.megboyzz.lks.profile.ui.model.SignInViewModel
import ru.megboyzz.lks.profile.ui.provider.ActivityProvider
import ru.megboyzz.lks.profile.ui.provider.LocalMainActivity
import ru.megboyzz.lks.profile.ui.screens.SearchScreenContent
import ru.megboyzz.lks.profile.ui.screens.SignInScreenContent
/**
* Место где лучше всего применять все CompositionProvider
*/
@Composable
fun MainNavHost() = ActivityProvider {
val navHostController = rememberNavController()
val viewModel by LocalMainActivity.current.viewModels<SignInViewModel>()
val state by viewModel.state.collectAsState()
if(state is SignInViewModel.SignInState.Success) {
navHostController.navigate(SearchScreen)
}
NavHost(
navController = navHostController,
startDestination = SignInScreen
) {
composable<SignInScreen> {
SignInScreenContent()
}
composable<SearchScreen> {
SearchScreenContent()
}
}
}
@Serializable
object SearchScreen
@Serializable
object SignInScreen
@@ -1,25 +0,0 @@
package ru.megboyzz.lks.profile.ui.components
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
import cafe.adriel.voyager.transitions.SlideTransition
import ru.megboyzz.lks.profile.ui.screens.SearchScreen
import ru.megboyzz.lks.profile.ui.theme.MainTheme
@Composable
fun MainNavigator(screen: Screen) {
BottomSheetNavigator(
sheetShape = RoundedCornerShape(12.dp)
) {
MainTheme {
Navigator(screen) {
SlideTransition(it)
}
}
}
}
@@ -45,9 +45,9 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
import ru.megboyzz.lks.profile.R import ru.megboyzz.lks.profile.R
import ru.megboyzz.lks.profile.domain.entities.LksRequest 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.entities.LksStudent
import ru.megboyzz.lks.profile.domain.entities.Student import ru.megboyzz.lks.profile.domain.entities.Student
import ru.megboyzz.lks.profile.ui.asPainter import ru.megboyzz.lks.profile.ui.asPainter
@@ -171,7 +171,7 @@ fun SearchContextMenu(
onSearch: (LksRequest) -> Unit onSearch: (LksRequest) -> Unit
) { ) {
val bottomSheetNavigator = LocalBottomSheetNavigator.current
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
var surname by remember { mutableStateOf("") } var surname by remember { mutableStateOf("") }
@@ -231,7 +231,7 @@ fun SearchContextMenu(
department = departamnet.ifEmpty { null } department = departamnet.ifEmpty { null }
) )
onSearch(lksRequest) onSearch(lksRequest)
bottomSheetNavigator?.hide() //bottomSheetNavigator?.hide()
} }
) { ) {
Text("Поиск") Text("Поиск")
@@ -15,12 +15,14 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -113,6 +115,8 @@ fun MainScaffold(
content: @Composable (PaddingValues) -> Unit content: @Composable (PaddingValues) -> Unit
) { ) {
var expanded by remember { mutableStateOf(false) }
Scaffold( Scaffold(
topBar = { topBar = {
Surface( Surface(
@@ -120,7 +124,23 @@ fun MainScaffold(
modifier = Modifier.clickableWithNoEffect(onTopClick) modifier = Modifier.clickableWithNoEffect(onTopClick)
) { ) {
TopAppBar( TopAppBar(
title = { Text("LKS Search") } title = { Text("LKS Search") },
actions = {
IconButton(onClick = { expanded =! expanded }) {
Icon(Icons.Default.MoreVert, contentDescription = "More options")
}
SimpleDropdownMenu(
expanded = expanded,
onDismiss = { expanded =! expanded },
) {
SimpleDropdownMenuItem("История поиска") {
expanded =! expanded
}
SimpleDropdownMenuItem("Избранное") {
expanded =! expanded
}
}
}
) )
} }
}, },
@@ -1,25 +1,21 @@
package ru.megboyzz.lks.profile.ui.model package ru.megboyzz.lks.profile.ui.model
import android.util.Log import androidx.lifecycle.ViewModel
import cafe.adriel.voyager.core.model.ScreenModel import androidx.lifecycle.viewModelScope
import cafe.adriel.voyager.core.model.screenModelScope import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.megboyzz.lks.profile.domain.entities.LksRequest import ru.megboyzz.lks.profile.domain.entities.LksRequest
import ru.megboyzz.lks.profile.domain.entities.LksStudent import ru.megboyzz.lks.profile.domain.entities.LksResponse
import ru.megboyzz.lks.profile.domain.repositories.LksRepository import ru.megboyzz.lks.profile.domain.repositories.LksRepository
import java.io.Serializable
import javax.inject.Inject import javax.inject.Inject
class SearchScreenModel @Inject constructor( @HiltViewModel
class SearchViewModel @Inject constructor(
private val lksRepository: LksRepository, private val lksRepository: LksRepository,
) : ScreenModel, Serializable { ) : ViewModel() {
init {
Log.i(this::class.simpleName, "its new init!")
}
private val _request = MutableStateFlow(LksRequest()) private val _request = MutableStateFlow(LksRequest())
val request = _request.asStateFlow() val request = _request.asStateFlow()
@@ -27,7 +23,7 @@ class SearchScreenModel @Inject constructor(
private val _curPageNumber = MutableStateFlow(1) private val _curPageNumber = MutableStateFlow(1)
val curPageNumber = _curPageNumber.asStateFlow() val curPageNumber = _curPageNumber.asStateFlow()
private val _responseList = MutableStateFlow(LksStudent()) private val _responseList = MutableStateFlow(LksResponse())
val responseList = _responseList.asStateFlow() val responseList = _responseList.asStateFlow()
sealed class UiState(val status: String? = null) { sealed class UiState(val status: String? = null) {
@@ -48,11 +44,11 @@ class SearchScreenModel @Inject constructor(
suspend fun resetStudents() { suspend fun resetStudents() {
_curPageNumber.value = 1 _curPageNumber.value = 1
_responseList.emit(LksStudent()) _responseList.emit(LksResponse())
} }
fun newSearch(request: LksRequest) { fun newSearch(request: LksRequest) {
screenModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
state.emit(UiState.Loading) state.emit(UiState.Loading)
resetStudents() resetStudents()
state.emit(update(request)) state.emit(update(request))
@@ -70,9 +66,9 @@ class SearchScreenModel @Inject constructor(
if(result.isFailure) if(result.isFailure)
return UiState.Error("Ошибка :-(") return UiState.Error("Ошибка :-(")
val list = result.getOrElse { LksStudent() } val list = result.getOrElse { LksResponse() }
val updatedStudents = LksStudent( val updatedStudents = LksResponse(
students = _responseList.value.students + list.students, students = _responseList.value.students + list.students,
availablePages = list.availablePages availablePages = list.availablePages
) )
@@ -2,17 +2,11 @@ package ru.megboyzz.lks.profile.ui.model
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import cafe.adriel.voyager.core.model.ScreenModel import androidx.lifecycle.ViewModel
import cafe.adriel.voyager.core.model.screenModelScope import androidx.lifecycle.viewModelScope
import cafe.adriel.voyager.hilt.ScreenModelFactory import dagger.hilt.android.lifecycle.HiltViewModel
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 dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@@ -20,16 +14,15 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.megboyzz.lks.profile.domain.DefaultCookies import ru.megboyzz.lks.profile.domain.DefaultCookies
import ru.megboyzz.lks.profile.domain.repositories.LksRepository import ru.megboyzz.lks.profile.domain.repositories.LksRepository
import ru.megboyzz.lks.profile.ui.screens.SearchScreen import javax.inject.Inject
@HiltViewModel
class SignInScreenModel @AssistedInject constructor( class SignInViewModel @Inject constructor(
@ApplicationContext val context: Context, @ApplicationContext val context: Context,
private val lksRepository: LksRepository, private val lksRepository: LksRepository,
@Assisted private val navigator: Navigator,
@DefaultCookies @DefaultCookies
private val defaultCookies: Map<String, String> private val defaultCookies: Map<String, String>
) : ScreenModel { ) : ViewModel() {
private val _username = MutableStateFlow("") private val _username = MutableStateFlow("")
val username = _username.asStateFlow() val username = _username.asStateFlow()
@@ -51,28 +44,35 @@ class SignInScreenModel @AssistedInject constructor(
private var cookie: Map<String, String> = mapOf() private var cookie: Map<String, String> = mapOf()
private fun makeSuspendToast(text: String) = screenModelScope.launch(Dispatchers.Main) { private fun makeSuspendToast(text: String) = viewModelScope.launch(Dispatchers.Main) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show() Toast.makeText(context, text, Toast.LENGTH_LONG).show()
} }
init { private var isUseDefaultCookie = false
screenModelScope.launch(Dispatchers.IO) { init {
_loadingScreen.emit(true) viewModelScope.launch(Dispatchers.IO) {
state.emit(SignInState.Loading)
cookie = lksRepository.restoreCooke() cookie = lksRepository.restoreCooke()
//if(cookie.isEmpty()) cookie = defaultCookies if(cookie.isEmpty()) {
isUseDefaultCookie = true
cookie = defaultCookies
}
if(lksRepository.cookiesIsValid(cookie)) { if(lksRepository.cookiesIsValid(cookie)) {
navigator.push(SearchScreen()) state.emit(SignInState.Success)
makeSuspendToast("Успех!") if(isUseDefaultCookie)
makeSuspendToast("Успех с дефолтными куками!")
else
makeSuspendToast("Успех!")
}else { }else {
state.emit(SignInState.Error("Error!"))
makeSuspendToast("Не успех!") makeSuspendToast("Не успех!")
} }
_loadingScreen.emit(false)
} }
state.onEach { state.onEach {
if(it == SignInState.Success) navigator.push(SearchScreen()) //if(it == SignInState.Success) navigator.push(SearchScreen())
}.launchIn(screenModelScope) }.launchIn(viewModelScope)
} }
@@ -85,7 +85,7 @@ class SignInScreenModel @AssistedInject constructor(
} }
fun signIn() { fun signIn() {
screenModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
state.emit(SignInState.Loading) state.emit(SignInState.Loading)
cookie = lksRepository.signInAndGetCookies(_username.value, _password.value) cookie = lksRepository.signInAndGetCookies(_username.value, _password.value)
if(cookie.isEmpty()) if(cookie.isEmpty())
@@ -96,10 +96,4 @@ class SignInScreenModel @AssistedInject constructor(
} }
} }
} }
@AssistedFactory
interface Factory : ScreenModelFactory {
fun create(navigator: Navigator): SignInScreenModel
}
} }
@@ -0,0 +1,21 @@
package ru.megboyzz.lks.profile.ui.provider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.platform.LocalContext
import ru.megboyzz.lks.profile.MainActivity
val LocalMainActivity = compositionLocalOf<MainActivity> {
error("No MainActivity provided")
}
@Composable
fun ActivityProvider(
content: @Composable () -> Unit
) {
val mainActivity = LocalContext.current as MainActivity
CompositionLocalProvider(LocalMainActivity provides mainActivity) {
content()
}
}
@@ -1,72 +1,63 @@
package ru.megboyzz.lks.profile.ui.screens package ru.megboyzz.lks.profile.ui.screens
import android.widget.Toast import androidx.activity.viewModels
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.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerIcon.Companion.Text import ru.megboyzz.lks.profile.domain.entities.LksStudent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.hilt.getScreenModel
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
import ru.megboyzz.lks.profile.ui.components.CardsColumn import ru.megboyzz.lks.profile.ui.components.CardsColumn
import ru.megboyzz.lks.profile.ui.components.ErrorShield import ru.megboyzz.lks.profile.ui.components.ErrorShield
import ru.megboyzz.lks.profile.ui.components.LoadingShield import ru.megboyzz.lks.profile.ui.components.LoadingShield
import ru.megboyzz.lks.profile.ui.components.MainScaffold import ru.megboyzz.lks.profile.ui.components.MainScaffold
import ru.megboyzz.lks.profile.ui.components.ProfileCard import ru.megboyzz.lks.profile.ui.model.SearchViewModel
import ru.megboyzz.lks.profile.ui.model.SearchScreenModel import ru.megboyzz.lks.profile.ui.provider.LocalMainActivity
import ru.megboyzz.lks.profile.ui.screens.sub.LongPressMenu import ru.megboyzz.lks.profile.ui.screens.sub.LongPressMenu
import ru.megboyzz.lks.profile.ui.screens.sub.SearchBottomScreen import ru.megboyzz.lks.profile.ui.screens.sub.SearchBottomSheet
class SearchScreen : Screen { @Composable
@Composable fun SearchScreenContent() {
override fun Content() {
val bottomSheetNavigator = LocalBottomSheetNavigator.current val screenModel by LocalMainActivity.current.viewModels<SearchViewModel>()
val screenModel = getScreenModel<SearchScreenModel>("my_tag") val state by screenModel.state.collectAsState()
val state by screenModel.state.collectAsState() val list by screenModel.responseList.collectAsState()
val list by screenModel.responseList.collectAsState() var searchMenuExpanded by remember { mutableStateOf(false) }
MainScaffold(
onFloatingButtonClick = { bottomSheetNavigator.show(SearchBottomScreen(screenModel)) },
onTopClick = { }
) {
when (state) {
is SearchScreenModel.UiState.Loading -> LoadingShield("Загрузка челиков")
is SearchScreenModel.UiState.Error -> ErrorShield(state.status)
else -> {
CardsColumn(
modifier = Modifier.padding(it),
lksStudents = list.students,
onLongPress = { student -> bottomSheetNavigator.show(LongPressMenu(student)) },
onColumnOverflow = {
//Toast.makeText(context, "Hello!", Toast.LENGTH_SHORT).show()
screenModel.nextPage()
//screenModel.update(screenModel.request.value)
//screenModel.nextPage()
}
)
}
}
}
var isLongPressedStudent by remember {
mutableStateOf<LksStudent?>(null)
} }
MainScaffold(
onFloatingButtonClick = { searchMenuExpanded =! searchMenuExpanded },
onTopClick = { }
) {
when (state) {
is SearchViewModel.UiState.Loading -> LoadingShield("Загрузка челиков")
is SearchViewModel.UiState.Error -> ErrorShield(state.status)
else -> {
CardsColumn(
modifier = Modifier.padding(it),
lksStudents = list.students,
onLongPress = { student -> isLongPressedStudent = student },
onColumnOverflow = {
screenModel.nextPage()
}
)
}
}
}
if(searchMenuExpanded)
SearchBottomSheet { searchMenuExpanded =! searchMenuExpanded }
if(isLongPressedStudent != null)
LongPressMenu(isLongPressedStudent!!) { isLongPressedStudent = null }
} }
@@ -1,86 +0,0 @@
package ru.megboyzz.lks.profile.ui.screens
import android.os.Parcel
import android.os.Parcelable
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
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() = MainTheme {
val navigator = LocalNavigator.current!!
val viewModel = getScreenModel<SignInScreenModel, SignInScreenModel.Factory> { assistedFactory ->
assistedFactory.create(navigator)
}
val username by viewModel.username.collectAsState()
val password by viewModel.password.collectAsState()
val state by viewModel.state.collectAsState()
val loading by viewModel.loadingScreen.collectAsState()
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
if(loading){
LoadingShield("Проверка твоих куки файлов :-)")
} else {
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 ?: "Ошибка")
else -> {}
}
}
}
}
}
}
@@ -0,0 +1,74 @@
package ru.megboyzz.lks.profile.ui.screens
import androidx.activity.viewModels
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import ru.megboyzz.lks.profile.ui.components.LoadingShield
import ru.megboyzz.lks.profile.ui.model.SignInViewModel
import ru.megboyzz.lks.profile.ui.provider.LocalMainActivity
import ru.megboyzz.lks.profile.ui.theme.MainTheme
@Composable
fun SignInScreenContent() = MainTheme {
val viewModel by LocalMainActivity.current.viewModels<SignInViewModel>()
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
) {
when(state) {
is SignInViewModel.SignInState.Loading, SignInViewModel.SignInState.Success -> LoadingShield("Проверка твоих куки файлов :-)")
else -> {
OutlinedTextField(
value = username,
onValueChange = { viewModel.setUsername(it) }
)
OutlinedTextField(
value = password,
onValueChange = { viewModel.setPassword(it) }
)
OutlinedButton(
onClick = viewModel::signIn
) {
Text("Войти")
}
when (state) {
is SignInViewModel.SignInState.Idle -> Text("")
is SignInViewModel.SignInState.Success -> Text(state.status ?: "")
is SignInViewModel.SignInState.Loading -> CircularProgressIndicator()
is SignInViewModel.SignInState.Error -> Text(state.status ?: "Ошибка")
else -> {}
}
}
}
}
}
}
@@ -3,38 +3,48 @@ package ru.megboyzz.lks.profile.ui.screens.sub
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.screen.Screen
import ru.megboyzz.lks.profile.domain.entities.LksStudent import ru.megboyzz.lks.profile.domain.entities.LksStudent
import ru.megboyzz.lks.profile.ui.components.LongPressCardContextMenu import ru.megboyzz.lks.profile.ui.components.LongPressCardContextMenu
import ru.megboyzz.lks.profile.ui.copyToClipboard import ru.megboyzz.lks.profile.ui.copyToClipboard
import java.io.Serializable import ru.megboyzz.lks.profile.ui.goToVK
class LongPressMenu(private val lksStudent: LksStudent) : Screen, Serializable { @OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LongPressMenu(
lksStudent: LksStudent,
onDismiss: () -> Unit
) {
val context = LocalContext.current
@Composable val sheetState = rememberModalBottomSheetState()
override fun Content() { val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismiss
) {
LongPressCardContextMenu( LongPressCardContextMenu(
lksStudent = lksStudent, lksStudent = lksStudent,
onCopyName = { onCopyName = context::copyToClipboard,
context.copyToClipboard(lksStudent.studentFullName.toString()) onCopyGroup = context::copyToClipboard,
}, onGoToVk = {
onCopyGroup = {
context.copyToClipboard(lksStudent.group.toString())
},
onGoToVk = {
Toast.makeText(context, "Переход на VK", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Переход на VK", Toast.LENGTH_SHORT).show()
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://vk.com/search?q=${lksStudent.studentFullName?.split(" ")?.dropLast(1)?.joinToString(" ")}"))
browserIntent.`package` = "com.vkontakte.android" val searchString = lksStudent
context.startActivity(browserIntent) .studentFullName
?.split(" ")
?.dropLast(1)
?.joinToString(" ") ?: ""
context.goToVK(searchString)
} }
) )
} }
} }
@@ -1,25 +0,0 @@
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, Serializable {
@Composable
override fun Content() = MainTheme {
//val screenModel = getScreenModel<SearchScreenModel>("my_tag")
val screenModel = rememberScreenModel { searchScreenModel }
SearchContextMenu { lksRequest -> screenModel.newSearch(lksRequest) }
}
}
@@ -0,0 +1,34 @@
package ru.megboyzz.lks.profile.ui.screens.sub
import androidx.activity.viewModels
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
import ru.megboyzz.lks.profile.ui.components.SearchContextMenu
import ru.megboyzz.lks.profile.ui.model.SearchViewModel
import ru.megboyzz.lks.profile.ui.provider.LocalMainActivity
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchBottomSheet(onDismiss: () -> Unit) {
val viewModel by LocalMainActivity.current.viewModels<SearchViewModel>()
val sheetState = rememberModalBottomSheetState()
val coroutineScope = rememberCoroutineScope()
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismiss
) {
SearchContextMenu { lksRequest ->
viewModel.newSearch(lksRequest)
coroutineScope.launch { sheetState.hide() }
}
}
}
+9 -10
View File
@@ -11,11 +11,14 @@ lifecycleRuntimeKtx = "2.8.4"
activityCompose = "1.9.1" activityCompose = "1.9.1"
composeBom = "2024.04.01" composeBom = "2024.04.01"
jetbrainsKotlinJvm = "2.0.0" jetbrainsKotlinJvm = "2.0.0"
appcompat = "1.7.0" composeNavigation = "2.8.0-alpha08"
material = "1.12.0" room_version = "2.6.1"
voyagerVersion = "1.1.0-beta02" serialization = "1.6.3"
[libraries] [libraries]
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room_version" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room_version" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room_version" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroidCompiler" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroidCompiler" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroidCompiler" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroidCompiler" }
@@ -33,13 +36,8 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "composeNavigation" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization"}
voyager-bottom-sheet-navigator = { module = "cafe.adriel.voyager:voyager-bottom-sheet-navigator", version.ref = "voyagerVersion" }
voyager-hilt = { module = "cafe.adriel.voyager:voyager-hilt", version.ref = "voyagerVersion" }
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyagerVersion" }
voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyagerVersion" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyagerVersion" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
@@ -47,4 +45,5 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
android-library = { id = "com.android.library", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }