diff --git a/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt b/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt index 9272e0f..237b890 100644 --- a/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt +++ b/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import dagger.hilt.android.AndroidEntryPoint -import ru.fincode.tsudesk.ui.TSUDeskApp +import ru.fincode.tsudesk.presentation.TSUDeskApp @AndroidEntryPoint class MainActivity : ComponentActivity() { diff --git a/app/src/main/java/ru/fincode/tsudesk/TSUDeskRoot.kt b/app/src/main/java/ru/fincode/tsudesk/TSUDeskRoot.kt new file mode 100644 index 0000000..3681881 --- /dev/null +++ b/app/src/main/java/ru/fincode/tsudesk/TSUDeskRoot.kt @@ -0,0 +1,14 @@ +package ru.fincode.tsudesk + +import androidx.compose.runtime.Composable +import androidx.navigation.compose.rememberNavController +import ru.fincode.tsudesk.presentation.navigation.AppNavHost + +@Composable +fun TSUDeskRoot() { + val navController = rememberNavController() + + AppNavHost( + navController = navController + ) +} \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/ui/TSUDeskApp.kt b/app/src/main/java/ru/fincode/tsudesk/presentation/TSUDeskApp.kt similarity index 73% rename from app/src/main/java/ru/fincode/tsudesk/ui/TSUDeskApp.kt rename to app/src/main/java/ru/fincode/tsudesk/presentation/TSUDeskApp.kt index fb13721..a5d5f41 100644 --- a/app/src/main/java/ru/fincode/tsudesk/ui/TSUDeskApp.kt +++ b/app/src/main/java/ru/fincode/tsudesk/presentation/TSUDeskApp.kt @@ -1,11 +1,11 @@ -package ru.fincode.tsudesk.ui +package ru.fincode.tsudesk.presentation import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.rememberNavController -import ru.fincode.tsudesk.ui.navigation.AppNavHost +import ru.fincode.tsudesk.presentation.navigation.AppNavHost @Composable fun TSUDeskApp() { @@ -16,9 +16,7 @@ fun TSUDeskApp() { modifier = Modifier, color = MaterialTheme.colorScheme.background ) { - AppNavHost( - navController = navController - ) + AppNavHost(navController = navController) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainNavUi.kt b/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainNavUi.kt new file mode 100644 index 0000000..befad99 --- /dev/null +++ b/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainNavUi.kt @@ -0,0 +1,37 @@ +package ru.fincode.tsudesk.app.presentation.main + +import androidx.navigation.NavBackStackEntry +import ru.fincode.tsudesk.core.navigation.AppRoute +import ru.fincode.tsudesk.core.navigation.TopLevelDestination +import ru.fincode.tsudesk.core.navigation.route + +fun selectedTopLevel(entry: NavBackStackEntry?): TopLevelDestination { + if (entry == null) return TopLevelDestination.SCHEDULE + + return runCatching { + when (entry.route()) { + AppRoute.Schedule, + is AppRoute.ScheduleDetails -> TopLevelDestination.SCHEDULE + + AppRoute.News, + is AppRoute.NewsDetails -> TopLevelDestination.NEWS + + AppRoute.Progress -> TopLevelDestination.PROGRESS + + AppRoute.Settings -> TopLevelDestination.SETTINGS + + else -> TopLevelDestination.SCHEDULE + } + }.getOrDefault(TopLevelDestination.SCHEDULE) +} + +fun shouldShowBottomBar(entry: NavBackStackEntry?): Boolean { + if (entry == null) return false + + return runCatching { + when (entry.route()) { + AppRoute.Splash -> false + else -> true + } + }.getOrDefault(true) +} \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt b/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt new file mode 100644 index 0000000..0b3c5b0 --- /dev/null +++ b/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt @@ -0,0 +1,82 @@ +package ru.fincode.tsudesk.presentation.main + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import ru.fincode.core.ui.components.BottomBarItem +import ru.fincode.core.ui.components.TsudeskBottomBar +import ru.fincode.tsudesk.R +import ru.fincode.tsudesk.app.presentation.main.selectedTopLevel +import ru.fincode.tsudesk.app.presentation.main.shouldShowBottomBar +import ru.fincode.tsudesk.core.navigation.TopLevelDestination +import ru.fincode.tsudesk.core.navigation.navigateToTopLevel + +@Composable +fun MainScaffold( + navController: NavHostController, + content: @Composable (Modifier) -> Unit, +) { + val scheduleIcon = painterResource(R.drawable.ic_progress) + val newsIcon = painterResource(R.drawable.ic_news) + val progressIcon = painterResource(R.drawable.ic_progress) + val settingsIcon = painterResource(R.drawable.ic_progress) + + val items = listOf( + TopLevelItem( + destination = TopLevelDestination.SCHEDULE, + labelRes = R.string.tab_schedule, + icon = scheduleIcon + ), + TopLevelItem( + destination = TopLevelDestination.NEWS, + labelRes = R.string.tab_news, + icon = newsIcon + ), + TopLevelItem( + destination = TopLevelDestination.PROGRESS, + labelRes = R.string.tab_progress, + icon = progressIcon + ), + TopLevelItem( + destination = TopLevelDestination.SETTINGS, + labelRes = R.string.tab_settings, + icon = settingsIcon + ), + ) + + val backStackEntry by navController.currentBackStackEntryAsState() + val selected = selectedTopLevel(backStackEntry) + + Scaffold( + bottomBar = { + if (shouldShowBottomBar(backStackEntry)) { + val uiItems = items.map { item -> + BottomBarItem( + key = item.destination.name, + label = stringResource(item.labelRes), + icon = item.icon + ) + } + + TsudeskBottomBar( + items = uiItems, + selectedKey = selected.name, + onItemClick = { clicked -> + if (clicked.key == selected.name) return@TsudeskBottomBar + navController.navigateToTopLevel( + TopLevelDestination.valueOf(clicked.key) + ) + } + ) + } + } + ) { innerPadding -> + content(Modifier.padding(innerPadding)) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/presentation/main/TopLevelItem.kt b/app/src/main/java/ru/fincode/tsudesk/presentation/main/TopLevelItem.kt new file mode 100644 index 0000000..323d188 --- /dev/null +++ b/app/src/main/java/ru/fincode/tsudesk/presentation/main/TopLevelItem.kt @@ -0,0 +1,12 @@ +package ru.fincode.tsudesk.presentation.main + +import androidx.annotation.StringRes +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import ru.fincode.tsudesk.core.navigation.TopLevelDestination + +data class TopLevelItem( + val destination: TopLevelDestination, + @StringRes val labelRes: Int, + val icon: Painter, +) \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/presentation/navigation/AppNavHost.kt b/app/src/main/java/ru/fincode/tsudesk/presentation/navigation/AppNavHost.kt new file mode 100644 index 0000000..e2a89e6 --- /dev/null +++ b/app/src/main/java/ru/fincode/tsudesk/presentation/navigation/AppNavHost.kt @@ -0,0 +1,83 @@ +package ru.fincode.tsudesk.presentation.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation +import ru.fincode.tsudesk.presentation.main.MainScaffold +import ru.fincode.tsudesk.core.navigation.AppRoute +import ru.fincode.tsudesk.core.navigation.navigateRoute +import ru.fincode.tsudesk.feature.schedule.presentation.screen.ScheduleRoute + +import ru.fincode.tsudesk.feature.splash.presentation.screen.SplashRoute + + +@Composable +fun AppNavHost( + navController: NavHostController, +) { + NavHost( + navController = navController, + startDestination = AppRoute.Splash + ) { + composable { + SplashRoute( + onFinish = { + navController.navigateRoute(AppRoute.Main) { + popUpTo(AppRoute.Splash) { inclusive = true } + launchSingleTop = true + } + } + ) + } + + navigation(startDestination = AppRoute.Schedule) { + + composable { + MainScaffold(navController) { innerModifier -> + ScheduleRoute( + modifier = innerModifier, +// onOpenDetails = { lessonId -> +// navController.navigateRoute(AppRoute.ScheduleDetails(lessonId)) +// } + ) + } + } + + composable { + MainScaffold(navController) { innerModifier: Modifier -> +// NewsScreen( +// modifier = innerModifier, +// onOpenDetails = { id -> +// navController.navigateRoute(AppRoute.NewsDetails(id)) +// } +// ) + } + } + + composable { + MainScaffold(navController) { innerModifier: Modifier -> +// ProgressScreen(modifier = innerModifier) + } + } + + composable { + MainScaffold(navController) { innerModifier -> + // SettingsRoute(modifier = innerModifier) + } + } +// +// composable { entry -> +// val args = entry.route() +// ScheduleDetailsScreen(lessonId = args.lessonId) +// } +// +// composable { entry -> +// val args = entry.route() +// NewsDetailsScreen(id = args.id) +// } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/ui/navigation/AppNavHost.kt b/app/src/main/java/ru/fincode/tsudesk/ui/navigation/AppNavHost.kt deleted file mode 100644 index d624f36..0000000 --- a/app/src/main/java/ru/fincode/tsudesk/ui/navigation/AppNavHost.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ru.fincode.tsudesk.ui.navigation - -import androidx.compose.runtime.Composable -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.navigation -import ru.fincode.tsudesk.core.navigation.AppRoute -import ru.fincode.tsudesk.feature.schedule.presentation.navigation.scheduleGraph -import ru.fincode.tsudesk.feature.splash.presentation.navigation.splashGraph - -@Composable -fun AppNavHost( - navController: NavHostController -) { - NavHost( - navController = navController, - startDestination = AppRoute.Splash - ) { - splashGraph(navController) - - navigation( - startDestination = AppRoute.Schedule - ) { - scheduleGraph() -// newsGraph(navController) -// progressGraph(navController) - } - } -} diff --git a/app/src/main/res/drawable/ic_news.xml b/app/src/main/res/drawable/ic_news.xml new file mode 100644 index 0000000..beb2abb --- /dev/null +++ b/app/src/main/res/drawable/ic_news.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_progress.xml b/app/src/main/res/drawable/ic_progress.xml new file mode 100644 index 0000000..610f3c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_progress.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_schedule.xml b/app/src/main/res/drawable/ic_schedule.xml new file mode 100644 index 0000000..08e472f --- /dev/null +++ b/app/src/main/res/drawable/ic_schedule.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 306ee4c..c8a33c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,7 @@ TSUDesk + Расписание + Новости + Успеваемость + Настройки \ No newline at end of file diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index acdd578..aabc7ae 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { // Compose implementation(platform(libs.compose.bom)) implementation(libs.compose.runtime) + implementation("androidx.compose.material:material-icons-extended") // Navigation Compose implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.common) diff --git a/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/AppRoute.kt b/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/AppRoute.kt index d7a3c50..abc8cc5 100644 --- a/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/AppRoute.kt +++ b/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/AppRoute.kt @@ -25,6 +25,9 @@ sealed interface AppRoute { @Serializable data object Progress : AppRoute + @kotlinx.serialization.Serializable + data object Settings : AppRoute + @Serializable data class ScheduleDetails( val lessonId: Long diff --git a/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/TopLevelDestination.kt b/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/TopLevelDestination.kt index 1645ac4..8ce966d 100644 --- a/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/TopLevelDestination.kt +++ b/core/navigation/src/main/java/ru/fincode/tsudesk/core/navigation/TopLevelDestination.kt @@ -4,19 +4,19 @@ import kotlinx.serialization.Serializable @Serializable enum class TopLevelDestination { - SCHEDULE, - NEWS, - PROGRESS + SCHEDULE, NEWS, PROGRESS, SETTINGS } fun TopLevelDestination.toRoute(): AppRoute = when (this) { TopLevelDestination.SCHEDULE -> AppRoute.Schedule TopLevelDestination.NEWS -> AppRoute.News TopLevelDestination.PROGRESS -> AppRoute.Progress + TopLevelDestination.SETTINGS -> AppRoute.Settings } val TOP_LEVEL_DESTINATIONS: List = listOf( TopLevelDestination.SCHEDULE, TopLevelDestination.NEWS, - TopLevelDestination.PROGRESS + TopLevelDestination.PROGRESS, + TopLevelDestination.SETTINGS ) diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index f6f257b..c4b91cc 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -27,14 +27,27 @@ android { sourceCompatibility = jvm targetCompatibility = jvm } - kotlinOptions { - jvmTarget = jvm.toString() - } + kotlinOptions { jvmTarget = jvm.toString() } + buildFeatures { buildConfig = true + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtension.get() } } + dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) + + + // Compose UI + implementation(platform(libs.compose.bom)) + implementation(libs.compose.runtime) + implementation(libs.compose.ui) + implementation(libs.compose.foundation) + implementation(libs.compose.material3) + debugImplementation(libs.compose.ui.tooling) } \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/core/ui/components/TsudeskBottomBar.kt b/core/ui/src/main/java/ru/fincode/core/ui/components/TsudeskBottomBar.kt new file mode 100644 index 0000000..bacc231 --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/core/ui/components/TsudeskBottomBar.kt @@ -0,0 +1,40 @@ +package ru.fincode.core.ui.components + +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter + +data class BottomBarItem( + val key: String, + val label: String, + val icon: Painter, +) + +@Composable +fun TsudeskBottomBar( + items: List, + selectedKey: String, + onItemClick: (BottomBarItem) -> Unit, +) { + NavigationBar { + items.forEach { item -> + NavigationBarItem( + selected = item.key == selectedKey, + onClick = { onItemClick(item) }, + icon = { + Icon( + painter = item.icon, + contentDescription = item.label + ) + }, + label = { + Text(text = item.label) + }, + alwaysShowLabel = true + ) + } + } +} \ No newline at end of file diff --git a/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleRoute.kt b/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleRoute.kt index 9270b99..a5a0d3b 100644 --- a/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleRoute.kt +++ b/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleRoute.kt @@ -1,17 +1,22 @@ package ru.fincode.tsudesk.feature.schedule.presentation.screen import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @Composable fun ScheduleRoute( - viewModel: ScheduleViewModel = hiltViewModel() + modifier: Modifier = Modifier, +// onOpenDetails: (lessonId: Long) -> Unit, + viewModel: ScheduleViewModel = hiltViewModel(), ) { - val state = viewModel.state.collectAsStateWithLifecycle().value + val state by viewModel.state.collectAsStateWithLifecycle() ScheduleScreen( state = state, - onIntent = viewModel::onIntent + onIntent = viewModel::onIntent, + modifier = modifier ) } \ No newline at end of file diff --git a/feature/splash/src/main/java/ru/fincode/tsudesk/feature/splash/presentation/screen/SplashRoute.kt b/feature/splash/src/main/java/ru/fincode/tsudesk/feature/splash/presentation/screen/SplashRoute.kt index 387f8fb..d095a7b 100644 --- a/feature/splash/src/main/java/ru/fincode/tsudesk/feature/splash/presentation/screen/SplashRoute.kt +++ b/feature/splash/src/main/java/ru/fincode/tsudesk/feature/splash/presentation/screen/SplashRoute.kt @@ -12,12 +12,14 @@ fun SplashRoute( viewModel: SplashViewModel = hiltViewModel() ) { val state = viewModel.state.collectAsStateWithLifecycle().value - LaunchedEffect(Unit) { + + LaunchedEffect(viewModel) { viewModel.effects.collect { effect -> when (effect) { SplashEffect.OpenMain -> onFinish() } } } + SplashScreen(state = state) }