Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
11 commits
Select commit Hold shift + click to select a range
03206bc
ANDR/51: настройка сетевого слоя и внедрение зависимостей для модуля …
xMODDIIx May 7, 2026
c6d9877
ANDR/51: регистрация маршрутов и настройка навигации для экрана регис…
xMODDIIx May 7, 2026
71fb6ff
ANDR-51: Временное исправление модуля forgot-password и удаление лока…
xMODDIIx Jun 9, 2026
0edfb99
ANDR-51: Обновление базовых компонентов Button. Добавлен параметр isL…
xMODDIIx Jun 9, 2026
b542813
ANDR-51: Обновление сетевых интерфейсов и ресурсов навигации. Добавле…
xMODDIIx Jun 9, 2026
0eb4021
ANDR-51: Реализация репозитория регистрации и обработки ошибок. Настр…
xMODDIIx Jun 9, 2026
33443e9
ANDR-51: Реализация презентационной логики регистрации. Добавлена Vie…
xMODDIIx Jun 9, 2026
4c61e07
ANDR-51: Завершение реализации UI и интеграция фичи в приложение. Нас…
xMODDIIx Jun 9, 2026
0d5be7f
ANDR-51: Добавление тестов для фичи регистрации. Unit-тесты для вериф…
xMODDIIx Jun 9, 2026
8d38a3f
ANDR-51: Обновление превью регистрации для всех форм
xMODDIIx Jun 9, 2026
e255490
ANDR-51: Исправлены ошибки detekt и ktlint
xMODDIIx Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .idea/compiler.xml

This file was deleted.

2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ dependencies {
implementation(project(":feature:questions-or-collections:impl"))
implementation(project(":feature:public-collections:impl"))
implementation(project(":feature:selection-specializations:impl"))
implementation(project(":feature:authentication:api"))
implementation(project(":feature:authentication:impl"))
}

tasks.withType<Test> {
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/ru/yeahub/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.yeahub
import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import ru.yeahub.authentication.impl.registration.di.registrationFeatureModule
import ru.yeahub.detail_question.impl.di.detailQuestionFeatureModule
import ru.yeahub.example_details.impl.detailsFeatureModule
import ru.yeahub.example_home.impl.data.di.questionsMainFeatureModule
Expand Down Expand Up @@ -53,7 +54,8 @@ class Application : Application() {
CollectionsFeatureModule,
detailQuestionFeatureModule,
collectionsAndQuestionsFeatureModule,
specializationFeatureModule
specializationFeatureModule,
registrationFeatureModule
)
}
// проверка, что модули загружены
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ object FeatureRoute {
object PublicCollectionsFeature {
const val FEATURE_NAME = "public_collections"
}
object RegistrationFeature {
const val FEATURE_NAME = "registration"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.navigation.compose.rememberNavController
import org.koin.compose.getKoin
import ru.yeahub.core_ui.theme.Theme
import ru.yeahub.navigation_api.FeatureApi
import ru.yeahub.navigation_api.FeatureRoute
import ru.yeahub.navigation_api.NavigationPathManager
import ru.yeahub.navigation_impl.model.BottomNavigationItem
import timber.log.Timber
Expand Down Expand Up @@ -70,24 +71,26 @@ fun AppNavigation(
val features: Set<FeatureApi> = getKoin().getAll<FeatureApi>().toSet()
Timber.d("AppNavigation onCreate: Loaded features: ${features.map { it.javaClass.simpleName }}")
val navItems = getBottomNavItems()

features.forEach { feature ->
feature.initialize(pathManager)
}

val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val selectedRoute = getSelectedRoute(currentRoute, navItems)

currentRoute?.let { route ->
pathManager.setCurrentPath(route)
}

Scaffold(
modifier = modifier,
containerColor = Theme.colors.white900,
bottomBar = {
NavigationBar(
modifier = Modifier.clip(RoundedCornerShape(12.dp))
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.height(100.dp),
containerColor = Theme.colors.purple700
) {
Expand Down Expand Up @@ -120,7 +123,11 @@ fun AppNavigation(
.fillMaxWidth()
.background(
color = animateColorAsState(
targetValue = if (isSelected) Theme.colors.white900 else Color.Transparent,
targetValue = if (isSelected) {
Theme.colors.white900
} else {
Color.Transparent
},
animationSpec = tween(durationMillis = 80)
).value,
shape = RoundedCornerShape(8.dp)
Expand All @@ -139,28 +146,44 @@ fun AppNavigation(
},
alwaysShowLabel = false
)
Timber.d("NavSelected", "$currentRoute")
Timber.d("NavSelected: $currentRoute")
}
}
}
) { padding ->
Box(modifier = Modifier.padding(padding)) {
NavHost(
AppNavHost(
navController = navController,
startDestination = navItems[1].route,
modifier = Modifier,
) {
registerDynamicNavigation(
features = features,
pathManager = pathManager,
navController = navController,
navGraphBuilder = this
)
}
features = features,
pathManager = pathManager
)
}
}
}

/**
* Отдельный компонент для NavHost, чтобы избежать дублирования кода.
*/
@Composable
private fun AppNavHost(
navController: NavHostController,
features: Set<FeatureApi>,
pathManager: NavigationPathManager
) {
NavHost(
navController = navController,
startDestination = FeatureRoute.HomeFeature.FEATURE_NAME,
modifier = Modifier,
) {
registerDynamicNavigation(
features = features,
pathManager = pathManager,
navController = navController,
navGraphBuilder = this
)
}
}

/**
* Обработка нажатий на нижнюю панель навигации.
*/
Expand All @@ -186,9 +209,9 @@ private fun handleBottomNavClick(
// Навигируем на родительский маршрут другого таба
Timber.d(
"AppNavigation onClick: Navigating to different tab: " +
"${item.route} from: $currentRoute"
"${item.route} from: $currentRoute"
)

// Устанавливаем новый корневой путь
pathManager.setCurrentPath(item.route)
navController.navigate(item.route) {
Expand All @@ -210,21 +233,21 @@ private fun registerDynamicNavigation(
) {
val rootFeatures = features.filter { it.isRootFeature() }
val childFeatures = features.filter { !it.isRootFeature() }

// Регистрируем корневые фичи
rootFeatures.forEach { feature ->
Timber.d(
"AppNavigation registerGraph: Registering root feature: " +
"${feature.javaClass.simpleName}"
)

// Сбрасываем путь для корневой фичи
pathManager.setCurrentPath("")
pathManager.registerFeaturePath(feature.getFeatureName(), feature.getFeatureName())

feature.registerGraph(navGraphBuilder, navController, pathManager)
}

// Регистрируем дочерние фичи для каждой корневой фичи
registerChildFeatures(childFeatures, rootFeatures, pathManager, navController, navGraphBuilder)
}
Expand All @@ -241,17 +264,17 @@ private fun registerChildFeatures(
) {
childFeatures.forEach { childFeature ->
val dependentRootFeatures = childFeature.getDependentRootFeatures(rootFeatures)

// Если фича не указала зависимости, регистрируем для всех корневых фич
val targetRootFeatures = if (dependentRootFeatures.isEmpty()) {
rootFeatures
} else {
dependentRootFeatures
}

targetRootFeatures.forEach { rootFeature ->
pathManager.setCurrentPath(rootFeature.getFeatureName())

// Регистрируем дочернюю фичу
childFeature.registerGraph(
navGraphBuilder = navGraphBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import ru.yeahub.navigation_impl.model.BottomNavigationItem
* Фабрика для создания навигационных элементов.
*/
fun getBottomNavItems(): List<BottomNavigationItem> = listOf(
BottomNavigationItem.Profile,
BottomNavigationItem.Collections,
BottomNavigationItem.Home,
BottomNavigationItem.Questions
)

fun getSelectedRoute(currentRoute: String?, navItems: List<BottomNavigationItem>): String = when {
currentRoute == null -> navItems[1].route
currentRoute == null -> navItems[2].route
navItems.any { it.route == currentRoute } -> currentRoute
else -> navItems.find { currentRoute.startsWith(it.route) }?.route ?: navItems.last().route
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ sealed class BottomNavigationItem(
val label: String,
@DrawableRes val icon: Int
) {
data object Profile : BottomNavigationItem(
route = FeatureRoute.RegistrationFeature.FEATURE_NAME,
label = "Профиль",
icon = R.drawable.icon_tab_profile
)

data object Collections : BottomNavigationItem(
route = FeatureRoute.CollectionsFeature.FEATURE_NAME,
label = "Коллекции",
Expand Down
14 changes: 14 additions & 0 deletions core/navigation-impl/src/main/res/drawable/icon_tab_profile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="13dp"
android:height="18dp"
android:viewportWidth="13"
android:viewportHeight="18">
<path
android:pathData="M6.458,0C4.272,0 2.5,1.772 2.5,3.958C2.5,6.144 4.272,7.917 6.458,7.917C8.644,7.917 10.417,6.144 10.417,3.958C10.417,1.772 8.644,0 6.458,0ZM3.75,3.958C3.75,2.463 4.963,1.25 6.458,1.25C7.954,1.25 9.167,2.463 9.167,3.958C9.167,5.454 7.954,6.667 6.458,6.667C4.963,6.667 3.75,5.454 3.75,3.958Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M6.458,9.167C4.759,9.167 3.19,9.559 2.023,10.225C0.875,10.882 0,11.883 0,13.125C0,14.367 0.875,15.368 2.023,16.025C3.19,16.691 4.759,17.083 6.458,17.083C8.158,17.083 9.727,16.691 10.893,16.025C12.042,15.368 12.917,14.367 12.917,13.125C12.917,11.883 12.042,10.882 10.893,10.225C9.727,9.559 8.158,9.167 6.458,9.167ZM1.25,13.125C1.25,12.526 1.681,11.861 2.644,11.311C3.589,10.771 4.936,10.417 6.458,10.417C7.981,10.417 9.328,10.771 10.273,11.311C11.236,11.861 11.667,12.526 11.667,13.125C11.667,13.724 11.236,14.389 10.273,14.939C9.328,15.479 7.981,15.833 6.458,15.833C4.936,15.833 3.589,15.479 2.644,14.939C1.681,14.389 1.25,13.724 1.25,13.125Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package ru.yeahub.network_api.models

data class RegistrationRequestDto(
val nickname: String,
val username: String,
val email: String,
val password: String,
val isMailingAccepted: Boolean,
)
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
package ru.yeahub.network_impl

import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import ru.yeahub.network_api.ApiService
import ru.yeahub.network_api.models.AuthUserDto
import ru.yeahub.network_api.models.GetCollectionsResponse
import ru.yeahub.network_api.models.GetPublicQuestionResponse
import ru.yeahub.network_api.models.GetPublicQuestionsResponse
import ru.yeahub.network_api.models.GetSkillsResponse
import ru.yeahub.network_api.models.GetSpecializationResponse
import ru.yeahub.network_api.models.GetSpecializationsResponse
import ru.yeahub.network_api.models.LoginRequestDto
import ru.yeahub.network_api.models.LoginResponseDto
import ru.yeahub.network_api.models.RegistrationRequestDto

interface RetrofitApiService : ApiService {

@POST("auth/signUp")
override suspend fun register(
@Body request: RegistrationRequestDto
)

@GET("questions/public-questions")
override suspend fun getQuestions(
@Query("page") page: Int,
Expand Down Expand Up @@ -61,4 +72,8 @@ interface RetrofitApiService : ApiService {
@Query("specializations") specializationsId: Long,
@Query("isFree") isFree: Boolean
): GetCollectionsResponse

// Пустые реализации для ApiService, если они не нужны в Retrofit-слое
override suspend fun login(request: LoginRequestDto): LoginResponseDto = TODO("Not required for this feature")
override suspend fun getProfile(): AuthUserDto = TODO("Not required for this feature")
}
Loading
Loading