created vertical variant of questionnaires
This commit is contained in:
@ -0,0 +1,522 @@
|
|||||||
|
package com.dano.test1.questionnaire
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.*
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.LocalizationHelper
|
||||||
|
import com.dano.test1.MainActivity
|
||||||
|
import com.dano.test1.R
|
||||||
|
import com.dano.test1.network.TokenStore
|
||||||
|
import com.dano.test1.utils.ViewUtils
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
class QuestionnaireAllInOne(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {
|
||||||
|
|
||||||
|
private data class Section(
|
||||||
|
val index: Int,
|
||||||
|
val question: QuestionItem,
|
||||||
|
val card: MaterialCardView,
|
||||||
|
val sectionView: View,
|
||||||
|
val handler: QuestionHandler?
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sections = mutableListOf<Section>()
|
||||||
|
private lateinit var container: LinearLayout
|
||||||
|
private lateinit var btnSave: MaterialButton
|
||||||
|
private var setupComplete = false
|
||||||
|
|
||||||
|
override fun startQuestionnaire() {
|
||||||
|
val (meta, questionsList) = loadQuestionnaireFromJson(questionnaireFileName)
|
||||||
|
questionnaireMeta = meta
|
||||||
|
questions = questionsList
|
||||||
|
currentIndex = 0
|
||||||
|
buildAllInOneUi()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showCurrentQuestion() {
|
||||||
|
// Not used in all-in-one mode
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildAllInOneUi() {
|
||||||
|
context.setContentView(R.layout.questionnaire_all_in_one)
|
||||||
|
|
||||||
|
container = context.findViewById(R.id.questionContainer)
|
||||||
|
btnSave = context.findViewById(R.id.btnSave)
|
||||||
|
val btnBack = context.findViewById<MaterialButton>(R.id.btnBack)
|
||||||
|
val progressBar = context.findViewById<ProgressBar>(R.id.progressBar)
|
||||||
|
|
||||||
|
btnSave.text = LanguageManager.getText(languageID, "save")
|
||||||
|
ViewUtils.setTextSizePercentOfScreenHeight(btnSave, 0.025f)
|
||||||
|
|
||||||
|
btnSave.isEnabled = false
|
||||||
|
btnSave.alpha = 0.5f
|
||||||
|
|
||||||
|
btnBack.setOnClickListener {
|
||||||
|
(context as? MainActivity)?.finishQuestionnaire()
|
||||||
|
}
|
||||||
|
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
for ((idx, question) in questions.withIndex()) {
|
||||||
|
if (question is QuestionItem.LastPage) continue
|
||||||
|
|
||||||
|
val layoutResId = getLayoutResId(question.layout ?: "default_layout")
|
||||||
|
if (layoutResId == 0) continue
|
||||||
|
|
||||||
|
val sectionView = inflater.inflate(layoutResId, container, false)
|
||||||
|
adaptLayoutForEmbedding(sectionView)
|
||||||
|
LocalizationHelper.localizeViewTree(sectionView, languageID)
|
||||||
|
|
||||||
|
val card = wrapInCard(sectionView)
|
||||||
|
container.addView(card)
|
||||||
|
|
||||||
|
val handler = createEmbeddedHandler(question)
|
||||||
|
handler?.bind(sectionView, question)
|
||||||
|
reduceTextSizes(sectionView)
|
||||||
|
|
||||||
|
sections.add(Section(idx, question, card, sectionView, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
installChangeListeners()
|
||||||
|
recalculateVisibility()
|
||||||
|
|
||||||
|
// Allow DB restores to finish before enabling interaction tracking
|
||||||
|
container.postDelayed({
|
||||||
|
setupComplete = true
|
||||||
|
recalculateVisibility()
|
||||||
|
updateSaveButtonState()
|
||||||
|
}, 600)
|
||||||
|
|
||||||
|
btnSave.setOnClickListener {
|
||||||
|
handleSave(progressBar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEmbeddedHandler(question: QuestionItem): QuestionHandler? {
|
||||||
|
val noop = {}
|
||||||
|
val noopId: (String) -> Unit = {}
|
||||||
|
val toast: (String) -> Unit = { showToast(it) }
|
||||||
|
val metaId = questionnaireMeta.id
|
||||||
|
|
||||||
|
return when (question) {
|
||||||
|
is QuestionItem.RadioQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerRadioQuestion(
|
||||||
|
context, answers, points, languageID, noop, noop, noopId, toast, metaId
|
||||||
|
)
|
||||||
|
is QuestionItem.ClientCoachCodeQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerClientCoachCode(
|
||||||
|
answers, languageID, noop, noop, toast
|
||||||
|
)
|
||||||
|
is QuestionItem.DateSpinnerQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerDateSpinner(
|
||||||
|
context, answers, languageID, noop, noop, toast, metaId
|
||||||
|
)
|
||||||
|
is QuestionItem.ValueSpinnerQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerValueSpinner(
|
||||||
|
context, answers, languageID, noop, noop, noopId, toast, metaId
|
||||||
|
)
|
||||||
|
is QuestionItem.GlassScaleQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerGlassScaleQuestion(
|
||||||
|
context, answers, points, languageID, noop, noop, toast, metaId
|
||||||
|
)
|
||||||
|
is QuestionItem.ClientNotSigned ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerClientNotSigned(
|
||||||
|
answers, languageID, noop, noop, toast
|
||||||
|
)
|
||||||
|
is QuestionItem.StringSpinnerQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerStringSpinner(
|
||||||
|
context, answers, languageID, noop, noop, toast, metaId
|
||||||
|
)
|
||||||
|
is QuestionItem.MultiCheckboxQuestion ->
|
||||||
|
com.dano.test1.questionnaire.handlers.HandlerMultiCheckboxQuestion(
|
||||||
|
context, answers, points, languageID, noop, noop, toast, metaId
|
||||||
|
)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adaptLayoutForEmbedding(view: View) {
|
||||||
|
if (view is ConstraintLayout) {
|
||||||
|
view.layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
// Ensure the last field in the card is never clipped at the bottom
|
||||||
|
view.setPadding(
|
||||||
|
view.paddingLeft, view.paddingTop,
|
||||||
|
view.paddingRight, ViewUtils.dp(context, 12)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.Qprev)?.visibility = View.GONE
|
||||||
|
view.findViewById<View>(R.id.Qnext)?.visibility = View.GONE
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.gTop)?.let { guideline ->
|
||||||
|
if (guideline is androidx.constraintlayout.widget.Guideline) {
|
||||||
|
val params = guideline.layoutParams as? ConstraintLayout.LayoutParams
|
||||||
|
params?.guideBegin = ViewUtils.dp(context, 8)
|
||||||
|
guideline.layoutParams = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptInnerScrollView(view, R.id.radioScroll)
|
||||||
|
adaptInnerScrollView(view, R.id.scrollView)
|
||||||
|
adaptInnerScrollView(view, R.id.glassScroll)
|
||||||
|
|
||||||
|
view.findViewById<EditText>(R.id.client_code)?.let { et ->
|
||||||
|
val params = et.layoutParams as? ConstraintLayout.LayoutParams ?: return@let
|
||||||
|
if (params.matchConstraintPercentHeight > 0f) {
|
||||||
|
params.matchConstraintPercentHeight = 0f
|
||||||
|
params.height = ViewUtils.dp(context, 64)
|
||||||
|
et.layoutParams = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.findViewById<EditText>(R.id.coach_code)?.let { et ->
|
||||||
|
val params = et.layoutParams as? ConstraintLayout.LayoutParams ?: return@let
|
||||||
|
if (params.matchConstraintPercentHeight > 0f) {
|
||||||
|
params.matchConstraintPercentHeight = 0f
|
||||||
|
params.height = ViewUtils.dp(context, 64)
|
||||||
|
et.layoutParams = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales down all TextView text sizes by [factor] after the handler has set them.
|
||||||
|
* Spinners manage their own adapter text sizes and are skipped.
|
||||||
|
*/
|
||||||
|
private fun reduceTextSizes(view: View, factor: Float = 0.82f) {
|
||||||
|
if (view is Spinner) return
|
||||||
|
if (view is TextView) {
|
||||||
|
val dm = view.context.resources.displayMetrics
|
||||||
|
val currentSp = view.textSize / dm.scaledDensity
|
||||||
|
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, currentSp * factor)
|
||||||
|
}
|
||||||
|
if (view is ViewGroup) {
|
||||||
|
for (i in 0 until view.childCount) reduceTextSizes(view.getChildAt(i), factor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adaptInnerScrollView(root: View, scrollViewId: Int) {
|
||||||
|
val sv = root.findViewById<ScrollView>(scrollViewId) ?: return
|
||||||
|
val params = sv.layoutParams
|
||||||
|
if (params is ConstraintLayout.LayoutParams) {
|
||||||
|
params.height = ConstraintLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
params.bottomToTop = ConstraintLayout.LayoutParams.UNSET
|
||||||
|
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
|
||||||
|
sv.layoutParams = params
|
||||||
|
}
|
||||||
|
sv.isNestedScrollingEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrapInCard(sectionView: View): MaterialCardView {
|
||||||
|
val card = MaterialCardView(context).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
val margin = ViewUtils.dp(context, 6)
|
||||||
|
setMargins(0, margin, 0, margin)
|
||||||
|
}
|
||||||
|
radius = ViewUtils.dp(context, 16).toFloat()
|
||||||
|
cardElevation = ViewUtils.dp(context, 2).toFloat()
|
||||||
|
setCardBackgroundColor(Color.WHITE)
|
||||||
|
strokeColor = Color.parseColor("#D8D1F0")
|
||||||
|
strokeWidth = ViewUtils.dp(context, 1)
|
||||||
|
setContentPadding(
|
||||||
|
ViewUtils.dp(context, 4),
|
||||||
|
ViewUtils.dp(context, 8),
|
||||||
|
ViewUtils.dp(context, 4),
|
||||||
|
ViewUtils.dp(context, 12)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
card.addView(sectionView)
|
||||||
|
return card
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Visibility walk algorithm (reads from UI state, not answers map) ----
|
||||||
|
|
||||||
|
private fun recalculateVisibility() {
|
||||||
|
val visibleIndices = mutableSetOf<Int>()
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
while (i < questions.size) {
|
||||||
|
val q = questions[i]
|
||||||
|
if (q is QuestionItem.LastPage) { i++; continue }
|
||||||
|
|
||||||
|
visibleIndices.add(i)
|
||||||
|
|
||||||
|
val nextTarget = resolveNextFromUi(q)
|
||||||
|
when {
|
||||||
|
nextTarget != null -> {
|
||||||
|
if (questions.any { it is QuestionItem.LastPage && it.id == nextTarget }) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val targetIdx = questions.indexOfFirst { it.id == nextTarget }
|
||||||
|
i = if (targetIdx != -1) targetIdx else i + 1
|
||||||
|
}
|
||||||
|
hasBranchingOptions(q) && !hasAnswerInUi(q) -> break
|
||||||
|
else -> i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (section in sections) {
|
||||||
|
val shouldShow = visibleIndices.contains(section.index)
|
||||||
|
section.card.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveNextFromUi(q: QuestionItem): String? {
|
||||||
|
val section = sections.find { it.question === q } ?: return null
|
||||||
|
val view = section.sectionView
|
||||||
|
|
||||||
|
return when (q) {
|
||||||
|
is QuestionItem.RadioQuestion -> {
|
||||||
|
val radioGroup = view.findViewById<RadioGroup>(R.id.RadioGroup) ?: return null
|
||||||
|
val checkedId = radioGroup.checkedRadioButtonId
|
||||||
|
if (checkedId == -1) return null
|
||||||
|
val rb = radioGroup.findViewById<RadioButton>(checkedId) ?: return null
|
||||||
|
val key = rb.tag?.toString() ?: return null
|
||||||
|
q.options?.find { it.key == key }?.nextQuestionId
|
||||||
|
}
|
||||||
|
is QuestionItem.ValueSpinnerQuestion -> {
|
||||||
|
val spinner = view.findViewById<Spinner>(R.id.value_spinner) ?: return null
|
||||||
|
val selected = spinner.selectedItem?.toString() ?: return null
|
||||||
|
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||||
|
if (selected == prompt) return null
|
||||||
|
val selectedVal = selected.toIntOrNull()
|
||||||
|
if (selectedVal != null) {
|
||||||
|
q.options?.find { it.value == selectedVal }?.nextQuestionId
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasBranchingOptions(q: QuestionItem): Boolean {
|
||||||
|
return when (q) {
|
||||||
|
is QuestionItem.RadioQuestion ->
|
||||||
|
q.options?.any { it.nextQuestionId != null } ?: false
|
||||||
|
is QuestionItem.ValueSpinnerQuestion ->
|
||||||
|
q.options?.any { it.nextQuestionId != null } ?: false
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasAnswerInUi(q: QuestionItem): Boolean {
|
||||||
|
val section = sections.find { it.question === q } ?: return false
|
||||||
|
val view = section.sectionView
|
||||||
|
|
||||||
|
return when (q) {
|
||||||
|
is QuestionItem.RadioQuestion -> {
|
||||||
|
val rg = view.findViewById<RadioGroup>(R.id.RadioGroup) ?: return false
|
||||||
|
rg.checkedRadioButtonId != -1
|
||||||
|
}
|
||||||
|
is QuestionItem.ValueSpinnerQuestion -> {
|
||||||
|
val spinner = view.findViewById<Spinner>(R.id.value_spinner) ?: return false
|
||||||
|
val selected = spinner.selectedItem?.toString()
|
||||||
|
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||||
|
!selected.isNullOrEmpty() && selected != prompt
|
||||||
|
}
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Change listeners ----
|
||||||
|
|
||||||
|
private fun installChangeListeners() {
|
||||||
|
for (section in sections) {
|
||||||
|
val view = section.sectionView
|
||||||
|
|
||||||
|
when (section.question) {
|
||||||
|
is QuestionItem.RadioQuestion -> {
|
||||||
|
view.findViewById<RadioGroup>(R.id.RadioGroup)
|
||||||
|
?.setOnCheckedChangeListener { _, checkedId ->
|
||||||
|
if (!setupComplete || checkedId == -1) return@setOnCheckedChangeListener
|
||||||
|
recalculateVisibility()
|
||||||
|
updateSaveButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is QuestionItem.ValueSpinnerQuestion -> {
|
||||||
|
installSpinnerChangeListener(view, R.id.value_spinner)
|
||||||
|
}
|
||||||
|
is QuestionItem.StringSpinnerQuestion -> {
|
||||||
|
installSpinnerChangeListener(view, R.id.string_spinner)
|
||||||
|
}
|
||||||
|
is QuestionItem.DateSpinnerQuestion -> {
|
||||||
|
val listener = createSimpleSpinnerListener()
|
||||||
|
view.findViewById<Spinner>(R.id.spinner_value_day)?.onItemSelectedListener = listener
|
||||||
|
view.findViewById<Spinner>(R.id.spinner_value_month)?.onItemSelectedListener = listener
|
||||||
|
view.findViewById<Spinner>(R.id.spinner_value_year)?.onItemSelectedListener = listener
|
||||||
|
}
|
||||||
|
is QuestionItem.GlassScaleQuestion -> {
|
||||||
|
installGlassScaleClickListeners(view)
|
||||||
|
}
|
||||||
|
is QuestionItem.MultiCheckboxQuestion -> {
|
||||||
|
installCheckboxClickListeners(view)
|
||||||
|
}
|
||||||
|
is QuestionItem.ClientCoachCodeQuestion -> {
|
||||||
|
installEditTextWatcher(view, R.id.client_code)
|
||||||
|
installEditTextWatcher(view, R.id.coach_code)
|
||||||
|
}
|
||||||
|
is QuestionItem.ClientNotSigned -> {
|
||||||
|
installEditTextWatcher(view, R.id.coach_code)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installSpinnerChangeListener(view: View, spinnerId: Int) {
|
||||||
|
view.findViewById<Spinner>(spinnerId)?.onItemSelectedListener =
|
||||||
|
object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(
|
||||||
|
parent: AdapterView<*>?, v: View?, pos: Int, id: Long
|
||||||
|
) {
|
||||||
|
if (!setupComplete) return
|
||||||
|
recalculateVisibility()
|
||||||
|
updateSaveButtonState()
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSimpleSpinnerListener(): AdapterView.OnItemSelectedListener {
|
||||||
|
return object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, v: View?, pos: Int, id: Long) {
|
||||||
|
if (!setupComplete) return
|
||||||
|
updateSaveButtonState()
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installGlassScaleClickListeners(view: View) {
|
||||||
|
val table = view.findViewById<TableLayout>(R.id.glass_table) ?: return
|
||||||
|
for (r in 0 until table.childCount) {
|
||||||
|
val row = table.getChildAt(r) as? TableRow ?: continue
|
||||||
|
val radioGroup = row.getChildAt(1) as? RadioGroup ?: continue
|
||||||
|
for (c in 0 until radioGroup.childCount) {
|
||||||
|
val rb = getRadioFromChild(radioGroup.getChildAt(c)) ?: continue
|
||||||
|
rb.setOnClickListener {
|
||||||
|
if (setupComplete) updateSaveButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRadioFromChild(child: View): RadioButton? = when (child) {
|
||||||
|
is RadioButton -> child
|
||||||
|
is FrameLayout -> child.getChildAt(0) as? RadioButton
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installCheckboxClickListeners(view: View) {
|
||||||
|
val cont = view.findViewById<LinearLayout>(R.id.CheckboxContainer) ?: return
|
||||||
|
for (i in 0 until cont.childCount) {
|
||||||
|
val cb = cont.getChildAt(i) as? CheckBox ?: continue
|
||||||
|
cb.setOnClickListener {
|
||||||
|
if (setupComplete) updateSaveButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installEditTextWatcher(view: View, editTextId: Int) {
|
||||||
|
view.findViewById<EditText>(editTextId)?.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
if (!setupComplete) return
|
||||||
|
updateSaveButtonState()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Save button state ----
|
||||||
|
|
||||||
|
private fun updateSaveButtonState() {
|
||||||
|
val allValid = sections
|
||||||
|
.filter { it.card.visibility == View.VISIBLE }
|
||||||
|
.all { section ->
|
||||||
|
val handler = section.handler ?: return@all true
|
||||||
|
try { handler.validate() } catch (_: Exception) { false }
|
||||||
|
}
|
||||||
|
btnSave.isEnabled = allValid
|
||||||
|
btnSave.alpha = if (allValid) 1f else 0.5f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Save flow ----
|
||||||
|
|
||||||
|
private fun handleSave(progressBar: ProgressBar) {
|
||||||
|
val visibleSections = sections.filter { it.card.visibility == View.VISIBLE }
|
||||||
|
|
||||||
|
for (section in visibleSections) {
|
||||||
|
val handler = section.handler ?: continue
|
||||||
|
if (!handler.validate()) {
|
||||||
|
showToast(LanguageManager.getText(languageID, "fill_all_fields"))
|
||||||
|
scrollToSection(section)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (section in visibleSections) {
|
||||||
|
if (section.question is QuestionItem.ClientCoachCodeQuestion) {
|
||||||
|
saveClientCoachCodeFromUi(section.sectionView)
|
||||||
|
}
|
||||||
|
section.handler?.saveAnswer()
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(progressBar, true)
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
saveAnswersToDatabase(answers, questionnaireMeta.id)
|
||||||
|
|
||||||
|
GlobalValues.INTEGRATION_INDEX = points.sum()
|
||||||
|
val clientCode = answers["client_code"] as? String
|
||||||
|
if (clientCode != null) {
|
||||||
|
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||||
|
GlobalValues.LOADED_CLIENT_CODE = clientCode
|
||||||
|
}
|
||||||
|
|
||||||
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
|
if (elapsed < 1500L) delay(1500L - elapsed)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showLoading(progressBar, false)
|
||||||
|
endQuestionnaire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveClientCoachCodeFromUi(view: View) {
|
||||||
|
val clientCode = view.findViewById<EditText>(R.id.client_code)?.text?.toString() ?: ""
|
||||||
|
val coachCode = view.findViewById<EditText>(R.id.coach_code)?.text?.toString() ?: ""
|
||||||
|
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||||
|
answers["client_code"] = clientCode
|
||||||
|
answers["coach_code"] = TokenStore.getUsername(context) ?: coachCode
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLoading(progressBar: ProgressBar, show: Boolean) {
|
||||||
|
progressBar.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
|
btnSave.isEnabled = !show
|
||||||
|
btnSave.alpha = if (show) 0.5f else 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToSection(section: Section) {
|
||||||
|
val scrollView = context.findViewById<androidx.core.widget.NestedScrollView>(R.id.scrollContainer)
|
||||||
|
scrollView?.post {
|
||||||
|
scrollView.smoothScrollTo(0, section.card.top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.dano.test1.questionnaire
|
||||||
|
|
||||||
|
object QuestionnaireProgressiveCallbacks {
|
||||||
|
@JvmStatic
|
||||||
|
fun maybeTrim(questionId: String?) {
|
||||||
|
// Intentional no-op: handlers call this during user interaction,
|
||||||
|
// but trimming is handled by each questionnaire variant itself.
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@ import com.dano.test1.network.TokenStore
|
|||||||
import com.dano.test1.questionnaire.GlobalValues
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import com.dano.test1.questionnaire.QuestionItem
|
import com.dano.test1.questionnaire.QuestionItem
|
||||||
import com.dano.test1.questionnaire.QuestionnaireBase
|
import com.dano.test1.questionnaire.QuestionnaireBase
|
||||||
|
import com.dano.test1.questionnaire.QuestionnaireAllInOne
|
||||||
import com.dano.test1.questionnaire.QuestionnaireGeneric
|
import com.dano.test1.questionnaire.QuestionnaireGeneric
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
@ -225,7 +226,12 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
GlobalValues.LAST_CLIENT_CODE = GlobalValues.LOADED_CLIENT_CODE
|
GlobalValues.LAST_CLIENT_CODE = GlobalValues.LOADED_CLIENT_CODE
|
||||||
val fileName = questionnaireFiles[this] ?: return@setOnClickListener
|
val fileName = questionnaireFiles[this] ?: return@setOnClickListener
|
||||||
val questionnaire = QuestionnaireGeneric(fileName)
|
val variant = AbTestSettingsStore.effectiveVariant(activity)
|
||||||
|
val questionnaire: QuestionnaireBase<*> = if (variant == "B") {
|
||||||
|
QuestionnaireAllInOne(fileName)
|
||||||
|
} else {
|
||||||
|
QuestionnaireGeneric(fileName)
|
||||||
|
}
|
||||||
startQuestionnaire(questionnaire)
|
startQuestionnaire(questionnaire)
|
||||||
applySetButtonsEnabled(dynamicButtons.filter { it == this }, allowCompleted = false, force = false)
|
applySetButtonsEnabled(dynamicButtons.filter { it == this }, allowCompleted = false, force = false)
|
||||||
}
|
}
|
||||||
|
|||||||
71
app/src/main/res/layout/questionnaire_all_in_one.xml
Normal file
71
app/src/main/res/layout/questionnaire_all_in_one.xml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnBack"
|
||||||
|
android:layout_width="@dimen/nav_btn_size"
|
||||||
|
android:layout_height="@dimen/nav_btn_size"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text=""
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_chevron_left"
|
||||||
|
app:iconTint="@color/btn_nav_left_icon_tint"
|
||||||
|
app:iconSize="@dimen/nav_icon_size"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:cornerRadius="999dp"
|
||||||
|
app:backgroundTint="@color/btn_nav_left_tint"
|
||||||
|
app:rippleColor="@color/btn_nav_left_ripple" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scrollContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/questionContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="8dp" />
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:elevation="8dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnSave"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/nav_btn_size"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
app:cornerRadius="999dp"
|
||||||
|
app:backgroundTint="@color/btn_nav_right_tint"
|
||||||
|
app:rippleColor="@color/btn_nav_right_ripple" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user