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

[Jetsurvey] Simplification of survey screens #1058

Merged
merged 8 commits into from Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion Jetsurvey/app/build.gradle.kts
Expand Up @@ -83,7 +83,6 @@ dependencies {
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.runtime.livedata)
debugImplementation(libs.androidx.compose.ui.tooling)

implementation(libs.accompanist.permissions)
Expand Down
3 changes: 1 addition & 2 deletions Jetsurvey/app/src/main/AndroidManifest.xml
Expand Up @@ -30,8 +30,7 @@

<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
@@ -0,0 +1,103 @@
/*
* Copyright 2020 The Android Open Source Project
IanGClifton marked this conversation as resolved.
Show resolved Hide resolved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.compose.jetsurvey.survey

import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
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.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.compose.jetsurvey.theme.slightlyDeemphasizedAlpha
import com.example.compose.jetsurvey.theme.stronglyDeemphasizedAlpha

/**
* A scrollable container with the question's title, direction, and dynamic content.
*
* @param titleResourceId String resource to use for the question's title
* @param modifier Modifier to apply to the entire wrapper
* @param directionsResourceId String resource to use for the question's directions; the direction
* UI will be omitted if null is passed
* @param content Composable to display after the title and option directions
*/
@Composable
fun QuestionWrapper(
@StringRes titleResourceId: Int,
modifier: Modifier = Modifier,
@StringRes directionsResourceId: Int? = null,
content: @Composable () -> Unit,
) {
Column(
modifier = modifier
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.height(32.dp))
QuestionTitle(titleResourceId)
directionsResourceId?.let {
Spacer(Modifier.height(18.dp))
QuestionDirections(it)
}
Spacer(Modifier.height(18.dp))

content()
}
}

@Composable
private fun QuestionTitle(
@StringRes title: Int,
modifier: Modifier = Modifier,
) {
Text(
text = stringResource(id = title),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = slightlyDeemphasizedAlpha),
modifier = modifier
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.inverseOnSurface,
shape = MaterialTheme.shapes.small
)
.padding(vertical = 24.dp, horizontal = 16.dp)
)
}

@Composable
private fun QuestionDirections(
@StringRes directionsResourceId: Int,
modifier: Modifier = Modifier,
) {
Text(
text = stringResource(id = directionsResourceId),
color = MaterialTheme.colorScheme.onSurface
.copy(alpha = stronglyDeemphasizedAlpha),
style = MaterialTheme.typography.bodySmall,
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
)
}
Expand Up @@ -17,83 +17,102 @@
package com.example.compose.jetsurvey.survey

import android.net.Uri
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.compose.jetsurvey.R
import com.example.compose.jetsurvey.survey.question.DateQuestion
import com.example.compose.jetsurvey.survey.question.MultipleChoiceQuestion
import com.example.compose.jetsurvey.survey.question.PhotoQuestion
import com.example.compose.jetsurvey.survey.question.SingleChoiceQuestion
import com.example.compose.jetsurvey.survey.question.SliderQuestion
import com.example.compose.jetsurvey.survey.question.Superhero

data class SurveyResult(
val library: String,
@StringRes val result: Int,
@StringRes val description: Int
)

data class Survey(
@StringRes val title: Int,
val questions: List<Question>
)

data class Question(
val id: Int,
@StringRes val questionText: Int,
val answer: PossibleAnswer,
@StringRes val description: Int? = null,
val permissionsRequired: List<String> = emptyList(),
@StringRes val permissionsRationaleText: Int? = null
)

/**
* Type of supported actions for a survey
*/
enum class SurveyActionType { PICK_DATE, TAKE_PHOTO, SELECT_CONTACT }

sealed class SurveyActionResult {
data class Date(val dateMillis: Long) : SurveyActionResult()
data class Photo(val uri: Uri) : SurveyActionResult()
data class Contact(val contact: String) : SurveyActionResult()
@Composable
fun FreeTimeQuestion(
selectedAnswers: List<Int>,
onOptionSelected: (selected: Boolean, answer: Int) -> Unit,
modifier: Modifier = Modifier,
) {
MultipleChoiceQuestion(
titleResourceId = R.string.in_my_free_time,
directionsResourceId = R.string.select_all,
possibleAnswers = listOf(
R.string.read,
R.string.work_out,
R.string.draw,
R.string.play_games,
R.string.dance,
R.string.watch_movies,
),
selectedAnswers = selectedAnswers,
onOptionSelected = onOptionSelected,
modifier = modifier,
)
}

data class AnswerOption(@StringRes val textRes: Int, @DrawableRes val iconRes: Int? = null)

sealed class PossibleAnswer {
data class SingleChoice(val options: List<AnswerOption>) : PossibleAnswer()
data class MultipleChoice(val options: List<AnswerOption>) : PossibleAnswer()
data class Action(
@StringRes val label: Int,
val actionType: SurveyActionType
) : PossibleAnswer()

data class Slider(
val range: ClosedFloatingPointRange<Float>,
val steps: Int,
@StringRes val startText: Int,
@StringRes val endText: Int,
@StringRes val neutralText: Int,
val defaultValue: Float = 5.5f
) : PossibleAnswer()
@Composable
fun SuperheroQuestion(
selectedAnswer: Superhero?,
onOptionSelected: (Superhero) -> Unit,
modifier: Modifier = Modifier,
) {
SingleChoiceQuestion(
titleResourceId = R.string.pick_superhero,
directionsResourceId = R.string.select_one,
possibleAnswers = listOf(
Superhero(R.string.spark, R.drawable.spark),
Superhero(R.string.lenz, R.drawable.lenz),
Superhero(R.string.bugchaos, R.drawable.bug_of_chaos),
Superhero(R.string.frag, R.drawable.frag),
),
selectedAnswer = selectedAnswer,
onOptionSelected = onOptionSelected,
modifier = modifier,
)
}

sealed class Answer<T : PossibleAnswer> {
object PermissionsDenied : Answer<Nothing>()
data class SingleChoice(@StringRes val answer: Int) : Answer<PossibleAnswer.SingleChoice>()
data class MultipleChoice(val answersStringRes: Set<Int>) :
Answer<PossibleAnswer.MultipleChoice>()
@Composable
fun TakeawayQuestion(
dateInMillis: Long?,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
DateQuestion(
titleResourceId = R.string.takeaway,
directionsResourceId = R.string.select_date,
dateInMillis = dateInMillis,
onClick = onClick,
modifier = modifier,
)
}

data class Action(val result: SurveyActionResult) : Answer<PossibleAnswer.Action>()
data class Slider(val answerValue: Float) : Answer<PossibleAnswer.Slider>()
@Composable
fun FeelingAboutSelfiesQuestion(
value: Float?,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
) {
SliderQuestion(
titleResourceId = R.string.selfies,
value = value,
onValueChange = onValueChange,
startTextResource = R.string.strongly_dislike,
neutralTextResource = R.string.neutral,
endTextResource = R.string.strongly_like,
modifier = modifier,
)
}

/**
* Add or remove an answer from the list of selected answers depending on whether the answer was
* selected or deselected.
*/
fun Answer.MultipleChoice.withAnswerSelected(
@StringRes answer: Int,
selected: Boolean
): Answer.MultipleChoice {
val newStringRes = answersStringRes.toMutableSet()
if (!selected) {
newStringRes.remove(answer)
} else {
newStringRes.add(answer)
}
return Answer.MultipleChoice(newStringRes)
@Composable
fun TakeSelfieQuestion(
imageUri: Uri?,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
PhotoQuestion(
titleResourceId = R.string.selfie_skills,
imageUri = imageUri,
onClick = onClick,
modifier = modifier,
)
}