From 565abdca1878c108a9a551c4b0a50b1e88b2e654 Mon Sep 17 00:00:00 2001 From: 1billy17 Date: Fri, 9 May 2025 12:35:00 +0300 Subject: [PATCH] bee --- composeApp/build.gradle.kts | 56 +- .../project/ViewModel/DatePickerField.kt | 33 ++ .../org/example/project/ViewModel/GetPath.kt | 50 ++ .../example/project/ViewModel/IconPrinter.kt | 23 + .../project/ViewModel/ImageFromPath.kt | 44 ++ .../project/ViewModel/UserRepository.kt | 69 +++ .../composeResources/drawable/defPhoto.png | Bin 0 -> 10550 bytes .../composeResources/drawable/map.jpg | Bin 0 -> 45634 bytes .../kotlin/org/example/project/Models/Fond.kt | 9 + .../kotlin/org/example/project/Models/User.kt | 17 + .../project/ViewModel/DatePickerField.kt | 10 + .../project/ViewModel/FieldWithDropdown.kt | 106 ++++ .../project/ViewModel/FieldWithLabel.kt | 71 +++ .../project/ViewModel/FieldWithLabelInt.kt | 85 +++ .../org/example/project/ViewModel/GetPath.kt | 7 + .../example/project/ViewModel/IconPrinter.kt | 7 + .../project/ViewModel/ImageFromPath.kt | 7 + .../example/project/ViewModel/InfoButton.kt | 33 ++ .../project/ViewModel/RegRunnerViewModel.kt | 18 + .../project/ViewModel/TimerViewModel.kt | 11 +- .../project/ViewModel/UserRepository.kt | 14 + .../project/ViewModel/UserViewModel.kt | 36 ++ .../project/ui/Screens/AboutMarathon.kt | 346 +++++++++++ .../project/ui/Screens/ConfirmRunner.kt | 217 +++++++ .../project/ui/Screens/ConfirmSponsor.kt | 234 ++++++++ .../example/project/ui/Screens/ForRunner.kt | 203 ++++--- .../example/project/ui/Screens/ForSponsor.kt | 466 +++++++++++++++ .../example/project/ui/Screens/InfoRunner.kt | 215 +++++++ .../org/example/project/ui/Screens/Login.kt | 258 +++++++++ .../example/project/ui/Screens/MainScreen.kt | 185 +++--- .../example/project/ui/Screens/MoreInfo.kt | 268 ++++----- .../example/project/ui/Screens/RegMaraphon.kt | 422 ++++++++++++++ .../example/project/ui/Screens/RegRunner.kt | 540 ++++++++++++++++++ .../src/desktopMain/kotlin/Tables/Connect.kt | 17 + .../src/desktopMain/kotlin/Tables/Fonds.kt | 13 + .../src/desktopMain/kotlin/Tables/Users.kt | 18 + .../kotlin/ViewModel/DatePickerField.kt | 38 ++ .../desktopMain/kotlin/ViewModel/GetPath.kt | 29 + .../kotlin/ViewModel/IconPrinter.kt | 33 ++ .../kotlin/ViewModel/ImageFromPath.kt | 45 ++ .../kotlin/ViewModel/UserRepository.kt | 128 +++++ .../kotlin/org/example/project/main.kt | 1 + 42 files changed, 4056 insertions(+), 326 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/org/example/project/ViewModel/DatePickerField.kt create mode 100644 composeApp/src/androidMain/kotlin/org/example/project/ViewModel/GetPath.kt create mode 100644 composeApp/src/androidMain/kotlin/org/example/project/ViewModel/IconPrinter.kt create mode 100644 composeApp/src/androidMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt create mode 100644 composeApp/src/androidMain/kotlin/org/example/project/ViewModel/UserRepository.kt create mode 100644 composeApp/src/commonMain/composeResources/drawable/defPhoto.png create mode 100644 composeApp/src/commonMain/composeResources/drawable/map.jpg create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/Models/Fond.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/Models/User.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/DatePickerField.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithDropdown.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabelInt.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/GetPath.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/IconPrinter.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/InfoButton.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/RegRunnerViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/AboutMarathon.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmRunner.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmSponsor.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForSponsor.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/InfoRunner.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/Login.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegMaraphon.kt create mode 100644 composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegRunner.kt create mode 100644 composeApp/src/desktopMain/kotlin/Tables/Connect.kt create mode 100644 composeApp/src/desktopMain/kotlin/Tables/Fonds.kt create mode 100644 composeApp/src/desktopMain/kotlin/Tables/Users.kt create mode 100644 composeApp/src/desktopMain/kotlin/ViewModel/DatePickerField.kt create mode 100644 composeApp/src/desktopMain/kotlin/ViewModel/GetPath.kt create mode 100644 composeApp/src/desktopMain/kotlin/ViewModel/IconPrinter.kt create mode 100644 composeApp/src/desktopMain/kotlin/ViewModel/ImageFromPath.kt create mode 100644 composeApp/src/desktopMain/kotlin/ViewModel/UserRepository.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index af7c6d3..902bab3 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -18,10 +18,42 @@ kotlin { } jvm("desktop") - + sourceSets { - val desktopMain by getting - + val exposedVersion = "0.61.0" + + val desktopMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(libs.kotlinx.coroutines.swing) + implementation("io.ktor:ktor-client-okhttp:2.3.2") + + implementation("org.postgresql:postgresql:42.7.1") + + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-crypt:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jodatime:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-kotlin-datetime:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-json:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-money:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-spring-boot-starter:$exposedVersion") + + implementation("io.ktor:ktor-server-core:2.3.4") + implementation("io.ktor:ktor-server-netty:2.3.4") + implementation("io.ktor:ktor-server-content-negotiation:2.3.4") + implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.4") + implementation("io.ktor:ktor-server-status-pages:2.3.4") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + } + } + + val commonMain by getting { + resources.srcDir("src/commonMain/composeResources") + } + androidMain.dependencies { implementation(compose.preview) implementation(libs.androidx.activity.compose) @@ -40,10 +72,16 @@ kotlin { implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion") implementation(compose.preview) implementation(libs.androidx.lifecycle.viewmodel) - } - desktopMain.dependencies { - implementation(compose.desktop.currentOs) - implementation(libs.kotlinx.coroutines.swing) + implementation("media.kamel:kamel-image:0.7.0") + implementation("io.ktor:ktor-client-core:2.3.2") + implementation("io.ktor:ktor-client-cio:2.3.2") + implementation("io.ktor:ktor-client-okhttp:2.3.2") + implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") + + repositories { + google() + mavenCentral() + } } } } @@ -72,8 +110,12 @@ android { compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 + isCoreLibraryDesugaringEnabled = true } } +dependencies { + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") +} compose.desktop { application { diff --git a/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/DatePickerField.kt b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/DatePickerField.kt new file mode 100644 index 0000000..8515410 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/DatePickerField.kt @@ -0,0 +1,33 @@ +package org.example.project.ViewModel + +import android.app.DatePickerDialog +import android.widget.DatePicker +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalContext +import java.util.* + +@Composable +actual fun DatePickerField( + onDateSelected: (String) -> Unit, + trigger: Boolean, + onDismissRequest: () -> Unit +) { + val context = LocalContext.current + + if (trigger) { + LaunchedEffect(Unit) { + val calendar = Calendar.getInstance() + DatePickerDialog( + context, + { _: DatePicker, year: Int, month: Int, dayOfMonth: Int -> + val formatted = "%02d.%02d.%04d".format(dayOfMonth, month + 1, year) + onDateSelected(formatted) + onDismissRequest() + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ).show() + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/GetPath.kt b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/GetPath.kt new file mode 100644 index 0000000..4568ddb --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/GetPath.kt @@ -0,0 +1,50 @@ +package org.example.project.ViewModel + +import android.net.Uri +import android.provider.OpenableColumns +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalContext +import java.io.File +import java.io.FileOutputStream + +@Composable +actual fun GetPath(onPathSelected: (String) -> Unit) { + val context = LocalContext.current + var launched by remember { mutableStateOf(false) } + + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent(), + onResult = { uri: Uri? -> + if (uri != null) { + val inputStream = context.contentResolver.openInputStream(uri) + val fileName = getFileName(context.contentResolver, uri) ?: "temp_image.jpg" + val file = File(context.cacheDir, fileName) + val outputStream = FileOutputStream(file) + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + onPathSelected(file.absolutePath) + } + } + ) + + LaunchedEffect(Unit) { + if (!launched) { + launched = true + launcher.launch("image/*") + } + } +} + +fun getFileName(contentResolver: android.content.ContentResolver, uri: Uri): String? { + val returnCursor = contentResolver.query(uri, null, null, null, null) + returnCursor?.use { + val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (it.moveToFirst()) { + return it.getString(nameIndex) + } + } + return null +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/IconPrinter.kt b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/IconPrinter.kt new file mode 100644 index 0000000..7eea79d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/IconPrinter.kt @@ -0,0 +1,23 @@ +package org.example.project.ViewModel + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import org.example.project.R + +@Composable +actual fun IconPrinter(resourceName: String, modifier: Modifier) { + val resId = when (resourceName) { + "calendar" -> R.drawable.map + else -> return + } + + Image( + painter = painterResource(id = resId), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.FillBounds + ) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt new file mode 100644 index 0000000..137b335 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt @@ -0,0 +1,44 @@ +package org.example.project.ViewModel + +import android.graphics.BitmapFactory +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.imageResource +import org.example.project.R +import java.io.File + + +@Composable +actual fun ImageFromPath(path: String, modifier: Modifier) { + val imageBitmap: ImageBitmap? = when { + path.isEmpty() -> { + ImageBitmap.imageResource(id = R.drawable.basic_photo) + } + + else -> { + val file = File(path) + if (file.exists()) { + val bitmap = BitmapFactory.decodeFile(file.absolutePath) + bitmap?.asImageBitmap() + } else null + } + } + + if (imageBitmap != null) { + Image( + painter = BitmapPainter(imageBitmap), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.Crop + ) + } else { + Box(modifier = modifier.fillMaxSize()) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/UserRepository.kt b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/UserRepository.kt new file mode 100644 index 0000000..fee7d8a --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/example/project/ViewModel/UserRepository.kt @@ -0,0 +1,69 @@ +package org.example.project.ViewModel + +import android.os.Build +import androidx.annotation.RequiresApi +import org.example.project.Models.Fond +import org.example.project.Models.User +import java.time.LocalDate + +actual class UserRepository { + @RequiresApi(Build.VERSION_CODES.O) + actual fun fetchUsers(): List { + var users = listOf( + User( + id = 1, + email = "email1@gmail.com", + password = "qwerty", + name = "bob", + surname = "bobby", + gender = "Мужской", + photopath = "/Users/rinchi/Desktop/vpn4ik.jpg", + LocalDate.of(2006, 1, 16), + country = "Russia", + active = false + ), + User( + id = 2, + email = "email2@gmail.com", + password = "qwerty", + name = "bob", + surname = "bobby", + gender = "Мужской", + photopath = "/Users/rinchi/Desktop/vpn4ik.jpg", + LocalDate.of(2006, 1, 16), + country = "Russia", + active = false + ), ) + return users + } + actual fun regUser(user: User): Boolean { + return true + } + actual fun fetchFonds(): List { + var fonds = listOf( + Fond( + id = 1, + name = "first", + balance = 1000 + ), + Fond( + id = 2, + name = "second", + balance = 2000 + ), + ) + return fonds + } + actual fun withdrawalBalance(idFond: Int, dedSum: Int): Boolean { + return true + } + actual fun getUserIdByEmail(email: String): Int { + return 0 + } + actual fun updateUserActive(idUser: Int): Boolean{ + return true + } + actual fun topUpBalance(idFond: Int, sum: Int): Boolean{ + return true + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/defPhoto.png b/composeApp/src/commonMain/composeResources/drawable/defPhoto.png new file mode 100644 index 0000000000000000000000000000000000000000..854f3630f9b435496ea2ed25878d742b41af3be3 GIT binary patch literal 10550 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 I-?t6>UlhaEF#rGn literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/composeResources/drawable/map.jpg b/composeApp/src/commonMain/composeResources/drawable/map.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dc4ae8057b20cf1456db3acd309a8fafd7ca6c0b GIT binary patch literal 45634 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 Unit, + trigger: Boolean, + onDismissRequest: () -> Unit +) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithDropdown.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithDropdown.kt new file mode 100644 index 0000000..acd2645 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithDropdown.kt @@ -0,0 +1,106 @@ +package org.example.project.ViewModel + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +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.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp + +@Composable +fun FieldWithDropdown( + label: String, + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + fontSize: TextUnit, + labelWidth: Float +) { + var expanded by remember { mutableStateOf(false) } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Box(modifier = Modifier.weight(labelWidth)) { + Text( + text = label, + color = Color.DarkGray, + textAlign = TextAlign.Start, + fontSize = fontSize, + ) + } + + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .background(Color.White, shape = RoundedCornerShape(4.dp)) + .border( + width = 1.dp, + color = Color(0xFFCCCCCC), + shape = RoundedCornerShape(4.dp) + ) + .clickable { expanded = true } + .padding(12.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = selectedOption.ifEmpty { "..." }, + fontSize = fontSize, + color = if (selectedOption.isEmpty()) Color.LightGray else Color.Black + ) + + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = "Dropdown Arrow", + tint = Color.Gray + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .background(Color.White) + .fillMaxWidth() + ) { + options.forEach { option -> + DropdownMenuItem( + onClick = { + onOptionSelected(option) + expanded = false + } + ) { + Text(option, fontSize = fontSize, color = Color.Black) + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabel.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabel.kt new file mode 100644 index 0000000..d962fe1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabel.kt @@ -0,0 +1,71 @@ +package org.example.project.ViewModel + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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.TextUnit + +@Composable +fun FieldWithLabel( + label: String, + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + fontSize: TextUnit, + labelWidth: Float, + enabled: Boolean = true, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Box(modifier = Modifier.weight(labelWidth)) { + Text( + text = label, + color = Color.DarkGray, + textAlign = TextAlign.Start, + fontSize = fontSize, + modifier = Modifier + ) + } + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + placeholder = { + if (placeholder.isNotEmpty()) { + Text( + text = placeholder, + fontSize = fontSize, + color = Color.LightGray + ) + } + }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSize, + color = Color.Black + ), + enabled = enabled, + colors = androidx.compose.material.TextFieldDefaults.outlinedTextFieldColors( + backgroundColor = Color.White, + cursorColor = Color.Black, + textColor = Color.Black, + placeholderColor = Color.LightGray, + focusedBorderColor = Color(0xFFCCCCCC), + unfocusedBorderColor = Color(0xFFCCCCCC), + disabledBorderColor = Color(0xFFCCCCCC) + ), + modifier = Modifier.weight(1f).wrapContentWidth().fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabelInt.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabelInt.kt new file mode 100644 index 0000000..f224e49 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/FieldWithLabelInt.kt @@ -0,0 +1,85 @@ +package org.example.project.ViewModel + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit + +@Composable +fun FieldWithLabelInt( + label: String, + value: Int, + onValueChange: (Int) -> Unit, + placeholder: String, + fontSize: TextUnit, + labelWidth: Float, + enabled: Boolean = true, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Box(modifier = Modifier.weight(labelWidth)) { + Text( + text = label, + color = Color.DarkGray, + textAlign = TextAlign.Start, + fontSize = fontSize, + ) + } + + OutlinedTextField( + value = value.toString(), + onValueChange = { newValue -> + val intValue = newValue.toIntOrNull() + if (intValue != null) { + onValueChange(intValue) + } + }, + placeholder = { + if (placeholder.isNotEmpty()) { + Text( + text = placeholder, + fontSize = fontSize, + color = Color.LightGray + ) + } + }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSize, + color = Color.Black + ), + enabled = enabled, + colors = androidx.compose.material.TextFieldDefaults.outlinedTextFieldColors( + backgroundColor = Color.White, + cursorColor = Color.Black, + textColor = Color.Black, + placeholderColor = Color.LightGray, + focusedBorderColor = Color(0xFFCCCCCC), + unfocusedBorderColor = Color(0xFFCCCCCC), + disabledBorderColor = Color(0xFFCCCCCC) + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done + ), + modifier = Modifier + .weight(1f) + .wrapContentWidth() + .fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/GetPath.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/GetPath.kt new file mode 100644 index 0000000..efbcb70 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/GetPath.kt @@ -0,0 +1,7 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +expect fun GetPath(onPathSelected: (String) -> Unit) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/IconPrinter.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/IconPrinter.kt new file mode 100644 index 0000000..1ab6505 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/IconPrinter.kt @@ -0,0 +1,7 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +expect fun IconPrinter(resourceName: String, modifier: Modifier = Modifier) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt new file mode 100644 index 0000000..7ea323c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/ImageFromPath.kt @@ -0,0 +1,7 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +expect fun ImageFromPath(path: String = "", modifier: Modifier = Modifier) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/InfoButton.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/InfoButton.kt new file mode 100644 index 0000000..3d57283 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/InfoButton.kt @@ -0,0 +1,33 @@ +package org.example.project.ViewModel + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp + + +@Composable +fun InfoButton(text: String, fontSize: TextUnit, onClick: () -> Unit) { + Button( + onClick = onClick, + modifier = Modifier + .fillMaxWidth() + .height(80.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp), + ) { + Text(text = text, fontStyle = FontStyle.Italic, fontSize = fontSize, textAlign = TextAlign.Center) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/RegRunnerViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/RegRunnerViewModel.kt new file mode 100644 index 0000000..80de433 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/RegRunnerViewModel.kt @@ -0,0 +1,18 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel + +class RegRunnerViewModel : ViewModel() { + var email by mutableStateOf("") + var password by mutableStateOf("") + var secondPassword by mutableStateOf("") + var name by mutableStateOf("") + var surname by mutableStateOf("") + var gender by mutableStateOf("") + var country by mutableStateOf("") + var photoPath by mutableStateOf("") + var dateBirthday by mutableStateOf("") +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/TimerViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/TimerViewModel.kt index 5a9be4d..1fe6979 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/TimerViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/TimerViewModel.kt @@ -6,16 +6,21 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.util.Locale class TimerViewModel : ViewModel() { - private val _remainingTime = MutableStateFlow("18 дней 17 часов и 10 минут до старта марафона") - val remainingTime: StateFlow = _remainingTime - init { startTimer() } + private val _remainingTime = MutableStateFlow("18 дней 17 часов и 10 минут до старта марафона") + val remainingTime: StateFlow = _remainingTime + private fun startTimer() { viewModelScope.launch { var remainingMillis = (18 * 24 * 60 * 60 * 1000) + (17 * 60 * 60 * 1000) + (10 * 60 * 1000) diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserRepository.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserRepository.kt new file mode 100644 index 0000000..396d726 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserRepository.kt @@ -0,0 +1,14 @@ +package org.example.project.ViewModel + +import org.example.project.Models.Fond +import org.example.project.Models.User + +expect class UserRepository() { + fun fetchUsers(): List + fun regUser(user: User): Boolean + fun fetchFonds(): List + fun withdrawalBalance(idFond: Int, dedSum: Int): Boolean + fun getUserIdByEmail(email: String): Int + fun updateUserActive(idUser: Int): Boolean + fun topUpBalance(idFond: Int, sum: Int): Boolean +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserViewModel.kt new file mode 100644 index 0000000..3719460 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ViewModel/UserViewModel.kt @@ -0,0 +1,36 @@ +package org.example.project.ViewModel + +import org.example.project.Models.Fond +import org.example.project.Models.User + +class UserViewModel { + private val userRepository = UserRepository() + + fun getUsers(): List { + return userRepository.fetchUsers() + } + + fun regUser(user: User): Boolean { + return userRepository.regUser(user) + } + + fun getFond(): List{ + return userRepository.fetchFonds() + } + + fun withdrawalBalance(idFond: Int, dedSum: Int): Boolean{ + return userRepository.withdrawalBalance(idFond, dedSum) + } + + fun getUserIdByEmail(email: String): Int{ + return userRepository.getUserIdByEmail(email) + } + + fun updateUserActive(idUser: Int): Boolean{ + return userRepository.updateUserActive(idUser) + } + + fun topUpBalance(idFond: Int, sum: Int): Boolean{ + return userRepository.topUpBalance(idFond, sum) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/AboutMarathon.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/AboutMarathon.kt new file mode 100644 index 0000000..cf2553c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/AboutMarathon.kt @@ -0,0 +1,346 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DateRange +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +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.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.IconPrinter +import org.example.project.ViewModel.TimerViewModel + + +class AboutMarathonScreen(private val timerViewModel: TimerViewModel) : Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + Scaffold( + topBar = { AboutMarathonTopBar() }, + bottomBar = { AboutMarathonBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + Box( + modifier = Modifier + .padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 20.dp) + ) { + val fontSize = (maxWidth.value / 25).sp + val fontSizeSecond = (maxWidth.value / 35).sp + + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + Spacer(modifier = Modifier.weight(0.1f)) + + Text( + text = "Информация о Marathon Skills 2016", + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSize, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(0.6f) + .padding(end = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .aspectRatio(3f) + .fillMaxWidth() + .border(width = 2.dp, color = Color.Black, shape = RoundedCornerShape(15.dp)) + .clip(RoundedCornerShape(15.dp)) + .background(Color.LightGray) + .clickable { }, + contentAlignment = Alignment.Center + ) { + IconPrinter( + resourceName = "map", + modifier = Modifier.fillMaxSize() + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Box( + modifier = Modifier + .weight(1f) + .aspectRatio(1.7f) + .border(2.dp, Color.Black, RoundedCornerShape(15.dp)) + .clip(RoundedCornerShape(15.dp)) + .background(Color.LightGray) + .clickable { }, + contentAlignment = Alignment.Center + ) { + IconPrinter( + resourceName = "map", + modifier = Modifier.fillMaxSize() + ) + } + + Box( + modifier = Modifier + .weight(1f) + .aspectRatio(1.7f) + .border(2.dp, Color.Black, RoundedCornerShape(15.dp)) + .clip(RoundedCornerShape(15.dp)) + .background(Color.LightGray) + .clickable { }, + contentAlignment = Alignment.Center + ) { + IconPrinter( + resourceName = "map", + modifier = Modifier.fillMaxSize() + ) + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Box( + modifier = Modifier + .weight(1f) + .aspectRatio(1.7f) + .border(2.dp, Color.Black, RoundedCornerShape(15.dp)) + .clip(RoundedCornerShape(15.dp)) + .background(Color.LightGray) + .clickable { }, + contentAlignment = Alignment.Center + ) { + IconPrinter( + resourceName = "map", + modifier = Modifier.fillMaxSize() + ) + } + + Box( + modifier = Modifier + .weight(1f) + .aspectRatio(1.7f) + .border(2.dp, Color.Black, RoundedCornerShape(15.dp)) + .clip(RoundedCornerShape(15.dp)) + .background(Color.LightGray) + .clickable { }, + contentAlignment = Alignment.Center + ) { + IconPrinter( + resourceName = "map", + modifier = Modifier.fillMaxSize() + ) + } + } + } + + Column( + modifier = Modifier + .weight(1f) + .padding(start = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Информация о Marathon Skills 2016", + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.weight(0.3f)) + + Text( + text = "Информация о Marathon Skills 2016", + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.weight(0.3f)) + + Text( + text = "Информация о Marathon Skills 2016", + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.weight(0.3f)) + } + } + } + } + } + } + } + + @Composable + private fun AboutMarathonTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Logout", + color = Color.Black, + fontSize = fontSizeButton + ) + } + } + } + } + } + + @Composable + private fun AboutMarathonBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmRunner.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmRunner.kt new file mode 100644 index 0000000..9fae2ea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmRunner.kt @@ -0,0 +1,217 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.TimerViewModel + +class ConfirmRunnerScreen(private val timerViewModel: TimerViewModel, private val idRunner: Int) : + Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + Scaffold( + topBar = { ConfirmRunnerTopBar() }, + bottomBar = { ConfirmRunnerBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + Box( + modifier = Modifier.padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) + ) { + val fontSizeFirst = (maxWidth.value / 30).sp + val fontSizeSecond = (maxWidth.value / 40).sp + + Column( + modifier = Modifier + .fillMaxWidth().padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.weight(0.2f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Спасибо за вашу регистрацию в качестве бегуна!", + color = Color.Gray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.2f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Спасибо за вашу регистрацию в качестве бегуна в Marathon Skills 2016!\n С вами свяжутся по поводу оплаты.", + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.2f)) + + Button( + onClick = { navigator.push(InfoRunnerScreen(timerViewModel, idRunner)) }, + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Ок", fontSize = fontSizeSecond) + } + + Spacer(modifier = Modifier.weight(1f)) + } + } + } + } + } + + @Composable + private fun ConfirmRunnerTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Logout", + color = Color.Black, + fontSize = fontSizeButton + ) + } + } + } + } + } + + + @Composable + private fun ConfirmRunnerBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmSponsor.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmSponsor.kt new file mode 100644 index 0000000..2fc5556 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ConfirmSponsor.kt @@ -0,0 +1,234 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.TimerViewModel + +class ConfirmSponsorScreen(private val timerViewModel: TimerViewModel, private val sumSpons: String, private val runner: String, private val blagot: String) : + Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + Scaffold( + topBar = { ConfirmSponsorTopBar() }, + bottomBar = { ConfirmSponsorBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + Box( + modifier = Modifier.padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) + ) { + val fontSize = (maxWidth.value / 60).sp + val fontSizeFirst = (maxWidth.value / 30).sp + val fontSizeSecond = (maxWidth.value / 40).sp + val fontSizeThird = (maxWidth.value / 50).sp + val fontSizeFourth = (maxWidth.value / 10).sp + val fontSizeFiveth = (maxWidth.value / 20).sp + + Column( + modifier = Modifier + .fillMaxWidth().padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Спасибо за вашу спонсорскую поддержку!", + color = Color.Gray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Спасибо за поддержку бегуна в Marathon Skills 2016! Ваше пожертвование пойдёт в его благотворительную организацию", + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = runner, + color = Color.Black, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = blagot, + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFiveth, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = sumSpons, + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFourth, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Button( + onClick = { navigator.push(MainScreen()) }, + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Назад", fontSize = fontSizeSecond) + } + } + } + } + } + } + + @Composable + private fun ConfirmSponsorTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + } + } + } + } + + + + @Composable + private fun ConfirmSponsorBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForRunner.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForRunner.kt index 9873b18..68d5bb1 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForRunner.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForRunner.kt @@ -23,71 +23,88 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel +import org.example.project.ViewModel.RegRunnerViewModel import org.example.project.ViewModel.TimerViewModel class RunnerScreen(private val timerViewModel: TimerViewModel) : Screen { @Composable override fun Content() { - - val buttonSize = Modifier - .width(400.dp) - .height(80.dp) + val navigator = LocalNavigator.currentOrThrow Scaffold( topBar = { RunnerTopBar() }, bottomBar = { RunnerBottomBar(timerViewModel = timerViewModel) } ) { paddingValues -> + Box( modifier = Modifier .padding(paddingValues) - .fillMaxSize() ) { - Column( + BoxWithConstraints( modifier = Modifier .fillMaxWidth() .align(Alignment.Center) - .padding(horizontal = 30.dp), - horizontalAlignment = Alignment.CenterHorizontally, + .padding(horizontal = 100.dp) ) { - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text("Я участвовал ранее", - fontSize = 16.sp - ) - } + val fontSize = (maxWidth.value / 25).sp - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, ) { - Text("Я новый участник", - fontSize = 16.sp - ) + Spacer(Modifier.weight(1f)) + + Button( + onClick = { navigator.push(LoginScreen(timerViewModel)) }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text( + "Я участвовал ранее", + fontSize = fontSize + ) + } + + Spacer(modifier = Modifier.weight(0.25f)) + + Button( + onClick = { navigator.push(RegRunnerScreen(timerViewModel)) }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text( + "Я новый участник", + fontSize = fontSize + ) + } + + Spacer(Modifier.weight(1f)) } } - Button( - onClick = { }, + onClick = { navigator.push(LoginScreen(timerViewModel)) }, modifier = Modifier .align(Alignment.BottomEnd) .height(65.dp) @@ -108,61 +125,85 @@ class RunnerScreen(private val timerViewModel: TimerViewModel) : Screen { private fun RunnerTopBar() { val navigator = LocalNavigator.currentOrThrow - Row( - modifier = Modifier - .fillMaxWidth() - .background(Color.DarkGray) - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - - Button( - onClick = { navigator.pop() }, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - elevation = null, - shape = RoundedCornerShape(15.dp), + Box { + BoxWithConstraints( modifier = Modifier - .wrapContentWidth() - + .fillMaxWidth() + .align(Alignment.Center) ) { - Text(text = "Назад", - color = Color.Black, - ) + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.pop() }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + } } - - Spacer(modifier = Modifier.width(24.dp)) - - Text( - text = "MARATHON SKILLS 2016", - style = MaterialTheme.typography.h6, - modifier = Modifier.weight(1f), - textAlign = TextAlign.Left, - fontWeight = FontWeight.Bold, - fontSize = MaterialTheme.typography.h4.fontSize, - color = Color.White - ) - - Spacer(modifier = Modifier.width(64.dp)) } } @Composable private fun RunnerBottomBar(timerViewModel: TimerViewModel) { val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp - Column( - modifier = Modifier - .fillMaxWidth() - .background(Color.DarkGray) - .padding(8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text(text = remainingTime, - color = Color.White) + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForSponsor.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForSponsor.kt new file mode 100644 index 0000000..34efaee --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/ForSponsor.kt @@ -0,0 +1,466 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.FieldWithDropdown +import org.example.project.ViewModel.FieldWithLabel +import org.example.project.ViewModel.TimerViewModel +import org.example.project.ViewModel.UserViewModel + +class ForSponsorScreen(private val timerViewModel: TimerViewModel) : Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val viewModel = UserViewModel() + val fonds = viewModel.getFond() + val runners = viewModel.getUsers() + + Scaffold( + topBar = { ForSponsorTopBar() }, + bottomBar = { ForSposnorBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + Box( + modifier = Modifier.padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) + ) { + val fontSizeFirst = (maxWidth.value / 30).sp + val fontSizeSecond = (maxWidth.value / 40).sp + val fontSizeThird = (maxWidth.value / 50).sp + val fontSizeFourth = (maxWidth.value / 10).sp + val labelWidth = 0.5f + var fondsNames = fonds.map { it.name } + var runnersNames = runners.map { it.name } + var selectedFondName by remember { mutableStateOf("") } + var fio by remember { mutableStateOf("") } + var runner by remember { mutableStateOf("") } + var cardName by remember { mutableStateOf("") } + var cardNum by remember { mutableStateOf("") } + var srokM by remember { mutableStateOf("") } + var srokY by remember { mutableStateOf("") } + var cvc by remember { mutableStateOf("") } + var sumChangStr by remember { mutableStateOf("10")} + var sumChang by remember { mutableStateOf(sumChangStr.toInt()) } + var sumSpons by remember { mutableStateOf(0)} + + Column( + modifier = Modifier + .fillMaxWidth().padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "Спонсор бегуна", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.weight(0.1f)) + + Text( + text = "Пожалуйста выберите бегуна, которого вы хотели бы спонсировать и сумму, которую вы хотели бы спонсировать. Спасибо за вашу поддержку бегунов и их благотворительных учреждений.", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(0.2f)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Column(modifier = Modifier.weight(1f),) { + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Информация о спонсоре", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Ваше имя:", + value = fio, + onValueChange = { fio = it }, + placeholder = "Ваше имя", + fontSize = fontSizeSecond, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithDropdown( + label = "Бегун:", + options = runnersNames, + selectedOption = runner, + onOptionSelected = { runner = it }, + fontSize = fontSizeSecond, + labelWidth = labelWidth + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Карта:", + value = cardName, + onValueChange = { cardName = it }, + placeholder = "Владелец карты", + fontSize = fontSizeSecond, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Номер карты#:", + value = cardNum, + onValueChange = { cardNum = it }, + placeholder = "...", + fontSize = fontSizeSecond, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "Срок действия:", + fontSize = fontSizeSecond, + color = Color.Black, + modifier = Modifier.weight(labelWidth) + ) + + OutlinedTextField( + value = srokM, + onValueChange = { srokM = it }, + placeholder = { Text("MM", fontSize = fontSizeSecond) }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSizeSecond, + color = Color.Black + ), + modifier = Modifier + .weight((1f - labelWidth) / 2) + .padding(end = 4.dp), + singleLine = true + ) + + OutlinedTextField( + value = srokY, + onValueChange = { srokY = it }, + placeholder = { Text("YY", fontSize = fontSizeSecond) }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSizeSecond, + color = Color.Black + ), + modifier = Modifier + .weight((1f - labelWidth) / 2) + .padding(start = 4.dp), + singleLine = true + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "CVC:", + value = cvc, + onValueChange = { cvc = it }, + placeholder = "...", + fontSize = fontSizeSecond, + labelWidth = labelWidth, + ) + } + + Column(modifier = Modifier.weight(1f),) { + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Благотворительность", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithDropdown( + label = "", + options = fondsNames, + selectedOption = selectedFondName, + onOptionSelected = { selectedFondName = it }, + fontSize = fontSizeSecond, + labelWidth = 0.5f + ) + + Spacer(modifier = Modifier.weight(0.5f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Сумма пожертвования", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "$" + sumSpons.toString(), + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFourth, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Button( + modifier = Modifier + .weight(0.2f) + .aspectRatio(1f), + onClick = { + sumSpons -= sumChang + }, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + ) { + Text("-", fontSize = fontSizeSecond) + } + + OutlinedTextField( + value = sumChangStr, + onValueChange = { + sumChangStr = it + sumChang = it.toIntOrNull() ?: 0 + }, + placeholder = { Text("...", fontSize = fontSizeSecond) }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSizeSecond, + color = Color.Black, + textAlign = TextAlign.Center + ), + modifier = Modifier + .weight(0.6f) + .padding(horizontal = 4.dp), + singleLine = true + ) + + Button( + modifier = Modifier + .weight(0.2f) + .aspectRatio(1f), + onClick = { + sumSpons += sumChang + }, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + ) { + Text("+", fontSize = fontSizeSecond) + } + } + + Spacer(modifier = Modifier.weight(0.5f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Button( + onClick = { + val selectedFond = fonds.find { it.name == selectedFondName } + if (selectedFond != null) { + val result = viewModel.topUpBalance(selectedFond.id, sumSpons) + if (result){ + navigator.push(ConfirmSponsorScreen(timerViewModel, sumSpons.toString(), runner, selectedFondName)) + } + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Заплатить", fontSize = fontSizeThird) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Button( + onClick = { navigator.push(MainScreen()) }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Отмена", fontSize = fontSizeThird) + } + } + } + } + } + } + } + } + } + + @Composable + private fun ForSponsorTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + } + } + } + } + + + + @Composable + private fun ForSposnorBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/InfoRunner.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/InfoRunner.kt new file mode 100644 index 0000000..13305c2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/InfoRunner.kt @@ -0,0 +1,215 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +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.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.InfoButton +import org.example.project.ViewModel.TimerViewModel + + +class InfoRunnerScreen(private val timerViewModel: TimerViewModel, private val idRunner: Int) : Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + Scaffold( + topBar = { InfoRunnerTopBar() }, + bottomBar = { InfoRunnerBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + Box( + modifier = Modifier + .padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) + ) { + val fontSize = (maxWidth.value / 25).sp + val fontSizeButton = (maxWidth.value / 40).sp + + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Подробная информация", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSize, + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + InfoButton("Регистрация на марафон", fontSizeButton, {navigator.push(RegMaraphonScreen(timerViewModel, idRunner))}) + InfoButton("Редактирование профиля", fontSizeButton, {}) + InfoButton("Контакты", fontSizeButton, {}) + } + + Column( + modifier = Modifier + .weight(1f) + .padding(start = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + InfoButton("Мои результаты", fontSizeButton, {}) + InfoButton("Мои спонсоры", fontSizeButton, {}) + } + } + } + } + } + } + } + + @Composable + private fun InfoRunnerTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Logout", + color = Color.Black, + fontSize = fontSizeButton + ) + } + } + } + } + } + + @Composable + private fun InfoRunnerBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/Login.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/Login.kt new file mode 100644 index 0000000..2f0faa1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/Login.kt @@ -0,0 +1,258 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +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.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.FieldWithLabel +import org.example.project.ViewModel.TimerViewModel +import org.example.project.ViewModel.UserViewModel + +class LoginScreen(private val timerViewModel: TimerViewModel) : Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + Scaffold( + topBar = { LoginTopBar() }, + bottomBar = { LoginBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + val viewModel = UserViewModel() + val users = viewModel.getUsers() + + Box( + modifier = Modifier.padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) + ) { + val fontSize = (maxWidth.value / 25).sp + val fontSizeFirst = (maxWidth.value / 20).sp + val fontSizeSecond = (maxWidth.value / 35).sp + val labelWidth = 0.25f + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(Modifier.weight(0.25f)) + + Text( + text = "Форма авторизации", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.weight(0.25f)) + + Text( + text = "Пожалуйста, авторизуйтесь в системе, используя\n ваш адрес электронной почты и пароль", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(0.25f)) + + FieldWithLabel( + label = "Email:", + value = email, + onValueChange = { email = it }, + placeholder = "Enter your email address", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(Modifier.weight(0.25f)) + + FieldWithLabel( + label = "Password:", + value = password, + onValueChange = { password = it }, + placeholder = "Enter your password", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(Modifier.weight(0.25f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Button( + onClick = { + val user = users.find { it.email == email } + if (user != null && user.password == password) { + val userId = viewModel.getUserIdByEmail(user.email) + navigator.push(InfoRunnerScreen(timerViewModel, userId)) + } + }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text( + "Login", + fontSize = fontSize + ) + } + + Spacer(Modifier.weight(0.25f)) + + Button( + onClick = { navigator.push(MainScreen()) }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text( + "Cancel", + fontSize = fontSize + ) + } + } + + Spacer(Modifier.weight(1f)) + } + } + } + } + } + + @Composable + private fun LoginTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + } + } + } + } + + @Composable + private fun LoginBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MainScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MainScreen.kt index a27779c..8ee4551 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MainScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MainScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.unit.sp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import kotlinx.coroutines.delay class MainScreen : Screen { @@ -26,10 +25,6 @@ class MainScreen : Screen { val navigator = LocalNavigator.currentOrThrow val timerViewModel = TimerViewModel() - val buttonSize = Modifier - .width(400.dp) - .height(80.dp) - Scaffold( topBar = { MainWindowTopBar() }, bottomBar = { MainWindowBottomBar(timerViewModel) } @@ -37,65 +32,73 @@ class MainScreen : Screen { Box( modifier = Modifier .padding(paddingValues) - .fillMaxSize() ) { - Column( + BoxWithConstraints( modifier = Modifier .fillMaxWidth() .align(Alignment.Center) - .padding(horizontal = 30.dp), - horizontalAlignment = Alignment.CenterHorizontally, + .padding(horizontal = 100.dp) ) { - Button( - onClick = { navigator.push(RunnerScreen(timerViewModel)) }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) + val fontSize = (maxWidth.value / 25).sp + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Я хочу стать бегуном", - fontSize = 16.sp - ) - } + Spacer(modifier = Modifier.weight(0.25f)) - Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { navigator.push(RunnerScreen(timerViewModel)) }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Я хочу стать бегуном", fontSize = fontSize) + } - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text("Я хочу стать спонсором бегуна", - fontSize = 16.sp - ) - } + Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { navigator.push(ForSponsorScreen(timerViewModel))}, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Я хочу стать спонсором бегуна", fontSize = fontSize) + } - Button( - onClick = { navigator.push(MoreInfoScreen(timerViewModel)) }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text(text = "Я хочу узнать больше о событии", - fontSize = 16.sp - ) + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { navigator.push(MoreInfoScreen(timerViewModel)) }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Я хочу узнать больше о событии", fontSize = fontSize) + } + + Spacer(modifier = Modifier.weight(0.5f)) } } - Button( - onClick = { }, + onClick = { navigator.push(LoginScreen(timerViewModel)) }, modifier = Modifier .align(Alignment.BottomEnd) .height(65.dp) @@ -106,7 +109,7 @@ class MainScreen : Screen { ), shape = RoundedCornerShape(15.dp) ) { - Text(text = "Login", color = Color.Black) + Text("Login") } } } @@ -114,29 +117,39 @@ class MainScreen : Screen { @Composable private fun MainWindowTopBar() { - Column( - modifier = Modifier - .fillMaxWidth() - .background(Color.DarkGray) - .padding(32.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "MARATHON SKILLS 2016", - fontSize = MaterialTheme.typography.h4.fontSize, - fontWeight = FontWeight.Bold, - color = Color.White - ) + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 25).sp - Spacer(modifier = Modifier.height(20.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "MARATHON SKILLS 2016", + fontSize = fontSize, + fontWeight = FontWeight.Bold, + color = Color.White + ) - Text( - text = "Москва, Россия\nсреда 21 октября 2016", - textAlign = TextAlign.Center, - fontSize = MaterialTheme.typography.subtitle1.fontSize, - fontStyle = FontStyle.Italic, - color = Color.LightGray - ) + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "Москва, Россия\nсреда 21 октября 2016", + textAlign = TextAlign.Center, + fontSize = fontSize, + fontStyle = FontStyle.Italic, + color = Color.LightGray + ) + } + } } } @@ -144,15 +157,25 @@ class MainScreen : Screen { private fun MainWindowBottomBar(timerViewModel: TimerViewModel) { val remainingTime = timerViewModel.remainingTime.collectAsState().value - Column( - modifier = Modifier - .fillMaxWidth() - .background(Color.DarkGray) - .padding(8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text(text = remainingTime, - color = Color.White) + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = remainingTime, + color = Color.White, + fontSize = fontSize) + } + } } } } diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MoreInfo.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MoreInfo.kt index 273a457..15ad302 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MoreInfo.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/MoreInfo.kt @@ -3,12 +3,12 @@ package org.example.project.ui.Screens import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints 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.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentWidth @@ -23,7 +23,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -31,6 +30,7 @@ import androidx.compose.ui.unit.sp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.InfoButton import org.example.project.ViewModel.TimerViewModel @@ -38,133 +38,69 @@ class MoreInfoScreen(private val timerViewModel: TimerViewModel) : Screen { @Composable override fun Content() { - - val buttonSize = Modifier - .width(300.dp) - .height(80.dp) + val navigator = LocalNavigator.currentOrThrow Scaffold( topBar = { InfoTopBar() }, bottomBar = { InfoBottomBar(timerViewModel = timerViewModel) } ) { paddingValues -> + Box( modifier = Modifier .padding(paddingValues) - .fillMaxSize() ) { - Column( + BoxWithConstraints( modifier = Modifier - .padding(16.dp) - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) ) { - Text( - text = "Подробная информация", - color = Color.DarkGray, - textAlign = TextAlign.Center, - fontSize = MaterialTheme.typography.h5.fontSize, - ) + val fontSize = (maxWidth.value / 25).sp + val fontSizeButton = (maxWidth.value / 40).sp - Row( + Column( modifier = Modifier - .fillMaxWidth() - .weight(1f), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically + .padding(16.dp) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween ) { - Column( + Text( + text = "Подробная информация", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSize, + ) + + Row( modifier = Modifier - .weight(1f) - .padding(end = 8.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + .fillMaxWidth() + .weight(1f), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically ) { - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) + Column( + modifier = Modifier + .weight(1f) + .padding(end = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - Text(text = "Marathon Skills 2016", fontSize = 16.sp, - fontStyle = FontStyle.Italic - ) + InfoButton("Marathon Skills 2016", fontSizeButton, {navigator.push(AboutMarathonScreen(timerViewModel))}) + InfoButton("Предыдущие результаты", fontSizeButton, {}) + InfoButton("BMI калькулятор", fontSizeButton, {}) } - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) + Column( + modifier = Modifier + .weight(1f) + .padding(start = 8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - Text(text = "Предыдущие\nрезультаты", fontSize = 16.sp, - fontStyle = FontStyle.Italic) - } - - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text(text = "BMI калькулятор", fontSize = 16.sp, - fontStyle = FontStyle.Italic) - } - } - - Column( - modifier = Modifier - .weight(1f) - .padding(end = 8.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text(text = "Насколько долгий\nмарафон", fontSize = 16.sp, - fontStyle = FontStyle.Italic) - } - - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text(text = "Список\nблаготворительных\nорганизаций", fontSize = 16.sp, - fontStyle = FontStyle.Italic) - } - - Button( - onClick = { }, - modifier = buttonSize, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - shape = RoundedCornerShape(15.dp) - ) { - Text(text = "BMR калькулятор", fontSize = 16.sp, - fontStyle = FontStyle.Italic) + InfoButton("Насколько долгий марафон", fontSizeButton, {}) + InfoButton("Список\nблаготворительных организаций", fontSizeButton, {}) + InfoButton("BMR калькулятор", fontSizeButton, {}) } } } @@ -177,61 +113,85 @@ class MoreInfoScreen(private val timerViewModel: TimerViewModel) : Screen { private fun InfoTopBar() { val navigator = LocalNavigator.currentOrThrow - Row( - modifier = Modifier - .fillMaxWidth() - .background(Color.DarkGray) - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - - Button( - onClick = { navigator.pop() }, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.LightGray, - contentColor = Color.Black - ), - elevation = null, - shape = RoundedCornerShape(15.dp), + Box { + BoxWithConstraints( modifier = Modifier - .wrapContentWidth() - + .fillMaxWidth() + .align(Alignment.Center) ) { - Text(text = "Назад", - color = Color.Black, - ) + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.pop() }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + } } - - Spacer(modifier = Modifier.width(24.dp)) - - Text( - text = "MARATHON SKILLS 2016", - style = MaterialTheme.typography.h6, - modifier = Modifier.weight(1f), - textAlign = TextAlign.Left, - fontWeight = FontWeight.Bold, - fontSize = MaterialTheme.typography.h4.fontSize, - color = Color.White - ) - - Spacer(modifier = Modifier.width(64.dp)) } } @Composable private fun InfoBottomBar(timerViewModel: TimerViewModel) { val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp - Column( - modifier = Modifier - .fillMaxWidth() - .background(Color.DarkGray) - .padding(8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text(text = remainingTime, - color = Color.White) + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegMaraphon.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegMaraphon.kt new file mode 100644 index 0000000..f6495ec --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegMaraphon.kt @@ -0,0 +1,422 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Checkbox +import androidx.compose.material.MaterialTheme +import androidx.compose.material.RadioButton +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.ViewModel.FieldWithDropdown +import org.example.project.ViewModel.FieldWithLabelInt +import org.example.project.ViewModel.TimerViewModel +import org.example.project.ViewModel.UserViewModel + +class RegMaraphonScreen(private val timerViewModel: TimerViewModel, private val userId: Int) : Screen { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val viewModel = UserViewModel() + val fonds = viewModel.getFond() + + Scaffold( + topBar = { RegMaraphonTopBar() }, + bottomBar = { RegMaraphonBottomBar(timerViewModel = timerViewModel) } + ) { paddingValues -> + + Box( + modifier = Modifier.padding(paddingValues) + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 100.dp) + ) { + val fontSizeFirst = (maxWidth.value / 30).sp + val fontSizeSecond = (maxWidth.value / 40).sp + val fontSizeThird = (maxWidth.value / 50).sp + val fontSizeFourth = (maxWidth.value / 10).sp + var finalSum by remember { mutableStateOf(0) } + var previousAddonPrice by remember { mutableStateOf(0) } + var checked1 by remember { mutableStateOf(false) } + var checked2 by remember { mutableStateOf(false) } + var checked3 by remember { mutableStateOf(false) } + val options = listOf( + "Вариант А($0): Номер бегуна + RFID браслет", + "Вариант B($20): вариант A + бейсболка + бутылка воды", + "Вариант C($45): Вариант B + футболка + сувенирный буклет" + ) + var fondsNames = fonds.map { it.name } + var selectedOption by remember { mutableStateOf(options[0]) } + var vznos by remember { mutableStateOf("")} + + Column( + modifier = Modifier + .fillMaxWidth().padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "Регистрация на марафон", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.weight(0.1f)) + + Text( + text = "Пожалуйста, заполните всю информацию, чтобы зарегестрироваться на Skills Marathon 2016 проводимом в Москве. Russia. С вами свяжутся после регистрации для уточнения оплаты и другой информации.", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(0.2f)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Column(modifier = Modifier.weight(1f), ) { + Spacer(modifier = Modifier.weight(0.1f)) + + Text( + text = "Вид марафона", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier + ) + + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = checked1, + onCheckedChange = { isChecked -> + checked1 = isChecked + if (isChecked) { + finalSum += 145 + } else { + finalSum -= 145 + } + } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = "42km Полный марафон($145)", fontSize = fontSizeSecond) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = checked2, + onCheckedChange = { isChecked -> + checked2 = isChecked + if (isChecked) { + finalSum += 75 + } else { + finalSum -= 75 + } + } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = "21km Поулмарафон($75)", fontSize = fontSizeSecond) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = checked3, + onCheckedChange = { isChecked -> + checked3 = isChecked + if (isChecked) { + finalSum += 20 + } else { + finalSum -= 20 + } + } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = "5km Малая дистанция($20)", fontSize = fontSizeSecond) + } + + Text( + text = "Детали спонсорства", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier + ) + + FieldWithDropdown( + label = "Взнос:", + options = fondsNames, + selectedOption = vznos, + onOptionSelected = { vznos = it }, + fontSize = fontSizeSecond, + labelWidth = 0.5f + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabelInt( + label = "Сумма взноса:", + value = finalSum, + onValueChange = { finalSum = it }, + placeholder = "$$$", + fontSize = fontSizeSecond, + labelWidth = 0.5f, + enabled = false + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Button( + onClick = { + val selectedFond = fonds.find { it.name == vznos } + if (selectedFond != null && selectedFond.balance > finalSum) { + val result = viewModel.withdrawalBalance(selectedFond.id, finalSum) + val result2 = viewModel.updateUserActive(userId) + if (result && result2){ + navigator.push(ConfirmRunnerScreen(timerViewModel, userId)) + } + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Регистрация", fontSize = fontSizeThird) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Button( + onClick = { navigator.push(InfoRunnerScreen(timerViewModel, userId)) }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Отмена", fontSize = fontSizeThird) + } + } + } + + Column(modifier = Modifier.weight(1f)) { + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Варианты комплектов", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + options.forEach { text -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + RadioButton( + selected = (text == selectedOption), + onClick = { + val newAddonPrice = when (text) { + options[0] -> 0 + options[1] -> 20 + options[2] -> 45 + else -> 0 + } + + finalSum = finalSum - previousAddonPrice + newAddonPrice + + previousAddonPrice = newAddonPrice + selectedOption = text + } + ) + Spacer(modifier = Modifier.weight(0.01f)) + Text(text = text, fontSize = fontSizeSecond) + } + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Регистрационный взнос", + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.align(Alignment.Center) + ) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = "$" + finalSum.toString(), + color = Color.LightGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFourth, + modifier = Modifier.align(Alignment.Center) + ) + } + } + } + } + } + } + } + } + + @Composable + private fun RegMaraphonTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.push(InfoRunnerScreen(timerViewModel, userId)) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Logout", + color = Color.Black, + fontSize = fontSizeButton + ) + } + } + } + } + } + + + + @Composable + private fun RegMaraphonBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + Box { + BoxWithConstraints( + modifier = Modifier + .align(Alignment.Center) + ) { + val fontSize = (maxWidth.value / 50).sp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = fontSize + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegRunner.kt b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegRunner.kt new file mode 100644 index 0000000..b1e5b0e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/ui/Screens/RegRunner.kt @@ -0,0 +1,540 @@ +package org.example.project.ui.Screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +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.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DateRange +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import org.example.project.Models.User +import org.example.project.ViewModel.DatePickerField +import org.example.project.ViewModel.FieldWithDropdown +import org.example.project.ViewModel.FieldWithLabel +import org.example.project.ViewModel.GetPath +import org.example.project.ViewModel.ImageFromPath +import org.example.project.ViewModel.TimerViewModel +import org.example.project.ViewModel.UserViewModel +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException + +class RegRunnerScreen( + private val timerViewModel: TimerViewModel, +) : Screen { + + @Composable + override fun Content() { + val viewModel = UserViewModel() + var users = viewModel.getUsers() + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var secondPassword by remember { mutableStateOf("") } + var name by remember { mutableStateOf("") } + var surname by remember { mutableStateOf("") } + var gender by remember { mutableStateOf("") } + var country by remember { mutableStateOf("") } + var photoPath by remember { mutableStateOf("") } + var openPicker by remember { mutableStateOf(false) } + if (openPicker) { + GetPath { selected -> + photoPath = selected + openPicker = false + } + } + var showDatePicker by remember { mutableStateOf(false) } + var dateBirthday by remember { mutableStateOf("") } + if (showDatePicker) { + DatePickerField( + onDateSelected = { + dateBirthday = it + }, + trigger = true, + onDismissRequest = { showDatePicker = false } + ) + } + + Scaffold( + topBar = { RegRunnerTopBar() }, + bottomBar = { + BottomSection( + timerViewModel, + email, password, secondPassword, name, surname, gender, photoPath, dateBirthday, country + ) + } + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues).fillMaxSize() + ) { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 10.dp) + ) { + val fontSize = (maxWidth.value / 60).sp + val fontSizeFirst = (maxWidth.value / 30).sp + val fontSizeSecond = (maxWidth.value / 40).sp + val labelWidth = 0.5f + + Column( + modifier = Modifier + .fillMaxWidth().padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "Регистрация бегуна", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeFirst, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.weight(0.1f)) + + Text( + text = "Пожалуйста, заполните всю информацию, чтобы зарегестрироваться в качестве участника", + color = Color.DarkGray, + textAlign = TextAlign.Center, + fontSize = fontSizeSecond, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(0.2f)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Column(modifier = Modifier.weight(0.8f)) { + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Email:", + value = email, + onValueChange = { email = it + println(email)}, + placeholder = "Email", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Пароль:", + value = password, + onValueChange = { password = it }, + placeholder = "Пароль", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Повторите пароль:", + value = secondPassword, + onValueChange = { secondPassword = it }, + placeholder = "Повторите пароль", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Имя:", + value = name, + onValueChange = { name = it }, + placeholder = "Имя", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithLabel( + label = "Фамилия:", + value = surname, + onValueChange = { surname = it }, + placeholder = "Фамилия", + fontSize = fontSize, + labelWidth = labelWidth, + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + FieldWithDropdown( + label = "Пол:", + options = listOf("Мужской", "Остальное"), + selectedOption = gender, + onOptionSelected = { gender = it }, + fontSize = fontSize, + labelWidth = labelWidth + ) + } + + Column(modifier = Modifier.weight(1f)) { + ImageFromPath( + path = photoPath, + modifier = Modifier + .weight(0.4f) + .clip(RoundedCornerShape(8.dp)) + .border(1.dp, Color.Gray, RoundedCornerShape(8.dp)).align(Alignment.End) + ) + + Spacer(modifier = Modifier.weight(0.05f)) + + Column(modifier = Modifier.weight(0.3f)) { + Text( + text = "Фото файл:", + color = Color.DarkGray, + textAlign = TextAlign.Start, + fontSize = fontSize, + modifier = Modifier + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + + OutlinedTextField( + value = photoPath, + onValueChange = {photoPath = it}, + placeholder = { + Text( + text = "Photo_logo.jpg", + fontSize = fontSize, + color = Color.LightGray + ) + }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSize, + color = Color.Black + ), + enabled = true, + colors = androidx.compose.material.TextFieldDefaults.outlinedTextFieldColors( + backgroundColor = Color.White, + cursorColor = Color.Black, + textColor = Color.Black, + placeholderColor = Color.LightGray, + focusedBorderColor = Color(0xFFCCCCCC), + unfocusedBorderColor = Color(0xFFCCCCCC), + disabledBorderColor = Color(0xFFCCCCCC) + ), + modifier = Modifier.weight(0.6f).wrapContentWidth().fillMaxWidth() + ) + + Spacer(modifier = Modifier.weight(0.05f)) + + Button( + onClick = { openPicker = true}, + modifier = Modifier + .fillMaxWidth() + .weight(0.5f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text( + "Просмотр", + fontSize = fontSize + ) + } + } + + Spacer(modifier = Modifier.weight(0.05f)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Box(modifier = Modifier.weight(labelWidth)) { + Text( + text = "Дата рождения", + color = Color.DarkGray, + textAlign = TextAlign.Start, + fontSize = fontSize, + modifier = Modifier + ) + } + + OutlinedTextField( + value = dateBirthday, + onValueChange = {dateBirthday = it}, + placeholder = { + Text( + text = "...", + fontSize = fontSize, + color = Color.LightGray + ) + }, + textStyle = androidx.compose.ui.text.TextStyle( + fontSize = fontSize, + color = Color.Black + ), + enabled = true, + colors = androidx.compose.material.TextFieldDefaults.outlinedTextFieldColors( + backgroundColor = Color.White, + cursorColor = Color.Black, + textColor = Color.Black, + placeholderColor = Color.LightGray, + focusedBorderColor = Color(0xFFCCCCCC), + unfocusedBorderColor = Color(0xFFCCCCCC), + disabledBorderColor = Color(0xFFCCCCCC) + ), + modifier = Modifier.weight(0.9f).wrapContentWidth() + ) + + Spacer(modifier = Modifier.weight(0.05f)) + + Box( + modifier = Modifier + .weight(0.5f) + .fillMaxWidth() + .clip(RoundedCornerShape(15.dp)) + .background(Color.LightGray) + .clickable { showDatePicker = true } + .padding(vertical = 8.dp), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.DateRange, + contentDescription = "Выбрать дату", + tint = Color.Black, + modifier = Modifier.size(24.dp) + ) + } + } + Spacer(modifier = Modifier.weight(0.05f)) + + FieldWithDropdown( + label = "Страна:", + options = listOf("Russia", "Other"), + selectedOption = country, + onOptionSelected = { country = it }, + fontSize = fontSize, + labelWidth = labelWidth + ) + + Spacer(modifier = Modifier.width(16.dp)) + } + } + } + } + } + } + } + } + + @Composable + private fun RegRunnerTopBar() { + val navigator = LocalNavigator.currentOrThrow + + Box { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + ) { + val fontSizeButton = (maxWidth.value / 50).sp + val fontSizeText = (maxWidth.value / 25).sp + + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + Button( + onClick = { navigator.push(MainScreen()) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + elevation = null, + shape = RoundedCornerShape(15.dp), + modifier = Modifier + .wrapContentWidth() + + ) { + Text( + text = "Назад", + color = Color.Black, + fontSize = fontSizeButton + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + Text( + text = "MARATHON SKILLS 2016", + style = MaterialTheme.typography.h6, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Left, + fontWeight = FontWeight.Bold, + fontSize = fontSizeText, + color = Color.White + ) + + Spacer(modifier = Modifier.width(64.dp)) + } + } + } + } + + @Composable + private fun RegRunnerBottomBar(timerViewModel: TimerViewModel) { + val remainingTime = timerViewModel.remainingTime.collectAsState().value + + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.DarkGray) + .padding(8.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = remainingTime, + color = Color.White, + fontSize = 16.sp + ) + } + } + + @Composable + private fun BottomSection( + timerViewModel: TimerViewModel, + email: String, + password: String, + secondPassword: String, + name: String, + surname: String, + gender: String, + photoPath: String, + dateBirthday: String, + country: String + ){ + val navigator = LocalNavigator.currentOrThrow + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.White) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + Button( + onClick = { + val viewModel = UserViewModel() + var users = viewModel.getUsers() + val nextId = (users.maxByOrNull { it.id }?.id ?: 0) + 1 + val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") + val birthdayDate = if (dateBirthday.isNotEmpty()) { + try { + LocalDate.parse(dateBirthday, formatter) + } catch (e: DateTimeParseException) { + LocalDate.now() + } + } else { + LocalDate.now() + } + + if (secondPassword == password) { + val result = viewModel.regUser( + User( + id = nextId, + email = email, + password = password, + name = name, + surname = surname, + gender = gender, + photopath = photoPath, + birthday = birthdayDate, + country = country, + active = false + ) + ) + if (result){ + navigator.push(InfoRunnerScreen(timerViewModel, nextId)) + } + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Регистрация") + } + + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { navigator.push(MainScreen()) }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.LightGray, + contentColor = Color.Black + ), + shape = RoundedCornerShape(15.dp) + ) { + Text("Отмена") + } + } + + RegRunnerBottomBar(timerViewModel = timerViewModel) + } + } +} diff --git a/composeApp/src/desktopMain/kotlin/Tables/Connect.kt b/composeApp/src/desktopMain/kotlin/Tables/Connect.kt new file mode 100644 index 0000000..0fd39f9 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/Tables/Connect.kt @@ -0,0 +1,17 @@ +package org.example.project.Tables + +import org.example.project.Models.User +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction + +fun connectToDatabase() { + Database.connect( + "jdbc:postgresql://localhost:5447/multidb", + driver = "org.postgresql.Driver", + user = "multidb", + password = "multidb" + ) +} diff --git a/composeApp/src/desktopMain/kotlin/Tables/Fonds.kt b/composeApp/src/desktopMain/kotlin/Tables/Fonds.kt new file mode 100644 index 0000000..2b2b8b4 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/Tables/Fonds.kt @@ -0,0 +1,13 @@ +package org.example.project.Tables + +import org.example.project.Tables.Users.autoIncrement +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.javatime.date + +object Fonds : Table() { + val id = integer("id").autoIncrement() + val name = varchar("name", 255) + val balance = integer("balance") + + override val primaryKey = PrimaryKey(id) +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Tables/Users.kt b/composeApp/src/desktopMain/kotlin/Tables/Users.kt new file mode 100644 index 0000000..d2a3490 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/Tables/Users.kt @@ -0,0 +1,18 @@ +package org.example.project.Tables + +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.javatime.date + +object Users : Table() { + val id = integer("id").autoIncrement() + val email = varchar("email", 255) + val password = varchar("password", 255) + val name = varchar("name", 255) + val surname = varchar("surname", 255) + val gender = varchar("gender", 255) + val photopath = varchar("photopath", 255) + val birthday = date("birthday") + val country = varchar("country", 255) + val active = bool("active").default(false) + override val primaryKey = PrimaryKey(id) +} diff --git a/composeApp/src/desktopMain/kotlin/ViewModel/DatePickerField.kt b/composeApp/src/desktopMain/kotlin/ViewModel/DatePickerField.kt new file mode 100644 index 0000000..36155f4 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/ViewModel/DatePickerField.kt @@ -0,0 +1,38 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.* +import java.text.SimpleDateFormat +import javax.swing.* +import java.util.* + +@Composable +actual fun DatePickerField( + onDateSelected: (String) -> Unit, + trigger: Boolean, + onDismissRequest: () -> Unit +) { + if (trigger) { + LaunchedEffect(Unit) { + val dialog = JDialog() + dialog.title = "Выбор даты" + val model = SpinnerDateModel() + val spinner = JSpinner(model) + val panel = JPanel() + val button = JButton("OK") + panel.add(spinner) + panel.add(button) + dialog.contentPane = panel + dialog.setSize(200, 100) + + button.addActionListener { + val date = model.date + val formatted = SimpleDateFormat("dd.MM.yyyy").format(date) + onDateSelected(formatted) + dialog.dispose() + onDismissRequest() + } + + dialog.isVisible = true + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/ViewModel/GetPath.kt b/composeApp/src/desktopMain/kotlin/ViewModel/GetPath.kt new file mode 100644 index 0000000..9880649 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/ViewModel/GetPath.kt @@ -0,0 +1,29 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File +import javax.swing.JFileChooser + +@Composable +actual fun GetPath(onPathSelected: (String) -> Unit) { + LaunchedEffect(Unit) { + val selectedPath = withContext(Dispatchers.IO) { + val chooser = JFileChooser() + chooser.dialogTitle = "Выберите изображение" + chooser.fileSelectionMode = JFileChooser.FILES_ONLY + + val result = chooser.showOpenDialog(null) + if (result == JFileChooser.APPROVE_OPTION) { + val file: File = chooser.selectedFile + file.absolutePath + } else { + "" + } + } + + onPathSelected(selectedPath) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/ViewModel/IconPrinter.kt b/composeApp/src/desktopMain/kotlin/ViewModel/IconPrinter.kt new file mode 100644 index 0000000..3744214 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/ViewModel/IconPrinter.kt @@ -0,0 +1,33 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.Composable +import androidx.compose.foundation.Image +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.toComposeImageBitmap +import androidx.compose.ui.layout.ContentScale +import org.jetbrains.skia.Image +import java.io.IOException + +@Composable +actual fun IconPrinter(resourceName: String, modifier: Modifier) { + val imageBitmap = try { + val path = "drawable/${resourceName}.jpg" + val stream = Thread.currentThread().contextClassLoader.getResourceAsStream(path) + if (stream != null) { + Image.makeFromEncoded(stream.readAllBytes()).toComposeImageBitmap() + } else null + } catch (e: IOException) { + println("Ошибка загрузки иконки: ${e.message}") + null + } + + if (imageBitmap != null) { + Image( + painter = BitmapPainter(imageBitmap), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.FillBounds + ) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/ViewModel/ImageFromPath.kt b/composeApp/src/desktopMain/kotlin/ViewModel/ImageFromPath.kt new file mode 100644 index 0000000..479d9f6 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/ViewModel/ImageFromPath.kt @@ -0,0 +1,45 @@ +package org.example.project.ViewModel + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.toComposeImageBitmap +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import org.jetbrains.skia.Image +import java.io.File +import java.nio.file.Files + +@Composable +actual fun ImageFromPath(path: String, modifier: Modifier) { + val imageBitmap: ImageBitmap? = try { + if (path.isBlank()) { + val resourceStream = Thread.currentThread().contextClassLoader.getResourceAsStream("drawable/defPhoto.png") + val bytes = resourceStream.readAllBytes() + Image.makeFromEncoded(bytes).toComposeImageBitmap() + } else { + val file = File(path) + if (file.exists()) { + val bytes = Files.readAllBytes(file.toPath()) + Image.makeFromEncoded(bytes).toComposeImageBitmap() + } else null + } + } catch (e: Exception) { + println("Ошибка загрузки изображения: ${e.message}") + null + } + + if (imageBitmap != null) { + Image( + painter = BitmapPainter(imageBitmap), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.Crop + ) + } else { + Box(modifier = modifier.fillMaxSize()) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/ViewModel/UserRepository.kt b/composeApp/src/desktopMain/kotlin/ViewModel/UserRepository.kt new file mode 100644 index 0000000..04bc18f --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/ViewModel/UserRepository.kt @@ -0,0 +1,128 @@ +package org.example.project.ViewModel + +import org.example.project.Models.Fond +import org.example.project.Models.User +import org.example.project.Tables.Fonds +import org.example.project.Tables.Users +import org.example.project.Tables.connectToDatabase +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.SqlExpressionBuilder +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.update + +actual class UserRepository { + actual fun fetchUsers(): List { + connectToDatabase() + + return transaction { + Users.selectAll().map { + User( + id = it[Users.id], + email = it[Users.email], + password = it[Users.password], + name = it[Users.name], + surname = it[Users.surname], + gender = it[Users.gender], + photopath = it[Users.photopath], + birthday = it[Users.birthday], + country = it[Users.country], + active = it[Users.active] + ) + } + } + } + actual fun regUser(user: User): Boolean{ + connectToDatabase() + + return try { + transaction { + Users.insert { + it[id] = user.id + it[email] = user.email + it[password] = user.password + it[name] = user.name + it[surname] = user.surname + it[gender] = user.gender + it[photopath] = user.photopath + it[birthday] = user.birthday + it[country] = user.country + } + } + true + } catch (e: Exception) { + println("Ошибка при регистрации: ${e.message}") + false + } + } + actual fun fetchFonds(): List{ + connectToDatabase() + + return transaction { + Fonds.selectAll().map { + Fond( + id = it[Fonds.id], + name = it[Fonds.name], + balance = it[Fonds.balance] + ) + } + } + } + actual fun withdrawalBalance(idFond: Int, dedSum: Int): Boolean{ + connectToDatabase() + + return try { + transaction { + Fonds.update({Fonds.id eq idFond}){ + with(SqlExpressionBuilder) { + it.update(balance, balance - dedSum) + } + } + } + true + } catch (e: Exception){ + println("Ошибка при снятии: ${e.message}") + false + } + } + actual fun getUserIdByEmail(email: String): Int { + connectToDatabase() + + return transaction { + Users.selectAll().where { Users.email eq email }.single()[Users.id] + } + } + actual fun updateUserActive(idUser: Int): Boolean{ + connectToDatabase() + + return try { + transaction { + Users.update({Users.id eq idUser}){ + it[active] = true + } + } + true + } catch (e: Exception){ + println("Ошибка при обновлении: ${e.message}") + false + } + } + actual fun topUpBalance(idFond: Int, sum: Int): Boolean{ + connectToDatabase() + + return try { + transaction { + Fonds.update({Fonds.id eq idFond}){ + with(SqlExpressionBuilder) { + it.update(balance, balance + sum) + } + } + } + true + } catch (e: Exception){ + println("Ошибка при пополнении: ${e.message}") + false + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/org/example/project/main.kt b/composeApp/src/desktopMain/kotlin/org/example/project/main.kt index af78418..2e5f871 100644 --- a/composeApp/src/desktopMain/kotlin/org/example/project/main.kt +++ b/composeApp/src/desktopMain/kotlin/org/example/project/main.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.window.Window import org.example.project.App fun main() = application { + Window( onCloseRequest = ::exitApplication, title = "Marathon Skills 2016"