Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9bdb03d5e | ||
|
|
50f4102b93 | ||
|
|
62b7658fb9 | ||
|
|
193ad2d07a | ||
|
|
7d313e1c64 | ||
|
|
11cafec847 | ||
|
|
589464e69c | ||
|
|
e8ed594b9f | ||
|
|
e32b4feeef | ||
|
|
7d3d3517c5 |
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -5,6 +5,9 @@
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="DropdownSearchPrev">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
@@ -4,14 +4,13 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/app/data" />
|
||||
<option value="$PROJECT_DIR$/app/domain" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
@@ -45,6 +49,10 @@
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,76 @@
|
||||
kotlin version: 2.0.0
|
||||
error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /home/megboyzz/workdir/Projects/AndroidStudioProjects/LKSPROFILE/app/src/main/java/ru/megboyzz/lks/profile/ui/components/MainNavigation.kt:34:1: java.lang.IllegalStateException: Zero or multiple overrides found for serialize in FirRegularClassSymbol ru/megboyzz/lks/profile/ui/components/SearchScreen
|
||||
at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:56)
|
||||
at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:180)
|
||||
at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46)
|
||||
at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.runPlatformCheckers(convertToIr.kt:62)
|
||||
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:316)
|
||||
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:116)
|
||||
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:155)
|
||||
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:50)
|
||||
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:104)
|
||||
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:48)
|
||||
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:453)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:62)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:676)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1661)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||
at java.base/java.lang.Thread.run(Unknown Source)
|
||||
Caused by: java.lang.IllegalStateException: Zero or multiple overrides found for serialize in FirRegularClassSymbol ru/megboyzz/lks/profile/ui/components/SearchScreen
|
||||
at org.jetbrains.kotlinx.serialization.compiler.fir.SerializationFirResolveExtension.getFromSupertype(SerializationFirResolveExtension.kt:146)
|
||||
at org.jetbrains.kotlinx.serialization.compiler.fir.SerializationFirResolveExtension.generateFunctions(SerializationFirResolveExtension.kt:177)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.FirGeneratedMemberDeclarationsStorage$CallableStorage.generateMemberFunctions(FirGeneratedScopes.kt:198)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.FirGeneratedMemberDeclarationsStorage$CallableStorage.access$generateMemberFunctions(FirGeneratedScopes.kt:175)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.FirGeneratedMemberDeclarationsStorage$CallableStorage$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:71)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.FirGeneratedMemberDeclarationsStorage$CallableStorage$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:69)
|
||||
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCache.getValue(FirThreadUnsafeCachesFactory.kt:40)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.FirGeneratedClassDeclaredMemberScope.processFunctionsByName(FirGeneratedScopes.kt:281)
|
||||
at org.jetbrains.kotlin.fir.scopes.FirCompositeScope.processFunctionsByName(FirCompositeScope.kt:55)
|
||||
at org.jetbrains.kotlin.fir.scopes.FirNameAwareCompositeScope.processFunctionsByName(FirCompositeScope.kt:82)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.AbstractFirUseSiteMemberScope.collectDeclaredFunctions(AbstractFirUseSiteMemberScope.kt:78)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.AbstractFirUseSiteMemberScope.collectFunctions(AbstractFirUseSiteMemberScope.kt:72)
|
||||
at org.jetbrains.kotlin.fir.scopes.impl.AbstractFirUseSiteMemberScope.processFunctionsByName(AbstractFirUseSiteMemberScope.kt:63)
|
||||
at org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScopeKt.processAllFunctions(FirContainingNamesAwareScope.kt:19)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMultipleDefaultsInheritedFromSupertypesChecker.check(FirMultipleDefaultsInheritedFromSupertypesChecker.kt:44)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMultipleDefaultsInheritedFromSupertypesChecker$Regular.check(FirMultipleDefaultsInheritedFromSupertypesChecker.kt:32)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMultipleDefaultsInheritedFromSupertypesChecker$Regular.check(FirMultipleDefaultsInheritedFromSupertypesChecker.kt:29)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.DeclarationCheckersDiagnosticComponent.check(DeclarationCheckersDiagnosticComponent.kt:133)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.DeclarationCheckersDiagnosticComponent.visitRegularClass(DeclarationCheckersDiagnosticComponent.kt:57)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.DeclarationCheckersDiagnosticComponent.visitRegularClass(DeclarationCheckersDiagnosticComponent.kt:19)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.access$checkElement(AbstractDiagnosticCollectorVisitor.kt:29)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:1349)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:86)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:91)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:29)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:56)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:37)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1124)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:29)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36)
|
||||
at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34)
|
||||
... 32 more
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
kotlin("kapt")
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
id("com.google.devtools.ksp")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "ru.megboyzz.lks.profile"
|
||||
compileSdk = 34
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "ru.megboyzz.lks.profile"
|
||||
minSdk = 24
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 105
|
||||
versionName = "1.0.5"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -51,6 +52,7 @@ dependencies {
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.foundation.layout.android)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
@@ -59,19 +61,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)
|
||||
kapt(libs.hilt.android.compiler)
|
||||
|
||||
|
||||
implementation(libs.voyager.navigator)
|
||||
|
||||
implementation(libs.voyager.screenmodel)
|
||||
|
||||
implementation(libs.voyager.bottom.sheet.navigator)
|
||||
|
||||
implementation(libs.voyager.transitions)
|
||||
|
||||
implementation(libs.voyager.hilt)
|
||||
ksp(libs.hilt.android.compiler)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
|
||||
implementation(libs.jsoup)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,45 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "ru.megboyzz.lks.data"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
implementation(libs.jsoup)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,24 +0,0 @@
|
||||
package ru.megboyzz.lks.data
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("ru.megboyzz.lks.data.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -1,17 +0,0 @@
|
||||
package ru.megboyzz.lks.data
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,15 +0,0 @@
|
||||
plugins {
|
||||
id("java-library")
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm)
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import org.junit.Test
|
||||
import ru.megboyzz.lks.domain.entities.Student
|
||||
|
||||
class EntityTest {
|
||||
|
||||
@Test
|
||||
fun testStudent() {
|
||||
|
||||
val student = Student(
|
||||
surname = "Киц",
|
||||
name = "Михаил",
|
||||
patronymic = "Андреевич"
|
||||
)
|
||||
|
||||
assert(student.toString() == "Киц Михаил Андреевич")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStudent2() {
|
||||
val student = Student(
|
||||
surname = null,
|
||||
name = "Михаил",
|
||||
patronymic = "Андреевич"
|
||||
)
|
||||
|
||||
assert(student.toString() == "Михаил Андреевич")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStudent3() {
|
||||
|
||||
val student = Student(
|
||||
surname = "Киц",
|
||||
name = null,
|
||||
patronymic = "Андреевич"
|
||||
)
|
||||
println(student)
|
||||
assert(student.toString() == "Киц Андреевич")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStudent4() {
|
||||
|
||||
val student = Student(
|
||||
surname = "Киц",
|
||||
name = "Михаил",
|
||||
patronymic = null
|
||||
)
|
||||
|
||||
assert(student.toString() == "Киц Михаил")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStudent5() {
|
||||
|
||||
val student = Student(
|
||||
surname = null,
|
||||
name = null,
|
||||
patronymic = null
|
||||
)
|
||||
|
||||
assert(student.toString().isEmpty())
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"ИДО": "Институт дополнительного образования",
|
||||
"ИМО": "Институт международного образования",
|
||||
"ИДП": "Институт довузовской подготовки",
|
||||
"ИТХТ": "Институт тонких химических технологий имени М.В. Ломоносова",
|
||||
"ИИИ": "Институт искусственного интеллекта",
|
||||
"БК250": "Базовая кафедра № 250 - математического обеспечения информационных систем",
|
||||
"ИПТ": "Институт перспективных технологий и индустриального программирования",
|
||||
"БК235": "Базовая кафедра № 235 - цифровых устройств и систем защиты информации",
|
||||
"КСТУ": "Кафедра современных технологий управления",
|
||||
"БК536": "Базовая кафедра № 536 - программного обеспечения систем РЭА",
|
||||
"ИКДТ": "Институт кибербезопасности и цифровых технологий",
|
||||
"УПО": "Управление платных образовательных услуг",
|
||||
"КВМ3": "Кафедра высшей математики-3",
|
||||
"КФТМ": "Кафедра физики и технической механики",
|
||||
"УОРТ": "Учебный отдел Института радиотехнических и телекоммуникационных систем",
|
||||
"БК333": "Базовая кафедра № 333 имени профессора Лобанова Б.С. - систем радиоэлектронной борьбы",
|
||||
"БК334": "Базовая кафедра № 334 - дальней радиосвязи",
|
||||
"БК338": "Базовая кафедра № 338 - космической радиоэлектроники",
|
||||
"КИЭТ": "Кафедра инженерной экологии техносферы",
|
||||
"КИЯ1": "Кафедра иностранных языков 1",
|
||||
"КРЭСК": "Кафедра радиоэлектронных систем и комплексов",
|
||||
"КБПФ": "Кафедра биотехнологии и промышленной фармации",
|
||||
"КДГП": "Кафедра документоведения, истории государства и права",
|
||||
"БК340": "Базовая кафедра № 340 - информационных космических систем",
|
||||
"БК343": "Базовая кафедра № 343 - моделирования радиофизических процессов",
|
||||
"ЦДО": "Центр дистанционного обучения",
|
||||
"КФТМ2024": "Кафедра физики и технической механики (от 19.06.2024 № 26-ст)",
|
||||
"КИТГУ": "Кафедра информационных технологий в государственном управлении",
|
||||
"КМОСИТ": "Кафедра математического обеспечения и стандартизации информационных технологий",
|
||||
"КПК": "Колледж программирования и кибербезопасности",
|
||||
"КВТ": "Кафедра вычислительной техники",
|
||||
"ИИТ": "Институт информационных технологий",
|
||||
"КИГ": "Кафедра инженерной графики",
|
||||
"КЭТ": "Кафедра экономической теории",
|
||||
"КОИ": "Кафедра общей информатики",
|
||||
"ККПРЭС": "Кафедра конструирования и производства радиоэлектронных средств",
|
||||
"КСИ": "Кафедра системной инженерии",
|
||||
"Аспирантура": "Аспирантура",
|
||||
"ПОДГ": "Подготовительное отделение для иностранных граждан",
|
||||
"Ректорат": "Ректорат",
|
||||
"ИРИ": "Институт радиоэлектроники и информатики",
|
||||
"ЮО": "Юридический отдел",
|
||||
"УМУ": "Учебно-методическое управление",
|
||||
"УК": "Управление кадров",
|
||||
"ФФГБОУВОС": "Филиал федерального государственного бюджетного образовательного учреждения высшего образования «МИРЭА - Российский технологический университет» в г. Ставрополе",
|
||||
"УГОЧС": "Управление гражданской обороны и чрезвычайных ситуаций",
|
||||
"КН": "Кафедра наноэлектроники",
|
||||
"Бухгалтерия": "Бухгалтерия",
|
||||
"УОИМО": "Учебный отдел института международного образования",
|
||||
"СПВ86": "Сектор «Проспект Вернадского, 86»",
|
||||
"СИАО": "Студенческий информационно-архивный отдел",
|
||||
"УЭФ": "Управление экономики и финансов",
|
||||
"УМС": "Управление международных связей",
|
||||
"УВСР": "Управление по воспитательной и социальной работе",
|
||||
"ПР": "Первый отдел",
|
||||
"СГИ": "Служба главного инженера",
|
||||
"НТБ": "Научно-техническая библиотека",
|
||||
"УИ": "Управление информатизации",
|
||||
"КМС": "Кафедра метрологии и стандартизации",
|
||||
"ССт20": "Сектор «Стромынка, 20»",
|
||||
"ИАО": "Информационно-аналитический отдел",
|
||||
"ОСПР": "Отдел судебно-претензионной работы",
|
||||
"БК140": "Базовая кафедра № 140 - фотоники",
|
||||
"УД": "Управление делами",
|
||||
"УМП": "Управление мобилизационной подготовки",
|
||||
"БК130": "Базовая кафедра № 130 - твердотельной электроники",
|
||||
"НОЦТЦ": "Научно-образовательный центр «Технологический центр»",
|
||||
"БК131": "Базовая кафедра № 131 - оптико-физических измерений",
|
||||
"БК132": "Базовая кафедра № 132 - лазерной техники",
|
||||
"БК136": "Базовая кафедра № 136 - СВЧ приборов и устройств",
|
||||
"ФМШ": "Физико-математическая школа",
|
||||
"КПВВ": "Курсы по подготовке к поступлению в вуз",
|
||||
"ОМКО": "Отдел менеджмента качества образования",
|
||||
"ОТСО": "Отдел технических средств обучения",
|
||||
"ОЗЗИ": "Отдел обеспечения защиты информации",
|
||||
"ОТК": "Отдел технического контроля",
|
||||
"Автобаза": "Автобаза",
|
||||
"Академия ТВ": "Академия ТВ",
|
||||
"ГИВЦ": "Главный информационно-вычислительный центр",
|
||||
"УНИР": "Управление научно-исследовательских разработок",
|
||||
"КДЦ": "Культурно-досуговый центр",
|
||||
"МНТЦИС": "Межотраслевой научно-технический центр интеллектуальных технологий обработки информации и управления «Интеллектуальные системы»",
|
||||
"НТЦН": "Научно-технологический центр «Наука»",
|
||||
"НИЦРСВТ": "Научно-исследовательский центр разработки специальной вычислительной техники",
|
||||
"НИЦИПМ": "Научно-исследовательский центр «Инновационные полимерные материалы и изделия»",
|
||||
"ОИТОСБ": "Отдел инженерно-технического обеспечения средств безопасности",
|
||||
"ЦПИС": "Центр проектирования интегральных схем, устройств наноэлектроники и микросистем",
|
||||
"ЦЯП": "Центр языковой подготовки",
|
||||
"ИМПМО": "Институт молодежной политики и международных отношений",
|
||||
"НИИМФП": "Научно-исследовательский институт медицинской и фармацевтической промышленности",
|
||||
"ИПЦСНК": "Инженерно-научный производственный центр «Средства неразрушающего контроля» МИРЭА",
|
||||
"УЦВ": "Учебный центр подготовки водителей",
|
||||
"ИТП": "Институт технологического предпринимательства",
|
||||
"СУЛСДФ": "Специализированная учебно-научная лаборатория сверхбыстрой динамики ферроиков",
|
||||
"УНИИСУ": "Учебно-научный институт информатики и систем управления",
|
||||
"УМЦИТ": "Учебно-методический центр интерактивных технологий",
|
||||
"ОКБМИРЭА": "Особое конструкторское бюро МИРЭА",
|
||||
"ИУС": "Институт учета и статистики",
|
||||
"НИЦПНТП": "Научно-инжиниринговый центр «Перспективные научно-технические проекты»",
|
||||
"Наноцентр МИРЭА": "Наноцентр МИРЭА",
|
||||
"НТЦИРФ": "Научно-технологический центр «Интегральная радиофотоника»",
|
||||
"РИО": "Редакционно-издательский отдел",
|
||||
"ПЛМА": "Проблемная лаборатория молекулярной активации"
|
||||
}
|
||||
@@ -4,25 +4,19 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
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() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
Navigator(SignInScreen())
|
||||
MainNavHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package ru.megboyzz.lks.profile.data
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Connection
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
|
||||
private typealias Cookies = Map<String, String>
|
||||
|
||||
@@ -17,6 +17,7 @@ private fun Connection.dataMap(vararg pairs: Pair<String, String>) = this.data(m
|
||||
suspend fun signInLks(username: String, password: String) = withContext(Dispatchers.IO) {
|
||||
val cookies = Jsoup
|
||||
.connect("https://lk.mirea.ru/auth.php?login=yes")
|
||||
//.userAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
|
||||
.dataMap(
|
||||
"AUTH_FORM" to "Y",
|
||||
"TYPE" to "AUTH",
|
||||
@@ -35,33 +36,39 @@ suspend fun signInLks(username: String, password: String) = withContext(Dispatch
|
||||
return@withContext mapOf<String, String>()
|
||||
}
|
||||
|
||||
suspend fun fakeConnect(cookies: Cookies) = withContext(Dispatchers.IO) {
|
||||
fun fakeConnect(cookies: Cookies) {
|
||||
Jsoup
|
||||
.connect("https://lk.mirea.ru/")
|
||||
//.userAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
|
||||
.cookies(cookies)
|
||||
.referrer("https://lk.mirea.ru/")
|
||||
.execute()
|
||||
}
|
||||
|
||||
suspend fun getPage(payload: LksRequest, cookies: Cookies, page: Int) = withContext(Dispatchers.IO) {
|
||||
fun getProfilePage(payload: LksRequest, cookies: Cookies, page: Int): Document {
|
||||
|
||||
val sesid = cookies["BITRIX_SM_UIDH"]
|
||||
|
||||
fakeConnect(cookies)
|
||||
Jsoup
|
||||
|
||||
val conn = Jsoup
|
||||
.connect("https://lk.mirea.ru/staff/students/?${payload.getSubmitUrlPath(sesid ?: "")}&PAGEN_1=$page")
|
||||
.cookies(cookies)
|
||||
.referrer("https://lk.mirea.ru/")
|
||||
.execute().parse()
|
||||
.execute()
|
||||
|
||||
val doc = conn.parse()
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
fun Document.getTotalPages() = kotlin.runCatching {
|
||||
this.select("body > div.container > div.bx-pagination > div > ul > li:nth-child(6) > a > span").text().toInt()
|
||||
}.getOrNull() ?: 0
|
||||
|
||||
fun Document.findStudentsInPage(): LksResponse {
|
||||
fun findStudentsInPage(document: Document): LksResponse {
|
||||
|
||||
val table = this.select("body > div.container > table > tbody") ?: return LksResponse()
|
||||
val table = document.select("body > div.container > table > tbody") ?: return LksResponse()
|
||||
|
||||
val students = table.select("tr").map {
|
||||
|
||||
@@ -88,7 +95,7 @@ fun Document.findStudentsInPage(): LksResponse {
|
||||
|
||||
return LksResponse(
|
||||
students = students,
|
||||
availablePages = this.getTotalPages()
|
||||
availablePages = document.getTotalPages()
|
||||
)
|
||||
|
||||
}
|
||||
@@ -31,8 +31,8 @@ class DataModule {
|
||||
fun providesDefaultCookies() = mapOf(
|
||||
"PHPSESSID" to "deleted",
|
||||
"BITRIX_SM_SOUND_LOGIN_PLAYED" to "Y",
|
||||
"BITRIX_SM_UIDH" to "00000000000000000000000000000000",
|
||||
"BITRIX_SM_UIDL" to "ivachev.a.a%40edu.mirea.ru"
|
||||
"BITRIX_SM_UIDH" to "22f459c5c83cff9cd62c99693b00c361",
|
||||
"BITRIX_SM_UIDL" to "kits.m.a%40edu.mirea.ru"
|
||||
)
|
||||
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
package ru.megboyzz.lks.profile.data.repositories
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import ru.megboyzz.lks.profile.data.findStudentsInPage
|
||||
import ru.megboyzz.lks.profile.data.getProfilePage
|
||||
import ru.megboyzz.lks.profile.data.signInLks
|
||||
import ru.megboyzz.lks.profile.domain.CookiesPrefs
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class LksRepositoryImpl @Inject constructor(
|
||||
@CookiesPrefs
|
||||
private val sharedPreferences: SharedPreferences
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
|
||||
) : LksRepository {
|
||||
|
||||
private val cookieNamesKey = "cookie_names"
|
||||
@@ -23,7 +27,16 @@ class LksRepositoryImpl @Inject constructor(
|
||||
): Map<String, String> = signInLks(username, password)
|
||||
|
||||
override suspend fun cookiesIsValid(cookie: Map<String, String>): Boolean {
|
||||
return true
|
||||
|
||||
if(cookie.isEmpty()) return false
|
||||
|
||||
val document = getProfilePage(LksRequest(), cookie, 1)
|
||||
|
||||
val pages =
|
||||
document.select("body > div.container > div.bx-pagination > div > ul > li:nth-child(6) > a > span")
|
||||
.text()
|
||||
|
||||
return pages.isNotEmpty()
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +60,13 @@ class LksRepositoryImpl @Inject constructor(
|
||||
|
||||
override suspend fun getPage(lksRequest: LksRequest, cookie: Map<String, String>, pageNumber: Int): Result<LksResponse> {
|
||||
|
||||
val document = ru.megboyzz.lks.profile.data.getPage(lksRequest, cookie, pageNumber)
|
||||
Log.i("TEXT", "Its begin doc!")
|
||||
val document = getProfilePage(lksRequest, cookie, pageNumber)
|
||||
Log.i("TEXT", "Its begin end!")
|
||||
|
||||
val page = document.findStudentsInPage()
|
||||
Log.i("TEXT", "Its begin!")
|
||||
val page = findStudentsInPage(document)
|
||||
Log.i("TEXT", "Its end!")
|
||||
|
||||
return if(page == LksResponse())
|
||||
Result.failure(Exception())
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package ru.megboyzz.lks.profile.data.repositories
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
import ru.megboyzz.lks.profile.domain.repositories.StudentRepository
|
||||
|
||||
class StudentRepositoryImpl : StudentRepository {
|
||||
override fun getAllFavStudents(): List<LksResponse> {
|
||||
override fun getAllFavStudents(): List<LksStudent> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun addStudent(lksResponse: LksResponse) {
|
||||
override fun likeStudent(lksResponse: LksStudent) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun deleteFormFav(lksResponse: LksResponse) {
|
||||
override fun deleteFormFav(lksResponse: LksStudent) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,5 @@ data class LksRequest(
|
||||
* Метод для получения пути после https://lk.mirea.ru/staff/students? ...
|
||||
*/
|
||||
fun getSubmitUrlPath(sesid: String) =
|
||||
"sessid=$sesid&findname=${student}&findgroup=${group ?: ""}&finddepartment=${department ?: ""}&submit-filter=Найти"
|
||||
"sessid=$sesid&findname=${student ?: ""}&findgroup=${group ?: ""}&finddepartment=${department ?: ""}&submit-filter=Найти"
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package ru.megboyzz.lks.profile.domain.repositories
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
import java.io.Serializable
|
||||
|
||||
interface LksRepository {
|
||||
interface LksRepository: Serializable {
|
||||
|
||||
suspend fun signInAndGetCookies(username: String, password: String): Map<String, String>
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package ru.megboyzz.lks.profile.domain.repositories
|
||||
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
|
||||
interface StudentRepository {
|
||||
|
||||
fun getAllFavStudents(): List<LksResponse>
|
||||
fun getAllFavStudents(): List<LksStudent>
|
||||
|
||||
fun addStudent(lksResponse: LksResponse)
|
||||
fun likeStudent(lksResponse: LksStudent)
|
||||
|
||||
fun deleteFormFav(lksResponse: LksResponse)
|
||||
fun deleteFormFav(lksResponse: LksStudent)
|
||||
|
||||
}
|
||||
@@ -1,32 +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.SignInScreenModel
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
interface HiltModule {
|
||||
|
||||
/*@Binds
|
||||
@IntoMap
|
||||
@ScreenModelKey(SignInScreenModel::class)
|
||||
abstract fun bindSignInScreenModel(
|
||||
signInScreenModel: SignInScreenModel
|
||||
) : ScreenModel*/
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ScreenModelFactoryKey(SignInScreenModel.Factory::class)
|
||||
fun bindSignInScreenModelFactory(
|
||||
factory: SignInScreenModel.Factory
|
||||
): ScreenModelFactory
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Modifier.clickableWithNormalRipple(
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
) = this.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = true),
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Modifier.clickableWithNoEffect(
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
) = this.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Int.asPainter() = painterResource(this)
|
||||
|
||||
fun Context.copyToClipboard(content: String) {
|
||||
val clipboardManager = this.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
val clip = ClipData.newPlainText("label", content)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun Context.getDepartmentDict() : Map<String, String> {
|
||||
val jsonString = assets.open("dict.json").bufferedReader().use { it.readText() }
|
||||
return Json.decodeFromString(jsonString)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package ru.megboyzz.lks.profile.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DropdownSelectableSearch(
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
|
||||
var selectedOptionText by remember { mutableStateOf(options[0]) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded != expanded }
|
||||
) {
|
||||
TextField(
|
||||
readOnly = true,
|
||||
modifier = Modifier.clickable {
|
||||
expanded != expanded
|
||||
},
|
||||
value = selectedOptionText,
|
||||
onValueChange = { },
|
||||
label = { Text("Label") },
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded
|
||||
)
|
||||
},
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = {}
|
||||
) {
|
||||
options.forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = selectionOption) },
|
||||
onClick = {
|
||||
selectedOptionText = selectionOption
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DropdownSearchPrev() {
|
||||
Box(Modifier.padding(top =100.dp)) {
|
||||
DropdownSelectableSearch()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,358 @@
|
||||
package ru.megboyzz.lks.profile.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.outlined.AccountCircle
|
||||
import androidx.compose.material.icons.outlined.Person
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
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 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
|
||||
import ru.megboyzz.lks.profile.ui.clickableWithNormalRipple
|
||||
|
||||
@Composable
|
||||
fun ContextMenuContainer(
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp, 4.dp)
|
||||
.clip(RoundedCornerShape(80))
|
||||
.background(MaterialTheme.colorScheme.onBackground)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
content = content
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ContextMenuContainerPrev() {
|
||||
|
||||
ContextMenuContainer {
|
||||
|
||||
Text("Hello!")
|
||||
Text("Hello!")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
suggestions: List<String>? = null,
|
||||
label: String? = null
|
||||
) {
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
var dropDownWidth by remember { mutableIntStateOf(0) }
|
||||
|
||||
val icon = if (expanded)
|
||||
Icons.Filled.KeyboardArrowUp
|
||||
else
|
||||
Icons.Filled.KeyboardArrowDown
|
||||
|
||||
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onSizeChanged { dropDownWidth = it.width },
|
||||
label = { Text(label ?: "Label") },
|
||||
trailingIcon = {
|
||||
if(suggestions != null)
|
||||
Icon(
|
||||
icon,
|
||||
"contentDescription",
|
||||
Modifier.clickableWithNormalRipple{ expanded = !expanded }
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier
|
||||
.width(with(LocalDensity.current){dropDownWidth.toDp()})
|
||||
) {
|
||||
suggestions?.forEach { label ->
|
||||
DropdownMenuItem(
|
||||
onClick = { onValueChange(label) },
|
||||
text = { Text(text = label) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SearchTextFieldPrev() {
|
||||
|
||||
val suggestions = listOf("Item1","Item2","Item3")
|
||||
var value by remember { mutableStateOf("") }
|
||||
|
||||
SearchTextField(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
suggestions = suggestions
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchContextMenu(
|
||||
initialState: LksRequest,
|
||||
onSearch: (LksRequest) -> Unit
|
||||
) {
|
||||
var name by remember { mutableStateOf(initialState.student?.name ?: "") }
|
||||
var surname by remember { mutableStateOf(initialState.student?.patronymic ?: "") }
|
||||
var family by remember { mutableStateOf(initialState.student?.surname ?: "") }
|
||||
|
||||
var group by remember { mutableStateOf(initialState.group ?: "") }
|
||||
var departamnet by remember { mutableStateOf("") }
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = 24.dp)
|
||||
) {
|
||||
|
||||
SearchTextField(
|
||||
value = family,
|
||||
onValueChange = {family = it},
|
||||
label = "Фамилия"
|
||||
)
|
||||
|
||||
SearchTextField(
|
||||
value = name,
|
||||
onValueChange = {name = it},
|
||||
label = "Имя"
|
||||
)
|
||||
|
||||
SearchTextField(
|
||||
value = surname,
|
||||
onValueChange = {surname = it},
|
||||
label = "Отчество"
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(1.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.onBackground)
|
||||
|
||||
SearchTextField(
|
||||
value = group,
|
||||
onValueChange = {group = it},
|
||||
label = "Учебная группа"
|
||||
)
|
||||
|
||||
SearchTextField(
|
||||
value = departamnet,
|
||||
onValueChange = { departamnet = it },
|
||||
label = "Подразделение"
|
||||
)
|
||||
|
||||
Button(
|
||||
shape = OutlinedTextFieldDefaults.shape,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
val lksRequest = LksRequest(
|
||||
student = Student(
|
||||
name = name.ifEmpty { null },
|
||||
surname = family.ifEmpty { null },
|
||||
patronymic = surname.ifEmpty { null },
|
||||
),
|
||||
group = group.ifEmpty { null },
|
||||
department = departamnet.ifEmpty { null }
|
||||
)
|
||||
onSearch(lksRequest)
|
||||
//bottomSheetNavigator?.hide()
|
||||
}
|
||||
) {
|
||||
Text("Поиск")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SearchContextMenuPrev() {
|
||||
|
||||
SearchContextMenu(
|
||||
initialState = LksRequest()
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContextMenuButton(
|
||||
icon: Painter,
|
||||
label: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(0.dp)
|
||||
.clickableWithNormalRipple(onClick = onClick)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = null,
|
||||
tint = Color.Unspecified,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
Text(text = label)
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ContextMenuButtonPrev() {
|
||||
|
||||
ContextMenuButton(
|
||||
icon = R.drawable.vk_ic.asPainter(),
|
||||
label = "Переидти в VK",
|
||||
onClick = {}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LongPressCardContextMenu(
|
||||
lksStudent: LksStudent,
|
||||
onGoToVk: () -> Unit,
|
||||
onCopyName: (String) -> Unit,
|
||||
onCopyGroup: (String) -> Unit
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
|
||||
Text(
|
||||
"${lksStudent.studentFullName} ${lksStudent.group}"
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.onBackground)
|
||||
ContextMenuButton(
|
||||
icon = R.drawable.vk_ic.asPainter(),
|
||||
label = "Переидти в VK",
|
||||
onClick = onGoToVk
|
||||
)
|
||||
ContextMenuButton(
|
||||
icon = rememberVectorPainter(Icons.Outlined.AccountCircle),
|
||||
label = "Скопировать имя пользователя",
|
||||
onClick = { onCopyName.invoke(lksStudent.studentFullName ?: "") }
|
||||
)
|
||||
ContextMenuButton(
|
||||
icon = rememberVectorPainter(Icons.Outlined.Person),
|
||||
label = "Скопировать группу",
|
||||
onClick = { onCopyGroup.invoke(lksStudent.group ?: "") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun LongPressCardContextMenuPrev() {
|
||||
|
||||
val lksStudent = LksStudent(
|
||||
studentFullName = "Киц Михаил Андреевич",
|
||||
group = "ИКБО-03-20"
|
||||
)
|
||||
|
||||
LongPressCardContextMenu(
|
||||
lksStudent = lksStudent,
|
||||
onGoToVk = {},
|
||||
onCopyName = {},
|
||||
onCopyGroup = {}
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,254 @@
|
||||
package ru.megboyzz.lks.profile.ui.components
|
||||
|
||||
import android.provider.ContactsContract.Profile
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.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.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
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
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
import ru.megboyzz.lks.profile.ui.clickableWithNoEffect
|
||||
import ru.megboyzz.lks.profile.ui.clickableWithNormalRipple
|
||||
import ru.megboyzz.lks.profile.ui.theme.MainTheme
|
||||
|
||||
@Composable
|
||||
fun ProfileCard(lksStudent: LksStudent) {
|
||||
fun ProfileCard(
|
||||
lksStudent: LksStudent,
|
||||
onLongPress: () -> Unit
|
||||
) {
|
||||
|
||||
var toggled by remember { mutableStateOf(false) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.border(
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.defaultMinSize(minWidth = 240.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickableWithNormalRipple(
|
||||
onClick = { toggled = !toggled },
|
||||
onLongClick = onLongPress,
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = lksStudent.studentFullName ?: "<Нет имени>"
|
||||
)
|
||||
Text(
|
||||
text = lksStudent.group ?: "<Нет группы>"
|
||||
)
|
||||
AnimatedVisibility(toggled) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = lksStudent.department ?: "<Нет подразделения>"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScaffold(
|
||||
onFloatingButtonClick: () -> Unit,
|
||||
onTopClick: () -> Unit = {},
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
) {
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Surface(
|
||||
shadowElevation = 8.dp,
|
||||
modifier = Modifier.clickableWithNoEffect(onTopClick)
|
||||
) {
|
||||
TopAppBar(
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onFloatingButtonClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
content = content
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ProfileCardPrev() {
|
||||
|
||||
val student = LksStudent(
|
||||
studentFullName = "Киц Михаил Андреевич",
|
||||
department = "ИИТ",
|
||||
group = "ИКБО-03-20"
|
||||
)
|
||||
|
||||
ProfileCard(
|
||||
lksStudent = student
|
||||
) { }
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Main() {
|
||||
|
||||
val student = LksStudent(
|
||||
studentFullName = "Киц Михаил Андреевич",
|
||||
department = "ИИТ",
|
||||
group = "ИКБО-03-20"
|
||||
)
|
||||
|
||||
MainTheme {
|
||||
MainScaffold(
|
||||
onFloatingButtonClick = {}
|
||||
) {
|
||||
Column(modifier = Modifier.padding(it)) {
|
||||
ProfileCard(
|
||||
lksStudent = student
|
||||
) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CardsColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
lksStudents: List<LksStudent>,
|
||||
onLongPress: (LksStudent) -> Unit,
|
||||
onColumnOverflow: suspend () -> Unit,
|
||||
) {
|
||||
|
||||
val buffer = 1
|
||||
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// observe list scrolling
|
||||
val reachedBottom: Boolean by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - buffer
|
||||
}
|
||||
}
|
||||
|
||||
// load more if scrolled to bottom
|
||||
LaunchedEffect(reachedBottom) {
|
||||
if (reachedBottom) {
|
||||
isLoading = true
|
||||
withContext(Dispatchers.IO) {
|
||||
onColumnOverflow()
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(lksStudents) { student ->
|
||||
ProfileCard(
|
||||
lksStudent = student,
|
||||
onLongPress = { onLongPress(student) }
|
||||
)
|
||||
}
|
||||
if(isLoading)
|
||||
item {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package ru.megboyzz.lks.profile.ui.components
|
||||
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun LoadingShield(title: String? = null) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
if(title != null)
|
||||
Text(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorShield(title: String? = null) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = null
|
||||
)
|
||||
if(title != null)
|
||||
Text(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package ru.megboyzz.lks.profile.ui.model
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.megboyzz.lks.profile.domain.DefaultCookies
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksRequest
|
||||
import ru.megboyzz.lks.profile.domain.entities.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchScreenModel @Inject constructor(
|
||||
private val lksRepository: LksRepository,
|
||||
@DefaultCookies
|
||||
private val cookies: Map<String, String>
|
||||
) : ScreenModel {
|
||||
|
||||
private val _request = MutableStateFlow(LksRequest())
|
||||
val request = _request.asStateFlow()
|
||||
|
||||
private val _curPageNumber = MutableStateFlow(1)
|
||||
val curPageNumber = _curPageNumber.asStateFlow()
|
||||
|
||||
private val _responseList = MutableStateFlow(LksResponse())
|
||||
val responseList = _responseList.asStateFlow()
|
||||
|
||||
init {
|
||||
_curPageNumber.onEach {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
fun nextPage() = screenModelScope.launch { _curPageNumber.emit(_curPageNumber.value++) }
|
||||
|
||||
private fun update() = screenModelScope.launch {
|
||||
|
||||
val result = lksRepository.getPage(_request.value, cookies, curPageNumber.value)
|
||||
|
||||
val list = result.getOrElse { LksResponse() }
|
||||
|
||||
_responseList.emit(list)
|
||||
}
|
||||
|
||||
fun newSearch() = screenModelScope.launch {
|
||||
|
||||
_curPageNumber.emit(1)
|
||||
update()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package ru.megboyzz.lks.profile.ui.model
|
||||
|
||||
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.LksResponse
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SearchViewModel @Inject constructor(
|
||||
private val lksRepository: LksRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _request = MutableStateFlow(LksRequest())
|
||||
val request = _request.asStateFlow()
|
||||
|
||||
private val _curPageNumber = MutableStateFlow(1)
|
||||
val curPageNumber = _curPageNumber.asStateFlow()
|
||||
|
||||
private val _responseList = MutableStateFlow(LksResponse())
|
||||
val responseList = _responseList.asStateFlow()
|
||||
|
||||
sealed class UiState(val status: String? = null) {
|
||||
data object Loading: UiState()
|
||||
//data object Success: SignInState("Успешный вход")
|
||||
data class Error(val _status: String) : UiState(_status)
|
||||
data object Idle: UiState()
|
||||
}
|
||||
|
||||
val state = MutableStateFlow<UiState>(UiState.Idle)
|
||||
|
||||
suspend fun nextPage() {
|
||||
if(_responseList.value.availablePages > _curPageNumber.value) {
|
||||
_curPageNumber.emit(_curPageNumber.value + 1)
|
||||
update(_request.value)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetStudents() {
|
||||
_curPageNumber.value = 1
|
||||
_responseList.emit(LksResponse())
|
||||
}
|
||||
|
||||
fun newSearch(request: LksRequest) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
state.emit(UiState.Loading)
|
||||
resetStudents()
|
||||
state.emit(update(request))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(request: LksRequest): UiState {
|
||||
|
||||
_request.emit(request)
|
||||
|
||||
val repoCookies = lksRepository.restoreCooke()
|
||||
|
||||
val result = lksRepository.getPage(request, repoCookies, curPageNumber.value)
|
||||
|
||||
if(result.isFailure)
|
||||
return UiState.Error("Ошибка :-(")
|
||||
|
||||
val list = result.getOrElse { LksResponse() }
|
||||
|
||||
val updatedStudents = LksResponse(
|
||||
students = _responseList.value.students + list.students,
|
||||
availablePages = list.availablePages
|
||||
)
|
||||
|
||||
_responseList.emit(updatedStudents)
|
||||
|
||||
return UiState.Idle
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,26 +1,28 @@
|
||||
package ru.megboyzz.lks.profile.ui.model
|
||||
|
||||
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 android.content.Context
|
||||
import android.widget.Toast
|
||||
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.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.megboyzz.lks.profile.domain.DefaultCookies
|
||||
import ru.megboyzz.lks.profile.domain.repositories.LksRepository
|
||||
import ru.megboyzz.lks.profile.ui.screens.SearchScreen
|
||||
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
|
||||
) : ScreenModel {
|
||||
@DefaultCookies
|
||||
private val defaultCookies: Map<String, String>
|
||||
) : ViewModel() {
|
||||
|
||||
private val _username = MutableStateFlow("")
|
||||
val username = _username.asStateFlow()
|
||||
@@ -37,19 +39,40 @@ class SignInScreenModel @AssistedInject constructor(
|
||||
|
||||
val state = MutableStateFlow<SignInState>(SignInState.Idle)
|
||||
|
||||
private val _loadingScreen = MutableStateFlow(false)
|
||||
val loadingScreen = _loadingScreen.asStateFlow()
|
||||
|
||||
private var cookie: Map<String, String> = mapOf()
|
||||
|
||||
init {
|
||||
private fun makeSuspendToast(text: String) = viewModelScope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
screenModelScope.launch {
|
||||
private var isUseDefaultCookie = false
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
state.emit(SignInState.Loading)
|
||||
cookie = lksRepository.restoreCooke()
|
||||
if(lksRepository.cookiesIsValid(cookie))
|
||||
navigator.push(SearchScreen())
|
||||
if(cookie.isEmpty()) {
|
||||
isUseDefaultCookie = true
|
||||
cookie = defaultCookies
|
||||
}
|
||||
if(lksRepository.cookiesIsValid(cookie)) {
|
||||
state.emit(SignInState.Success)
|
||||
if(isUseDefaultCookie)
|
||||
makeSuspendToast("Успех с дефолтными куками!")
|
||||
else
|
||||
makeSuspendToast("Успех!")
|
||||
}else {
|
||||
state.emit(SignInState.Error("Error!"))
|
||||
makeSuspendToast("Не успех!")
|
||||
}
|
||||
}
|
||||
|
||||
state.onEach {
|
||||
if(it == SignInState.Success) navigator.push(SearchScreen())
|
||||
}.launchIn(screenModelScope)
|
||||
//if(it == SignInState.Success) navigator.push(SearchScreen())
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
}
|
||||
|
||||
@@ -62,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())
|
||||
@@ -73,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,45 +1,63 @@
|
||||
package ru.megboyzz.lks.profile.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.input.pointer.PointerIcon.Companion.Text
|
||||
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.model.SearchScreenModel
|
||||
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.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.SearchBottomSheet
|
||||
|
||||
class SearchScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
@Composable
|
||||
fun SearchScreenContent() {
|
||||
|
||||
val screenModel = getScreenModel<SearchScreenModel>()
|
||||
val screenModel by LocalMainActivity.current.viewModels<SearchViewModel>()
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Spacer(Modifier.height(20.dp))
|
||||
val navigator = LocalNavigator.current!!
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
val list by screenModel.responseList.collectAsState()
|
||||
|
||||
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,74 +0,0 @@
|
||||
package ru.megboyzz.lks.profile.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.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.model.SignInScreenModel
|
||||
|
||||
class SignInScreen : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
|
||||
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()
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
OutlinedTextField(
|
||||
value = username,
|
||||
onValueChange = { viewModel.setUsername(it) }
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { viewModel.setPassword(it) }
|
||||
)
|
||||
|
||||
OutlinedButton(
|
||||
onClick = viewModel::signIn
|
||||
) {
|
||||
Text("Войти")
|
||||
}
|
||||
|
||||
when(state) {
|
||||
is SignInScreenModel.SignInState.Idle -> Text("")
|
||||
is SignInScreenModel.SignInState.Success -> Text(state.status ?: "")
|
||||
is SignInScreenModel.SignInState.Loading -> CircularProgressIndicator()
|
||||
is SignInScreenModel.SignInState.Error -> Text(state.status ?: "Ошибка")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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 ru.megboyzz.lks.profile.domain.entities.LksStudent
|
||||
import ru.megboyzz.lks.profile.ui.components.LongPressCardContextMenu
|
||||
import ru.megboyzz.lks.profile.ui.copyToClipboard
|
||||
import ru.megboyzz.lks.profile.ui.goToVK
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LongPressMenu(
|
||||
lksStudent: LksStudent,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismiss
|
||||
) {
|
||||
LongPressCardContextMenu(
|
||||
lksStudent = lksStudent,
|
||||
onCopyName = context::copyToClipboard,
|
||||
onCopyGroup = context::copyToClipboard,
|
||||
onGoToVk = {
|
||||
Toast.makeText(context, "Переход на VK", Toast.LENGTH_SHORT).show()
|
||||
|
||||
val searchString = lksStudent
|
||||
.studentFullName
|
||||
?.split(" ")
|
||||
?.dropLast(1)
|
||||
?.joinToString(" ") ?: ""
|
||||
|
||||
context.goToVK(searchString)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package ru.megboyzz.lks.profile.ui.screens.sub
|
||||
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
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 request by viewModel.request.collectAsState()
|
||||
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val view = LocalView.current
|
||||
var isImeVisible by remember { mutableStateOf(false) }
|
||||
|
||||
// Observe keyboard visibility using ViewTreeObserver
|
||||
DisposableEffect(Unit) {
|
||||
val listener = ViewTreeObserver.OnPreDrawListener {
|
||||
// Check if the IME (keyboard) is visible
|
||||
isImeVisible = ViewCompat.getRootWindowInsets(view)
|
||||
?.isVisible(WindowInsetsCompat.Type.ime()) == true
|
||||
true
|
||||
}
|
||||
view.viewTreeObserver.addOnPreDrawListener(listener)
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnPreDrawListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismiss,
|
||||
//windowInsets = WindowInsets.ime,
|
||||
dragHandle = { BottomSheetDefaults.DragHandle() },
|
||||
modifier = Modifier.then(
|
||||
if (isImeVisible)
|
||||
Modifier.fillMaxHeight(1.0F) // Full height when keyboard is visible
|
||||
else
|
||||
Modifier.fillMaxHeight(0.73F) // Partial height when keyboard is hidden
|
||||
)
|
||||
) {
|
||||
SearchContextMenu(request) { lksRequest ->
|
||||
viewModel.newSearch(lksRequest)
|
||||
onDismiss()
|
||||
coroutineScope.launch { sheetState.hide() }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,46 +8,44 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
primary = Color(0xFFB0BEC5), // Серый с голубым оттенком
|
||||
secondary = Color(0xFF90A4AE), // Более мягкий серо-голубой
|
||||
tertiary = Color(0xFFB39DDB), // Нежный фиолетовый
|
||||
|
||||
background = Color(0xFF121212), // Темный фон для контраста
|
||||
surface = Color(0xFF1E1E1E), // Темная поверхность
|
||||
onPrimary = Color.Black,
|
||||
onSecondary = Color.Black,
|
||||
onTertiary = Color.Black,
|
||||
onBackground = Color(0xFFECEFF1), // Светлый текст на темном фоне
|
||||
onSurface = Color(0xFFECEFF1) // Светлый текст на поверхностях
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
primary = Color(0xFF207AEE), // Голубой с пастельным оттенком
|
||||
secondary = Color(0xFFB3E5FC), // Мягкий голубой
|
||||
tertiary = Color(0xFFC5CAE9), // Светлый фиолетовый
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
background = Color(0xFFF5F5F5), // Очень светлый фон
|
||||
surface = Color(0xFFFFFFFF), // Чисто белая поверхность
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
onSecondary = Color.Black,
|
||||
onTertiary = Color.Black,
|
||||
onBackground = Color(0xFF212121), // Темный текст на светлом фоне
|
||||
onSurface = Color(0xFF212121) // Темный текст на поверхностях
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MainTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
dynamicColor: Boolean = false, // Отключаем динамические цвета для четкости палитры
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val colorScheme = LightColorScheme
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
|
||||
@@ -14,11 +14,11 @@ val Typography = Typography(
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
),
|
||||
// Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
@@ -30,5 +30,4 @@ val Typography = Typography(
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M440,840v-240h80v80h320v80L520,760v80h-80ZM120,760v-80h240v80L120,760ZM280,600v-80L120,520v-80h160v-80h80v240h-80ZM440,520v-80h400v80L440,520ZM600,360v-240h80v80h160v80L680,280v80h-80ZM120,280v-80h400v80L120,280Z"
|
||||
android:fillColor="#e8eaed"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M42,37c0,2.762 -2.238,5 -5,5H11c-2.761,0 -5,-2.238 -5,-5V11c0,-2.762 2.239,-5 5,-5h26c2.762,0 5,2.238 5,5V37z"
|
||||
android:fillColor="#1976d2"/>
|
||||
<path
|
||||
android:pathData="M35.937,18.041c0.046,-0.151 0.068,-0.291 0.062,-0.416C35.984,17.263 35.735,17 35.149,17h-2.618c-0.661,0 -0.966,0.4 -1.144,0.801c0,0 -1.632,3.359 -3.513,5.574c-0.61,0.641 -0.92,0.625 -1.25,0.625C26.447,24 26,23.786 26,23.199v-5.185C26,17.32 25.827,17 25.268,17h-4.649C20.212,17 20,17.32 20,17.641c0,0.667 0.898,0.827 1,2.696v3.623C21,24.84 20.847,25 20.517,25c-0.89,0 -2.642,-3 -3.815,-6.932C16.448,17.294 16.194,17 15.533,17h-2.643C12.127,17 12,17.374 12,17.774c0,0.721 0.6,4.619 3.875,9.101C18.25,30.125 21.379,32 24.149,32c1.678,0 1.85,-0.427 1.85,-1.094v-2.972C26,27.133 26.183,27 26.717,27c0.381,0 1.158,0.25 2.658,2c1.73,2.018 2.044,3 3.036,3h2.618c0.608,0 0.957,-0.255 0.971,-0.75c0.003,-0.126 -0.015,-0.267 -0.056,-0.424c-0.194,-0.576 -1.084,-1.984 -2.194,-3.326c-0.615,-0.743 -1.222,-1.479 -1.501,-1.879C32.062,25.36 31.991,25.176 32,25c0.009,-0.185 0.105,-0.361 0.249,-0.607C32.223,24.393 35.607,19.642 35.937,18.041z"
|
||||
android:fillColor="#fff"
|
||||
tools:ignore="VectorPath" />
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -5,5 +5,6 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
id("com.google.dagger.hilt.android") version "2.51.1" apply false
|
||||
id("com.google.devtools.ksp") version "2.2.0-2.0.2" apply false
|
||||
id("com.google.dagger.hilt.android") version "2.57" apply false
|
||||
}
|
||||
@@ -1,21 +1,25 @@
|
||||
[versions]
|
||||
agp = "8.5.1"
|
||||
hiltAndroidCompiler = "2.51.1"
|
||||
jsoup = "1.18.1"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.13.1"
|
||||
agp = "8.11.1"
|
||||
hiltAndroidCompiler = "2.57"
|
||||
jsoup = "1.21.1"
|
||||
kotlin = "2.2.0"
|
||||
coreKtx = "1.16.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
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"
|
||||
junitVersion = "1.3.0"
|
||||
espressoCore = "3.7.0"
|
||||
lifecycleRuntimeKtx = "2.9.2"
|
||||
activityCompose = "1.10.1"
|
||||
composeBom = "2025.07.00"
|
||||
jetbrainsKotlinJvm = "2.2.0"
|
||||
composeNavigation = "2.9.3"
|
||||
room_version = "2.7.2"
|
||||
serialization = "1.9.0"
|
||||
foundationLayoutAndroid = "1.8.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 +37,9 @@ 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"}
|
||||
androidx-foundation-layout-android = { group = "androidx.compose.foundation", name = "foundation-layout-android", version.ref = "foundationLayoutAndroid" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
@@ -47,4 +47,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" }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Wed Aug 21 19:37:41 MSK 2024
|
||||
#Fri Aug 01 15:44:12 MSK 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -21,5 +21,3 @@ dependencyResolutionManagement {
|
||||
|
||||
rootProject.name = "LKS PROFILE"
|
||||
include(":app")
|
||||
include(":app:domain")
|
||||
include(":app:data")
|
||||
|
||||