diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b2f4c70..6dcfe8a 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/MainActivity.kt b/app/src/main/java/ru/megboyzz/lks/profile/MainActivity.kt index 032121d..581bc44 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/MainActivity.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/MainActivity.kt @@ -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() } } } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/data/Core.kt b/app/src/main/java/ru/megboyzz/lks/profile/data/Core.kt index c6c6a50..5e6e394 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/data/Core.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/data/Core.kt @@ -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 @@ -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() ) diff --git a/app/src/main/java/ru/megboyzz/lks/profile/data/repositories/LksRepositoryImpl.kt b/app/src/main/java/ru/megboyzz/lks/profile/data/repositories/LksRepositoryImpl.kt index ed49901..0f64fe0 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/data/repositories/LksRepositoryImpl.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/data/repositories/LksRepositoryImpl.kt @@ -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, pageNumber: Int): Result { + override suspend fun getPage(lksRequest: LksRequest, cookie: Map, pageNumber: Int): Result { 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) diff --git a/app/src/main/java/ru/megboyzz/lks/profile/domain/entities/LksResponse.kt b/app/src/main/java/ru/megboyzz/lks/profile/domain/entities/LksResponse.kt index 1b95b78..7336ea4 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/domain/entities/LksResponse.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/domain/entities/LksResponse.kt @@ -1,6 +1,6 @@ package ru.megboyzz.lks.profile.domain.entities -data class LksStudent( +data class LksResponse( val students: List = emptyList(), val availablePages: Int = 0 ) \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/domain/repositories/LksRepository.kt b/app/src/main/java/ru/megboyzz/lks/profile/domain/repositories/LksRepository.kt index 604f851..ac3e22c 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/domain/repositories/LksRepository.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/domain/repositories/LksRepository.kt @@ -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): Boolean - suspend fun getPage(lksRequest: LksRequest, cookie: Map, pageNumber: Int): Result + suspend fun getPage(lksRequest: LksRequest, cookie: Map, pageNumber: Int): Result fun saveCookie(cookie: Map) diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/HiltModule.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/HiltModule.kt deleted file mode 100755 index 5d08af2..0000000 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/HiltModule.kt +++ /dev/null @@ -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 - -} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/Util.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/Util.kt index 8f2b7fc..34e8bc3 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/Util.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/Util.kt @@ -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 @@ -51,4 +53,12 @@ fun Context.copyToClipboard(content: String) { clipboardManager.setPrimaryClip(clip) 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) } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/DropdownMenu.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/DropdownMenu.kt new file mode 100644 index 0000000..7e7becc --- /dev/null +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/DropdownMenu.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigation.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigation.kt new file mode 100644 index 0000000..606cb36 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigation.kt @@ -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() + + val state by viewModel.state.collectAsState() + + if(state is SignInViewModel.SignInState.Success) { + navHostController.navigate(SearchScreen) + } + + NavHost( + navController = navHostController, + startDestination = SignInScreen + ) { + composable { + SignInScreenContent() + } + composable { + SearchScreenContent() + } + } +} + +@Serializable +object SearchScreen + +@Serializable +object SignInScreen \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigator.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigator.kt deleted file mode 100755 index bf73cdd..0000000 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigator.kt +++ /dev/null @@ -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) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchBottomComponents.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchBottomComponents.kt index 6cc6832..e2c6bae 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchBottomComponents.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchBottomComponents.kt @@ -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("Поиск") diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchComponents.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchComponents.kt index 4eebfbd..1e69188 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchComponents.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/components/SearchComponents.kt @@ -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 + } + } + } ) } }, diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SearchScreenModel.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SearchViewModel.kt similarity index 76% rename from app/src/main/java/ru/megboyzz/lks/profile/ui/model/SearchScreenModel.kt rename to app/src/main/java/ru/megboyzz/lks/profile/ui/model/SearchViewModel.kt index 8039155..a15f470 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SearchScreenModel.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SearchViewModel.kt @@ -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 ) diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SignInScreenModel.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SignInViewModel.kt similarity index 65% rename from app/src/main/java/ru/megboyzz/lks/profile/ui/model/SignInScreenModel.kt rename to app/src/main/java/ru/megboyzz/lks/profile/ui/model/SignInViewModel.kt index 39b88f0..9365184 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SignInScreenModel.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/model/SignInViewModel.kt @@ -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 -) : ScreenModel { +) : ViewModel() { private val _username = MutableStateFlow("") val username = _username.asStateFlow() @@ -51,28 +44,35 @@ class SignInScreenModel @AssistedInject constructor( private var cookie: Map = 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 - } - } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/provider/MainActivityCompositionProvider.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/provider/MainActivityCompositionProvider.kt new file mode 100644 index 0000000..7777b6e --- /dev/null +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/provider/MainActivityCompositionProvider.kt @@ -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 { + error("No MainActivity provided") +} + +@Composable +fun ActivityProvider( + content: @Composable () -> Unit +) { + val mainActivity = LocalContext.current as MainActivity + CompositionLocalProvider(LocalMainActivity provides mainActivity) { + content() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SearchScreen.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SearchScreen.kt index abb90ed..2f03144 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SearchScreen.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SearchScreen.kt @@ -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() - val screenModel = getScreenModel("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(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 } + } diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SignInScreen.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SignInScreen.kt deleted file mode 100755 index 886edcb..0000000 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SignInScreen.kt +++ /dev/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 { 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 -> {} - } - } - - } - } - - } -} diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SignInScreenContent.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SignInScreenContent.kt new file mode 100755 index 0000000..5dd985c --- /dev/null +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/SignInScreenContent.kt @@ -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() + + 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 -> {} + } + } + } + + } + } + +} diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/LongPressMenu.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/LongPressMenu.kt index 0a5ffda..b85e54c 100755 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/LongPressMenu.kt +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/LongPressMenu.kt @@ -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) } ) - - } } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/SearchBottomScreen.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/SearchBottomScreen.kt deleted file mode 100755 index ab3bae2..0000000 --- a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/SearchBottomScreen.kt +++ /dev/null @@ -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("my_tag") - val screenModel = rememberScreenModel { searchScreenModel } - - SearchContextMenu { lksRequest -> screenModel.newSearch(lksRequest) } - - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/SearchBottomSheet.kt b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/SearchBottomSheet.kt new file mode 100755 index 0000000..b969d04 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/lks/profile/ui/screens/sub/SearchBottomSheet.kt @@ -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() + + val sheetState = rememberModalBottomSheetState() + val coroutineScope = rememberCoroutineScope() + + ModalBottomSheet( + sheetState = sheetState, + onDismissRequest = onDismiss + ) { + SearchContextMenu { lksRequest -> + viewModel.newSearch(lksRequest) + coroutineScope.launch { sheetState.hide() } + } + } + +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 986e6f3..1066eee 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }