From 7fc63778a50db10c799e276f0fb57f33043083c9 Mon Sep 17 00:00:00 2001 From: Gerard Paligot Date: Wed, 1 Nov 2023 13:37:49 +0100 Subject: [PATCH] refactor(m3): OpenFeedback components more customizable. * Be able to customize horizontal/vertical arrangement with OpenFeedbackLayout. * Be able to customize a custom VoteCard with OpenFeedbackLayout. * Use 2 or 4 columns according to the device orientation. --- .../android/m3/OpenFeedbackLayout.kt | 72 ++++++++++++++++++- .../io/openfeedback/android/m3/VoteCard.kt | 71 ++++++++++++------ .../io/openfeedback/android/m3/VoteItems.kt | 67 +++++++++++++---- 3 files changed, 173 insertions(+), 37 deletions(-) diff --git a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt index 2a7ada9..7c0ecd2 100644 --- a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt +++ b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt @@ -1,23 +1,32 @@ package io.openfeedback.android.m3 +import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import io.openfeedback.android.FirebaseConfig import io.openfeedback.android.viewmodels.OpenFeedbackUiState import io.openfeedback.android.viewmodels.OpenFeedbackViewModel +import io.openfeedback.android.viewmodels.models.UIDot import io.openfeedback.android.viewmodels.models.UISessionFeedback import io.openfeedback.android.viewmodels.models.UIVoteItem +@OptIn(ExperimentalMaterial3Api::class) @Composable fun OpenFeedback( config: FirebaseConfig, @@ -27,6 +36,7 @@ fun OpenFeedback( modifier: Modifier = Modifier, loading: @Composable () -> Unit = { Loading(modifier = modifier) } ) { + val configuration = LocalConfiguration.current val context = LocalContext.current val viewModel: OpenFeedbackViewModel = viewModel( factory = OpenFeedbackViewModel.Factory.create( @@ -37,6 +47,7 @@ fun OpenFeedback( language = language ) ) + val columnCount = if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 4 else 2 val uiState = viewModel.uiState.collectAsState() when (uiState.value) { is OpenFeedbackUiState.Loading -> loading() @@ -45,7 +56,16 @@ fun OpenFeedback( OpenFeedbackLayout( sessionFeedback = session, modifier = modifier, - onClick = { voteItem -> viewModel.vote(voteItem = voteItem) } + columnCount = columnCount, + content = { + VoteCard( + voteModel = it, + onClick = { voteItem -> viewModel.vote(voteItem = voteItem) }, + modifier = Modifier + .height(100.dp) + .fillMaxWidth() + ) + } ) } } @@ -56,10 +76,19 @@ fun OpenFeedback( fun OpenFeedbackLayout( sessionFeedback: UISessionFeedback, modifier: Modifier = Modifier, - onClick: (voteItem: UIVoteItem) -> Unit + columnCount: Int = 2, + horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(8.dp), + verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp), + content: @Composable ColumnScope.(UIVoteItem) -> Unit ) { Column(modifier = modifier) { - VoteItems(voteItems = sessionFeedback.voteItem, onClick = onClick) + VoteItems( + voteItems = sessionFeedback.voteItem, + columnCount = columnCount, + horizontalArrangement = horizontalArrangement, + verticalArrangement = verticalArrangement, + content = content + ) Box( modifier = Modifier .fillMaxWidth() @@ -70,3 +99,40 @@ fun OpenFeedbackLayout( } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun OpenFeedbackLayoutPreview() { + MaterialTheme { + OpenFeedbackLayout( + sessionFeedback = UISessionFeedback( + comments = emptyList(), + listOf( + UIVoteItem( + id = "", + text = "Fun", + dots = listOf(UIDot(x = .5f, y = .5f, color = "FF00CC")), + votedByUser = true + ), + UIVoteItem( + id = "", + text = "Fun", + dots = listOf(UIDot(x = .5f, y = .5f, color = "FF00CC")), + votedByUser = true + ) + ) + ), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + VoteCard( + voteModel = it, + onClick = {}, + modifier = Modifier + .height(100.dp) + .fillMaxWidth() + ) + } + } +} diff --git a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteCard.kt b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteCard.kt index 1c4376a..d3f14f9 100644 --- a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteCard.kt +++ b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteCard.kt @@ -1,33 +1,35 @@ package io.openfeedback.android.m3 import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.openfeedback.android.viewmodels.models.UIDot import io.openfeedback.android.viewmodels.models.UIVoteItem @ExperimentalMaterial3Api @Composable -internal fun VoteCard( +fun VoteCard( voteModel: UIVoteItem, modifier: Modifier = Modifier, style: TextStyle = MaterialTheme.typography.bodyMedium, backgroundColor: Color = MaterialTheme.colorScheme.surface, - contentColor: Color = MaterialTheme.colorScheme.onSurface, + contentColor: Color = contentColorFor(backgroundColor = backgroundColor), shape: Shape = MaterialTheme.shapes.medium, onClick: (voteItem: UIVoteItem) -> Unit ) { @@ -39,23 +41,32 @@ internal fun VoteCard( modifier = modifier, onClick = { onClick(voteModel) } ) { - Box(modifier = Modifier.height(100.dp)) { - Canvas(modifier = Modifier.fillMaxSize()) { - voteModel.dots.forEach { dot -> - val offset = Offset(x = this.size.width * dot.x, y = this.size.height * dot.y) - drawCircle( - color = Color( - dot.color.substring(0, 2).toInt(16), - dot.color.substring(2, 4).toInt(16), - dot.color.substring(4, 6).toInt(16), - 255 / 3 - ), - radius = 30.dp.value, - center = offset, - style = Fill - ) + Box( + modifier = Modifier + .drawBehind { + voteModel.dots.forEach { dot -> + val offset = + Offset(x = this.size.width * dot.x, y = this.size.height * dot.y) + drawCircle( + color = Color( + dot.color + .substring(0, 2) + .toInt(16), + dot.color + .substring(2, 4) + .toInt(16), + dot.color + .substring(4, 6) + .toInt(16), + 255 / 3 + ), + radius = 30.dp.value, + center = offset, + style = Fill + ) + } } - } + ) { Text( text = voteModel.text, style = style, @@ -65,3 +76,21 @@ internal fun VoteCard( } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun VoteCardPreview() { + MaterialTheme { + VoteCard( + voteModel = UIVoteItem( + id = "", + text = "Fun", + dots = listOf(UIDot(x = .5f, y = .5f, color = "FF00CC")), + votedByUser = true + ), + onClick = {}, + modifier = Modifier.size(height = 100.dp, width = 200.dp) + ) + } +} diff --git a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteItems.kt b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteItems.kt index f446aab..4327426 100644 --- a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteItems.kt +++ b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/VoteItems.kt @@ -1,13 +1,18 @@ package io.openfeedback.android.m3 import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.openfeedback.android.viewmodels.models.UIDot import io.openfeedback.android.viewmodels.models.UIVoteItem @ExperimentalMaterial3Api @@ -16,26 +21,62 @@ internal fun VoteItems( voteItems: List, modifier: Modifier = Modifier, columnCount: Int = 2, - onClick: (voteItem: UIVoteItem) -> Unit + horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(8.dp), + verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp), + content: @Composable ColumnScope.(UIVoteItem) -> Unit ) { - val spaceGrid = 8.dp Row( modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(spaceGrid) + horizontalArrangement = horizontalArrangement ) { 0.until(columnCount).forEach { column -> - Box(modifier = Modifier.weight(1f)) { - Column(verticalArrangement = Arrangement.spacedBy(spaceGrid)) { - voteItems.filterIndexed { index, _ -> + Column( + verticalArrangement = verticalArrangement, + modifier = Modifier.weight(1f) + ) { + voteItems + .filterIndexed { index, _ -> index % columnCount == column - }.forEach { voteItem -> - VoteCard( - voteModel = voteItem, - onClick = onClick - ) } - } + .forEach { voteItem -> + content(voteItem) + } } } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun VoteItemsPreview() { + MaterialTheme { + VoteItems( + voteItems = listOf( + UIVoteItem( + id = "", + text = "Fun", + dots = listOf(UIDot(x = .5f, y = .5f, color = "FF00CC")), + votedByUser = true + ), + UIVoteItem( + id = "", + text = "Fun", + dots = listOf(UIDot(x = .5f, y = .5f, color = "FF00CC")), + votedByUser = true + ) + ), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + content = { + VoteCard( + voteModel = it, + onClick = {}, + modifier = Modifier + .height(100.dp) + .fillMaxWidth() + ) + } + ) + } +}