Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -112,21 +112,17 @@ internal constructor(
}
}

fun isFirstPosition(taskId: String): Boolean = withReady {
taskSequenceHandler.isFirstPosition(taskId)
}

fun isLastPosition(taskId: String): Boolean = withReady {
taskSequenceHandler.isLastPosition(taskId)
}
private fun isFirstPosition(taskId: String): Boolean =
withReadyOrNull { taskSequenceHandler.isFirstPosition(taskId) } ?: false

fun isLastPositionWithValue(task: Task, newValue: TaskData?): Boolean = withReady {
if (taskDataHandler.getData(task) == newValue) {
taskSequenceHandler.isLastPosition(task.id)
} else {
taskSequenceHandler.checkIfTaskIsLastWithValue(task.id to newValue)
}
}
private fun isLastPositionWithValue(task: Task, newValue: TaskData?): Boolean =
withReadyOrNull {
if (taskDataHandler.getData(task) == newValue) {
taskSequenceHandler.isLastPosition(task.id)
} else {
taskSequenceHandler.checkIfTaskIsLastWithValue(task.id to newValue)
}
} ?: false

fun isAtFirstTask(): Boolean = withReady { taskSequenceHandler.isFirstPosition(it.currentTaskId) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.groundplatform.android.ui.datacollection.components.refactor

import org.groundplatform.android.ui.datacollection.components.ButtonAction
package org.groundplatform.android.ui.datacollection.components

data class ButtonActionState(
val action: ButtonAction,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,129 +15,95 @@
*/
package org.groundplatform.android.ui.datacollection.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
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.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.groundplatform.android.model.submission.TaskData

class TaskButton(initialAction: ButtonAction) {

private lateinit var clickCallback: () -> Unit

private var action: MutableState<ButtonAction> = mutableStateOf(initialAction)
private var enabled: MutableState<Boolean> = mutableStateOf(true)
private var hidden: MutableState<Boolean> = mutableStateOf(false)

private var taskUpdatedCallback: ((button: TaskButton, taskData: TaskData?) -> Unit)? = null

@Composable
fun CreateButton() {
if (!hidden.value) {
when (action.value.theme) {
ButtonAction.Theme.DARK_GREEN ->
Button(onClick = { clickCallback() }, enabled = enabled.value) { Content() }
ButtonAction.Theme.LIGHT_GREEN ->
FilledTonalButton(onClick = { clickCallback() }, enabled = enabled.value) { Content() }
ButtonAction.Theme.OUTLINED ->
OutlinedButton(onClick = { clickCallback() }, enabled = enabled.value) { Content() }
ButtonAction.Theme.TRANSPARENT ->
OutlinedButton(
border = null,
onClick = { clickCallback() },
enabled = enabled.value,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
) {
Content()
}
import org.groundplatform.android.ui.common.ExcludeFromJacocoGeneratedReport
import org.groundplatform.android.ui.theme.AppTheme

@Composable
fun TaskButton(
modifier: Modifier = Modifier,
state: ButtonActionState,
onClick: (ButtonAction) -> Unit,
) {
when (state.action.theme) {
ButtonAction.Theme.DARK_GREEN ->
Button(modifier = modifier, onClick = { onClick(state.action) }, enabled = state.isEnabled) {
Content(action = state.action)
}
ButtonAction.Theme.LIGHT_GREEN ->
FilledTonalButton(
modifier = modifier,
onClick = { onClick(state.action) },
enabled = state.isEnabled,
) {
Content(action = state.action)
}
ButtonAction.Theme.OUTLINED ->
OutlinedButton(
modifier = modifier,
onClick = { onClick(state.action) },
enabled = state.isEnabled,
) {
Content(action = state.action)
}
ButtonAction.Theme.TRANSPARENT ->
OutlinedButton(
modifier = modifier,
border = null,
onClick = { onClick(state.action) },
enabled = state.isEnabled,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
) {
Content(action = state.action)
}
}
}
}

@Composable
private fun Content() {
// Icon
action.value.drawableId?.let {
@Composable
private fun Content(modifier: Modifier = Modifier, action: ButtonAction) {
when {
action.drawableId != null -> {
Icon(
imageVector = ImageVector.vectorResource(id = it),
contentDescription = action.value.contentDescription?.let { resId -> stringResource(resId) },
modifier = modifier,
imageVector = ImageVector.vectorResource(id = action.drawableId),
contentDescription = action.contentDescription?.let { resId -> stringResource(resId) },
)
}

// Label
action.value.textId?.let { textId -> Text(text = stringResource(id = textId)) }
}

/** Updates the `visibility` property button. */
fun showIfTrue(result: Boolean): TaskButton = if (result) show() else hide()

/** Updates the `isEnabled` property of button. */
fun enableIfTrue(result: Boolean): TaskButton = if (result) enable() else disable()

fun getAction(): ButtonAction = action.value

fun done(): TaskButton {
action.value = ButtonAction.DONE
return this
}

fun next(): TaskButton {
action.value = ButtonAction.NEXT
return this
}

fun toggleDone(done: Boolean): TaskButton {
if (action.value == ButtonAction.NEXT && done) {
done()
} else if (action.value == ButtonAction.DONE && !done) {
next()
action.textId != null -> {
Text(modifier = modifier, text = stringResource(id = action.textId))
}
return this
}

fun show(): TaskButton {
hidden.value = false
return this
}

fun hide(): TaskButton {
hidden.value = true
return this
}

fun enable(): TaskButton {
enabled.value = true
return this
}

fun disable(): TaskButton {
enabled.value = false
return this
}

/** Register a callback to be invoked when this view is clicked. */
fun setOnClickListener(block: () -> Unit): TaskButton {
this.clickCallback = block
return this
}

/** Register a callback to be invoked when [TaskData] is updated. */
fun setOnValueChanged(block: (button: TaskButton, taskData: TaskData?) -> Unit): TaskButton {
this.taskUpdatedCallback = block
return this
}
}

/** Must be called when a new [TaskData] is available. */
fun onValueChanged(taskData: TaskData?) {
taskUpdatedCallback?.let { it(this, taskData) }
@Preview(showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
private fun TaskButtonAllPreview() {
AppTheme {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
ButtonAction.entries.forEach { action ->
TaskButton(state = ButtonActionState(action), onClick = {})
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.groundplatform.android.ui.datacollection.components.refactor
package org.groundplatform.android.ui.datacollection.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.groundplatform.android.ui.common.ExcludeFromJacocoGeneratedReport
import org.groundplatform.android.ui.datacollection.components.ButtonAction
import org.groundplatform.android.ui.datacollection.tasks.location.LocationAccuracyCard
import org.groundplatform.android.ui.theme.AppTheme

@Suppress("UnusedPrivateMember") // To be implemented in the follow up PR
@Composable
fun TaskFooter(
modifier: Modifier = Modifier,
headerCard: (@Composable () -> Unit)? = null,
buttonActionStates: List<ButtonActionState>,
onButtonClicked: (ButtonAction) -> Unit,
) {
Row(
modifier = modifier.padding(24.dp).fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
buttonActionStates.forEach { state ->
if (state.isVisible) {
TaskButton(state = state, onClick = { onButtonClicked(state.action) })
Column(modifier = modifier.padding(24.dp).fillMaxWidth()) {
if (headerCard != null) {
headerCard()
Spacer(Modifier.height(12.dp))
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
buttonActionStates.forEach { state ->
if (state.isVisible) {
TaskButton(state = state, onClick = { onButtonClicked(state.action) })
}
}
}
}
Expand All @@ -49,10 +55,25 @@ fun TaskFooter(
@Preview(showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
private fun TaskFooterPreview() {
private fun TaskFooterNoHeaderPreview() {
val actions =
listOf(ButtonAction.PREVIOUS, ButtonAction.UNDO, ButtonAction.REDO, ButtonAction.NEXT)
AppTheme {
TaskFooter(buttonActionStates = actions.map { ButtonActionState(it) }, onButtonClicked = {})
}
}

@Preview(showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
private fun TaskFooterWithHeaderPreview() {
val actions =
listOf(ButtonAction.PREVIOUS, ButtonAction.UNDO, ButtonAction.REDO, ButtonAction.NEXT)
AppTheme {
TaskFooter(
headerCard = { LocationAccuracyCard(onDismiss = {}) },
buttonActionStates = actions.map { ButtonActionState(it) },
onButtonClicked = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
package org.groundplatform.android.ui.datacollection.components

import android.view.View
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import org.groundplatform.android.databinding.TaskFragActionButtonsBinding
import org.groundplatform.android.databinding.TaskFragWithCombinedHeaderBinding
import org.groundplatform.android.databinding.TaskFragWithHeaderBinding
import org.groundplatform.android.ui.datacollection.tasks.AbstractTaskViewModel

/** Wrapper class for holding entire task's view (except toolbar). */
sealed interface TaskView {

/** Container for adding the action buttons for the task. */
val actionButtonsContainer: TaskFragActionButtonsBinding
/** ComposeView for the action buttons. */
val actionButtonsContainer: ComposeView

/** Root-level view for the current task. */
val root: View
Expand Down Expand Up @@ -59,7 +59,7 @@ data class TaskViewWithHeader(private val binding: TaskFragWithHeaderBinding) :
data class TaskViewWithCombinedHeader(private val binding: TaskFragWithCombinedHeaderBinding) :
TaskView {

override val actionButtonsContainer = binding.actionButtons
override val actionButtonsContainer: ComposeView = binding.actionButtons

override val root = binding.root

Expand Down
Loading
Loading