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.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
@@ -59,10 +60,14 @@ dependencies {
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.runtime)
implementation(libs.navigation.compose)
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
ksp(libs.androidx.room.compiler)
implementation(libs.jsoup)
implementation(libs.kotlinx.serialization.json)
}
@@ -4,22 +4,9 @@ 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.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 ru.megboyzz.lks.profile.ui.components.MainNavigator
import ru.megboyzz.lks.profile.ui.screens.SearchScreen
import ru.megboyzz.lks.profile.ui.screens.SignInScreen
import ru.megboyzz.lks.profile.ui.theme.MainTheme
import ru.megboyzz.lks.profile.ui.components.MainNavHost
import ru.megboyzz.lks.profile.ui.provider.ActivityProvider
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@@ -29,7 +16,7 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
MainNavigator(SignInScreen())
MainNavHost()
}
}
}
@@ -8,6 +8,7 @@ import kotlinx.coroutines.withContext
import org.jsoup.Connection
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import ru.megboyzz.lks.profile.domain.entities.LksResponse
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()
}.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 {
@@ -92,7 +93,7 @@ fun findStudentsInPage(document: Document): LksStudent {
}
return LksStudent(
return LksResponse(
students = students,
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.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
@@ -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!")
val document = getProfilePage(lksRequest, cookie, pageNumber)
@@ -67,7 +68,7 @@ class LksRepositoryImpl @Inject constructor(
val page = findStudentsInPage(document)
Log.i("TEXT", "Its end!")
return if(page == LksStudent())
return if(page == LksResponse())
Result.failure(Exception())
else
Result.success(page)
@@ -1,6 +1,6 @@
package ru.megboyzz.lks.profile.domain.entities
data class LksStudent(
data class LksResponse(
val students: List<LksStudent> = emptyList(),
val availablePages: Int = 0
)
@@ -1,6 +1,7 @@
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
@@ -10,7 +11,7 @@ interface LksRepository: Serializable {
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>)
@@ -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.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
@@ -52,3 +54,11 @@ fun Context.copyToClipboard(content: String) {
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
import ru.megboyzz.lks.profile.R
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.Student
import ru.megboyzz.lks.profile.ui.asPainter
@@ -171,7 +171,7 @@ fun SearchContextMenu(
onSearch: (LksRequest) -> Unit
) {
val bottomSheetNavigator = LocalBottomSheetNavigator.current
var name by remember { mutableStateOf("") }
var surname by remember { mutableStateOf("") }
@@ -231,7 +231,7 @@ fun SearchContextMenu(
department = departamnet.ifEmpty { null }
)
onSearch(lksRequest)
bottomSheetNavigator?.hide()
//bottomSheetNavigator?.hide()
}
) {
Text("Поиск")
@@ -15,12 +15,14 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
@@ -113,6 +115,8 @@ fun MainScaffold(
content: @Composable (PaddingValues) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
Scaffold(
topBar = {
Surface(
@@ -120,7 +124,23 @@ fun MainScaffold(
modifier = Modifier.clickableWithNoEffect(onTopClick)
) {
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
import android.util.Log
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
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 java.io.Serializable
import javax.inject.Inject
class SearchScreenModel @Inject constructor(
@HiltViewModel
class SearchViewModel @Inject constructor(
private val lksRepository: LksRepository,
) : ScreenModel, Serializable {
init {
Log.i(this::class.simpleName, "its new init!")
}
) : ViewModel() {
private val _request = MutableStateFlow(LksRequest())
val request = _request.asStateFlow()
@@ -27,7 +23,7 @@ class SearchScreenModel @Inject constructor(
private val _curPageNumber = MutableStateFlow(1)
val curPageNumber = _curPageNumber.asStateFlow()
private val _responseList = MutableStateFlow(LksStudent())
private val _responseList = MutableStateFlow(LksResponse())
val responseList = _responseList.asStateFlow()
sealed class UiState(val status: String? = null) {
@@ -48,11 +44,11 @@ class SearchScreenModel @Inject constructor(
suspend fun resetStudents() {
_curPageNumber.value = 1
_responseList.emit(LksStudent())
_responseList.emit(LksResponse())
}
fun newSearch(request: LksRequest) {
screenModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.IO) {
state.emit(UiState.Loading)
resetStudents()
state.emit(update(request))
@@ -70,9 +66,9 @@ class SearchScreenModel @Inject constructor(
if(result.isFailure)
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,
availablePages = list.availablePages
)
@@ -2,17 +2,11 @@ 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
import cafe.adriel.voyager.navigator.Navigator
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.AndroidEntryPoint
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
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
@@ -20,16 +14,15 @@ 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
import javax.inject.Inject
class SignInScreenModel @AssistedInject constructor(
@HiltViewModel
class SignInViewModel @Inject constructor(
@ApplicationContext val context: Context,
private val lksRepository: LksRepository,
@Assisted private val navigator: Navigator,
@DefaultCookies
private val defaultCookies: Map<String, String>
) : ScreenModel {
) : ViewModel() {
private val _username = MutableStateFlow("")
val username = _username.asStateFlow()
@@ -51,28 +44,35 @@ class SignInScreenModel @AssistedInject constructor(
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()
}
init {
private var isUseDefaultCookie = false
screenModelScope.launch(Dispatchers.IO) {
_loadingScreen.emit(true)
init {
viewModelScope.launch(Dispatchers.IO) {
state.emit(SignInState.Loading)
cookie = lksRepository.restoreCooke()
//if(cookie.isEmpty()) cookie = defaultCookies
if(cookie.isEmpty()) {
isUseDefaultCookie = true
cookie = defaultCookies
}
if(lksRepository.cookiesIsValid(cookie)) {
navigator.push(SearchScreen())
makeSuspendToast("Успех!")
state.emit(SignInState.Success)
if(isUseDefaultCookie)
makeSuspendToast("Успех с дефолтными куками!")
else
makeSuspendToast("Успех!")
}else {
state.emit(SignInState.Error("Error!"))
makeSuspendToast("Не успех!")
}
_loadingScreen.emit(false)
}
state.onEach {
if(it == SignInState.Success) navigator.push(SearchScreen())
}.launchIn(screenModelScope)
//if(it == SignInState.Success) navigator.push(SearchScreen())
}.launchIn(viewModelScope)
}
@@ -85,7 +85,7 @@ class SignInScreenModel @AssistedInject constructor(
}
fun signIn() {
screenModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.IO) {
state.emit(SignInState.Loading)
cookie = lksRepository.signInAndGetCookies(_username.value, _password.value)
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
import android.widget.Toast
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.activity.viewModels
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.collectAsState
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.input.pointer.PointerIcon.Companion.Text
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.domain.entities.LksStudent
import ru.megboyzz.lks.profile.ui.components.CardsColumn
import ru.megboyzz.lks.profile.ui.components.ErrorShield
import ru.megboyzz.lks.profile.ui.components.LoadingShield
import ru.megboyzz.lks.profile.ui.components.MainScaffold
import ru.megboyzz.lks.profile.ui.components.ProfileCard
import ru.megboyzz.lks.profile.ui.model.SearchScreenModel
import ru.megboyzz.lks.profile.ui.model.SearchViewModel
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.SearchBottomScreen
import ru.megboyzz.lks.profile.ui.screens.sub.SearchBottomSheet
class SearchScreen : Screen {
@Composable
override fun Content() {
@Composable
fun SearchScreenContent() {
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()
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 searchMenuExpanded by remember { mutableStateOf(false) }
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.net.Uri
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.rememberCoroutineScope
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.ui.components.LongPressCardContextMenu
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
override fun Content() {
val context = LocalContext.current
val sheetState = rememberModalBottomSheetState()
val coroutineScope = rememberCoroutineScope()
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismiss
) {
LongPressCardContextMenu(
lksStudent = lksStudent,
onCopyName = {
context.copyToClipboard(lksStudent.studentFullName.toString())
},
onCopyGroup = {
context.copyToClipboard(lksStudent.group.toString())
},
onGoToVk = {
onCopyName = context::copyToClipboard,
onCopyGroup = context::copyToClipboard,
onGoToVk = {
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"
context.startActivity(browserIntent)
val searchString = lksStudent
.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"
composeBom = "2024.04.01"
jetbrainsKotlinJvm = "2.0.0"
appcompat = "1.7.0"
material = "1.12.0"
voyagerVersion = "1.1.0-beta02"
composeNavigation = "2.8.0-alpha08"
room_version = "2.6.1"
serialization = "1.6.3"
[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" }
hilt-android = { module = "com.google.dagger:hilt-android", 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-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
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" }
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "composeNavigation" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization"}
[plugins]
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" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }