diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/components/standart/TTFloatingActionButton.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/components/standart/TTFloatingActionButton.kt deleted file mode 100644 index 1c26fa0..0000000 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/components/standart/TTFloatingActionButton.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.prodhack.moscow2025.presentation.components.standart - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.prodhack.moscow2025.R - -@Composable -fun TTFloatingActionButton( - modifier: Modifier, - onClick: () -> Unit, - text: String -) { - val colorScheme = MaterialTheme.colorScheme - val typography = MaterialTheme.typography - - ExtendedFloatingActionButton( - modifier = modifier, - onClick = { - onClick() - }, - shape = RoundedCornerShape(10.dp), - containerColor = colorScheme.tertiaryContainer, - contentColor = colorScheme.onTertiaryContainer, - elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 5.dp) - ) { - Row { - Text( - text = text, - style = typography.titleMedium, - fontSize = 16.sp - ) - Spacer(Modifier.width(10.dp)) - Icon( - painter = painterResource(R.drawable.add_square_outline), - contentDescription = null, - modifier = Modifier.size(22.dp) - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt index 6211ce4..3d2a30b 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt @@ -118,7 +118,11 @@ fun TTasksNavHost( } composable(AppDestination.ResumeCreation.route) { - CreateResumeScreen({ navController.popBackStack() }) + CreateResumeScreen({ navController.popBackStack() }, openResumeDetails = { id -> + navController.navigate(AppDestination.ResumeDetails.route, Bundle().apply { + putString(AppDestination.ResumeDetails.ARG_ID, id) + }) + }) } } } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt index 0075b7c..cfb5eb2 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt @@ -42,14 +42,16 @@ import com.prodhack.moscow2025.presentation.components.standart.TTTextFieldWithD import com.prodhack.moscow2025.presentation.components.standart.TTTextFieldWithSearch import com.prodhack.moscow2025.presentation.theme.Paddings import com.prodhack.moscow2025.presentation.theme.Shapes +import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope import com.prodhack.moscow2025.presentation.utils.toReadableText import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable import org.koin.androidx.compose.koinViewModel @Composable -fun CreateResumeScreen( +fun ErrorCollectorScope.CreateResumeScreen( goBack: () -> Unit, + openResumeDetails: (String) -> Unit, viewModel: CreateResumeViewModel = koinViewModel() ) { val colorScheme = MaterialTheme.colorScheme @@ -122,7 +124,11 @@ fun CreateResumeScreen( error = formState.value.errors[ResumeField.Experience], dropdownItems = viewModel.experienceOptions, dropDownItem = { - Text(text = it.toReadableText(), style = typography.labelLarge, fontSize = 16.sp) + Text( + text = it.toReadableText(), + style = typography.labelLarge, + fontSize = 16.sp + ) }, onDropdownItemSelected = viewModel::onExperienceSelect ) @@ -187,99 +193,117 @@ fun CreateResumeScreen( Spacer(modifier = Modifier.height(Paddings.large)) - SectionCard(title = "Подробнее о вашем опыте работы:") { - formState.value.workExperience.forEachIndexed { index, workExp -> - WorkExperienceForm( - index = index, - workExp = workExp, - errors = formState.value.errors, - onPlaceChange = { viewModel.changeWorkExperiencePlace(index, it) }, - onDescriptionChange = { viewModel.changeWorkExperienceDescription(index, it) }, - onDurationChange = { viewModel.changeWorkExperienceMonthDuration(index, it) }, - onRemove = { viewModel.removeExperience(index) } - ) - Spacer(modifier = Modifier.height(Paddings.medium)) - } - - if (formState.value.workExperience.isEmpty()) { - EmptyStateText() - Spacer(modifier = Modifier.height(Paddings.medium)) - } - - AddItemButton( - text = "Добавить", - onClick = viewModel::addNewExperience, - containerColor = colorScheme.onSecondary, - contentColor = colorScheme.secondary + SectionCard(title = "Подробнее о вашем опыте работы:") { + formState.value.workExperience.forEachIndexed { index, workExp -> + WorkExperienceForm( + index = index, + workExp = workExp, + errors = formState.value.errors, + onPlaceChange = { viewModel.changeWorkExperiencePlace(index, it) }, + onDescriptionChange = { + viewModel.changeWorkExperienceDescription( + index, + it + ) + }, + onDurationChange = { + viewModel.changeWorkExperienceMonthDuration( + index, + it + ) + }, + onRemove = { viewModel.removeExperience(index) } ) + Spacer(modifier = Modifier.height(Paddings.medium)) } + + if (formState.value.workExperience.isEmpty()) { + EmptyStateText() + Spacer(modifier = Modifier.height(Paddings.medium)) + } + + AddItemButton( + text = "Добавить", + onClick = viewModel::addNewExperience, + containerColor = colorScheme.onSecondary, + contentColor = colorScheme.secondary + ) + } Spacer(modifier = Modifier.height(Paddings.large)) - SectionCard(title = "Ваше образование:") { - formState.value.education.forEachIndexed { index, education -> - EducationForm( - index = index, - education = education, - errors = formState.value.errors, - grades = viewModel.educationGradeOptions, - onPlaceChange = { viewModel.changeEducationPlace(index, it) }, - onGradeChange = { viewModel.changeEducationGrade(index, it) }, - onSpecializationChange = { viewModel.changeEducationSpecialization(index, it) }, - onDescriptionChange = { viewModel.changeEducationDescription(index, it) }, - onRemove = { viewModel.removeEducation(index) } - ) - Spacer(modifier = Modifier.height(Paddings.medium)) - } - - if (formState.value.education.isEmpty()) { - EmptyStateText() - Spacer(modifier = Modifier.height(Paddings.medium)) - } - - AddItemButton( - text = "Добавить", - onClick = viewModel::addNewEducation, - containerColor = colorScheme.onSecondary, - contentColor = colorScheme.secondary + SectionCard(title = "Ваше образование:") { + formState.value.education.forEachIndexed { index, education -> + EducationForm( + index = index, + education = education, + errors = formState.value.errors, + grades = viewModel.educationGradeOptions, + onPlaceChange = { viewModel.changeEducationPlace(index, it) }, + onGradeChange = { viewModel.changeEducationGrade(index, it) }, + onSpecializationChange = { + viewModel.changeEducationSpecialization( + index, + it + ) + }, + onDescriptionChange = { viewModel.changeEducationDescription(index, it) }, + onRemove = { viewModel.removeEducation(index) } ) + Spacer(modifier = Modifier.height(Paddings.medium)) } + if (formState.value.education.isEmpty()) { + EmptyStateText() + Spacer(modifier = Modifier.height(Paddings.medium)) + } + + AddItemButton( + text = "Добавить", + onClick = viewModel::addNewEducation, + containerColor = colorScheme.onSecondary, + contentColor = colorScheme.secondary + ) + } + Spacer(modifier = Modifier.height(Paddings.large)) - SectionCard(title = "Интересные проекты:") { - formState.value.projects.forEachIndexed { index, project -> - ProjectForm( - index = index, - project = project, - errors = formState.value.errors, - onNameChange = { viewModel.changeProjectName(index, it) }, - onDescriptionChange = { viewModel.changeProjectDescription(index, it) }, - onRemove = { viewModel.removeProject(index) } - ) - Spacer(modifier = Modifier.height(Paddings.medium)) - } - - if (formState.value.projects.isEmpty()) { - EmptyStateText() - Spacer(modifier = Modifier.height(Paddings.medium)) - } - - AddItemButton( - text = "Добавить", - onClick = viewModel::addNewProject, - containerColor = colorScheme.onSecondary, - contentColor = colorScheme.secondary + SectionCard(title = "Интересные проекты:") { + formState.value.projects.forEachIndexed { index, project -> + ProjectForm( + index = index, + project = project, + errors = formState.value.errors, + onNameChange = { viewModel.changeProjectName(index, it) }, + onDescriptionChange = { viewModel.changeProjectDescription(index, it) }, + onRemove = { viewModel.removeProject(index) } ) + Spacer(modifier = Modifier.height(Paddings.medium)) } + if (formState.value.projects.isEmpty()) { + EmptyStateText() + Spacer(modifier = Modifier.height(Paddings.medium)) + } + + AddItemButton( + text = "Добавить", + onClick = viewModel::addNewProject, + containerColor = colorScheme.onSecondary, + contentColor = colorScheme.secondary + ) + } + Spacer(modifier = Modifier.height(Paddings.large)) + + val resumeFillState = viewModel.resumeFillState.collectAsStateWithCallbacks { + openResumeDetails(it) + } BigButton( onClick = viewModel::submit, - buttonText = "Узнать свою ЗП", - isLoading = viewModel.resumeFillState.collectAsState().value.isLoading + buttonText = "Узнать свою зарплату", + isLoading = resumeFillState.value.isLoading ) Spacer(modifier = Modifier.height(Paddings.large)) - } } } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt index 9ef0a61..025f4cc 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt @@ -1,9 +1,7 @@ package com.prodhack.moscow2025.presentation.screens.main -import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -16,21 +14,25 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FabPosition import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import com.prodhack.moscow2025.R import com.prodhack.moscow2025.presentation.components.standart.BigButton -import com.prodhack.moscow2025.presentation.components.standart.TTFloatingActionButton import com.prodhack.moscow2025.presentation.components.standart.TopLogo import com.prodhack.moscow2025.presentation.dataModels.UIResumeBaseInfo import com.prodhack.moscow2025.presentation.theme.Paddings @@ -45,31 +47,64 @@ fun ErrorCollectorScope.MainScreen( openResumeDetails: (String) -> Unit, openCreateResume: () -> Unit, viewModel: MainScreenViewModel = koinViewModel() +) { + Scaffold( + modifier = modifier, + floatingActionButton = { + ExtendedFloatingActionButton( + onClick = { + openCreateResume() + }, + icon = { + Icon( + painter = painterResource(R.drawable.ic_plus), + "Extended floating action button." + ) + }, + text = { Text(text = "Добавить резюме") }, + ) + }, floatingActionButtonPosition = FabPosition.Center + ) { + val items = viewModel.resumeList.collectAsLazyPagingItems() + + MainScreenContent( + items = items, + openCreateResume = openCreateResume, + openResumeDetails = openResumeDetails + ) + } +} + +@Composable +private fun MainScreenContent( + modifier: Modifier = Modifier, + items: LazyPagingItems, + openCreateResume: () -> Unit, + openResumeDetails: (String) -> Unit ) { val typography = MaterialTheme.typography val colorScheme = MaterialTheme.colorScheme val shapes = MaterialTheme.shapes + Column( + modifier = modifier + .fillMaxSize() + .padding(horizontal = Paddings.large), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TopLogo() + Spacer(modifier = Modifier.height(Paddings.medium)) + Text( + text = "Ваши резюме", + style = typography.titleLarge, + fontSize = 32.sp, + color = colorScheme.onBackground + ) - Box { - Column( - modifier = modifier - .fillMaxSize() - .padding(horizontal = Paddings.large), - horizontalAlignment = Alignment.CenterHorizontally - ) { - TopLogo() - Spacer(modifier = Modifier.height(Paddings.medium)) - Text( - text = "Ваши резюме", - style = typography.titleLarge, - fontSize = 32.sp, - color = colorScheme.onBackground - ) - - Spacer(modifier = Modifier.height(Paddings.large)) - - val items = viewModel.resumeList.collectAsLazyPagingItems() + Spacer(modifier = Modifier.height(Paddings.large)) + PullToRefreshBox(items.loadState.refresh is LoadState.Loading, onRefresh = { + items.refresh() + }) { if (items.itemCount == 0 && items.loadState.append.endOfPaginationReached) { Text( text = "Здесь пока ничего нет", @@ -82,9 +117,7 @@ fun ErrorCollectorScope.MainScreen( Spacer(modifier = Modifier.height(Paddings.large)) BigButton( - onClick = { - TODO() - }, + onClick = openCreateResume, buttonText = "Создать резюме", isLoading = false ) @@ -100,6 +133,14 @@ fun ErrorCollectorScope.MainScreen( fontSize = 24.sp, color = colorScheme.onError ) + + Spacer(modifier = Modifier.height(Paddings.large)) + + BigButton( + onClick = { items.retry() }, + buttonText = "Попробовать снова", + isLoading = false + ) } else { LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, @@ -123,17 +164,6 @@ fun ErrorCollectorScope.MainScreen( } } } - - val context = LocalContext.current - TTFloatingActionButton( - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = Paddings.medium), - onClick = { - openCreateResume() - }, - text = "Добавить резюме" - ) } } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/resumeDetails/ResumeDetailsScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/resumeDetails/ResumeDetailsScreen.kt index d25bb69..1f5f861 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/resumeDetails/ResumeDetailsScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/resumeDetails/ResumeDetailsScreen.kt @@ -17,8 +17,11 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardColors import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FabPosition import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -33,13 +36,11 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavBackStackEntry import com.prodhack.moscow2025.R import com.prodhack.moscow2025.domain.models.Education -import com.prodhack.moscow2025.domain.models.EducationGrades -import com.prodhack.moscow2025.domain.models.ExperienceType import com.prodhack.moscow2025.domain.models.Project import com.prodhack.moscow2025.domain.models.ResumeModel import com.prodhack.moscow2025.domain.models.WorkExperience -import com.prodhack.moscow2025.presentation.components.standart.BigButton import com.prodhack.moscow2025.presentation.components.standart.TBubble +import com.prodhack.moscow2025.presentation.components.standart.TTFloatingActionButton import com.prodhack.moscow2025.presentation.navigation.AppDestination import com.prodhack.moscow2025.presentation.theme.Paddings import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope @@ -82,16 +83,21 @@ fun ErrorCollectorScope.ResumeDetailsScreen( } } ) { resume -> - ResumeDetailsContent( - resume = resume, - onBack = { navController.popBackStack() }, - onEdit = { - Toast.makeText(context, "Редактирование пока недоступно", Toast.LENGTH_SHORT).show() - }, - onHistory = { - Toast.makeText(context, "История появится позже", Toast.LENGTH_SHORT).show() - } - ) + Scaffold(floatingActionButton = { + ExtendedFloatingActionButton( + onClick = { }, + icon = { Icon(painter = painterResource(R.drawable.ic_pen), "Extended floating action button.") }, + text = { Text(text = "Редактировать резюме") }, + ) + }, floatingActionButtonPosition = FabPosition.Center) { + ResumeDetailsContent( + resume = resume, + onBack = { navController.popBackStack() }, + onHistory = { + Toast.makeText(context, "История появится позже", Toast.LENGTH_SHORT).show() + } + ) + } } } @@ -99,7 +105,6 @@ fun ErrorCollectorScope.ResumeDetailsScreen( private fun ResumeDetailsContent( resume: ResumeModel, onBack: () -> Unit, - onEdit: () -> Unit, onHistory: () -> Unit ) { val typography = MaterialTheme.typography @@ -131,7 +136,14 @@ private fun ResumeDetailsContent( style = typography.titleLarge, fontSize = 22.sp ) - Spacer(modifier = Modifier.size(24.dp)) + Icon( + modifier = Modifier + .size(24.dp) + .noRippleClickable(onHistory), + painter = painterResource(R.drawable.ic_history), + tint = colorScheme.onBackground, + contentDescription = "open history" + ) } Column( diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 0000000..52c9711 --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_pen.xml b/app/src/main/res/drawable/ic_pen.xml new file mode 100644 index 0000000..64e2740 --- /dev/null +++ b/app/src/main/res/drawable/ic_pen.xml @@ -0,0 +1,10 @@ + + +