Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🏠 Implement the Home UI-feature #568

Merged
merged 9 commits into from
Sep 6, 2023
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ buildscript {
classpath(libs.kotlin.gradle.plugin)
classpath(libs.aboutlibraries.plugin)
classpath(libs.kotlin.atomicfu)
classpath(libs.moko.resources.generator)
}
}

Expand Down
40 changes: 40 additions & 0 deletions features/home/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import extension.commonDependencies
import extension.setFrameworkBaseName

plugins {
id("com.escodro.multiplatform")
alias(libs.plugins.compose)
id(libs.plugins.moko.multiplatform.resources.get().pluginId) // Use version from classpath
}

kotlin {
setFrameworkBaseName("home")

commonDependencies {
implementation(projects.domain)
implementation(projects.resources)

implementation(compose.runtime)
implementation(compose.materialIconsExtended)
implementation(compose.material)
implementation(compose.material3)
implementation(libs.koin.compose.jb)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.moko.resources.compose)
implementation(libs.moko.mvvm.compose)
}

// Explicit dependency due to Moko issues with Kotlin 1.9.0
// https://github.com/icerockdev/moko-resources/issues/531
sourceSets {
val commonMain by getting
val androidMain by getting {
dependsOn(commonMain)
}
}
}

android {
namespace = "com.escodro.home"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package com.escodro.home.presentation

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.NavigationRailItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

/**
* Alkaa Home screen.
*/
@Suppress("LongParameterList")
@Composable
fun Home(
onTaskClick: (Long) -> Unit,
onAboutClick: () -> Unit,
onTrackerClick: () -> Unit,
onOpenSourceClick: () -> Unit,
onTaskSheetOpen: () -> Unit,
onCategorySheetOpen: (Long?) -> Unit,
) {
val (currentSection, setCurrentSection) = rememberSaveable { mutableStateOf(HomeSection.Tasks) }
val navItems = HomeSection.values().toList().toImmutableList()

val actions = remember {
object : HomeActions {
override val onTaskClick = onTaskClick
override val onAboutClick = onAboutClick
override val onTrackerClick = onTrackerClick
override val onOpenSourceClick = onOpenSourceClick
override val onTaskSheetOpen = onTaskSheetOpen
override val onCategorySheetOpen = onCategorySheetOpen
override val setCurrentSection = setCurrentSection
}
}

Crossfade(currentSection) { homeSection ->
AlkaaHomeScaffold(
homeSection = homeSection,
navItems = navItems,
actions = actions,
)
}
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AlkaaHomeScaffold(
homeSection: HomeSection,
navItems: ImmutableList<HomeSection>,
actions: HomeActions,
) {
Scaffold(
topBar = {
AlkaaTopBar(currentSection = homeSection)
},
contentWindowInsets = WindowInsets(0, 0, 0, 0),
content = { paddingValues ->
Row(
Modifier
.fillMaxSize()
.padding(paddingValues)
.consumeWindowInsets(paddingValues)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal),
),
) {
Column(Modifier.fillMaxSize()) {
AlkaaContent(
homeSection = homeSection,
modifier = Modifier,
actions = actions,
)
}
}
},
bottomBar = {
AlkaaBottomNav(
currentSection = homeSection,
onSectionSelect = actions.setCurrentSection,
items = navItems,
)
},
)
}

@Composable
private fun AlkaaNavRail(
currentSection: HomeSection,
onSectionSelect: (HomeSection) -> Unit,
items: ImmutableList<HomeSection>,
modifier: Modifier = Modifier,
) {
NavigationRail(modifier = modifier) {
items.forEach { section ->
val selected = section == currentSection
NavigationRailItem(
selected = selected,
onClick = { onSectionSelect(section) },
alwaysShowLabel = true,
icon = { Icon(imageVector = section.icon, contentDescription = null) },
label = { Text(stringResource(section.title)) },
colors = NavigationRailItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
indicatorColor = MaterialTheme.colorScheme.primaryContainer,
),
)
}
}
}

@Composable
private fun AlkaaContent(
homeSection: HomeSection,
actions: HomeActions,
modifier: Modifier = Modifier,
) {
when (homeSection) {
HomeSection.Tasks -> {}
// TaskListSection(
// modifier = modifier,
// onItemClick = actions.onTaskClick,
// onBottomShow = actions.onTaskSheetOpen,
// )

HomeSection.Search -> {}
// SearchSection(modifier = modifier, onItemClick = actions.onTaskClick)

HomeSection.Categories -> {}
// CategoryListSection(
// modifier = modifier,
// onShowBottomSheet = actions.onCategorySheetOpen,
// )

HomeSection.Settings -> {}
// PreferenceSection(
// modifier = modifier,
// onAboutClick = actions.onAboutClick,
// onTrackerClick = actions.onTrackerClick,
// onOpenSourceClick = actions.onOpenSourceClick,
// )
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AlkaaTopBar(currentSection: HomeSection) {
CenterAlignedTopAppBar(
title = {
Text(
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Light),
text = stringResource(currentSection.title),
color = MaterialTheme.colorScheme.tertiary,
)
},
)
}

@Composable
private fun AlkaaBottomNav(
currentSection: HomeSection,
onSectionSelect: (HomeSection) -> Unit,
items: ImmutableList<HomeSection>,
) {
BottomAppBar(containerColor = MaterialTheme.colorScheme.background) {
items.forEach { section ->
val selected = section == currentSection
val title = section.title
NavigationBarItem(
selected = selected,
onClick = { onSectionSelect(section) },
icon = {
Icon(
imageVector = section.icon,
contentDescription = stringResource(title),
)
},
label = { Text(stringResource(title)) },
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.escodro.home.presentation

/**
* Actions to be performed on the Home screen.
*/
internal interface HomeActions {
/**
* Action to be performed when a task is clicked.
*/
val onTaskClick: (Long) -> Unit

/**
* Action to be performed when the about item is clicked.
*/
val onAboutClick: () -> Unit

/**
* Action to be performed when the task tracker item is clicked.
*/
val onTrackerClick: () -> Unit

/**
* Action to be performed when the open source item is clicked.
*/
val onOpenSourceClick: () -> Unit

/**
* Action to be performed when the task bottom sheet is opened.
*/
val onTaskSheetOpen: () -> Unit

/**
* Action to be performed when the category bottom sheet is opened.
*/
val onCategorySheetOpen: (Long?) -> Unit

/**
* Action to be performed when the current bottom nav section is changed.
*/
val setCurrentSection: (HomeSection) -> Unit
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.escodro.home.presentation

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Bookmark
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.MoreHoriz
import androidx.compose.material.icons.outlined.Search
import androidx.compose.ui.graphics.vector.ImageVector
import com.escodro.resources.MR
import dev.icerock.moko.resources.StringResource

/**
* Enum to represent the sections available in the bottom app bar.
*
* @property title title to be shown in top app bar.
* @property icon icon to be shown in the bottom app bar
*/
internal enum class HomeSection(
val title: StringResource,
val icon: ImageVector,
) {
Tasks(MR.strings.home_title_tasks, Icons.Outlined.Check),
Search(MR.strings.home_title_search, Icons.Outlined.Search),
Categories(MR.strings.home_title_categories, Icons.Outlined.Bookmark),
Settings(MR.strings.home_title_settings, Icons.Outlined.MoreHoriz),
}
8 changes: 7 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ sqldelight = "2.0.0"

# Moko
moko = "0.16.1"
moko_resources = "0.23.0"

# AtomicFU
atomicfu = "0.22.0"
Expand Down Expand Up @@ -145,6 +146,10 @@ sqldelight_coroutines = { module = "app.cash.sqldelight:coroutines-extensions",

# Moko
moko_mvvm_core = { module = "dev.icerock.moko:mvvm-core", version.ref = "moko" }
moko_mvvm_compose = { module = "dev.icerock.moko:mvvm-flow-compose", version.ref = "moko" }
moko_resources_generator = { module = "dev.icerock.moko:resources-generator", version.ref = "moko_resources" }
moko_resources_core = { module = "dev.icerock.moko:resources", version.ref = "moko_resources" }
moko_resources_compose = { module = "dev.icerock.moko:resources-compose", version.ref = "moko_resources" }

# Test
test_junit = { module = "junit:junit", version.ref = "test_junit" }
Expand Down Expand Up @@ -176,5 +181,6 @@ kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyanalysis" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
compose = {id = "org.jetbrains.compose", version.ref = "jb_compose_compiler" }
compose = { id = "org.jetbrains.compose", version.ref = "jb_compose_compiler" }
moko_multiplatform_resources = { id = "dev.icerock.mobile.multiplatform-resources" }