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

Add a setting option composable method to easily create a group of mutually exclusive options #27

Merged
merged 17 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.infomaniak.swisstransfer.ui.icons.app

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.infomaniak.swisstransfer.ui.icons.AppIcons
import androidx.compose.ui.graphics.StrokeCap.Companion.Round as strokeCapRound
import androidx.compose.ui.graphics.StrokeJoin.Companion.Round as strokeJoinRound

val AppIcons.Checkmark: ImageVector
get() {
if (_checkmark != null) {
return _checkmark!!
}
_checkmark = Builder(
name = "Checkmark",
defaultWidth = 16.0.dp,
defaultHeight = 16.0.dp,
viewportWidth = 16.0f,
viewportHeight = 16.0f
).apply {
path(
fill = null,
stroke = SolidColor(Color(0xFF9F9F9F)),
strokeLineWidth = 2.0f,
strokeLineCap = strokeCapRound,
strokeLineJoin = strokeJoinRound,
strokeLineMiter = 4.0f,
pathFillType = NonZero
) {
moveTo(15.0f, 3.0f)
lineTo(5.593f, 13.419f)
lineTo(1.0f, 9.085f)
}
}.build()
return _checkmark!!
}

private var _checkmark: ImageVector? = null

@Preview
@Composable
private fun Preview() {
Box {
Image(
imageVector = AppIcons.Checkmark,
contentDescription = null,
modifier = Modifier.size(AppIcons.previewSize)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@ import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.infomaniak.swisstransfer.R
import com.infomaniak.swisstransfer.ui.components.*
import com.infomaniak.swisstransfer.ui.components.TwoPaneScaffold
import com.infomaniak.swisstransfer.ui.icons.AppIcons
import com.infomaniak.swisstransfer.ui.icons.app.Add
import com.infomaniak.swisstransfer.ui.icons.app.Bell
import com.infomaniak.swisstransfer.ui.icons.app.SpeechBubble
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.EndIconType
import com.infomaniak.swisstransfer.ui.screen.main.settings.SettingsOptionScreens.*
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.EndIconType.CHEVRON
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.EndIconType.OPEN_OUTSIDE
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingDivider
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingItem
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingTitle
Expand All @@ -55,7 +61,7 @@ import com.infomaniak.swisstransfer.ui.utils.PreviewTablet
fun SettingsScreenWrapper(
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
TwoPaneScaffold<Any>( // TODO: Replace Any with item type
TwoPaneScaffold<SettingsOptionScreens>(
windowAdaptiveInfo,
listPane = {
SettingsScreen(
Expand All @@ -66,20 +72,28 @@ fun SettingsScreenWrapper(
)
},
detailPane = {
// Show the detail pane content if selected item is available
if (currentDestination?.content == null) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Select a setting item", color = SwissTransferTheme.colors.secondaryTextColor)
}
} else {
Text("Show selected item")
var lastSelectedScreen by rememberSaveable { mutableStateOf<SettingsOptionScreens?>(null) }

val destination = currentDestination?.content ?: lastSelectedScreen
currentDestination?.content?.let { lastSelectedScreen = it }

when (destination) {
THEME -> SettingsThemeScreen()
NOTIFICATIONS -> {}
VALIDITY_PERIOD -> {}
DOWNLOAD_LIMIT -> {}
EMAIL_LANGUAGE -> {}
DISCOVER_INFOMANIAK -> {}
SHARE_IDEAS -> {}
GIVE_FEEDBACK -> {}
null -> NoSelectionEmptyState()
}
}
)
}

@Composable
private fun SettingsScreen(onItemClick: (Any) -> Unit) {
private fun SettingsScreen(onItemClick: (SettingsOptionScreens) -> Unit) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(
modifier = Modifier.padding(horizontal = Margin.Medium, vertical = Margin.Large),
Expand All @@ -89,28 +103,50 @@ private fun SettingsScreen(onItemClick: (Any) -> Unit) {

SettingTitle(R.string.settingsCategoryGeneral)
// TODO: Use correct icon
SettingItem(R.string.settingsOptionTheme, AppIcons.Add, "TODO", EndIconType.CHEVRON) {}
SettingItem(R.string.settingsOptionNotifications, AppIcons.Bell, "TODO", endIcon = EndIconType.OPEN_OUTSIDE) {}
SettingItem(R.string.settingsOptionTheme, AppIcons.Add, "TODO", CHEVRON) { onItemClick(THEME) }
SettingItem(R.string.settingsOptionNotifications, AppIcons.Bell, "TODO", endIcon = OPEN_OUTSIDE) {
onItemClick(NOTIFICATIONS)
}

SettingDivider()

SettingTitle(R.string.settingsCategoryDefaultSettings)
// TODO: Use correct icon
SettingItem(R.string.settingsOptionValidityPeriod, AppIcons.Add, "TODO", endIcon = EndIconType.CHEVRON) {}
SettingItem(R.string.settingsOptionValidityPeriod, AppIcons.Add, "TODO", endIcon = CHEVRON) {
onItemClick(VALIDITY_PERIOD)
}
// TODO: Use correct icon
SettingItem(R.string.settingsOptionDownloadLimit, AppIcons.Add, "TODO", endIcon = EndIconType.CHEVRON) {}
SettingItem(R.string.settingsOptionEmailLanguage, AppIcons.SpeechBubble, "TODO", endIcon = EndIconType.CHEVRON) {}
SettingItem(R.string.settingsOptionDownloadLimit, AppIcons.Add, "TODO", endIcon = CHEVRON) {
onItemClick(DOWNLOAD_LIMIT)
}
SettingItem(R.string.settingsOptionEmailLanguage, AppIcons.SpeechBubble, "TODO", endIcon = CHEVRON) {
onItemClick(EMAIL_LANGUAGE)
}

SettingDivider()

SettingTitle(R.string.settingsCategoryAbout)
SettingItem(R.string.settingsOptionDiscoverInfomaniak, endIcon = EndIconType.OPEN_OUTSIDE) {}
SettingItem(R.string.settingsOptionShareIdeas, endIcon = EndIconType.OPEN_OUTSIDE) {}
SettingItem(R.string.settingsOptionGiveFeedback, endIcon = EndIconType.OPEN_OUTSIDE) {}
SettingItem(R.string.settingsOptionDiscoverInfomaniak, endIcon = OPEN_OUTSIDE) { onItemClick(DISCOVER_INFOMANIAK) }
SettingItem(R.string.settingsOptionShareIdeas, endIcon = OPEN_OUTSIDE) { onItemClick(SHARE_IDEAS) }
SettingItem(R.string.settingsOptionGiveFeedback, endIcon = OPEN_OUTSIDE) { onItemClick(GIVE_FEEDBACK) }
SettingItem(R.string.version, description = "0.0.1", onClick = null)
}
}

// Show the detail pane content if selected item is available
@Composable
private fun NoSelectionEmptyState() {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Select a setting item", color = SwissTransferTheme.colors.secondaryTextColor)
}
}

enum class SettingsOptionScreens {
THEME, NOTIFICATIONS,
VALIDITY_PERIOD, DOWNLOAD_LIMIT, EMAIL_LANGUAGE,
DISCOVER_INFOMANIAK, SHARE_IDEAS, GIVE_FEEDBACK,
}

@PreviewMobile
@PreviewTablet
@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Infomaniak SwissTransfer - Android
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.swisstransfer.ui.screen.main.settings

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import com.infomaniak.swisstransfer.R
import com.infomaniak.swisstransfer.ui.icons.AppIcons
import com.infomaniak.swisstransfer.ui.icons.app.Add
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.MutuallyExclusiveOptions
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingOption
import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingTitle
import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme
import com.infomaniak.swisstransfer.ui.utils.PreviewMobile
import com.infomaniak.swisstransfer.ui.utils.PreviewTablet

@Composable
fun SettingsThemeScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
SettingTitle(titleRes = R.string.appName)

val (selectedItem, setSelectedItem) = rememberSaveable { mutableIntStateOf(0) } // TODO: Use DataStore or Realm
MutuallyExclusiveOptions(ThemeOption.entries, selectedItem, setSelectedItem)
}
}

enum class ThemeOption(override val title: Int, override val icon: ImageVector) : SettingOption {
SYSTEM(R.string.appName, AppIcons.Add),
LIGHT(R.string.appName, AppIcons.Add),
DARK(R.string.appName, AppIcons.Add),
}

@PreviewMobile
@PreviewTablet
@Composable
private fun SettingsThemeScreenPreview() {
SwissTransferTheme {
Surface {
SettingsThemeScreen()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Infomaniak SwissTransfer - Android
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.swisstransfer.ui.screen.main.settings.components

import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.infomaniak.swisstransfer.R
import com.infomaniak.swisstransfer.ui.components.SharpRippleButton
import com.infomaniak.swisstransfer.ui.icons.AppIcons
import com.infomaniak.swisstransfer.ui.icons.app.Add
import com.infomaniak.swisstransfer.ui.icons.app.Checkmark
import com.infomaniak.swisstransfer.ui.theme.Margin
import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme

@Composable
fun MutuallyExclusiveOptions(items: List<SettingOption>, selectedItem: Int, setSelectedItem: (Int) -> Unit) {
Column(Modifier.selectableGroup()) {
items.forEachIndexed { index, item ->
if (index > 0) HorizontalDivider(Modifier.padding(horizontal = Margin.Medium))
SettingOptionItem(item, isSelected = selectedItem == index) { setSelectedItem(index) }
}
}
}

@Composable
private fun SettingOptionItem(item: SettingOption, isSelected: Boolean, onClick: () -> Unit) {
SharpRippleButton(
modifier = Modifier.selectable(selected = isSelected, onClick = {}), // onClick left empty because handled by button
onClick = onClick
) {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = Margin.Medium, vertical = Margin.Medium),
verticalAlignment = Alignment.CenterVertically,
) {
item.icon?.let {
Icon(imageVector = it, contentDescription = null)
Spacer(modifier = Modifier.width(Margin.Medium))
}

Text(text = stringResource(id = item.title), Modifier.weight(1f))

if (isSelected) Spacer(modifier = Modifier.width(Margin.Medium))
AnimatedVisibility(
visible = isSelected,
enter = scaleIn(),
exit = scaleOut(),
) {
Icon(
imageVector = AppIcons.Checkmark,
contentDescription = null,
tint = SwissTransferTheme.materialColors.primary,
)
}
}
}
}

interface SettingOption {
@get:StringRes
val title: Int
val icon: ImageVector?
}

@Preview(name = "Light")
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL)
@Composable
private fun SettingOptionItemPreview() {
SwissTransferTheme {
Surface {
Column {
val item = object : SettingOption {
override val title: Int = R.string.appName
override val icon: ImageVector = AppIcons.Add
}
SettingOptionItem(item, true) {}
SettingOptionItem(item, false) {}
}
}
}
}
Loading