Skip to content

Commit f9d7ecc

Browse files
Okuro3499dogi
andauthored
teams: smoother survey rating scaling (fixes #13498) (#13499)
Co-authored-by: dogi <dogi@users.noreply.github.com>
1 parent e334904 commit f9d7ecc

4 files changed

Lines changed: 106 additions & 157 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId "org.ole.planet.myplanet"
1313
minSdk = 26
1414
targetSdk = 36
15-
versionCode = 5590
16-
versionName = "0.55.90"
15+
versionCode = 5591
16+
versionName = "0.55.91"
1717
ndkVersion = '26.3.11579264'
1818
vectorDrawables.useSupportLibrary = true
1919
}

app/src/main/java/org/ole/planet/myplanet/model/RealmExamQuestion.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ open class RealmExamQuestion : RealmObject() {
2222
var marks: String? = null
2323
var choices: String? = null
2424
var hasOtherOption: Boolean = false
25+
var scaleMax: Int = 9
2526
private fun setCorrectChoiceArray(array: JsonArray, question: RealmExamQuestion?) {
2627
for (i in 0 until array.size()) {
2728
question?.correctChoice?.add(JsonUtils.getString(array, i).lowercase(Locale.getDefault()))
@@ -90,6 +91,7 @@ open class RealmExamQuestion : RealmObject() {
9091
}
9192

9293
hasOtherOption = JsonUtils.getBoolean("hasOtherOption", question)
94+
scaleMax = JsonUtils.getInt("scaleMax", question).let { if (it <= 0) 9 else it }
9395
val isMultipleChoice = type?.startsWith("select") == true && question.has("choices")
9496
if (isMultipleChoice) {
9597
insertCorrectChoice(question["choices"].asJsonArray, question, this)

app/src/main/java/org/ole/planet/myplanet/ui/exam/ExamTakingFragment.kt

Lines changed: 100 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package org.ole.planet.myplanet.ui.exam
22

3+
import android.graphics.Color
4+
import android.graphics.Typeface
5+
import android.graphics.drawable.GradientDrawable
6+
import android.graphics.drawable.StateListDrawable
37
import android.os.Bundle
48
import android.text.Editable
59
import android.text.TextUtils
610
import android.text.TextWatcher
11+
import android.view.Gravity
712
import android.view.LayoutInflater
813
import android.view.View
914
import android.view.ViewGroup
1015
import android.widget.Button
1116
import android.widget.CompoundButton
17+
import android.widget.LinearLayout
1218
import android.widget.RadioButton
1319
import android.widget.Toast
1420
import androidx.core.content.ContextCompat
@@ -329,7 +335,7 @@ class ExamTakingFragment : BaseExamFragment(), View.OnClickListener, CompoundBut
329335
}
330336
question?.type.equals("ratingScale", ignoreCase = true) -> {
331337
binding.llRatingScale.visibility = View.VISIBLE
332-
setupRatingScale(ans)
338+
setupRatingScale(question, ans)
333339
}
334340
}
335341
binding.tvHeader.text = question?.header
@@ -379,28 +385,49 @@ class ExamTakingFragment : BaseExamFragment(), View.OnClickListener, CompoundBut
379385
}
380386

381387
private var selectedRatingButton: Button? = null
382-
383-
private fun setupRatingScale(oldAnswer: String) {
384-
val ratingButtons = listOf(
385-
binding.rbRating1,
386-
binding.rbRating2,
387-
binding.rbRating3,
388-
binding.rbRating4,
389-
binding.rbRating5,
390-
binding.rbRating6,
391-
binding.rbRating7,
392-
binding.rbRating8,
393-
binding.rbRating9
394-
)
395-
396-
ratingButtons.forEachIndexed { index, button ->
388+
private var dynamicRatingButtons: List<Button> = emptyList()
389+
390+
private fun setupRatingScale(question: RealmExamQuestion?, oldAnswer: String) {
391+
val scaleMax = (question?.scaleMax ?: 0).let { if (it <= 0) 9 else it }
392+
binding.llRatingScale.removeAllViews()
393+
dynamicRatingButtons = emptyList()
394+
395+
val buttonSizePx = (60 * resources.displayMetrics.density).toInt()
396+
val marginPx = (8 * resources.displayMetrics.density).toInt()
397+
val buttonsPerRow = 3
398+
val buttons = mutableListOf<Button>()
399+
var currentRow: LinearLayout? = null
400+
401+
val useGradient = scaleMax == 9
402+
403+
for (i in 1..scaleMax) {
404+
val positionInRow = (i - 1) % buttonsPerRow
405+
if (positionInRow == 0) {
406+
currentRow = LinearLayout(requireContext()).apply {
407+
orientation = LinearLayout.HORIZONTAL
408+
gravity = Gravity.CENTER
409+
layoutParams = LinearLayout.LayoutParams(
410+
LinearLayout.LayoutParams.WRAP_CONTENT,
411+
LinearLayout.LayoutParams.WRAP_CONTENT
412+
).also { it.bottomMargin = marginPx }
413+
}
414+
binding.llRatingScale.addView(currentRow)
415+
}
416+
val isLastInRow = positionInRow == buttonsPerRow - 1 || i == scaleMax
417+
val ratio = if (scaleMax > 1) (i - 1).toFloat() / (scaleMax - 1) else 0f
418+
val button = createRatingButton(i, ratio, buttonSizePx, if (isLastInRow) 0 else marginPx, useGradient)
419+
currentRow?.addView(button)
420+
buttons.add(button)
421+
}
422+
423+
dynamicRatingButtons = buttons
424+
425+
buttons.forEachIndexed { index, button ->
397426
button.setOnClickListener {
398427
selectedRatingButton?.isSelected = false
399-
400428
button.isSelected = true
401429
selectedRatingButton = button
402430
ans = (index + 1).toString()
403-
404431
updateNavButtons()
405432
}
406433
}
@@ -409,24 +436,62 @@ class ExamTakingFragment : BaseExamFragment(), View.OnClickListener, CompoundBut
409436
selectRatingValue(oldAnswer.toIntOrNull() ?: 1)
410437
}
411438
}
412-
413-
private fun selectRatingValue(value: Int) {
414-
val ratingButtons = listOf(
415-
binding.rbRating1,
416-
binding.rbRating2,
417-
binding.rbRating3,
418-
binding.rbRating4,
419-
binding.rbRating5,
420-
binding.rbRating6,
421-
binding.rbRating7,
422-
binding.rbRating8,
423-
binding.rbRating9
424-
)
425439

440+
private fun createRatingButton(number: Int, colorRatio: Float, sizePx: Int, marginEndPx: Int, useGradient: Boolean): Button {
441+
val selectedColor = ContextCompat.getColor(requireContext(), R.color.colorPrimary)
442+
val cornerPx = 8 * resources.displayMetrics.density
443+
val strokePx = (2 * resources.displayMetrics.density).toInt()
444+
445+
val unselectedBg: Int
446+
val unselectedStroke: Int
447+
if (useGradient) {
448+
unselectedBg = interpolateColor(0xFFFFE9EA.toInt(), 0xFFE9FBE9.toInt(), colorRatio)
449+
unselectedStroke = unselectedBg
450+
} else {
451+
unselectedBg = ContextCompat.getColor(requireContext(), R.color.card_bg)
452+
unselectedStroke = ContextCompat.getColor(requireContext(), R.color.daynight_textColor)
453+
}
454+
455+
fun makeShape(fillColor: Int, strokeColor: Int) = GradientDrawable().apply {
456+
shape = GradientDrawable.RECTANGLE
457+
cornerRadius = cornerPx
458+
setColor(fillColor)
459+
setStroke(strokePx, strokeColor)
460+
}
461+
462+
val stateList = StateListDrawable().apply {
463+
addState(intArrayOf(android.R.attr.state_selected), makeShape(selectedColor, selectedColor))
464+
addState(intArrayOf(android.R.attr.state_pressed), makeShape(selectedColor, selectedColor))
465+
addState(intArrayOf(), makeShape(unselectedBg, unselectedStroke))
466+
}
467+
468+
return Button(requireContext()).apply {
469+
text = number.toString()
470+
textSize = 16f
471+
setTypeface(null, Typeface.BOLD)
472+
setTextColor(ContextCompat.getColorStateList(requireContext(), R.color.rating_button_text_color))
473+
background = stateList
474+
minHeight = 0
475+
minWidth = 0
476+
setPadding(0, 0, 0, 0)
477+
layoutParams = LinearLayout.LayoutParams(sizePx, sizePx).apply {
478+
marginEnd = marginEndPx
479+
}
480+
}
481+
}
482+
483+
private fun interpolateColor(start: Int, end: Int, ratio: Float): Int {
484+
val r = (Color.red(start) + (Color.red(end) - Color.red(start)) * ratio).toInt()
485+
val g = (Color.green(start) + (Color.green(end) - Color.green(start)) * ratio).toInt()
486+
val b = (Color.blue(start) + (Color.blue(end) - Color.blue(start)) * ratio).toInt()
487+
return Color.rgb(r, g, b)
488+
}
489+
490+
private fun selectRatingValue(value: Int) {
426491
selectedRatingButton?.isSelected = false
427-
428-
if (value in 1..9) {
429-
val button = ratingButtons[value - 1]
492+
val buttons = dynamicRatingButtons
493+
if (value in 1..buttons.size) {
494+
val button = buttons[value - 1]
430495
button.isSelected = true
431496
selectedRatingButton = button
432497
}
@@ -751,6 +816,7 @@ class ExamTakingFragment : BaseExamFragment(), View.OnClickListener, CompoundBut
751816
}
752817
answerTextWatcher?.let { binding.etAnswer.removeTextChangedListener(it) }
753818
selectedRatingButton = null
819+
dynamicRatingButtons = emptyList()
754820
_binding = null
755821
}
756822
}

app/src/main/res/layout/fragment_exam_taking.xml

Lines changed: 2 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -139,131 +139,12 @@
139139
<LinearLayout
140140
android:id="@+id/ll_rating_scale"
141141
android:layout_width="match_parent"
142-
android:layout_height="match_parent"
142+
android:layout_height="wrap_content"
143143
android:layout_gravity="center"
144144
android:gravity="center"
145145
android:orientation="vertical"
146146
android:padding="@dimen/padding_large"
147-
android:visibility="gone">
148-
149-
<LinearLayout
150-
android:layout_width="wrap_content"
151-
android:layout_height="wrap_content"
152-
android:orientation="vertical"
153-
android:gravity="center"
154-
android:layout_gravity="center">
155-
156-
<LinearLayout
157-
android:layout_width="wrap_content"
158-
android:layout_height="wrap_content"
159-
android:orientation="horizontal"
160-
android:layout_marginBottom="8dp">
161-
162-
<Button
163-
android:id="@+id/rb_rating_1"
164-
android:layout_width="60dp"
165-
android:layout_height="60dp"
166-
android:layout_marginEnd="8dp"
167-
android:background="@drawable/bg_rating_button_1"
168-
android:text="1"
169-
android:textColor="@color/rating_button_text_color"
170-
android:textSize="16sp"
171-
android:textStyle="bold" />
172-
<Button
173-
android:id="@+id/rb_rating_2"
174-
android:layout_width="60dp"
175-
android:layout_height="60dp"
176-
android:layout_marginEnd="8dp"
177-
android:background="@drawable/bg_rating_button_2"
178-
android:text="2"
179-
android:textColor="@color/rating_button_text_color"
180-
android:textSize="16sp"
181-
android:textStyle="bold" />
182-
<Button
183-
android:id="@+id/rb_rating_3"
184-
android:layout_width="60dp"
185-
android:layout_height="60dp"
186-
android:background="@drawable/bg_rating_button_3"
187-
android:text="3"
188-
android:textColor="@color/rating_button_text_color"
189-
android:textSize="16sp"
190-
android:textStyle="bold" />
191-
</LinearLayout>
192-
193-
<LinearLayout
194-
android:layout_width="wrap_content"
195-
android:layout_height="wrap_content"
196-
android:orientation="horizontal"
197-
android:layout_marginBottom="8dp">
198-
199-
<Button
200-
android:id="@+id/rb_rating_4"
201-
android:layout_width="60dp"
202-
android:layout_height="60dp"
203-
android:layout_marginEnd="8dp"
204-
android:background="@drawable/bg_rating_button_4"
205-
android:text="4"
206-
android:textColor="@color/rating_button_text_color"
207-
android:textSize="16sp"
208-
android:textStyle="bold" />
209-
<Button
210-
android:id="@+id/rb_rating_5"
211-
android:layout_width="60dp"
212-
android:layout_height="60dp"
213-
android:layout_marginEnd="8dp"
214-
android:background="@drawable/bg_rating_button_5"
215-
android:text="5"
216-
android:textColor="@color/rating_button_text_color"
217-
android:textSize="16sp"
218-
android:textStyle="bold" />
219-
<Button
220-
android:id="@+id/rb_rating_6"
221-
android:layout_width="60dp"
222-
android:layout_height="60dp"
223-
android:background="@drawable/bg_rating_button_6"
224-
android:text="6"
225-
android:textColor="@color/rating_button_text_color"
226-
android:textSize="16sp"
227-
android:textStyle="bold" />
228-
</LinearLayout>
229-
230-
<LinearLayout
231-
android:layout_width="wrap_content"
232-
android:layout_height="wrap_content"
233-
android:orientation="horizontal">
234-
235-
<Button
236-
android:id="@+id/rb_rating_7"
237-
android:layout_width="60dp"
238-
android:layout_height="60dp"
239-
android:layout_marginEnd="8dp"
240-
android:background="@drawable/bg_rating_button_7"
241-
android:text="7"
242-
android:textColor="@color/rating_button_text_color"
243-
android:textSize="16sp"
244-
android:textStyle="bold" />
245-
<Button
246-
android:id="@+id/rb_rating_8"
247-
android:layout_width="60dp"
248-
android:layout_height="60dp"
249-
android:layout_marginEnd="8dp"
250-
android:background="@drawable/bg_rating_button_8"
251-
android:text="8"
252-
android:textColor="@color/rating_button_text_color"
253-
android:textSize="16sp"
254-
android:textStyle="bold" />
255-
<Button
256-
android:id="@+id/rb_rating_9"
257-
android:layout_width="60dp"
258-
android:layout_height="60dp"
259-
android:background="@drawable/bg_rating_button_9"
260-
android:text="9"
261-
android:textColor="@color/rating_button_text_color"
262-
android:textSize="16sp"
263-
android:textStyle="bold" />
264-
</LinearLayout>
265-
</LinearLayout>
266-
</LinearLayout>
147+
android:visibility="gone" />
267148
</LinearLayout>
268149
</androidx.core.widget.NestedScrollView>
269150

0 commit comments

Comments
 (0)