forked from Olympic/NTO-2025-Android-TeamTask
kotlin poezdec
This commit is contained in:
76
.kotlin/errors/errors-1764509512206.log
Normal file
76
.kotlin/errors/errors-1764509512206.log
Normal file
@@ -0,0 +1,76 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing C:/Users/Admin/AndroidStudioProjects/NTO-2025-Android-TeamTask/app/src/main/java/ru/myitschool/work/App.kt:7:5: java.lang.IllegalArgumentException: source must not be null
|
||||
at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57)
|
||||
at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249)
|
||||
at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46)
|
||||
at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77)
|
||||
at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88)
|
||||
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319)
|
||||
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118)
|
||||
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148)
|
||||
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||
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:675)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||
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.IllegalArgumentException: source must not be null
|
||||
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68)
|
||||
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39)
|
||||
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19)
|
||||
at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
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)
|
||||
... 33 more
|
||||
|
||||
|
||||
76
.kotlin/errors/errors-1764509520395.log
Normal file
76
.kotlin/errors/errors-1764509520395.log
Normal file
@@ -0,0 +1,76 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing C:/Users/Admin/AndroidStudioProjects/NTO-2025-Android-TeamTask/app/src/main/java/ru/myitschool/work/App.kt:7:5: java.lang.IllegalArgumentException: source must not be null
|
||||
at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57)
|
||||
at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249)
|
||||
at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46)
|
||||
at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77)
|
||||
at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88)
|
||||
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319)
|
||||
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118)
|
||||
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148)
|
||||
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||
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:675)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||
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.IllegalArgumentException: source must not be null
|
||||
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68)
|
||||
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39)
|
||||
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42)
|
||||
at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19)
|
||||
at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48)
|
||||
at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151)
|
||||
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30)
|
||||
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)
|
||||
... 33 more
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*plugins {
|
||||
composeCompiler
|
||||
kotlinAndroid
|
||||
kotlinSerialization version Version.Kotlin.language
|
||||
androidApplication
|
||||
}*/
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.myitschool.work.data.repo
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
|
||||
// DataStore для хранения auth-кода
|
||||
val Context.authDataStore by preferencesDataStore(name = "auth_data")
|
||||
@@ -1,6 +1,9 @@
|
||||
package ru.myitschool.work.data.repo
|
||||
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import ru.myitschool.work.App
|
||||
import ru.myitschool.work.data.source.NetworkDataSource
|
||||
|
||||
@@ -10,23 +13,17 @@ object AuthRepository {
|
||||
private val KEY_CODE = stringPreferencesKey("auth_code")
|
||||
private var codeCache: String? = null
|
||||
|
||||
// suspend fun checkAndSave(text: String): Result<Boolean> {
|
||||
// return NetworkDataSource.checkAuth(text).onSuccess { success ->
|
||||
// if (success) {
|
||||
// codeCache = text
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
suspend fun checkAndSave(text: String): Result<Boolean> {
|
||||
return NetworkDataSource.checkAuth(text).onSuccess { success ->
|
||||
if (success) {
|
||||
codeCache = text
|
||||
dataStore.edit { prefs ->
|
||||
prefs[KEY_CODE] = text
|
||||
// Проверка кода на сервере и сохранение при успехе
|
||||
suspend fun checkAndSave(text: String): Result<Boolean> {
|
||||
return NetworkDataSource.checkAuth(text).onSuccess { success ->
|
||||
if (success) {
|
||||
codeCache = text
|
||||
dataStore.edit { prefs ->
|
||||
prefs[KEY_CODE] = text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Сохранённый код (из кэша или DataStore) */
|
||||
suspend fun getSavedCode(): String? {
|
||||
@@ -47,6 +44,4 @@ suspend fun checkAndSave(text: String): Result<Boolean> {
|
||||
prefs.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,6 +19,11 @@ import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.source.dto.UserDto
|
||||
|
||||
object NetworkDataSource {
|
||||
|
||||
/** Поставь false, когда поднимешь настоящий бэкенд */
|
||||
private const val USE_STUB = true
|
||||
|
||||
// Реальный клиент Ktor (для будущего)
|
||||
private val client by lazy {
|
||||
HttpClient(CIO) {
|
||||
install(ContentNegotiation) {
|
||||
@@ -34,36 +39,78 @@ object NetworkDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
val response = client.get(getUrl(code, Constants.AUTH_URL))
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> true
|
||||
else -> error(response.bodyAsText())
|
||||
// ----------------- Заглушечные данные -----------------
|
||||
|
||||
private val stubUser = UserDto(
|
||||
name = "Тестовый пользователь",
|
||||
booking = listOf(
|
||||
UserDto.BookingDto(
|
||||
room = "Опенспейс 1",
|
||||
time = "2025-01-05"
|
||||
),
|
||||
UserDto.BookingDto(
|
||||
room = "Опенспейс 2",
|
||||
time = "2025-01-06"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
private val stubAvailableBookings: List<UserDto.BookingDto> = listOf(
|
||||
UserDto.BookingDto(room = "Опенспейс 1", time = "2025-01-10"),
|
||||
UserDto.BookingDto(room = "Опенспейс 1", time = "2025-01-11"),
|
||||
UserDto.BookingDto(room = "Опенспейс 2", time = "2025-01-10"),
|
||||
UserDto.BookingDto(room = "Опенспейс 3", time = "2025-01-12"),
|
||||
)
|
||||
|
||||
// ----------------- Публичные методы -----------------
|
||||
|
||||
suspend fun checkAuth(code: String): Result<Boolean> {
|
||||
if (USE_STUB) {
|
||||
// Примитивная проверка заглушки: 4 символа → ок
|
||||
return Result.success(code.length == 4)
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.get(getUrl(code, Constants.AUTH_URL))
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> true
|
||||
else -> error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getUserInfo(code: String): Result<UserDto> {
|
||||
if (USE_STUB) {
|
||||
return Result.success(stubUser)
|
||||
}
|
||||
|
||||
|
||||
suspend fun getUserInfo(code: String): Result<UserDto> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.get(getUrl(code, Constants.INFO_URL))
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> response.body<UserDto>()
|
||||
else -> error(response.bodyAsText())
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.get(getUrl(code, Constants.INFO_URL))
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> response.body<UserDto>()
|
||||
else -> error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAvailableBookings(
|
||||
code: String
|
||||
): Result<List<UserDto.BookingDto>> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.get(getUrl(code, Constants.BOOKING_URL))
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> response.body<List<UserDto.BookingDto>>()
|
||||
else -> error(response.bodyAsText())
|
||||
): Result<List<UserDto.BookingDto>> {
|
||||
if (USE_STUB) {
|
||||
return Result.success(stubAvailableBookings)
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.get(getUrl(code, Constants.BOOKING_URL))
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> response.body<List<UserDto.BookingDto>>()
|
||||
else -> error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,24 +119,32 @@ object NetworkDataSource {
|
||||
code: String,
|
||||
room: String,
|
||||
time: String
|
||||
): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.post(getUrl(code, Constants.BOOK_URL)) {
|
||||
setBody(
|
||||
MultiPartFormDataContent(
|
||||
formData {
|
||||
append("room", room)
|
||||
append("time", time)
|
||||
}
|
||||
): Result<Unit> {
|
||||
if (USE_STUB) {
|
||||
// Типа всё хорошо
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val response = client.post(getUrl(code, Constants.BOOK_URL)) {
|
||||
setBody(
|
||||
MultiPartFormDataContent(
|
||||
formData {
|
||||
append("room", room)
|
||||
append("time", time)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> Unit
|
||||
else -> error(response.bodyAsText())
|
||||
}
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> Unit
|
||||
else -> error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
|
||||
private fun getUrl(code: String, targetUrl: String) =
|
||||
"${Constants.HOST}/api/$code$targetUrl"
|
||||
}
|
||||
@@ -2,10 +2,7 @@ package ru.myitschool.work.ui.screen
|
||||
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
@@ -15,6 +12,8 @@ import ru.myitschool.work.ui.nav.AuthScreenDestination
|
||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||
import ru.myitschool.work.ui.screen.auth.AuthScreen
|
||||
import ru.myitschool.work.ui.screen.book.BookScreen
|
||||
import ru.myitschool.work.ui.screen.main.MainScreen
|
||||
|
||||
@Composable
|
||||
fun AppNavHost(
|
||||
@@ -32,18 +31,10 @@ fun AppNavHost(
|
||||
AuthScreen(navController = navController)
|
||||
}
|
||||
composable<MainScreenDestination> {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "Hello")
|
||||
}
|
||||
MainScreen(navController = navController)
|
||||
}
|
||||
composable<BookScreenDestination> {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "Hello")
|
||||
}
|
||||
BookScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -41,7 +38,10 @@ fun AuthScreen(
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.actionFlow.collect {
|
||||
navController.navigate(MainScreenDestination)
|
||||
navController.navigate(MainScreenDestination) {
|
||||
// очищаем backstack до экрана авторизации
|
||||
popUpTo(MainScreenDestination) { inclusive = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ fun AuthScreen(
|
||||
when (val currentState = state) {
|
||||
is AuthState.Data -> Content(viewModel, currentState)
|
||||
is AuthState.Loading -> {
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(64.dp)
|
||||
)
|
||||
@@ -73,24 +74,40 @@ private fun Content(
|
||||
viewModel: AuthViewModel,
|
||||
state: AuthState.Data
|
||||
) {
|
||||
var inputText by remember { mutableStateOf("") }
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
if (state.isErrorVisible) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestIds.Auth.ERROR),
|
||||
text = "Неверный код или ошибка сервера",
|
||||
color = Color.Red,
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
}
|
||||
|
||||
TextField(
|
||||
modifier = Modifier.testTag(TestIds.Auth.CODE_INPUT).fillMaxWidth(),
|
||||
value = inputText,
|
||||
onValueChange = {
|
||||
inputText = it
|
||||
viewModel.onIntent(AuthIntent.TextInput(it))
|
||||
},
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Auth.CODE_INPUT)
|
||||
.fillMaxWidth(),
|
||||
value = state.code,
|
||||
onValueChange = { viewModel.onIntent(AuthIntent.TextInput(it)) },
|
||||
label = { Text(stringResource(R.string.auth_label)) }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Auth.SIGN_BUTTON)
|
||||
.fillMaxWidth(),
|
||||
onClick = {
|
||||
viewModel.onIntent(AuthIntent.Send(inputText))
|
||||
viewModel.onIntent(AuthIntent.Send(state.code))
|
||||
},
|
||||
enabled = true
|
||||
enabled = state.isButtonEnabled
|
||||
) {
|
||||
Text(stringResource(R.string.auth_sign_in))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
package ru.myitschool.work.ui.screen.book
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import ru.myitschool.work.core.TestIds
|
||||
|
||||
@Composable
|
||||
fun BookScreen(
|
||||
navController: NavController,
|
||||
viewModel: BookViewModel = viewModel()
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.onIntent(BookIntent.Load)
|
||||
|
||||
viewModel.actionFlow.collect { action ->
|
||||
when (action) {
|
||||
BookViewModel.Action.CloseWithSuccess -> {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val current = state) {
|
||||
is BookState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
is BookState.Error -> {
|
||||
BookErrorContent(
|
||||
message = current.message,
|
||||
onBack = { navController.popBackStack() },
|
||||
onRefresh = { viewModel.onIntent(BookIntent.Refresh) }
|
||||
)
|
||||
}
|
||||
|
||||
is BookState.Empty -> {
|
||||
BookEmptyContent(
|
||||
onBack = { navController.popBackStack() },
|
||||
onRefresh = { viewModel.onIntent(BookIntent.Refresh) }
|
||||
)
|
||||
}
|
||||
|
||||
is BookState.Data -> {
|
||||
BookDataContent(
|
||||
state = current,
|
||||
onBack = { navController.popBackStack() },
|
||||
onRefresh = { viewModel.onIntent(BookIntent.Refresh) },
|
||||
onSelectDate = { viewModel.onIntent(BookIntent.SelectDate(it)) },
|
||||
onSelectPlace = { viewModel.onIntent(BookIntent.SelectPlace(it)) },
|
||||
onBook = { viewModel.onIntent(BookIntent.Book) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BookErrorContent(
|
||||
message: String,
|
||||
onBack: () -> Unit,
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Book.ERROR),
|
||||
text = message,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON),
|
||||
onClick = onRefresh
|
||||
) {
|
||||
Text("Обновить")
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
|
||||
onClick = onBack
|
||||
) {
|
||||
Text("Назад")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BookEmptyContent(
|
||||
onBack: () -> Unit,
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Book.EMPTY),
|
||||
text = "Всё забронировано"
|
||||
)
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON),
|
||||
onClick = onRefresh
|
||||
) {
|
||||
Text("Обновить")
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
|
||||
onClick = onBack
|
||||
) {
|
||||
Text("Назад")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BookDataContent(
|
||||
state: BookState.Data,
|
||||
onBack: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onSelectDate: (Int) -> Unit,
|
||||
onSelectPlace: (Int) -> Unit,
|
||||
onBook: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
|
||||
onClick = onBack
|
||||
) {
|
||||
Text("Назад")
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON),
|
||||
onClick = onRefresh
|
||||
) {
|
||||
Text("Обновить")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
// Вкладки с датами
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
itemsIndexed(state.dates) { index, dateLabel ->
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Book.getIdDateItemByPosition(index)),
|
||||
tonalElevation = if (index == state.selectedDateIndex) 4.dp else 0.dp
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.testTag(TestIds.Book.ITEM_DATE),
|
||||
text = dateLabel,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
// Список мест
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
itemsIndexed(state.places, key = { _, item -> item.id }) { index, item ->
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestIds.Book.getIdPlaceItemByPosition(index))
|
||||
.selectable(
|
||||
selected = item.isSelected,
|
||||
onClick = { onSelectPlace(item.id) }
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Book.ITEM_PLACE_TEXT),
|
||||
text = "${item.roomName} — ${item.time}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
RadioButton(
|
||||
modifier = Modifier.testTag(TestIds.Book.ITEM_PLACE_SELECTOR),
|
||||
selected = item.isSelected,
|
||||
onClick = { onSelectPlace(item.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestIds.Book.BOOK_BUTTON),
|
||||
onClick = onBook,
|
||||
enabled = state.places.any { it.isSelected }
|
||||
) {
|
||||
Text("Забронировать")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package ru.myitschool.work.ui.screen.main
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import ru.myitschool.work.core.TestIds
|
||||
import ru.myitschool.work.ui.nav.AuthScreenDestination
|
||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
navController: NavController,
|
||||
viewModel: MainViewModel = viewModel()
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.onIntent(MainIntent.Load)
|
||||
|
||||
viewModel.actionFlow.collect { action ->
|
||||
when (action) {
|
||||
MainViewModel.Action.NavigateToAuth -> {
|
||||
navController.navigate(AuthScreenDestination) {
|
||||
popUpTo(AuthScreenDestination) { inclusive = true }
|
||||
}
|
||||
}
|
||||
MainViewModel.Action.NavigateToBooking -> {
|
||||
navController.navigate(BookScreenDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val current = state) {
|
||||
is MainState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
is MainState.Error -> {
|
||||
MainErrorContent(
|
||||
message = current.message,
|
||||
onRefresh = { viewModel.onIntent(MainIntent.Refresh) }
|
||||
)
|
||||
}
|
||||
|
||||
is MainState.Data -> {
|
||||
MainDataContent(
|
||||
state = current,
|
||||
onRefresh = { viewModel.onIntent(MainIntent.Refresh) },
|
||||
onLogout = { viewModel.onIntent(MainIntent.Logout) },
|
||||
onAddBooking = { viewModel.onIntent(MainIntent.AddBooking) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainErrorContent(
|
||||
message: String,
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Main.ERROR),
|
||||
text = message,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON),
|
||||
onClick = onRefresh
|
||||
) {
|
||||
Text("Обновить")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainDataContent(
|
||||
state: MainState.Data,
|
||||
onRefresh: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
onAddBooking: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.testTag(TestIds.Main.PROFILE_IMAGE),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("🙂")
|
||||
}
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Main.PROFILE_NAME),
|
||||
text = state.name,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Main.LOGOUT_BUTTON),
|
||||
onClick = onLogout
|
||||
) {
|
||||
Text("Выход")
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON),
|
||||
onClick = onRefresh
|
||||
) {
|
||||
Text("Обновить")
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier.testTag(TestIds.Main.ADD_BUTTON),
|
||||
onClick = onAddBooking
|
||||
) {
|
||||
Text("Бронь")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
itemsIndexed(state.bookings, key = { _, item -> item.id }) { index, item ->
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestIds.Main.getIdItemByPosition(index))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Main.ITEM_DATE),
|
||||
text = item.dateLabel,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE),
|
||||
text = item.roomName,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user