Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
210 changes: 58 additions & 152 deletions composeApp/src/commonMain/kotlin/com/example/testkmp/App.kt
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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<Home> {
//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<SignIn> {
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<SignUp> {
SignUpScreen(
onNavigateToSignIn = {
navController.navigate(SignIn)
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<UserInfo?> {
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<UserInfo?> {
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
}
}
}
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -21,13 +29,23 @@ val sharedModule = module {

factory { AddCategoryUseCase(get()) }

factory { SignUpUseCase(get()) }

factory { SignInUseCase(get()) }

factory { SignOutUseCase(get()) }

single<DatabaseRepository> { FakeRepositoryImpl() }

single<AuthRepository> { SupabaseAuthRepositoryImpl() }
}

val viewModelModule = module {

viewModelOf(::HomeViewModel)

viewModelOf(::AuthViewModel)

}

val appModule = module {
Expand Down
Loading
Loading