diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index a81fdcd..18cb44a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -56,10 +56,12 @@ kotlin { implementation(libs.supabase.auth) implementation(libs.supabase.realtime) implementation(libs.supabase.storage) -// implementation(libs.supabase.functions) // Ktor implementation(libs.ktor.client.core) + + // Navigation + implementation(libs.androidx.navigation.compose) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/App.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/App.kt index 3aa1e93..e377c26 100644 --- a/composeApp/src/commonMain/kotlin/com/example/testkmp/App.kt +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/App.kt @@ -1,46 +1,37 @@ package com.example.testkmp -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.FabPosition -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Scaffold -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.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 -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute +import com.example.testkmp.data.supabase import com.example.testkmp.di.appModule -import com.example.testkmp.domain.models.Categories -import com.example.testkmp.presentation.components.CategoriesItem -import com.example.testkmp.presentation.DataState -import com.example.testkmp.presentation.HomeViewModel -import com.example.testkmp.presentation.components.AddCategoryDialog -import com.example.testkmp.presentation.components.AddTaskDialog -import org.koin.compose.KoinApplication +import com.example.testkmp.presentation.screens.HomeScreen +import com.example.testkmp.presentation.screens.SignUpScreen +import io.github.jan.supabase.auth.auth import org.koin.compose.viewmodel.koinViewModel +import com.example.testkmp.presentation.AuthViewModel +import com.example.testkmp.presentation.screens.SignInScreen +import io.github.jan.supabase.auth.user.UserInfo +import kotlinx.serialization.Serializable +import org.koin.compose.KoinApplication +import com.example.testkmp.SignUp +import io.github.jan.supabase.auth.status.SessionStatus +import io.github.jan.supabase.auth.user.UserSession + + +@Serializable +object SignIn + +@Serializable +object SignUp + +@Serializable +object Home @Composable fun App(modifier: Modifier) { @@ -49,129 +40,44 @@ fun App(modifier: Modifier) { modules(appModule) } ) { - TaskManagerTheme { - - val viewModel: HomeViewModel = koinViewModel() - - // collectAsState - не привязан к жц, collectAsStateWithLifecycle - привязан, актулально только для Android - val dataState by viewModel.dataState.collectAsState() - var showAddCategoryDialog by remember { mutableStateOf(false) } - - LaunchedEffect(Unit) { - viewModel.loadCatsData() - } - - Scaffold( - floatingActionButton = { - FloatingActionButton( - onClick = { - showAddCategoryDialog = true - }, - containerColor = ActionButtonColor, - ) { - Text("+") - } - }, - - floatingActionButtonPosition = FabPosition.End - ) { - when(val state = dataState) { - is DataState.Error -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxSize() - .padding(bottom = 14.dp) + val authViewModel: AuthViewModel = koinViewModel() + val navController = rememberNavController() - ) { - Text( - text = "Не удалось загрузить", - textAlign = TextAlign.Center - ) - } - } - - DataState.Loading -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 14.dp) + val sessionStatus by supabase.auth.sessionStatus.collectAsState() - ) { - Text( - text = "Категории", - fontSize = 24.sp, - modifier = Modifier - .padding(top = 18.dp, bottom = 6.dp) - ) - Spacer( - modifier = Modifier - .fillMaxWidth(0.85F) - .height(2.dp) - .background(Color.Gray) - ) - - CircularProgressIndicator( - strokeWidth = 10.dp, - modifier = Modifier - .padding(top = 40.dp) - .size(70.dp) + val startDestination = when (sessionStatus) { + is SessionStatus.Authenticated -> Home + else -> SignIn + } - ) - } + NavHost( + navController = navController, + startDestination = startDestination + + ) { + composable { + //val home: Home = backStackEntry.toRoute() + HomeScreen( + modifier = modifier, + onNavigateToSignIn = { + navController.navigate(SignIn) } - - is DataState.Success -> { - LazyVerticalGrid( - columns = GridCells.Adaptive(300.dp), - modifier = modifier.padding(horizontal = 6.dp), - userScrollEnabled = true, - ) { - - item(span = { GridItemSpan(maxLineSpan) }) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 8.dp) - - ) { - Text( - text = "Категории", - fontSize = 24.sp, - modifier = Modifier - .padding(top = 18.dp, bottom = 6.dp) - ) - Spacer( - modifier = Modifier - .fillMaxWidth(0.85F) - .height(2.dp) - .background(Color.Gray) - ) - } - - } - items(state.data) { item -> - CategoriesItem( - item, - { - - } - ) - } - } + ) + } + composable { + SignInScreen( + onNavigateToSignUp = { + navController.navigate(SignUp) + }, + onNavigateToHome = { + navController.navigate(Home) } - } + ) } - - if (showAddCategoryDialog) { - AddCategoryDialog( - onDismiss = { showAddCategoryDialog = false }, - onConfirm = { title, description -> - viewModel.addCategory(Categories(name = title, description = description!!)) - showAddCategoryDialog = false + composable { + SignUpScreen( + onNavigateToSignIn = { + navController.navigate(SignIn) } ) } diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/data/SupabaseAuthRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/data/SupabaseAuthRepositoryImpl.kt new file mode 100644 index 0000000..0930bb6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/data/SupabaseAuthRepositoryImpl.kt @@ -0,0 +1,92 @@ +package com.example.testkmp.data + +import com.example.testkmp.domain.repositories.AuthRepository +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.providers.builtin.Email +import io.github.jan.supabase.auth.user.UserInfo +import com.example.testkmp.domain.models.Result +import io.github.jan.supabase.auth.exception.AuthErrorCode +import io.github.jan.supabase.auth.exception.AuthRestException +import kotlinx.coroutines.delay + +class SupabaseAuthRepositoryImpl() : AuthRepository { + + private val auth = supabase.auth + + override suspend fun signUp( + email: String, + password: String, + username: String? + ): Result { + return try { + when { + email.isBlank() -> return Result.Error("Email не может быть пустым!") + password.isBlank() -> return Result.Error("Пароль не может быть пустым!") + password.length < 6 -> return Result.Error("Пароль должен быть длиннее 6 символов!") + + } + + val signUpResult = auth.signUpWith(Email) { + this.email = email + this.password = password + } + + if (signUpResult != null) { + Result.Success( + UserInfo( + id = signUpResult.id, + aud = signUpResult.aud, + email = signUpResult.email, + ) + ) + } else { + Result.Error("Не удалось создать пользователя") + } + } catch (e: Exception) { + return if (e is AuthRestException) + Result.Error("Supabase error: ${e.errorCode}") + else + Result.Error("Error: $e") + + } + } + + override suspend fun signIn( + email: String, + password: String + ): Result { + return try { + + auth.signInWith(Email) { + this.email = email + this.password = password + } + + val session = auth.currentSessionOrNull() + ?: throw Exception("No session after sign up") + println(session) + + val userInfo = UserInfo( + id = session.user!!.id, + email = session.user!!.email, + aud = session.user!!.aud + ) + + Result.Success(userInfo) + } catch (e: Exception) { + println(e.message.toString()) + Result.Error(e.message.toString()) + } + } + + override suspend fun signOut(): Boolean { + try { + val res = supabase.auth.signOut() + println(res) + return true + } catch (e: Exception) { + println(e) + throw e + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/di/SharedModule.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/di/SharedModule.kt index 779880e..76293e2 100644 --- a/composeApp/src/commonMain/kotlin/com/example/testkmp/di/SharedModule.kt +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/di/SharedModule.kt @@ -1,12 +1,20 @@ package com.example.testkmp.di import com.example.testkmp.data.FakeRepositoryImpl +import com.example.testkmp.data.SupabaseAuthRepositoryImpl +import com.example.testkmp.data.supabase +import com.example.testkmp.domain.repositories.AuthRepository import com.example.testkmp.domain.repositories.DatabaseRepository import com.example.testkmp.domain.usecases.AddCategoryUseCase import com.example.testkmp.domain.usecases.GetAllCategoriesUseCase import com.example.testkmp.domain.usecases.GetAllTasksUseCase import com.example.testkmp.domain.usecases.GetTasksInCategoryUseCase +import com.example.testkmp.domain.usecases.auth.SignInUseCase +import com.example.testkmp.domain.usecases.auth.SignOutUseCase +import com.example.testkmp.domain.usecases.auth.SignUpUseCase +import com.example.testkmp.presentation.AuthViewModel import com.example.testkmp.presentation.HomeViewModel +import io.github.jan.supabase.SupabaseClient import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module @@ -21,13 +29,23 @@ val sharedModule = module { factory { AddCategoryUseCase(get()) } + factory { SignUpUseCase(get()) } + + factory { SignInUseCase(get()) } + + factory { SignOutUseCase(get()) } + single { FakeRepositoryImpl() } + + single { SupabaseAuthRepositoryImpl() } } val viewModelModule = module { viewModelOf(::HomeViewModel) + viewModelOf(::AuthViewModel) + } val appModule = module { diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/models/Result.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/models/Result.kt new file mode 100644 index 0000000..ebe6a1e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/models/Result.kt @@ -0,0 +1,16 @@ +package com.example.testkmp.domain.models + +import io.github.jan.supabase.auth.exception.AuthErrorCode +import io.github.jan.supabase.auth.user.UserInfo + +sealed class Result { + + object Loading : Result() + data class Success(val user: UserInfo?) : Result() + data class Error(val error: String) : Result() + + fun isLoading() : Boolean = this is Loading + fun isSuccess() : Boolean = this is Success + fun isError() : Boolean = this is Error + +} diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/repositories/AuthRepository.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/repositories/AuthRepository.kt new file mode 100644 index 0000000..03bea2e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/repositories/AuthRepository.kt @@ -0,0 +1,21 @@ +package com.example.testkmp.domain.repositories + +import io.github.jan.supabase.auth.user.UserInfo +import com.example.testkmp.domain.models.Result + +interface AuthRepository { + + suspend fun signUp( + email: String, + password: String, + username: String? = null + ) : Result + + suspend fun signIn( + email: String, + password: String, + ) : Result + + suspend fun signOut() : Boolean + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignInUseCase.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignInUseCase.kt new file mode 100644 index 0000000..892677d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignInUseCase.kt @@ -0,0 +1,16 @@ +package com.example.testkmp.domain.usecases.auth + +import com.example.testkmp.domain.models.Result +import com.example.testkmp.domain.repositories.AuthRepository +import io.github.jan.supabase.auth.user.UserInfo + +class SignInUseCase( + private val authRepository: AuthRepository +) { + suspend operator fun invoke( + email: String, + password: String + ) : Result { + return authRepository.signIn(email, password) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignOutUseCase.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignOutUseCase.kt new file mode 100644 index 0000000..5e2b821 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignOutUseCase.kt @@ -0,0 +1,13 @@ +package com.example.testkmp.domain.usecases.auth + +import com.example.testkmp.domain.models.Result +import com.example.testkmp.domain.repositories.AuthRepository +import io.github.jan.supabase.auth.user.UserInfo + +class SignOutUseCase( + private val authRepository: AuthRepository +) { + suspend operator fun invoke() : Boolean { + return authRepository.signOut() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignUpUseCase.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignUpUseCase.kt new file mode 100644 index 0000000..2b3e3e8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/domain/usecases/auth/SignUpUseCase.kt @@ -0,0 +1,19 @@ +package com.example.testkmp.domain.usecases.auth + +import com.example.testkmp.domain.repositories.AuthRepository +import io.github.jan.supabase.auth.user.UserInfo +import com.example.testkmp.domain.models.Result + +class SignUpUseCase( + private val authRepository: AuthRepository +) { + + suspend operator fun invoke( + email: String, + pass: String, + username: String? = null + ) : Result { + return authRepository.signUp(email, pass, username) + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/AuthViewModel.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/AuthViewModel.kt new file mode 100644 index 0000000..f44b41e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/AuthViewModel.kt @@ -0,0 +1,58 @@ +package com.example.testkmp.presentation + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.testkmp.domain.usecases.auth.SignUpUseCase +import kotlinx.coroutines.launch +import com.example.testkmp.domain.models.Result +import com.example.testkmp.domain.usecases.auth.SignInUseCase +import com.example.testkmp.domain.usecases.auth.SignOutUseCase +import io.github.jan.supabase.auth.user.UserInfo +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class AuthViewModel( + private val signUpUseCase: SignUpUseCase, + private val signInUseCase: SignInUseCase, + private val signOutUseCase: SignOutUseCase +) : ViewModel() { + + private var _authState = MutableStateFlow>(Result.Loading) + val authState: StateFlow> = _authState + + fun signUp(email: String, pass: String, username: String? = null) { + viewModelScope.launch { + val result = signUpUseCase(email, pass, username) + + when(result) { + is Result.Success -> {println("success")} + is Result.Error -> {println(result.error)} + is Result.Loading -> {println("loading")} + } + + } + } + + fun signIn(email: String, pass: String) { + viewModelScope.launch { + _authState.value = Result.Loading + try { + val result = signInUseCase(email, pass) + _authState.value = result + } catch (e: Exception) { + _authState.value = Result.Error(e.message.toString()) + } + } + } + + fun signOut() { + viewModelScope.launch { + try { + signOutUseCase() + _authState.value = Result.Loading + } catch (e: Exception) { + _authState.value = Result.Error(e.message.toString()) + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeScreen.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeScreen.kt deleted file mode 100644 index 3bc5ea5..0000000 --- a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeScreen.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.testkmp.presentation - -class HomeScreen { - -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeViewModel.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeViewModel.kt index e783f49..ce4f22c 100644 --- a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/HomeViewModel.kt @@ -1,6 +1,5 @@ package com.example.testkmp.presentation -import androidx.compose.runtime.MutableState import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.testkmp.domain.models.Categories @@ -9,14 +8,9 @@ import com.example.testkmp.domain.usecases.AddCategoryUseCase import com.example.testkmp.domain.usecases.GetAllCategoriesUseCase import com.example.testkmp.domain.usecases.GetAllTasksUseCase import com.example.testkmp.domain.usecases.GetTasksInCategoryUseCase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject class HomeViewModel( private val addCategoryUseCase: AddCategoryUseCase, diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/CategoriesItem.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/CategoriesItem.kt index 417dc5e..7e17994 100644 --- a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/CategoriesItem.kt +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/CategoriesItem.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.unit.sp import com.example.testkmp.domain.models.Categories import com.example.testkmp.domain.models.Task import com.example.testkmp.presentation.HomeViewModel -import com.example.testkmp.presentation.TaskItem import org.koin.compose.viewmodel.koinViewModel @Composable diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/TaskItem.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/TaskItem.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/TaskItem.kt rename to composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/TaskItem.kt index ccf202b..8b066ff 100644 --- a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/TaskItem.kt +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/components/TaskItem.kt @@ -1,4 +1,4 @@ -package com.example.testkmp.presentation +package com.example.testkmp.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/HomeScreen.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/HomeScreen.kt new file mode 100644 index 0000000..3130887 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/HomeScreen.kt @@ -0,0 +1,189 @@ +package com.example.testkmp.presentation.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.FabPosition +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Scaffold +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.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 +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.testkmp.ActionButtonColor +import com.example.testkmp.TaskManagerTheme +import com.example.testkmp.domain.models.Categories +import com.example.testkmp.presentation.AuthViewModel +import com.example.testkmp.presentation.DataState +import com.example.testkmp.presentation.HomeViewModel +import com.example.testkmp.presentation.components.AddCategoryDialog +import com.example.testkmp.presentation.components.CategoriesItem +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun HomeScreen( + modifier: Modifier, + onNavigateToSignIn: () -> Unit, + ) { + TaskManagerTheme { + + val viewModel: HomeViewModel = koinViewModel() + val authViewModel: AuthViewModel = koinViewModel() + + + // collectAsState - не привязан к жц, collectAsStateWithLifecycle - привязан, актулально только для Android + val dataState by viewModel.dataState.collectAsState() + var showAddCategoryDialog by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + viewModel.loadCatsData() + } + + Scaffold( + floatingActionButton = { + FloatingActionButton( + onClick = { + showAddCategoryDialog = true + }, + containerColor = ActionButtonColor, + ) { + Text("+") + } + }, + + floatingActionButtonPosition = FabPosition.End + ) { + when(val state = dataState) { + is DataState.Error -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxSize() + .padding(bottom = 14.dp) + + ) { + Text( + text = "Не удалось загрузить", + textAlign = TextAlign.Center + ) + } + } + + DataState.Loading -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxWidth() + .padding(bottom = 14.dp) + + ) { + Text( + text = "Категории", + fontSize = 24.sp, + modifier = Modifier + .padding(top = 18.dp, bottom = 6.dp) + ) + Spacer( + modifier = Modifier + .fillMaxWidth(0.85F) + .height(2.dp) + .background(Color.Gray) + ) + + CircularProgressIndicator( + strokeWidth = 10.dp, + modifier = Modifier + .padding(top = 40.dp) + .size(70.dp) + + ) + } + } + + is DataState.Success -> { + LazyVerticalGrid( + columns = GridCells.Adaptive(300.dp), + modifier = modifier.padding(horizontal = 6.dp), + userScrollEnabled = true, + ) { + + item(span = { GridItemSpan(maxLineSpan) }) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) + + ) { + Text( + text = "Категории", + fontSize = 24.sp, + modifier = Modifier + .padding(top = 18.dp, bottom = 6.dp) + ) + Spacer( + modifier = Modifier + .fillMaxWidth(0.85F) + .height(2.dp) + .background(Color.Gray) + ) + + Text( + text = "Выйти", + fontSize = 24.sp, + modifier = Modifier + .clickable{ + authViewModel.signOut() + onNavigateToSignIn() + } + ) + } + + } + items(state.data) { item -> + CategoriesItem( + item, + { + + } + ) + } + } + } + } + } + + if (showAddCategoryDialog) { + AddCategoryDialog( + onDismiss = { showAddCategoryDialog = false }, + onConfirm = { title, description -> + viewModel.addCategory(Categories(name = title, description = description!!)) + showAddCategoryDialog = false + } + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/SignInScreen.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/SignInScreen.kt new file mode 100644 index 0000000..b5ec81e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/SignInScreen.kt @@ -0,0 +1,113 @@ +package com.example.testkmp.presentation.screens + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +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.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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import com.example.testkmp.SignUp +import com.example.testkmp.TaskManagerTheme +import com.example.testkmp.domain.models.Result +import com.example.testkmp.presentation.AuthViewModel +import org.koin.compose.viewmodel.koinViewModel + +@Preview(showBackground = true) +@Composable +fun SignInScreen( + onNavigateToSignUp: () -> Unit, + onNavigateToHome: () -> Unit +) { + TaskManagerTheme { + + val viewModel: AuthViewModel = koinViewModel() + val authState by viewModel.authState.collectAsState() + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxSize() + .padding(bottom = 14.dp) + + ) { + var login by remember { mutableStateOf("") } + var pass by remember { mutableStateOf("") } + + Text( + text = "Добро пожаловать!", + fontSize = 22.sp + ) + + OutlinedTextField( + value = login, + onValueChange = { login = it }, + label = { Text("Логин") }, + singleLine = true, + modifier = Modifier.fillMaxWidth(0.85F) + ) + + OutlinedTextField( + value = pass, + onValueChange = { pass = it }, + label = { Text("Пароль") }, + singleLine = true, + modifier = Modifier.fillMaxWidth(0.85F) + ) + + Button( + modifier = Modifier + .fillMaxWidth(0.3F) + .padding(top = 10.dp), + onClick = { + if(login.isNotBlank() && pass.isNotBlank()) { + viewModel.signIn(login, pass) + } + } + ) { + Text("Войти") + } + + Button( + onClick = { + onNavigateToSignUp() + } + ) { + Text( + text = "Зарегиcтрироваться", + ) + } + + when (authState) { + is Result.Error -> { + + } + is Result.Loading -> { + + } + is Result.Success -> { + onNavigateToHome() + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/SignUpScreen.kt b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/SignUpScreen.kt new file mode 100644 index 0000000..138ecd3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/testkmp/presentation/screens/SignUpScreen.kt @@ -0,0 +1,105 @@ +package com.example.testkmp.presentation.screens + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.example.testkmp.TaskManagerTheme +import com.example.testkmp.presentation.AuthViewModel +import org.koin.compose.viewmodel.koinViewModel + +@Preview(showBackground = true) +@Composable +fun SignUpScreen( + onNavigateToSignIn: () -> Unit +) { + TaskManagerTheme { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxSize() + .padding(bottom = 14.dp) + + ) { + val viewModel: AuthViewModel = koinViewModel() + + var login by remember { mutableStateOf("") } + var email by remember { mutableStateOf("") } + var pass by remember { mutableStateOf("") } + + Text( + text = "Добро пожаловать!", + fontSize = 22.sp + ) + + OutlinedTextField( + value = login, + onValueChange = { login = it }, + label = { Text("Имя") }, + singleLine = true, + modifier = Modifier.fillMaxWidth(0.85F) + ) + + OutlinedTextField( + value = email, + onValueChange = { email = it }, + label = { Text("Почта") }, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email), + modifier = Modifier.fillMaxWidth(0.85F) + ) + + OutlinedTextField( + value = pass, + onValueChange = { pass = it }, + label = { Text("Пароль") }, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + modifier = Modifier.fillMaxWidth(0.85F) + ) + + Button( + modifier = Modifier + .padding(top = 10.dp), + onClick = { + if(login.isNotBlank() && pass.isNotBlank()) { + viewModel.signUp(email, pass, login) + } + } + ) { + Text("Зарегистрироваться") + } + + Button( + onClick = { + onNavigateToSignIn() + } + ) { + Text( + text = "Войти", + ) + } + + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3fbf46d..6b90fdf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ koin = "4.1.1" firebase = "2.2.0" supabase = "3.4.1" ktor = "3.4.1" +navigation-compose = "2.9.2" [libraries] @@ -54,6 +55,7 @@ ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "k ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin" } +androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigation-compose" } [plugins]