From f7f26234a23de3e7c9efab3bd6b44fc2373743da Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Wed, 28 Jan 2026 05:23:02 +0400 Subject: [PATCH 01/28] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B8=20=D0=A0=D0=B5?= =?UTF-8?q?=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D1=8F=20Pro?= =?UTF-8?q?file-edit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yeahub/navigation_api/FeatureRoute.kt | 10 ++++++++- .../impl/ProfileEditFeatureImpl.kt | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt diff --git a/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt b/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt index 446c60e6..36d1626d 100644 --- a/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt +++ b/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt @@ -9,7 +9,7 @@ package ru.yeahub.navigation_api * - Построение путей с учетом родительских маршрутов */ object FeatureRoute { - + fun createFeatureRoute(parentRoute: String, featureName: String): String = if (parentRoute.isEmpty()) featureName else "$parentRoute/$featureName" @@ -46,9 +46,11 @@ object FeatureRoute { object DetailsFeature { const val FEATURE_NAME = "details" } + object PublicQuestionsFeature { const val FEATURE_NAME = "public_questions" } + object DetailQuestionFeature { const val FEATURE_NAME = "detail_question" } @@ -56,10 +58,16 @@ object FeatureRoute { object SpecializationsFeature { const val FEATURE_NAME = "specializations" } + object CollectionsFeature { const val FEATURE_NAME = "collections" } + object PublicCollectionsFeature { const val FEATURE_NAME = "public_collections" } + + object ProfileEditFeature { + const val FEATURE_NAME = "profile_edit" + } } \ No newline at end of file diff --git a/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt b/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt new file mode 100644 index 00000000..b9e97579 --- /dev/null +++ b/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt @@ -0,0 +1,21 @@ +package ru.yeahub.profile_edit.impl + +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import ru.yeahub.navigation_api.FeatureApi +import ru.yeahub.navigation_api.FeatureRoute +import ru.yeahub.navigation_api.NavigationPathManager + +class ProfileEditFeatureImpl : FeatureApi { + override fun getFeatureName(): String = FeatureRoute.ProfileEditFeature.FEATURE_NAME + + override fun registerGraph( + navGraphBuilder: NavGraphBuilder, + navController: NavHostController, + pathManager: NavigationPathManager, + modifier: Modifier, + ) { + TODO("Not yet implemented") + } +} \ No newline at end of file From 586ce43aceed42f2e4cdc8e555f14d9fee1d36df Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 10:24:16 +0400 Subject: [PATCH 02/28] DropDownMenu with previews --- .../yeahub/core_ui/component/DropDownMenu.kt | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt new file mode 100644 index 00000000..e7127560 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -0,0 +1,243 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuAnchorType +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import ru.yeahub.core_ui.component.textInput.ColorsTextInputYeaHub +import ru.yeahub.core_ui.component.textInput.TextInputColorsDefaults +import ru.yeahub.core_ui.component.textInput.getTextInputColors +import ru.yeahub.core_ui.example.dynamicPreview.StandardScreenSizePreview +import ru.yeahub.core_ui.example.staticPreview.StaticPreview +import ru.yeahub.core_ui.theme.Theme +import ru.yeahub.ui.R + +private sealed class DropDownMenuState { + data object Default : DropDownMenuState() + data object Expanded : DropDownMenuState() + data object Error : DropDownMenuState() + data object Disabled : DropDownMenuState() +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun DropDownMenu( + modifier: Modifier = Modifier, + placeholder: String, + items: List, + selected: String, + onSelected: (String) -> Unit, + supportingText: String? = null, + shape: Shape = RoundedCornerShape(12.dp), + isExpanded: Boolean = false, + isEnabled: Boolean = true, + isError: Boolean = false, + colors: ColorsTextInputYeaHub = TextInputColorsDefaults.defaultColors(), +) { + var expanded by remember { mutableStateOf(isExpanded) } + val containerColor by colors.containerColor(isEnabled) + val contentColor by colors.contentColor(isEnabled) + + val state = when { + !isEnabled -> DropDownMenuState.Disabled + isError -> DropDownMenuState.Error + isExpanded -> DropDownMenuState.Expanded + else -> DropDownMenuState.Default + } + + val defaultBorder = when (state) { + DropDownMenuState.Expanded -> TextInputColorsDefaults.activeBorder() + DropDownMenuState.Error -> TextInputColorsDefaults.errorBorder() + else -> TextInputColorsDefaults.defaultsBorder() + } + + ExposedDropdownMenuBox( + modifier = modifier + .width(328.dp) + .height(58.dp), + expanded = expanded, + onExpandedChange = { if (isEnabled) expanded = it }, + ) { + OutlinedTextField( + value = selected, + onValueChange = {}, + readOnly = true, + enabled = isEnabled, + isError = isError, + placeholder = { + Text( + text = placeholder, + style = Theme.typography.body3, + color = Theme.colors.black300.copy(alpha = 0.6f), + ) + }, + supportingText = supportingText?.let { + { Text(text = it, style = Theme.typography.body3, color = Theme.colors.black300) } + }, + colors = OutlinedTextFieldDefaults.colors( + cursorColor = if (expanded) contentColor else Color.Transparent, + unfocusedTextColor = contentColor, + focusedTextColor = contentColor, + disabledTextColor = contentColor, + errorTextColor = contentColor, + unfocusedContainerColor = containerColor, + focusedContainerColor = containerColor, + disabledContainerColor = containerColor, + errorContainerColor = containerColor, + unfocusedBorderColor = defaultBorder, + focusedBorderColor = TextInputColorsDefaults.focusBorder(), + disabledBorderColor = TextInputColorsDefaults.defaultsBorder(), + errorBorderColor = TextInputColorsDefaults.errorBorder(), + ), + trailingIcon = { + Icon( + painterResource(R.drawable.arrow_vector), + null, + modifier = Modifier.rotate(if (expanded) 180f else 0f), + ) + }, + shape = shape, + modifier = Modifier + .fillMaxWidth() + .menuAnchor(MenuAnchorType.PrimaryNotEditable, isEnabled), + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + containerColor = colors.containerColor(isEnabled).value, + ) { + items.forEach { item -> + DropdownMenuItem( + text = { + Text( + item, + style = Theme.typography.body3, + color = colors.contentColor(isEnabled).value, + ) + }, + onClick = { + onSelected(item) + expanded = false + }, + ) + } + } + } +} + +@StandardScreenSizePreview +@Composable +fun DropDownMenuPreview() { + val items = listOf("Android", "Backend", "Frontend") + var selected: String by rememberSaveable { mutableStateOf("") } + + Column(modifier = Modifier.padding(12.dp)) { + DropDownMenu( + placeholder = "Выбери значение", + items = items, + onSelected = { selected = it }, + selected = selected, + isExpanded = true, + colors = getTextInputColors(), + ) + } +} + +class DropDownMenuParamsProvider : PreviewParameterProvider { + override val values = sequenceOf( + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + isExpanded = true, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + isError = true, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + isEnabled = false, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + selected = "Android Mobile Developer", + isEnabled = false, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + selected = "Android Mobile Developer", + isEnabled = true, + ), + ) +} + +@StaticPreview +@Composable +fun DropDownMenuPreview( + @PreviewParameter(DropDownMenuParamsProvider::class) params: DropDownMenuParams, +) { + Box( + Modifier + .background(Color.White) + .padding(10.dp), + ) { + DropDownMenu( + placeholder = params.placeholder, + items = params.items, + selected = params.selected, + onSelected = params.onSelected, + isExpanded = params.isExpanded, + isError = params.isError, + isEnabled = params.isEnabled, + ) + } +} + +data class DropDownMenuParams( + val placeholder: String, + val items: List, + val selected: String = "", + val onSelected: (String) -> Unit = {}, + val modifier: Modifier = Modifier, + val isEnabled: Boolean = true, + val isError: Boolean = false, + val isExpanded: Boolean = false, + val colors: ColorsTextInputYeaHub = getTextInputColors(), +) + From fc9f769c6da1e1393f6035ac1bac7f018bf9a4b4 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 10:28:36 +0400 Subject: [PATCH 03/28] DropDownMenu with previews --- .../ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt index e7127560..3f7d3b2f 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -83,6 +83,7 @@ fun DropDownMenu( .width(328.dp) .height(58.dp), expanded = expanded, + //noinspection AssignedValueIsNeverRead onExpandedChange = { if (isEnabled) expanded = it }, ) { OutlinedTextField( From a84e4372da11ea231875e8da3d37250789931fdf Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 10:29:46 +0400 Subject: [PATCH 04/28] DropDownMenu with previews --- .../ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt index 3f7d3b2f..e7127560 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -83,7 +83,6 @@ fun DropDownMenu( .width(328.dp) .height(58.dp), expanded = expanded, - //noinspection AssignedValueIsNeverRead onExpandedChange = { if (isEnabled) expanded = it }, ) { OutlinedTextField( From 7eb959026e975869727006c0ee420534ae2ee5ab Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 12:50:44 +0400 Subject: [PATCH 05/28] UploadPhotoButton with previews --- .../core_ui/component/UploadPhotoButton.kt | 114 ++++++++++++++++++ core/ui/src/main/res/drawable/image.xml | 39 ++++++ 2 files changed, 153 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt create mode 100644 core/ui/src/main/res/drawable/image.xml diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt new file mode 100644 index 00000000..ca95bcc6 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt @@ -0,0 +1,114 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import ru.yeahub.core_ui.theme.Theme +import ru.yeahub.ui.R + + +@Composable +fun UploadPhotoButton( + modifier: Modifier = Modifier, + title: String = "Кликни для изменения", + helper: String = "JPG, PNG, JPEG (не более 5мб)", + onClick: () -> Unit, +) { + val shape = RoundedCornerShape(12.dp) + + Surface( + modifier = modifier + .fillMaxWidth() + .height(164.dp) + .dashedRoundedBorder( + color = Theme.colors.purple400, + strokeWidth = 2.dp, + cornerRadius = 12.dp, + dashLength = 8.dp, + gapLength = 8.dp, + ) + .clickable(onClick = onClick), + shape = shape, + color = Color.Transparent, + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 28.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Icon( + painter = painterResource(R.drawable.image), + contentDescription = null, + tint = Theme.colors.purple700, + modifier = Modifier.size(32.dp), + ) + + Spacer(Modifier.height(12.dp)) + + Text(text = title, style = Theme.typography.body3, color = Theme.colors.purple700) + + Spacer(Modifier.height(12.dp)) + + Text(text = helper, style = Theme.typography.body7, color = Theme.colors.black200) + } + } +} + + +@Preview(showBackground = true) +@Composable +fun UUploadPhotoButtonPreview() { + Column(Modifier.padding(16.dp)) { + UploadPhotoButton(onClick = {}) + } +} + +fun Modifier.dashedRoundedBorder( + color: Color, + strokeWidth: Dp = 2.dp, + cornerRadius: Dp = 12.dp, + dashLength: Dp = 12.dp, + gapLength: Dp = 12.dp, +) = drawWithCache { + val strokePx = strokeWidth.toPx() + val radiusPx = cornerRadius.toPx() + val dashPx = dashLength.toPx() + val gapPx = gapLength.toPx() + + val stroke = Stroke( + width = strokePx, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPx, gapPx), 0f), + ) + + onDrawBehind { + drawRoundRect( + color = color, + style = stroke, + cornerRadius = CornerRadius(radiusPx, radiusPx), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/image.xml b/core/ui/src/main/res/drawable/image.xml new file mode 100644 index 00000000..6d444a37 --- /dev/null +++ b/core/ui/src/main/res/drawable/image.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + From 3e60ed24536842fdd0ae513d1bd0e3ce1d348674 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:42:37 +0400 Subject: [PATCH 06/28] DefaultTextField refactor --- .../core_ui/component/textInput/TextInput.kt | 127 +++++++++++------- 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt index 410680b9..bfc8ab96 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults @@ -30,6 +31,7 @@ import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -58,7 +60,6 @@ import ru.yeahub.ui.R private sealed class TextInputState { data object Default : TextInputState() data object Focused : TextInputState() - data object Active : TextInputState() data object Error : TextInputState() data object Disabled : TextInputState() } @@ -80,7 +81,7 @@ fun TextInput( shape: Shape = RoundedCornerShape(12.dp), contentPadding: PaddingValues = OutlinedTextFieldDefaults.contentPadding(), singleLine: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { val isFocused by interactionSource.collectIsFocusedAsState() @@ -96,7 +97,7 @@ fun TextInput( Box( modifier = modifier .width(300.dp) - .height(300.dp) + .height(300.dp), ) { SearchBar( inputField = { @@ -106,7 +107,7 @@ fun TextInput( onValueChange(newValue) }, modifier = Modifier, - label = label, + placeholder = label, isFocused = isFocused, isEnabled = isEnabled, isError = isError, @@ -114,7 +115,7 @@ fun TextInput( colors = colors, shape = shape, singleLine = singleLine, - interactionSource = interactionSource + interactionSource = interactionSource, ) }, expanded = expanded, @@ -124,7 +125,7 @@ fun TextInput( shape = shape, colors = SearchBarDefaults.colors( containerColor = Color.Transparent, - dividerColor = Color.Transparent + dividerColor = Color.Transparent, ), ) { Surface( @@ -132,7 +133,7 @@ fun TextInput( .fillMaxWidth(), shape = MaterialTheme.shapes.medium, color = Theme.colors.white900, - border = BorderStroke(1.dp, TextInputColorsDefaults.defaultsBorder()) + border = BorderStroke(1.dp, TextInputColorsDefaults.defaultsBorder()), ) { LazyColumn( modifier = Modifier.fillMaxWidth(), @@ -145,13 +146,13 @@ fun TextInput( onValueChange(suggestion) onExpandedChange(false) } - .padding(contentPadding) + .padding(contentPadding), ) { Text( text = suggestion, color = colors.contentColor(isEnabled).value, style = Theme.typography.body3, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) } } @@ -166,7 +167,7 @@ fun TextInput( fun DefaultTextField( value: String, onValueChange: (String) -> Unit, - label: String, + placeholder: String, modifier: Modifier = Modifier, onExpandedChange: (Boolean) -> Unit, colors: ColorsTextInputYeaHub = TextInputColorsDefaults.defaultColors(), @@ -175,7 +176,10 @@ fun DefaultTextField( isError: Boolean = false, singleLine: Boolean = true, shape: Shape = RoundedCornerShape(12.dp), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + readOnly: Boolean = false, + leadingIcon: (@Composable (() -> Unit))? = null, + trailingIcon: (@Composable (() -> Unit))? = null, ) { val containerColor by colors.containerColor(isEnabled) val contentColor by colors.contentColor(isEnabled) @@ -186,26 +190,44 @@ fun DefaultTextField( !isEnabled -> TextInputState.Disabled isError -> TextInputState.Error isFocused -> TextInputState.Focused - value.isNotEmpty() -> TextInputState.Active else -> TextInputState.Default } val iconColor = when (state) { - TextInputState.Active -> Theme.colors.black900 - else -> Theme.colors.black300 + TextInputState.Default -> Theme.colors.black300 + TextInputState.Disabled -> Theme.colors.black300 + else -> Theme.colors.black900 } val defaultBorder = when (state) { - TextInputState.Active -> TextInputColorsDefaults.activeBorder() + TextInputState.Focused -> TextInputColorsDefaults.activeBorder() TextInputState.Error -> TextInputColorsDefaults.errorBorder() else -> TextInputColorsDefaults.defaultsBorder() } + + fun provideIconColor( + slot: (@Composable () -> Unit)?, + color: Color, + ): (@Composable () -> Unit)? = + slot?.let { content -> + { + CompositionLocalProvider(LocalContentColor provides color) { + content() + } + } + } + + val leadingIconSlot = provideIconColor(leadingIcon, iconColor) + + val trailingIconSlot = provideIconColor(trailingIcon, iconColor) + OutlinedTextField( value = value, onValueChange = onValueChange, - label = { + readOnly = readOnly, + placeholder = { Text( - text = label, + text = placeholder, color = Theme.colors.black300, style = Theme.typography.body3, ) @@ -215,17 +237,8 @@ fun DefaultTextField( .height(58.dp), enabled = isEnabled, singleLine = singleLine, - leadingIcon = - { - Icon( - painter = painterResource(id = R.drawable.icon_search), - contentDescription = "Поиск", - tint = iconColor, - modifier = modifier - .width(20.dp) - .height(20.dp) - ) - }, + leadingIcon = leadingIconSlot, + trailingIcon = trailingIconSlot, isError = isError, shape = shape, textStyle = Theme.typography.body3, @@ -249,7 +262,7 @@ fun DefaultTextField( onExpandedChange(false) keyboardController?.hide() focusManager.clearFocus(force = true) - } + }, ), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), interactionSource = interactionSource, @@ -268,7 +281,7 @@ object TextInputColorsDefaults { containerColor = containerColor, contentColor = contentColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -307,7 +320,7 @@ data class TextInputParams( val value: String, val onValueChange: (String) -> Unit, val onExpandedChange: (Boolean) -> Unit, - val label: String, + val placeholder: String, val modifier: Modifier = Modifier, val isEnabled: Boolean = true, val isError: Boolean = false, @@ -315,6 +328,15 @@ data class TextInputParams( val isFocus: Boolean = false, val colors: ColorsTextInputYeaHub = getTextInputColors(), val singleLine: Boolean = true, + val leadingIcon: (@Composable () -> Unit)? = { + Icon( + painter = painterResource(R.drawable.icon_search), + contentDescription = "Поиск", + modifier = modifier + .width(20.dp) + .height(20.dp), + ) + }, ) fun getTextInputColors(): ColorsTextInputYeaHub { @@ -332,33 +354,43 @@ class TextInputParamsProvider : PreviewParameterProvider { value = "", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = true, - isError = false + isError = false, ), TextInputParams( value = "text", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = true, - isError = false + isError = false, + isFocus = true, ), TextInputParams( value = "", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = false, - isError = false + isError = false, ), TextInputParams( value = "", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = true, - isError = true + isError = true, + ), + TextInputParams( + value = "text", + onValueChange = {}, + onExpandedChange = {}, + placeholder = "placeholder", + isEnabled = true, + isError = false, + isFocus = false, ), ) } @@ -366,22 +398,23 @@ class TextInputParamsProvider : PreviewParameterProvider { @StaticPreview @Composable fun TextInputPreview( - @PreviewParameter(TextInputParamsProvider::class) params: TextInputParams + @PreviewParameter(TextInputParamsProvider::class) params: TextInputParams, ) { Box( Modifier .background(Color.White) - .padding(10.dp) + .padding(10.dp), ) { DefaultTextField( value = params.value, onValueChange = params.onValueChange, onExpandedChange = params.onExpandedChange, - label = params.label, + placeholder = params.placeholder, isEnabled = params.isEnabled, isError = params.isError, colors = params.colors, - isFocused = params.isFocus + isFocused = params.isFocus, + leadingIcon = params.leadingIcon, ) } } @@ -389,7 +422,7 @@ fun TextInputPreview( @StandardScreenSizePreview @Composable fun TextInputDynamicPreview( - @PreviewParameter(TextInputPreviewProvider::class) params: Pair> + @PreviewParameter(TextInputPreviewProvider::class) params: Pair>, ) { val mockViewModel = object : SuggestionsViewModel() { init { @@ -413,7 +446,7 @@ fun ScreenSuggestions( modifier = modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { TextInput( value = text, @@ -424,7 +457,7 @@ fun ScreenSuggestions( suggestions = suggestions, onQueryChanged = { viewModel.onQueryChange(it, suggestions) }, expanded = expanded, - onExpandedChange = { expanded = it } + onExpandedChange = { expanded = it }, ) } } @@ -436,4 +469,4 @@ class TextInputPreviewProvider : PreviewParameterProvider Date: Sun, 1 Feb 2026 12:44:25 +0400 Subject: [PATCH 07/28] DropDownMenu DefaultTextField reused --- .../yeahub/core_ui/component/DropDownMenu.kt | 128 +++++++----------- 1 file changed, 51 insertions(+), 77 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt index e7127560..1e71381d 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -13,8 +13,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.Icon import androidx.compose.material3.MenuAnchorType -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -32,6 +30,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import ru.yeahub.core_ui.component.textInput.ColorsTextInputYeaHub +import ru.yeahub.core_ui.component.textInput.DefaultTextField +import ru.yeahub.core_ui.component.textInput.TextInput import ru.yeahub.core_ui.component.textInput.TextInputColorsDefaults import ru.yeahub.core_ui.component.textInput.getTextInputColors import ru.yeahub.core_ui.example.dynamicPreview.StandardScreenSizePreview @@ -39,13 +39,6 @@ import ru.yeahub.core_ui.example.staticPreview.StaticPreview import ru.yeahub.core_ui.theme.Theme import ru.yeahub.ui.R -private sealed class DropDownMenuState { - data object Default : DropDownMenuState() - data object Expanded : DropDownMenuState() - data object Error : DropDownMenuState() - data object Disabled : DropDownMenuState() -} - @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable fun DropDownMenu( @@ -54,28 +47,23 @@ fun DropDownMenu( items: List, selected: String, onSelected: (String) -> Unit, - supportingText: String? = null, shape: Shape = RoundedCornerShape(12.dp), isExpanded: Boolean = false, isEnabled: Boolean = true, isError: Boolean = false, colors: ColorsTextInputYeaHub = TextInputColorsDefaults.defaultColors(), + trailingIcon: @Composable (() -> Unit)? = { + Icon( + painterResource(R.drawable.arrow_vector), + null, + ) + }, + leadingIcon: @Composable (() -> Unit)? = null, ) { var expanded by remember { mutableStateOf(isExpanded) } - val containerColor by colors.containerColor(isEnabled) - val contentColor by colors.contentColor(isEnabled) - val state = when { - !isEnabled -> DropDownMenuState.Disabled - isError -> DropDownMenuState.Error - isExpanded -> DropDownMenuState.Expanded - else -> DropDownMenuState.Default - } - - val defaultBorder = when (state) { - DropDownMenuState.Expanded -> TextInputColorsDefaults.activeBorder() - DropDownMenuState.Error -> TextInputColorsDefaults.errorBorder() - else -> TextInputColorsDefaults.defaultsBorder() + val trailingIconRotating: (@Composable (() -> Unit))? = trailingIcon?.let { icon -> + { Box(Modifier.rotate(if (expanded) 180f else 0f)) { icon() } } } ExposedDropdownMenuBox( @@ -85,48 +73,21 @@ fun DropDownMenu( expanded = expanded, onExpandedChange = { if (isEnabled) expanded = it }, ) { - OutlinedTextField( + DefaultTextField( value = selected, onValueChange = {}, readOnly = true, - enabled = isEnabled, + isEnabled = isEnabled, isError = isError, - placeholder = { - Text( - text = placeholder, - style = Theme.typography.body3, - color = Theme.colors.black300.copy(alpha = 0.6f), - ) - }, - supportingText = supportingText?.let { - { Text(text = it, style = Theme.typography.body3, color = Theme.colors.black300) } - }, - colors = OutlinedTextFieldDefaults.colors( - cursorColor = if (expanded) contentColor else Color.Transparent, - unfocusedTextColor = contentColor, - focusedTextColor = contentColor, - disabledTextColor = contentColor, - errorTextColor = contentColor, - unfocusedContainerColor = containerColor, - focusedContainerColor = containerColor, - disabledContainerColor = containerColor, - errorContainerColor = containerColor, - unfocusedBorderColor = defaultBorder, - focusedBorderColor = TextInputColorsDefaults.focusBorder(), - disabledBorderColor = TextInputColorsDefaults.defaultsBorder(), - errorBorderColor = TextInputColorsDefaults.errorBorder(), - ), - trailingIcon = { - Icon( - painterResource(R.drawable.arrow_vector), - null, - modifier = Modifier.rotate(if (expanded) 180f else 0f), - ) - }, + placeholder = placeholder, + trailingIcon = trailingIconRotating, + leadingIcon = leadingIcon, shape = shape, modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryNotEditable, isEnabled), + onExpandedChange = { }, + isFocused = isExpanded, ) ExposedDropdownMenu( @@ -153,24 +114,6 @@ fun DropDownMenu( } } -@StandardScreenSizePreview -@Composable -fun DropDownMenuPreview() { - val items = listOf("Android", "Backend", "Frontend") - var selected: String by rememberSaveable { mutableStateOf("") } - - Column(modifier = Modifier.padding(12.dp)) { - DropDownMenu( - placeholder = "Выбери значение", - items = items, - onSelected = { selected = it }, - selected = selected, - isExpanded = true, - colors = getTextInputColors(), - ) - } -} - class DropDownMenuParamsProvider : PreviewParameterProvider { override val values = sequenceOf( DropDownMenuParams( @@ -204,6 +147,13 @@ class DropDownMenuParamsProvider : PreviewParameterProvider selected = "Android Mobile Developer", isEnabled = true, ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + selected = "Android Mobile Developer", + isEnabled = true, + isExpanded = true, + ), ) } @@ -229,6 +179,31 @@ fun DropDownMenuPreview( } } +@StandardScreenSizePreview +@Composable +fun DropDownMenuPreview() { + val items = listOf("Android", "Backend", "Frontend") + var selected: String by rememberSaveable { mutableStateOf("") } + + Column(modifier = Modifier.padding(12.dp)) { + DropDownMenu( + placeholder = "Выбери значение", + items = items, + onSelected = { selected = it }, + selected = selected, + colors = getTextInputColors(), + ) + TextInput( + value = "", + onValueChange = { }, + label = "label", + expanded = false, + onExpandedChange = { }, + onQueryChanged = { }, + ) + } +} + data class DropDownMenuParams( val placeholder: String, val items: List, @@ -239,5 +214,4 @@ data class DropDownMenuParams( val isError: Boolean = false, val isExpanded: Boolean = false, val colors: ColorsTextInputYeaHub = getTextInputColors(), -) - +) \ No newline at end of file From 24ef98851ced38325dc40d4ccf1cf75e70940005 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:46:07 +0400 Subject: [PATCH 08/28] UploadPhotoButton lines fix --- .../java/ru/yeahub/core_ui/component/UploadPhotoButton.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt index ca95bcc6..3fbe0863 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.unit.dp import ru.yeahub.core_ui.theme.Theme import ru.yeahub.ui.R - @Composable fun UploadPhotoButton( modifier: Modifier = Modifier, @@ -78,10 +77,9 @@ fun UploadPhotoButton( } } - @Preview(showBackground = true) @Composable -fun UUploadPhotoButtonPreview() { +fun UploadPhotoButtonPreview() { Column(Modifier.padding(16.dp)) { UploadPhotoButton(onClick = {}) } @@ -98,12 +96,10 @@ fun Modifier.dashedRoundedBorder( val radiusPx = cornerRadius.toPx() val dashPx = dashLength.toPx() val gapPx = gapLength.toPx() - val stroke = Stroke( width = strokePx, pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPx, gapPx), 0f), ) - onDrawBehind { drawRoundRect( color = color, From 16d84b924985be5870a02b7e97bcec890bfbf05b Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:58:35 +0400 Subject: [PATCH 09/28] add font for ProfileEdit TabBar --- core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt b/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt index 78a60294..b8411339 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt @@ -62,6 +62,12 @@ data class Typography( fontSize = 22.sp, lineHeight = 26.sp, ), + val head6: TextStyle = TextStyle( + fontFamily = sfProDisplay, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + lineHeight = 24.sp, + ), //Body val body1: TextStyle = TextStyle( fontFamily = manrope, From 38e7403bb4ac78080d23cae6e107822322627717 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:58:42 +0400 Subject: [PATCH 10/28] add TabBar --- .../ru/yeahub/core_ui/component/TabBar.kt | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt new file mode 100644 index 00000000..dd1c5667 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt @@ -0,0 +1,141 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import ru.yeahub.core_ui.example.dynamicPreview.StandardScreenSizePreview +import ru.yeahub.core_ui.theme.Theme + +@Immutable +data class CoreTopTabsColors( + val containerColor: Color, + val indicatorColor: Color, + val selectedTextColor: Color, + val unselectedTextColor: Color, +) + +object CoreTopTabsDefaults { + @Composable + fun colors( + containerColor: Color = Theme.colors.white900, + indicatorColor: Color = Theme.colors.purple700, + selectedTextColor: Color = Theme.colors.black900, + unselectedTextColor: Color = Theme.colors.black500, + ) = CoreTopTabsColors( + containerColor = containerColor, + indicatorColor = indicatorColor, + selectedTextColor = selectedTextColor, + unselectedTextColor = unselectedTextColor, + ) +} + +@Composable +fun CoreTopTabs( + tabs: List, + selectedIndex: Int, + onSelected: (Int) -> Unit, + modifier: Modifier = Modifier, + colors: CoreTopTabsColors = CoreTopTabsDefaults.colors(), + edgePadding: Dp = 0.dp, + tabSpacing: Dp = 0.dp, + indicatorHeight: Dp = 2.dp, + indicatorHorizontalPadding: Dp = 16.dp, +) { + ScrollableTabRow( + selectedTabIndex = selectedIndex, + modifier = modifier, + containerColor = colors.containerColor, + contentColor = colors.selectedTextColor, + edgePadding = edgePadding, + divider = { HorizontalDivider(thickness = 0.dp) }, + indicator = { positions -> + val pos = positions[selectedIndex] + Box( + Modifier + .tabIndicatorOffset(pos) + .padding(horizontal = indicatorHorizontalPadding) + .height(indicatorHeight) + .background(colors.indicatorColor), + ) + }, + ) { + tabs.forEachIndexed { index, title -> + val isSelected = index == selectedIndex + Tab( + selected = isSelected, + onClick = { onSelected(index) }, + text = { + Text( + text = title, + color = if (isSelected) colors.selectedTextColor else colors.unselectedTextColor, + style = Theme.typography.head6, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Visible, + ) + }, + ) + } + } +} + +data class YhTabsParams( + val items: List, + val selectedIndex: Int, +) + +class YhTabsParamsProvider : PreviewParameterProvider { + override val values = sequenceOf( + YhTabsParams(listOf("Личная информация", "Обо мне", "Навыки"), 0), + YhTabsParams( + listOf( + "Tab 1", + "Очень длинный таб", + "Очень длинный таб", + "Очень длинный таб", + "Tab 1", + "Tab 1", + ), + 2, + ), + ) +} + +@StandardScreenSizePreview +@Composable +fun YhTabsPreview( + @PreviewParameter(YhTabsParamsProvider::class) p: YhTabsParams, +) { + var selected by remember { mutableIntStateOf(p.selectedIndex) } + + Box( + Modifier + .background(Color.White) + .padding(0.dp), + ) { + CoreTopTabs( + tabs = p.items, + selectedIndex = selected, + onSelected = { selected = it }, + ) + } +} \ No newline at end of file From c7448b72f32428a2007a53637786994d776c6573 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 14:27:43 +0400 Subject: [PATCH 11/28] added secondary color for OutlineButton and preview --- .../ru/yeahub/core_ui/component/Button.kt | 87 +++++++++++++------ 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt index e3ca6cc4..d9d443a5 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt @@ -52,7 +52,7 @@ fun PrimaryButton( interactionSource = interactionSource, shape = shape, contentPadding = contentPadding, - content = content + content = content, ) } @@ -91,7 +91,7 @@ fun OutlineButton( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = RoundedCornerShape(12.dp), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, - content: @Composable RowScope.() -> Unit + content: @Composable RowScope.() -> Unit, ) { DefaultButton( onClick = onClick, @@ -102,7 +102,7 @@ fun OutlineButton( interactionSource = interactionSource, shape = shape, contentPadding = contentPadding, - content = content + content = content, ) } @@ -116,7 +116,7 @@ private fun DefaultButton( border: BorderStroke? = null, shape: Shape = ButtonDefaults.shape, contentPadding: PaddingValues = ButtonDefaults.ContentPadding, - content: @Composable RowScope.() -> Unit + content: @Composable RowScope.() -> Unit, ) { val contentColor: Color by colors.contentColor(enabled) val containerColor: Color by colors.containerColor(enabled) @@ -129,16 +129,16 @@ private fun DefaultButton( color = containerColor, contentColor = contentColor, border = border, - interactionSource = interactionSource + interactionSource = interactionSource, ) { CompositionLocalProvider( - value = LocalContentColor provides contentColor + value = LocalContentColor provides contentColor, ) { Row( modifier = Modifier .padding(contentPadding), horizontalArrangement = Arrangement.Center, - content = content + content = content, ) } } @@ -150,13 +150,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.white900, containerColor: Color = Theme.colors.purple700, disabledContentColor: Color = Theme.colors.white900, - disabledContainerColor: Color = Theme.colors.black100 + disabledContainerColor: Color = Theme.colors.black100, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -165,13 +165,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.white900, containerColor: Color = Theme.colors.red600, disabledContentColor: Color = Theme.colors.white900, - disabledContainerColor: Color = Theme.colors.red200 + disabledContainerColor: Color = Theme.colors.red200, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -180,13 +180,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.purple700, containerColor: Color = Theme.colors.purple100, disabledContentColor: Color = Theme.colors.black200, - disabledContainerColor: Color = Theme.colors.black50 + disabledContainerColor: Color = Theme.colors.black50, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -195,13 +195,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.white900, containerColor: Color = Theme.colors.red600, disabledContentColor: Color = Theme.colors.white900, - disabledContainerColor: Color = Theme.colors.red200 + disabledContainerColor: Color = Theme.colors.red200, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -216,18 +216,44 @@ object YeahubButtonDefaults { contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @Composable fun outlineBorderDefaults( width: Dp = 1.dp, - borderColor: Color = Theme.colors.red200 + borderColor: Color = Theme.colors.red200, + ): BorderStroke { + return BorderStroke( + width = width, + color = borderColor, + ) + } + + @Composable + fun secondaryOutlinedButtonColors( + contentColor: Color = Theme.colors.purple700, + containerColor: Color = Color.Transparent, + disabledContentColor: Color = Theme.colors.purple200, + disabledContainerColor: Color = Color.Transparent, + ): YeahubButtonColors { + return YeahubButtonColors( + contentColor = contentColor, + containerColor = containerColor, + disabledContentColor = disabledContentColor, + disabledContainerColor = disabledContainerColor, + ) + } + + @Composable + fun secondaryOutlineBorderDefaults( + width: Dp = 1.dp, + borderColor: Color = Theme.colors.purple700, ): BorderStroke { return BorderStroke( width = width, - color = borderColor + color = borderColor, ) } } @@ -260,7 +286,7 @@ interface ButtonColors { } @Preview( - showBackground = true + showBackground = true, ) @Composable fun ButtonPreviews() { @@ -268,21 +294,21 @@ fun ButtonPreviews() { modifier = Modifier .fillMaxWidth() .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + verticalArrangement = Arrangement.spacedBy(16.dp), ) { // Primary Button Text("Primary Buttons", style = MaterialTheme.typography.titleMedium) PrimaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = true + enabled = true, ) { Text("Enabled Primary Button") } PrimaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = false + enabled = false, ) { Text("Disabled Primary Button") } @@ -292,14 +318,14 @@ fun ButtonPreviews() { SecondaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = true + enabled = true, ) { Text("Enabled Secondary Button") } SecondaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = false + enabled = false, ) { Text("Disabled Secondary Button") } @@ -309,16 +335,25 @@ fun ButtonPreviews() { OutlineButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = true + enabled = true, ) { Text("Enabled Outline Button") } OutlineButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = false + enabled = false, ) { Text("Disabled Outline Button") } + OutlineButton( + onClick = {}, + colors = YeahubButtonDefaults.secondaryOutlinedButtonColors(), + border = YeahubButtonDefaults.secondaryOutlineBorderDefaults(), + modifier = Modifier.fillMaxWidth(), + enabled = true, + ) { + Text("Enabled Outline Button with secondary colors") + } } } From 458a6952bd29510e9a66c07677893e21c53e2085 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 14:27:57 +0400 Subject: [PATCH 12/28] added SmallScreenSizePreview --- .../dynamicPreview/SmallScreenSizePreview.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt b/core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt new file mode 100644 index 00000000..3b19ff76 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt @@ -0,0 +1,13 @@ +package ru.yeahub.core_ui.example.dynamicPreview + +import androidx.compose.ui.tooling.preview.Preview + +private const val SMALL_SCREEN_WIDTH_DP = 320 +private const val SMALL_SCREEN_HEIGHT_DP = 480 + +@Preview( + widthDp = SMALL_SCREEN_WIDTH_DP, + heightDp = SMALL_SCREEN_HEIGHT_DP, + showBackground = true, +) +annotation class SmallScreenSizePreview() From c1f2779e20f0baf2fb87e7e18fabbd6114a0ad64 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 14:28:57 +0400 Subject: [PATCH 13/28] added UnsavedChangesDialog with preview --- .../yeahub/core_ui/component/AlertDialog.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt new file mode 100644 index 00000000..45bbbf74 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt @@ -0,0 +1,44 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import ru.yeahub.core_ui.example.dynamicPreview.SmallScreenSizePreview + +@Composable +fun UnsavedChangesDialog( + onStay: () -> Unit, + onLeave: () -> Unit, +) { + AlertDialog( + onDismissRequest = onStay, + title = { Text("Подтвердить действие") }, + text = { + Text( + "У вас есть несохраненные данные.\nВы хотите продолжить?", + ) + }, + dismissButton = { + PrimaryButton(onClick = onStay) { + Text("Да") + } + }, + confirmButton = { + OutlineButton( + onClick = onLeave, + colors = YeahubButtonDefaults.secondaryOutlinedButtonColors(), + border = YeahubButtonDefaults.secondaryOutlineBorderDefaults(), + ) { + Text( + "нет", + ) + } + }, + ) +} + +@SmallScreenSizePreview +@Composable +fun UnsavedChangesDialogPreview() { + UnsavedChangesDialog({}, {}) +} \ No newline at end of file From 03e08dd9de7e180e5892a5f23fdfb7fd147097ed Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Wed, 28 Jan 2026 05:23:02 +0400 Subject: [PATCH 14/28] resolve empty lines merge conflict --- .../src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt | 7 ++++++- .../ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt b/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt index d844e48a..e482f832 100644 --- a/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt +++ b/core/navigation-api/src/main/java/ru/yeahub/navigation_api/FeatureRoute.kt @@ -46,9 +46,11 @@ object FeatureRoute { object DetailsFeature { const val FEATURE_NAME = "details" } + object PublicQuestionsFeature { const val FEATURE_NAME = "public_questions" } + object DetailQuestionFeature { const val FEATURE_NAME = "detail_question" } @@ -56,13 +58,16 @@ object FeatureRoute { object SpecializationsFeature { const val FEATURE_NAME = "specializations" } + object CollectionsFeature { const val FEATURE_NAME = "collections" } + object PublicCollectionsFeature { const val FEATURE_NAME = "public_collections" } - object ProfileEditFeature{ + + object ProfileEditFeature { const val FEATURE_NAME = "profile_edit" } } \ No newline at end of file diff --git a/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt b/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt index b27b502f..b9e97579 100644 --- a/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt +++ b/feature/profile-edit/impl/src/main/java/ru/yeahub/profile_edit/impl/ProfileEditFeatureImpl.kt @@ -10,7 +10,6 @@ import ru.yeahub.navigation_api.NavigationPathManager class ProfileEditFeatureImpl : FeatureApi { override fun getFeatureName(): String = FeatureRoute.ProfileEditFeature.FEATURE_NAME - override fun registerGraph( navGraphBuilder: NavGraphBuilder, navController: NavHostController, From a4efd9f5df15936f539cd007a7b104ecfb1be7f7 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 10:24:16 +0400 Subject: [PATCH 15/28] DropDownMenu with previews --- .../yeahub/core_ui/component/DropDownMenu.kt | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt new file mode 100644 index 00000000..e7127560 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -0,0 +1,243 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuAnchorType +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import ru.yeahub.core_ui.component.textInput.ColorsTextInputYeaHub +import ru.yeahub.core_ui.component.textInput.TextInputColorsDefaults +import ru.yeahub.core_ui.component.textInput.getTextInputColors +import ru.yeahub.core_ui.example.dynamicPreview.StandardScreenSizePreview +import ru.yeahub.core_ui.example.staticPreview.StaticPreview +import ru.yeahub.core_ui.theme.Theme +import ru.yeahub.ui.R + +private sealed class DropDownMenuState { + data object Default : DropDownMenuState() + data object Expanded : DropDownMenuState() + data object Error : DropDownMenuState() + data object Disabled : DropDownMenuState() +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun DropDownMenu( + modifier: Modifier = Modifier, + placeholder: String, + items: List, + selected: String, + onSelected: (String) -> Unit, + supportingText: String? = null, + shape: Shape = RoundedCornerShape(12.dp), + isExpanded: Boolean = false, + isEnabled: Boolean = true, + isError: Boolean = false, + colors: ColorsTextInputYeaHub = TextInputColorsDefaults.defaultColors(), +) { + var expanded by remember { mutableStateOf(isExpanded) } + val containerColor by colors.containerColor(isEnabled) + val contentColor by colors.contentColor(isEnabled) + + val state = when { + !isEnabled -> DropDownMenuState.Disabled + isError -> DropDownMenuState.Error + isExpanded -> DropDownMenuState.Expanded + else -> DropDownMenuState.Default + } + + val defaultBorder = when (state) { + DropDownMenuState.Expanded -> TextInputColorsDefaults.activeBorder() + DropDownMenuState.Error -> TextInputColorsDefaults.errorBorder() + else -> TextInputColorsDefaults.defaultsBorder() + } + + ExposedDropdownMenuBox( + modifier = modifier + .width(328.dp) + .height(58.dp), + expanded = expanded, + onExpandedChange = { if (isEnabled) expanded = it }, + ) { + OutlinedTextField( + value = selected, + onValueChange = {}, + readOnly = true, + enabled = isEnabled, + isError = isError, + placeholder = { + Text( + text = placeholder, + style = Theme.typography.body3, + color = Theme.colors.black300.copy(alpha = 0.6f), + ) + }, + supportingText = supportingText?.let { + { Text(text = it, style = Theme.typography.body3, color = Theme.colors.black300) } + }, + colors = OutlinedTextFieldDefaults.colors( + cursorColor = if (expanded) contentColor else Color.Transparent, + unfocusedTextColor = contentColor, + focusedTextColor = contentColor, + disabledTextColor = contentColor, + errorTextColor = contentColor, + unfocusedContainerColor = containerColor, + focusedContainerColor = containerColor, + disabledContainerColor = containerColor, + errorContainerColor = containerColor, + unfocusedBorderColor = defaultBorder, + focusedBorderColor = TextInputColorsDefaults.focusBorder(), + disabledBorderColor = TextInputColorsDefaults.defaultsBorder(), + errorBorderColor = TextInputColorsDefaults.errorBorder(), + ), + trailingIcon = { + Icon( + painterResource(R.drawable.arrow_vector), + null, + modifier = Modifier.rotate(if (expanded) 180f else 0f), + ) + }, + shape = shape, + modifier = Modifier + .fillMaxWidth() + .menuAnchor(MenuAnchorType.PrimaryNotEditable, isEnabled), + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + containerColor = colors.containerColor(isEnabled).value, + ) { + items.forEach { item -> + DropdownMenuItem( + text = { + Text( + item, + style = Theme.typography.body3, + color = colors.contentColor(isEnabled).value, + ) + }, + onClick = { + onSelected(item) + expanded = false + }, + ) + } + } + } +} + +@StandardScreenSizePreview +@Composable +fun DropDownMenuPreview() { + val items = listOf("Android", "Backend", "Frontend") + var selected: String by rememberSaveable { mutableStateOf("") } + + Column(modifier = Modifier.padding(12.dp)) { + DropDownMenu( + placeholder = "Выбери значение", + items = items, + onSelected = { selected = it }, + selected = selected, + isExpanded = true, + colors = getTextInputColors(), + ) + } +} + +class DropDownMenuParamsProvider : PreviewParameterProvider { + override val values = sequenceOf( + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + isExpanded = true, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + isError = true, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + isEnabled = false, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + selected = "Android Mobile Developer", + isEnabled = false, + ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + selected = "Android Mobile Developer", + isEnabled = true, + ), + ) +} + +@StaticPreview +@Composable +fun DropDownMenuPreview( + @PreviewParameter(DropDownMenuParamsProvider::class) params: DropDownMenuParams, +) { + Box( + Modifier + .background(Color.White) + .padding(10.dp), + ) { + DropDownMenu( + placeholder = params.placeholder, + items = params.items, + selected = params.selected, + onSelected = params.onSelected, + isExpanded = params.isExpanded, + isError = params.isError, + isEnabled = params.isEnabled, + ) + } +} + +data class DropDownMenuParams( + val placeholder: String, + val items: List, + val selected: String = "", + val onSelected: (String) -> Unit = {}, + val modifier: Modifier = Modifier, + val isEnabled: Boolean = true, + val isError: Boolean = false, + val isExpanded: Boolean = false, + val colors: ColorsTextInputYeaHub = getTextInputColors(), +) + From 15772e3f6c32253b152e8f267316b0a96fffa1b4 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 10:28:36 +0400 Subject: [PATCH 16/28] DropDownMenu with previews --- .../ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt index e7127560..3f7d3b2f 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -83,6 +83,7 @@ fun DropDownMenu( .width(328.dp) .height(58.dp), expanded = expanded, + //noinspection AssignedValueIsNeverRead onExpandedChange = { if (isEnabled) expanded = it }, ) { OutlinedTextField( From 8a622da669efee923825d1189f6630c23d4c842a Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 10:29:46 +0400 Subject: [PATCH 17/28] DropDownMenu with previews --- .../ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt index 3f7d3b2f..e7127560 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -83,7 +83,6 @@ fun DropDownMenu( .width(328.dp) .height(58.dp), expanded = expanded, - //noinspection AssignedValueIsNeverRead onExpandedChange = { if (isEnabled) expanded = it }, ) { OutlinedTextField( From dca3b20e4e614aaac3061764beee179f0fea1d36 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Fri, 30 Jan 2026 12:50:44 +0400 Subject: [PATCH 18/28] UploadPhotoButton with previews --- .../core_ui/component/UploadPhotoButton.kt | 114 ++++++++++++++++++ core/ui/src/main/res/drawable/image.xml | 39 ++++++ 2 files changed, 153 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt create mode 100644 core/ui/src/main/res/drawable/image.xml diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt new file mode 100644 index 00000000..ca95bcc6 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt @@ -0,0 +1,114 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import ru.yeahub.core_ui.theme.Theme +import ru.yeahub.ui.R + + +@Composable +fun UploadPhotoButton( + modifier: Modifier = Modifier, + title: String = "Кликни для изменения", + helper: String = "JPG, PNG, JPEG (не более 5мб)", + onClick: () -> Unit, +) { + val shape = RoundedCornerShape(12.dp) + + Surface( + modifier = modifier + .fillMaxWidth() + .height(164.dp) + .dashedRoundedBorder( + color = Theme.colors.purple400, + strokeWidth = 2.dp, + cornerRadius = 12.dp, + dashLength = 8.dp, + gapLength = 8.dp, + ) + .clickable(onClick = onClick), + shape = shape, + color = Color.Transparent, + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 28.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Icon( + painter = painterResource(R.drawable.image), + contentDescription = null, + tint = Theme.colors.purple700, + modifier = Modifier.size(32.dp), + ) + + Spacer(Modifier.height(12.dp)) + + Text(text = title, style = Theme.typography.body3, color = Theme.colors.purple700) + + Spacer(Modifier.height(12.dp)) + + Text(text = helper, style = Theme.typography.body7, color = Theme.colors.black200) + } + } +} + + +@Preview(showBackground = true) +@Composable +fun UUploadPhotoButtonPreview() { + Column(Modifier.padding(16.dp)) { + UploadPhotoButton(onClick = {}) + } +} + +fun Modifier.dashedRoundedBorder( + color: Color, + strokeWidth: Dp = 2.dp, + cornerRadius: Dp = 12.dp, + dashLength: Dp = 12.dp, + gapLength: Dp = 12.dp, +) = drawWithCache { + val strokePx = strokeWidth.toPx() + val radiusPx = cornerRadius.toPx() + val dashPx = dashLength.toPx() + val gapPx = gapLength.toPx() + + val stroke = Stroke( + width = strokePx, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPx, gapPx), 0f), + ) + + onDrawBehind { + drawRoundRect( + color = color, + style = stroke, + cornerRadius = CornerRadius(radiusPx, radiusPx), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/image.xml b/core/ui/src/main/res/drawable/image.xml new file mode 100644 index 00000000..6d444a37 --- /dev/null +++ b/core/ui/src/main/res/drawable/image.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + From 9bd2b34716d0ec1309588e4488da8bd31f33e65e Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:42:37 +0400 Subject: [PATCH 19/28] DefaultTextField refactor --- .../core_ui/component/textInput/TextInput.kt | 127 +++++++++++------- 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt index 410680b9..bfc8ab96 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/textInput/TextInput.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults @@ -30,6 +31,7 @@ import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -58,7 +60,6 @@ import ru.yeahub.ui.R private sealed class TextInputState { data object Default : TextInputState() data object Focused : TextInputState() - data object Active : TextInputState() data object Error : TextInputState() data object Disabled : TextInputState() } @@ -80,7 +81,7 @@ fun TextInput( shape: Shape = RoundedCornerShape(12.dp), contentPadding: PaddingValues = OutlinedTextFieldDefaults.contentPadding(), singleLine: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { val isFocused by interactionSource.collectIsFocusedAsState() @@ -96,7 +97,7 @@ fun TextInput( Box( modifier = modifier .width(300.dp) - .height(300.dp) + .height(300.dp), ) { SearchBar( inputField = { @@ -106,7 +107,7 @@ fun TextInput( onValueChange(newValue) }, modifier = Modifier, - label = label, + placeholder = label, isFocused = isFocused, isEnabled = isEnabled, isError = isError, @@ -114,7 +115,7 @@ fun TextInput( colors = colors, shape = shape, singleLine = singleLine, - interactionSource = interactionSource + interactionSource = interactionSource, ) }, expanded = expanded, @@ -124,7 +125,7 @@ fun TextInput( shape = shape, colors = SearchBarDefaults.colors( containerColor = Color.Transparent, - dividerColor = Color.Transparent + dividerColor = Color.Transparent, ), ) { Surface( @@ -132,7 +133,7 @@ fun TextInput( .fillMaxWidth(), shape = MaterialTheme.shapes.medium, color = Theme.colors.white900, - border = BorderStroke(1.dp, TextInputColorsDefaults.defaultsBorder()) + border = BorderStroke(1.dp, TextInputColorsDefaults.defaultsBorder()), ) { LazyColumn( modifier = Modifier.fillMaxWidth(), @@ -145,13 +146,13 @@ fun TextInput( onValueChange(suggestion) onExpandedChange(false) } - .padding(contentPadding) + .padding(contentPadding), ) { Text( text = suggestion, color = colors.contentColor(isEnabled).value, style = Theme.typography.body3, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) } } @@ -166,7 +167,7 @@ fun TextInput( fun DefaultTextField( value: String, onValueChange: (String) -> Unit, - label: String, + placeholder: String, modifier: Modifier = Modifier, onExpandedChange: (Boolean) -> Unit, colors: ColorsTextInputYeaHub = TextInputColorsDefaults.defaultColors(), @@ -175,7 +176,10 @@ fun DefaultTextField( isError: Boolean = false, singleLine: Boolean = true, shape: Shape = RoundedCornerShape(12.dp), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + readOnly: Boolean = false, + leadingIcon: (@Composable (() -> Unit))? = null, + trailingIcon: (@Composable (() -> Unit))? = null, ) { val containerColor by colors.containerColor(isEnabled) val contentColor by colors.contentColor(isEnabled) @@ -186,26 +190,44 @@ fun DefaultTextField( !isEnabled -> TextInputState.Disabled isError -> TextInputState.Error isFocused -> TextInputState.Focused - value.isNotEmpty() -> TextInputState.Active else -> TextInputState.Default } val iconColor = when (state) { - TextInputState.Active -> Theme.colors.black900 - else -> Theme.colors.black300 + TextInputState.Default -> Theme.colors.black300 + TextInputState.Disabled -> Theme.colors.black300 + else -> Theme.colors.black900 } val defaultBorder = when (state) { - TextInputState.Active -> TextInputColorsDefaults.activeBorder() + TextInputState.Focused -> TextInputColorsDefaults.activeBorder() TextInputState.Error -> TextInputColorsDefaults.errorBorder() else -> TextInputColorsDefaults.defaultsBorder() } + + fun provideIconColor( + slot: (@Composable () -> Unit)?, + color: Color, + ): (@Composable () -> Unit)? = + slot?.let { content -> + { + CompositionLocalProvider(LocalContentColor provides color) { + content() + } + } + } + + val leadingIconSlot = provideIconColor(leadingIcon, iconColor) + + val trailingIconSlot = provideIconColor(trailingIcon, iconColor) + OutlinedTextField( value = value, onValueChange = onValueChange, - label = { + readOnly = readOnly, + placeholder = { Text( - text = label, + text = placeholder, color = Theme.colors.black300, style = Theme.typography.body3, ) @@ -215,17 +237,8 @@ fun DefaultTextField( .height(58.dp), enabled = isEnabled, singleLine = singleLine, - leadingIcon = - { - Icon( - painter = painterResource(id = R.drawable.icon_search), - contentDescription = "Поиск", - tint = iconColor, - modifier = modifier - .width(20.dp) - .height(20.dp) - ) - }, + leadingIcon = leadingIconSlot, + trailingIcon = trailingIconSlot, isError = isError, shape = shape, textStyle = Theme.typography.body3, @@ -249,7 +262,7 @@ fun DefaultTextField( onExpandedChange(false) keyboardController?.hide() focusManager.clearFocus(force = true) - } + }, ), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), interactionSource = interactionSource, @@ -268,7 +281,7 @@ object TextInputColorsDefaults { containerColor = containerColor, contentColor = contentColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -307,7 +320,7 @@ data class TextInputParams( val value: String, val onValueChange: (String) -> Unit, val onExpandedChange: (Boolean) -> Unit, - val label: String, + val placeholder: String, val modifier: Modifier = Modifier, val isEnabled: Boolean = true, val isError: Boolean = false, @@ -315,6 +328,15 @@ data class TextInputParams( val isFocus: Boolean = false, val colors: ColorsTextInputYeaHub = getTextInputColors(), val singleLine: Boolean = true, + val leadingIcon: (@Composable () -> Unit)? = { + Icon( + painter = painterResource(R.drawable.icon_search), + contentDescription = "Поиск", + modifier = modifier + .width(20.dp) + .height(20.dp), + ) + }, ) fun getTextInputColors(): ColorsTextInputYeaHub { @@ -332,33 +354,43 @@ class TextInputParamsProvider : PreviewParameterProvider { value = "", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = true, - isError = false + isError = false, ), TextInputParams( value = "text", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = true, - isError = false + isError = false, + isFocus = true, ), TextInputParams( value = "", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = false, - isError = false + isError = false, ), TextInputParams( value = "", onValueChange = {}, onExpandedChange = {}, - label = "text", + placeholder = "placeholder", isEnabled = true, - isError = true + isError = true, + ), + TextInputParams( + value = "text", + onValueChange = {}, + onExpandedChange = {}, + placeholder = "placeholder", + isEnabled = true, + isError = false, + isFocus = false, ), ) } @@ -366,22 +398,23 @@ class TextInputParamsProvider : PreviewParameterProvider { @StaticPreview @Composable fun TextInputPreview( - @PreviewParameter(TextInputParamsProvider::class) params: TextInputParams + @PreviewParameter(TextInputParamsProvider::class) params: TextInputParams, ) { Box( Modifier .background(Color.White) - .padding(10.dp) + .padding(10.dp), ) { DefaultTextField( value = params.value, onValueChange = params.onValueChange, onExpandedChange = params.onExpandedChange, - label = params.label, + placeholder = params.placeholder, isEnabled = params.isEnabled, isError = params.isError, colors = params.colors, - isFocused = params.isFocus + isFocused = params.isFocus, + leadingIcon = params.leadingIcon, ) } } @@ -389,7 +422,7 @@ fun TextInputPreview( @StandardScreenSizePreview @Composable fun TextInputDynamicPreview( - @PreviewParameter(TextInputPreviewProvider::class) params: Pair> + @PreviewParameter(TextInputPreviewProvider::class) params: Pair>, ) { val mockViewModel = object : SuggestionsViewModel() { init { @@ -413,7 +446,7 @@ fun ScreenSuggestions( modifier = modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { TextInput( value = text, @@ -424,7 +457,7 @@ fun ScreenSuggestions( suggestions = suggestions, onQueryChanged = { viewModel.onQueryChange(it, suggestions) }, expanded = expanded, - onExpandedChange = { expanded = it } + onExpandedChange = { expanded = it }, ) } } @@ -436,4 +469,4 @@ class TextInputPreviewProvider : PreviewParameterProvider Date: Sun, 1 Feb 2026 12:44:25 +0400 Subject: [PATCH 20/28] DropDownMenu DefaultTextField reused --- .../yeahub/core_ui/component/DropDownMenu.kt | 128 +++++++----------- 1 file changed, 51 insertions(+), 77 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt index e7127560..1e71381d 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/DropDownMenu.kt @@ -13,8 +13,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.Icon import androidx.compose.material3.MenuAnchorType -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -32,6 +30,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import ru.yeahub.core_ui.component.textInput.ColorsTextInputYeaHub +import ru.yeahub.core_ui.component.textInput.DefaultTextField +import ru.yeahub.core_ui.component.textInput.TextInput import ru.yeahub.core_ui.component.textInput.TextInputColorsDefaults import ru.yeahub.core_ui.component.textInput.getTextInputColors import ru.yeahub.core_ui.example.dynamicPreview.StandardScreenSizePreview @@ -39,13 +39,6 @@ import ru.yeahub.core_ui.example.staticPreview.StaticPreview import ru.yeahub.core_ui.theme.Theme import ru.yeahub.ui.R -private sealed class DropDownMenuState { - data object Default : DropDownMenuState() - data object Expanded : DropDownMenuState() - data object Error : DropDownMenuState() - data object Disabled : DropDownMenuState() -} - @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable fun DropDownMenu( @@ -54,28 +47,23 @@ fun DropDownMenu( items: List, selected: String, onSelected: (String) -> Unit, - supportingText: String? = null, shape: Shape = RoundedCornerShape(12.dp), isExpanded: Boolean = false, isEnabled: Boolean = true, isError: Boolean = false, colors: ColorsTextInputYeaHub = TextInputColorsDefaults.defaultColors(), + trailingIcon: @Composable (() -> Unit)? = { + Icon( + painterResource(R.drawable.arrow_vector), + null, + ) + }, + leadingIcon: @Composable (() -> Unit)? = null, ) { var expanded by remember { mutableStateOf(isExpanded) } - val containerColor by colors.containerColor(isEnabled) - val contentColor by colors.contentColor(isEnabled) - val state = when { - !isEnabled -> DropDownMenuState.Disabled - isError -> DropDownMenuState.Error - isExpanded -> DropDownMenuState.Expanded - else -> DropDownMenuState.Default - } - - val defaultBorder = when (state) { - DropDownMenuState.Expanded -> TextInputColorsDefaults.activeBorder() - DropDownMenuState.Error -> TextInputColorsDefaults.errorBorder() - else -> TextInputColorsDefaults.defaultsBorder() + val trailingIconRotating: (@Composable (() -> Unit))? = trailingIcon?.let { icon -> + { Box(Modifier.rotate(if (expanded) 180f else 0f)) { icon() } } } ExposedDropdownMenuBox( @@ -85,48 +73,21 @@ fun DropDownMenu( expanded = expanded, onExpandedChange = { if (isEnabled) expanded = it }, ) { - OutlinedTextField( + DefaultTextField( value = selected, onValueChange = {}, readOnly = true, - enabled = isEnabled, + isEnabled = isEnabled, isError = isError, - placeholder = { - Text( - text = placeholder, - style = Theme.typography.body3, - color = Theme.colors.black300.copy(alpha = 0.6f), - ) - }, - supportingText = supportingText?.let { - { Text(text = it, style = Theme.typography.body3, color = Theme.colors.black300) } - }, - colors = OutlinedTextFieldDefaults.colors( - cursorColor = if (expanded) contentColor else Color.Transparent, - unfocusedTextColor = contentColor, - focusedTextColor = contentColor, - disabledTextColor = contentColor, - errorTextColor = contentColor, - unfocusedContainerColor = containerColor, - focusedContainerColor = containerColor, - disabledContainerColor = containerColor, - errorContainerColor = containerColor, - unfocusedBorderColor = defaultBorder, - focusedBorderColor = TextInputColorsDefaults.focusBorder(), - disabledBorderColor = TextInputColorsDefaults.defaultsBorder(), - errorBorderColor = TextInputColorsDefaults.errorBorder(), - ), - trailingIcon = { - Icon( - painterResource(R.drawable.arrow_vector), - null, - modifier = Modifier.rotate(if (expanded) 180f else 0f), - ) - }, + placeholder = placeholder, + trailingIcon = trailingIconRotating, + leadingIcon = leadingIcon, shape = shape, modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryNotEditable, isEnabled), + onExpandedChange = { }, + isFocused = isExpanded, ) ExposedDropdownMenu( @@ -153,24 +114,6 @@ fun DropDownMenu( } } -@StandardScreenSizePreview -@Composable -fun DropDownMenuPreview() { - val items = listOf("Android", "Backend", "Frontend") - var selected: String by rememberSaveable { mutableStateOf("") } - - Column(modifier = Modifier.padding(12.dp)) { - DropDownMenu( - placeholder = "Выбери значение", - items = items, - onSelected = { selected = it }, - selected = selected, - isExpanded = true, - colors = getTextInputColors(), - ) - } -} - class DropDownMenuParamsProvider : PreviewParameterProvider { override val values = sequenceOf( DropDownMenuParams( @@ -204,6 +147,13 @@ class DropDownMenuParamsProvider : PreviewParameterProvider selected = "Android Mobile Developer", isEnabled = true, ), + DropDownMenuParams( + items = listOf("Android", "Backend", "Frontend"), + placeholder = "Выбери значение", + selected = "Android Mobile Developer", + isEnabled = true, + isExpanded = true, + ), ) } @@ -229,6 +179,31 @@ fun DropDownMenuPreview( } } +@StandardScreenSizePreview +@Composable +fun DropDownMenuPreview() { + val items = listOf("Android", "Backend", "Frontend") + var selected: String by rememberSaveable { mutableStateOf("") } + + Column(modifier = Modifier.padding(12.dp)) { + DropDownMenu( + placeholder = "Выбери значение", + items = items, + onSelected = { selected = it }, + selected = selected, + colors = getTextInputColors(), + ) + TextInput( + value = "", + onValueChange = { }, + label = "label", + expanded = false, + onExpandedChange = { }, + onQueryChanged = { }, + ) + } +} + data class DropDownMenuParams( val placeholder: String, val items: List, @@ -239,5 +214,4 @@ data class DropDownMenuParams( val isError: Boolean = false, val isExpanded: Boolean = false, val colors: ColorsTextInputYeaHub = getTextInputColors(), -) - +) \ No newline at end of file From 0ce181eb668459e4b813122826de39cd68273889 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:46:07 +0400 Subject: [PATCH 21/28] UploadPhotoButton lines fix --- .../java/ru/yeahub/core_ui/component/UploadPhotoButton.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt index ca95bcc6..3fbe0863 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/UploadPhotoButton.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.unit.dp import ru.yeahub.core_ui.theme.Theme import ru.yeahub.ui.R - @Composable fun UploadPhotoButton( modifier: Modifier = Modifier, @@ -78,10 +77,9 @@ fun UploadPhotoButton( } } - @Preview(showBackground = true) @Composable -fun UUploadPhotoButtonPreview() { +fun UploadPhotoButtonPreview() { Column(Modifier.padding(16.dp)) { UploadPhotoButton(onClick = {}) } @@ -98,12 +96,10 @@ fun Modifier.dashedRoundedBorder( val radiusPx = cornerRadius.toPx() val dashPx = dashLength.toPx() val gapPx = gapLength.toPx() - val stroke = Stroke( width = strokePx, pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPx, gapPx), 0f), ) - onDrawBehind { drawRoundRect( color = color, From 9006bfea42bcc721ac025f2d8838f17384eeec1f Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:58:35 +0400 Subject: [PATCH 22/28] add font for ProfileEdit TabBar --- core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt b/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt index 78a60294..b8411339 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/theme/Typography.kt @@ -62,6 +62,12 @@ data class Typography( fontSize = 22.sp, lineHeight = 26.sp, ), + val head6: TextStyle = TextStyle( + fontFamily = sfProDisplay, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + lineHeight = 24.sp, + ), //Body val body1: TextStyle = TextStyle( fontFamily = manrope, From 5263a302ff16aeaa77021398723dad1d1b7c2034 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 12:58:42 +0400 Subject: [PATCH 23/28] add TabBar --- .../ru/yeahub/core_ui/component/TabBar.kt | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt new file mode 100644 index 00000000..dd1c5667 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/TabBar.kt @@ -0,0 +1,141 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import ru.yeahub.core_ui.example.dynamicPreview.StandardScreenSizePreview +import ru.yeahub.core_ui.theme.Theme + +@Immutable +data class CoreTopTabsColors( + val containerColor: Color, + val indicatorColor: Color, + val selectedTextColor: Color, + val unselectedTextColor: Color, +) + +object CoreTopTabsDefaults { + @Composable + fun colors( + containerColor: Color = Theme.colors.white900, + indicatorColor: Color = Theme.colors.purple700, + selectedTextColor: Color = Theme.colors.black900, + unselectedTextColor: Color = Theme.colors.black500, + ) = CoreTopTabsColors( + containerColor = containerColor, + indicatorColor = indicatorColor, + selectedTextColor = selectedTextColor, + unselectedTextColor = unselectedTextColor, + ) +} + +@Composable +fun CoreTopTabs( + tabs: List, + selectedIndex: Int, + onSelected: (Int) -> Unit, + modifier: Modifier = Modifier, + colors: CoreTopTabsColors = CoreTopTabsDefaults.colors(), + edgePadding: Dp = 0.dp, + tabSpacing: Dp = 0.dp, + indicatorHeight: Dp = 2.dp, + indicatorHorizontalPadding: Dp = 16.dp, +) { + ScrollableTabRow( + selectedTabIndex = selectedIndex, + modifier = modifier, + containerColor = colors.containerColor, + contentColor = colors.selectedTextColor, + edgePadding = edgePadding, + divider = { HorizontalDivider(thickness = 0.dp) }, + indicator = { positions -> + val pos = positions[selectedIndex] + Box( + Modifier + .tabIndicatorOffset(pos) + .padding(horizontal = indicatorHorizontalPadding) + .height(indicatorHeight) + .background(colors.indicatorColor), + ) + }, + ) { + tabs.forEachIndexed { index, title -> + val isSelected = index == selectedIndex + Tab( + selected = isSelected, + onClick = { onSelected(index) }, + text = { + Text( + text = title, + color = if (isSelected) colors.selectedTextColor else colors.unselectedTextColor, + style = Theme.typography.head6, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Visible, + ) + }, + ) + } + } +} + +data class YhTabsParams( + val items: List, + val selectedIndex: Int, +) + +class YhTabsParamsProvider : PreviewParameterProvider { + override val values = sequenceOf( + YhTabsParams(listOf("Личная информация", "Обо мне", "Навыки"), 0), + YhTabsParams( + listOf( + "Tab 1", + "Очень длинный таб", + "Очень длинный таб", + "Очень длинный таб", + "Tab 1", + "Tab 1", + ), + 2, + ), + ) +} + +@StandardScreenSizePreview +@Composable +fun YhTabsPreview( + @PreviewParameter(YhTabsParamsProvider::class) p: YhTabsParams, +) { + var selected by remember { mutableIntStateOf(p.selectedIndex) } + + Box( + Modifier + .background(Color.White) + .padding(0.dp), + ) { + CoreTopTabs( + tabs = p.items, + selectedIndex = selected, + onSelected = { selected = it }, + ) + } +} \ No newline at end of file From 6a99c1563adfdad4ff2be16eec8b55dca2bff16b Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 14:27:43 +0400 Subject: [PATCH 24/28] added secondary color for OutlineButton and preview --- .../ru/yeahub/core_ui/component/Button.kt | 87 +++++++++++++------ 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt index e3ca6cc4..d9d443a5 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/Button.kt @@ -52,7 +52,7 @@ fun PrimaryButton( interactionSource = interactionSource, shape = shape, contentPadding = contentPadding, - content = content + content = content, ) } @@ -91,7 +91,7 @@ fun OutlineButton( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = RoundedCornerShape(12.dp), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, - content: @Composable RowScope.() -> Unit + content: @Composable RowScope.() -> Unit, ) { DefaultButton( onClick = onClick, @@ -102,7 +102,7 @@ fun OutlineButton( interactionSource = interactionSource, shape = shape, contentPadding = contentPadding, - content = content + content = content, ) } @@ -116,7 +116,7 @@ private fun DefaultButton( border: BorderStroke? = null, shape: Shape = ButtonDefaults.shape, contentPadding: PaddingValues = ButtonDefaults.ContentPadding, - content: @Composable RowScope.() -> Unit + content: @Composable RowScope.() -> Unit, ) { val contentColor: Color by colors.contentColor(enabled) val containerColor: Color by colors.containerColor(enabled) @@ -129,16 +129,16 @@ private fun DefaultButton( color = containerColor, contentColor = contentColor, border = border, - interactionSource = interactionSource + interactionSource = interactionSource, ) { CompositionLocalProvider( - value = LocalContentColor provides contentColor + value = LocalContentColor provides contentColor, ) { Row( modifier = Modifier .padding(contentPadding), horizontalArrangement = Arrangement.Center, - content = content + content = content, ) } } @@ -150,13 +150,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.white900, containerColor: Color = Theme.colors.purple700, disabledContentColor: Color = Theme.colors.white900, - disabledContainerColor: Color = Theme.colors.black100 + disabledContainerColor: Color = Theme.colors.black100, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -165,13 +165,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.white900, containerColor: Color = Theme.colors.red600, disabledContentColor: Color = Theme.colors.white900, - disabledContainerColor: Color = Theme.colors.red200 + disabledContainerColor: Color = Theme.colors.red200, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -180,13 +180,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.purple700, containerColor: Color = Theme.colors.purple100, disabledContentColor: Color = Theme.colors.black200, - disabledContainerColor: Color = Theme.colors.black50 + disabledContainerColor: Color = Theme.colors.black50, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -195,13 +195,13 @@ object YeahubButtonDefaults { contentColor: Color = Theme.colors.white900, containerColor: Color = Theme.colors.red600, disabledContentColor: Color = Theme.colors.white900, - disabledContainerColor: Color = Theme.colors.red200 + disabledContainerColor: Color = Theme.colors.red200, ): YeahubButtonColors { return YeahubButtonColors( contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @@ -216,18 +216,44 @@ object YeahubButtonDefaults { contentColor = contentColor, containerColor = containerColor, disabledContentColor = disabledContentColor, - disabledContainerColor = disabledContainerColor + disabledContainerColor = disabledContainerColor, ) } @Composable fun outlineBorderDefaults( width: Dp = 1.dp, - borderColor: Color = Theme.colors.red200 + borderColor: Color = Theme.colors.red200, + ): BorderStroke { + return BorderStroke( + width = width, + color = borderColor, + ) + } + + @Composable + fun secondaryOutlinedButtonColors( + contentColor: Color = Theme.colors.purple700, + containerColor: Color = Color.Transparent, + disabledContentColor: Color = Theme.colors.purple200, + disabledContainerColor: Color = Color.Transparent, + ): YeahubButtonColors { + return YeahubButtonColors( + contentColor = contentColor, + containerColor = containerColor, + disabledContentColor = disabledContentColor, + disabledContainerColor = disabledContainerColor, + ) + } + + @Composable + fun secondaryOutlineBorderDefaults( + width: Dp = 1.dp, + borderColor: Color = Theme.colors.purple700, ): BorderStroke { return BorderStroke( width = width, - color = borderColor + color = borderColor, ) } } @@ -260,7 +286,7 @@ interface ButtonColors { } @Preview( - showBackground = true + showBackground = true, ) @Composable fun ButtonPreviews() { @@ -268,21 +294,21 @@ fun ButtonPreviews() { modifier = Modifier .fillMaxWidth() .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + verticalArrangement = Arrangement.spacedBy(16.dp), ) { // Primary Button Text("Primary Buttons", style = MaterialTheme.typography.titleMedium) PrimaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = true + enabled = true, ) { Text("Enabled Primary Button") } PrimaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = false + enabled = false, ) { Text("Disabled Primary Button") } @@ -292,14 +318,14 @@ fun ButtonPreviews() { SecondaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = true + enabled = true, ) { Text("Enabled Secondary Button") } SecondaryButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = false + enabled = false, ) { Text("Disabled Secondary Button") } @@ -309,16 +335,25 @@ fun ButtonPreviews() { OutlineButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = true + enabled = true, ) { Text("Enabled Outline Button") } OutlineButton( onClick = {}, modifier = Modifier.fillMaxWidth(), - enabled = false + enabled = false, ) { Text("Disabled Outline Button") } + OutlineButton( + onClick = {}, + colors = YeahubButtonDefaults.secondaryOutlinedButtonColors(), + border = YeahubButtonDefaults.secondaryOutlineBorderDefaults(), + modifier = Modifier.fillMaxWidth(), + enabled = true, + ) { + Text("Enabled Outline Button with secondary colors") + } } } From f2940385ef352081af93dd8404d19ec7d348081b Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 14:27:57 +0400 Subject: [PATCH 25/28] added SmallScreenSizePreview --- .../dynamicPreview/SmallScreenSizePreview.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt b/core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt new file mode 100644 index 00000000..3b19ff76 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/example/dynamicPreview/SmallScreenSizePreview.kt @@ -0,0 +1,13 @@ +package ru.yeahub.core_ui.example.dynamicPreview + +import androidx.compose.ui.tooling.preview.Preview + +private const val SMALL_SCREEN_WIDTH_DP = 320 +private const val SMALL_SCREEN_HEIGHT_DP = 480 + +@Preview( + widthDp = SMALL_SCREEN_WIDTH_DP, + heightDp = SMALL_SCREEN_HEIGHT_DP, + showBackground = true, +) +annotation class SmallScreenSizePreview() From e2498e87fe512badf2f5ba1024328a1c62823653 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 14:28:57 +0400 Subject: [PATCH 26/28] added UnsavedChangesDialog with preview --- .../yeahub/core_ui/component/AlertDialog.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt new file mode 100644 index 00000000..45bbbf74 --- /dev/null +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt @@ -0,0 +1,44 @@ +package ru.yeahub.core_ui.component + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import ru.yeahub.core_ui.example.dynamicPreview.SmallScreenSizePreview + +@Composable +fun UnsavedChangesDialog( + onStay: () -> Unit, + onLeave: () -> Unit, +) { + AlertDialog( + onDismissRequest = onStay, + title = { Text("Подтвердить действие") }, + text = { + Text( + "У вас есть несохраненные данные.\nВы хотите продолжить?", + ) + }, + dismissButton = { + PrimaryButton(onClick = onStay) { + Text("Да") + } + }, + confirmButton = { + OutlineButton( + onClick = onLeave, + colors = YeahubButtonDefaults.secondaryOutlinedButtonColors(), + border = YeahubButtonDefaults.secondaryOutlineBorderDefaults(), + ) { + Text( + "нет", + ) + } + }, + ) +} + +@SmallScreenSizePreview +@Composable +fun UnsavedChangesDialogPreview() { + UnsavedChangesDialog({}, {}) +} \ No newline at end of file From 49fe97d628e66fa46540dea5bff7bfecbb987df9 Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Sun, 1 Feb 2026 15:45:18 +0400 Subject: [PATCH 27/28] UnsavedChangesDialog color fix --- .../ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt index 45bbbf74..45cd52e2 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt @@ -4,6 +4,7 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.runtime.Composable import ru.yeahub.core_ui.example.dynamicPreview.SmallScreenSizePreview +import ru.yeahub.core_ui.theme.colors @Composable fun UnsavedChangesDialog( @@ -34,6 +35,7 @@ fun UnsavedChangesDialog( ) } }, + containerColor = colors.white900, ) } From e1a79dc047961ca0054bddb84a0601118b7bac9e Mon Sep 17 00:00:00 2001 From: Yrun00 Date: Mon, 2 Feb 2026 04:49:05 +0400 Subject: [PATCH 28/28] UnsavedChangesDialog fix font and typo --- .../java/ru/yeahub/core_ui/component/AlertDialog.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt index 45cd52e2..89a5e150 100644 --- a/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt +++ b/core/ui/src/main/java/ru/yeahub/core_ui/component/AlertDialog.kt @@ -4,6 +4,7 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.runtime.Composable import ru.yeahub.core_ui.example.dynamicPreview.SmallScreenSizePreview +import ru.yeahub.core_ui.theme.Theme import ru.yeahub.core_ui.theme.colors @Composable @@ -13,10 +14,16 @@ fun UnsavedChangesDialog( ) { AlertDialog( onDismissRequest = onStay, - title = { Text("Подтвердить действие") }, + title = { + Text( + text = "Подтвердить действие", + style = Theme.typography.head4, + ) + }, text = { Text( "У вас есть несохраненные данные.\nВы хотите продолжить?", + style = Theme.typography.body3, ) }, dismissButton = { @@ -31,7 +38,7 @@ fun UnsavedChangesDialog( border = YeahubButtonDefaults.secondaryOutlineBorderDefaults(), ) { Text( - "нет", + "Нет", ) } },