Skip to content

Commit

Permalink
Merge pull request #693 from Orange-OpenSource/develop
Browse files Browse the repository at this point in the history
Release 0.17.0
  • Loading branch information
florentmaitre authored Nov 6, 2023
2 parents 4cbe0f3 + db1bb0f commit 4f11267
Show file tree
Hide file tree
Showing 342 changed files with 6,405 additions and 4,158 deletions.
2 changes: 1 addition & 1 deletion DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repositories {

```groovy
dependencies {
implementation 'com.orange.ods.android:ods-lib:0.16.0'
implementation 'com.orange.ods.android:ods-lib:0.17.0'
}
```

Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ android {
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
val versionCodeProperty = project.findTypedProperty<String>("versionCode")
versionCode = versionCodeProperty?.toInt() ?: 8
versionCode = versionCodeProperty?.toInt() ?: 9
versionName = version.toString()
val versionNameSuffixProperty = project.findTypedProperty<String>("versionNameSuffix")
versionNameSuffix = versionNameSuffixProperty
Expand Down
48 changes: 48 additions & 0 deletions app/src/main/java/com/orange/ods/app/ui/AppBar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
*
* Copyright 2021 Orange
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* /
*/

package com.orange.ods.app.ui

import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.*
import com.orange.ods.compose.component.appbar.top.OdsLargeTopAppBarInternal
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarInternal

/**
* Displays the unique top app bar of the application which can be regular or large.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBar(
appBarState: AppBarState,
upPress: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?
) {
with(appBarState) {
if (isLarge) {
OdsLargeTopAppBarInternal(
title = title,
navigationIcon = getNavigationIcon(upPress),
actions = actions,
overflowMenuActions = overflowMenuActions,
scrollBehavior = if (hasScrollBehavior) scrollBehavior else null
)
} else {
OdsTopAppBarInternal(
title = title,
navigationIcon = getNavigationIcon(upPress),
actions = actions,
overflowMenuActions = overflowMenuActions,
elevated = false // elevation is managed in MainScreen cause of tabs
)
}
}
}
116 changes: 116 additions & 0 deletions app/src/main/java/com/orange/ods/app/ui/AppBarActions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
*
* Copyright 2021 Orange
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* /
*/

package com.orange.ods.app.ui

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import com.orange.ods.app.R
import com.orange.ods.app.ui.AppBarAction.Companion.defaultActions
import com.orange.ods.app.ui.utilities.UiString
import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuActionItem
import com.orange.ods.compose.component.content.OdsComponentContent
import com.orange.ods.compose.component.textfield.search.OdsSearchTextField

enum class AppBarAction {
Search, ChangeTheme, ChangeMode;

companion object {
val defaultActions = listOf(ChangeTheme, ChangeMode)
}

@Composable
fun getOdsTopAppBarAction(onActionClick: (AppBarAction) -> Unit) = when (this) {
ChangeTheme -> getChangeThemeAction(onActionClick)
ChangeMode -> getChangeModeAction(onActionClick)
Search -> getSearchAction(onActionClick)
}
}

data class AppBarOverflowMenuAction(
val title: UiString,
val onClick: () -> Unit
) {
@Composable
fun getOdsTopAppBarOverflowMenuAction() = OdsTopAppBarOverflowMenuActionItem(
text = title.asString(),
onClick = onClick
)
}

@Composable
fun getDefaultActions(onActionClick: (AppBarAction) -> Unit): List<OdsTopAppBarActionButton> =
defaultActions.map { it.getOdsTopAppBarAction(onActionClick = onActionClick) }

@Composable
fun getHomeActions(onActionClick: (AppBarAction) -> Unit): List<OdsTopAppBarActionButton> =
listOf(getSearchAction(onActionClick)) + getDefaultActions(onActionClick = onActionClick)

@Composable
fun getSearchFieldAction(onTextChange: (TextFieldValue) -> Unit): OdsComponentContent<Nothing> {
return object : OdsComponentContent<Nothing>() {

@Composable
override fun Content(modifier: Modifier) {
val focusRequester = remember { FocusRequester() }
OdsSearchTextField(
value = LocalAppBarManager.current.searchedText,
onValueChange = onTextChange,
placeholder = stringResource(id = R.string.search_text_field_hint),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester)
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}
}

@Composable
private fun getSearchAction(onClick: (AppBarAction) -> Unit) = OdsTopAppBarActionButton(
onClick = { onClick(AppBarAction.Search) },
painter = painterResource(id = R.drawable.ic_search),
contentDescription = stringResource(id = R.string.search_content_description)
)

@Composable
private fun getChangeThemeAction(onClick: (AppBarAction) -> Unit) = OdsTopAppBarActionButton(
onClick = { onClick(AppBarAction.ChangeTheme) },
painter = painterResource(id = R.drawable.ic_palette),
contentDescription = stringResource(id = R.string.top_app_bar_action_change_mode_to_dark_desc)
)

@Composable
private fun getChangeModeAction(onClick: (AppBarAction) -> Unit): OdsTopAppBarActionButton {
val configuration = LocalConfiguration.current

val painterRes = if (configuration.isDarkModeEnabled) R.drawable.ic_ui_light_mode else R.drawable.ic_ui_dark_mode
val iconDesc =
if (configuration.isDarkModeEnabled) R.string.top_app_bar_action_change_mode_to_light_desc else R.string.top_app_bar_action_change_mode_to_dark_desc

return OdsTopAppBarActionButton(
onClick = { onClick(AppBarAction.ChangeMode) },
painter = painterResource(id = painterRes),
contentDescription = stringResource(id = iconDesc)
)
}
187 changes: 187 additions & 0 deletions app/src/main/java/com/orange/ods/app/ui/AppBarState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
*
* Copyright 2021 Orange
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* /
*/

package com.orange.ods.app.ui

import android.os.Bundle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import com.orange.ods.app.R
import com.orange.ods.app.domain.recipes.LocalRecipes
import com.orange.ods.app.ui.AppBarAction.Companion.defaultActions
import com.orange.ods.app.ui.AppBarState.Companion.CustomDefaultConfiguration
import com.orange.ods.app.ui.components.appbars.top.TopAppBarCustomizationState
import com.orange.ods.app.ui.components.utilities.clickOnElement
import com.orange.ods.app.ui.utilities.NavigationItem
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarNavigationIcon
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuActionItem
import com.orange.ods.compose.component.content.OdsComponentContent
import com.orange.ods.extension.orElse
import kotlin.math.max

val LocalAppBarManager = staticCompositionLocalOf<AppBarManager> { error("CompositionLocal AppBarManager not present") }

interface AppBarManager {
val searchedText: TextFieldValue

fun setCustomAppBar(appBarConfiguration: AppBarConfiguration)

fun updateAppBarTabs(tabsConfiguration: TabsConfiguration)
fun clearAppBarTabs()
}

/**
* AppBar state source of truth.
*
* The app bar is managed according to the [Screen] displayed except when its type is [ScreenType.WithCustomizableTopAppBar]. In this case, the app bar is
* displayed according to the provided [AppBarConfiguration].
*/
class AppBarState(
private val navController: NavController,
private val searchText: MutableState<TextFieldValue>,
private val customAppBarConfiguration: MutableState<AppBarConfiguration>,
val tabsState: AppBarTabsState
) : AppBarManager {

companion object {
val CustomDefaultConfiguration = AppBarConfiguration(
isLarge = false,
largeTitleRes = R.string.empty,
scrollBehavior = TopAppBarCustomizationState.ScrollBehavior.Collapsible,
isNavigationIconEnabled = true,
actionCount = defaultActions.size,
isOverflowMenuEnabled = false
)
}

private val currentBackStackEntry: NavBackStackEntry?
@Composable get() = navController.currentBackStackEntryAsState().value

private val currentScreenRoute: String?
@Composable get() = currentBackStackEntry?.destination?.route

private val currentScreenRouteArgs: Bundle?
@Composable get() = currentBackStackEntry?.arguments

private val currentScreen: Screen?
@Composable get() = currentScreenRoute?.let { getScreen(it, currentScreenRouteArgs) }

private val isCustom: Boolean
@Composable get() = currentScreen?.hasCustomAppBar.orElse { false }

private val showNavigationIcon: Boolean
@Composable get() = (isCustom && customAppBarConfiguration.value.isNavigationIconEnabled)
|| (!isCustom && currentScreen?.isHome == false)

val isLarge: Boolean
@Composable get() = currentScreen?.isLargeAppBar == true

val title: String
@Composable get() = if (isCustom) {
stringResource(id = customAppBarConfiguration.value.largeTitleRes)
} else {
currentScreen?.title?.asString().orEmpty()
}

val actions: List<OdsComponentContent<Nothing>>
@Composable get() {
val screenAppBarActions = currentScreen?.getAppBarActions { searchText.value = it }.orEmpty()
return if (isCustom) {
val context = LocalContext.current
val customActionCount = max(0, customAppBarConfiguration.value.actionCount - AppBarAction.defaultActions.size)
val customActions = NavigationItem.values()
.take(customActionCount)
.map {
val contentDescription = stringResource(id = it.textResId)
OdsTopAppBarActionButton(painter = painterResource(id = it.iconResId), contentDescription = contentDescription) {
clickOnElement(context, contentDescription)
}
}
screenAppBarActions.take(customAppBarConfiguration.value.actionCount) + customActions
} else {
screenAppBarActions
}
}

val overflowMenuActions: List<OdsTopAppBarOverflowMenuActionItem>
@Composable get() = if (isCustom && customAppBarConfiguration.value.isOverflowMenuEnabled) {
val context = LocalContext.current
LocalRecipes.current.map { recipe ->
OdsTopAppBarOverflowMenuActionItem(
text = recipe.title,
onClick = { clickOnElement(context, recipe.title) }
)
}
} else {
emptyList()
}

val hasScrollBehavior: Boolean
get() = customAppBarConfiguration.value.scrollBehavior != TopAppBarCustomizationState.ScrollBehavior.None

@Composable
fun getNavigationIcon(upPress: () -> Unit) = if (showNavigationIcon) {
OdsTopAppBarNavigationIcon(Icons.Filled.ArrowBack, stringResource(id = R.string.top_app_bar_back_icon_desc), upPress)
} else {
null
}

// ----------------------------------------------------------
// AppBarManager implementation
// ----------------------------------------------------------

override val searchedText: TextFieldValue
get() = searchText.value

override fun setCustomAppBar(appBarConfiguration: AppBarConfiguration) {
customAppBarConfiguration.value = appBarConfiguration
}

override fun updateAppBarTabs(tabsConfiguration: TabsConfiguration) {
tabsState.updateAppBarTabs(tabsConfiguration)
}

override fun clearAppBarTabs() {
tabsState.clearAppBarTabs()
}

}

@Composable
fun rememberAppBarState(
navController: NavController,
searchedText: MutableState<TextFieldValue> = remember { mutableStateOf(TextFieldValue("")) },
customAppBarConfiguration: MutableState<AppBarConfiguration> = remember { mutableStateOf(CustomDefaultConfiguration) },
tabsState: AppBarTabsState = rememberAppBarTabsState()
) = remember(navController, searchedText, customAppBarConfiguration, tabsState) {
AppBarState(navController, searchedText, customAppBarConfiguration, tabsState)
}

data class AppBarConfiguration constructor(
val isLarge: Boolean,
val largeTitleRes: Int,
val scrollBehavior: TopAppBarCustomizationState.ScrollBehavior,
val isNavigationIconEnabled: Boolean,
val actionCount: Int,
val isOverflowMenuEnabled: Boolean
)
Loading

0 comments on commit 4f11267

Please sign in to comment.