mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-11-15 00:52:31 +01:00
Harden beacon sessions and mark automatic messages
This commit is contained in:
1 parent
19bcdb428d
commit
f13fe54fde
63 files changed
+1905
-252
No files matched your search
@@ -473,7 +473,17 @@ profiles.changeAfterSwitch=Die Änderung wirkt erst nach einem erneuten Wechsel
|
||||
profiles.alreadyActive=Dieses Profil ist bereits aktiv.
|
||||
profiles.selectFirst=Zuerst ein Profil in der Tabelle wählen.
|
||||
beacon.invalidMessage=Der Bakentext ist ungültig: {0}
|
||||
beacon.intervalTooShort=Einen ganzzahligen Bakenabstand von mindestens {0} Minute eingeben.
|
||||
beacon.intervalTooShort=Einen ganzzahligen Bakenabstand von mindestens {0} Minuten eingeben.
|
||||
beacon.status.main=Hauptkategorie: {0}
|
||||
beacon.status.second=Zweite Kategorie: {0}
|
||||
beacon.status.enabled=aktiv
|
||||
beacon.status.failed=Versand fehlgeschlagen
|
||||
beacon.status.unavailable=nicht verfügbar
|
||||
beacon.reason.session=in dieser Sitzung noch nicht aktiviert
|
||||
beacon.reason.user=durch den Benutzer ausgeschaltet
|
||||
beacon.reason.primaryQrg=Haupt-QRG fehlt oder ist ungültig
|
||||
beacon.reason.secondaryQrg=zweite QRG fehlt oder ist ungültig
|
||||
beacon.reason.automaticQrg=automatische QRG-Änderung um mehr als 5 kHz
|
||||
notification.invalidPort=„{0}" ist kein gültiger TCP-Port. Einen Wert von 1 bis 65535 eingeben.
|
||||
notification.invalidSpotter=„{0}" ist kein gültiges Spotter-Rufzeichen.
|
||||
notification.invalidCallsign=Bitte ein gültiges Rufzeichen eingeben.
|
||||
|
||||
@@ -482,7 +482,17 @@ profiles.changeAfterSwitch=The change takes effect after switching to this profi
|
||||
profiles.alreadyActive=This profile is already active.
|
||||
profiles.selectFirst=Select a profile in the table first.
|
||||
beacon.invalidMessage=The beacon message is invalid: {0}
|
||||
beacon.intervalTooShort=Enter a whole beacon interval of at least {0} minute.
|
||||
beacon.intervalTooShort=Enter a whole beacon interval of at least {0} minutes.
|
||||
beacon.status.main=Main category: {0}
|
||||
beacon.status.second=Second category: {0}
|
||||
beacon.status.enabled=active
|
||||
beacon.status.failed=send failed
|
||||
beacon.status.unavailable=unavailable
|
||||
beacon.reason.session=not activated in this session
|
||||
beacon.reason.user=switched off by the operator
|
||||
beacon.reason.primaryQrg=primary QRG missing or invalid
|
||||
beacon.reason.secondaryQrg=second QRG missing or invalid
|
||||
beacon.reason.automaticQrg=automatic QRG change greater than 5 kHz
|
||||
notification.invalidPort="{0}" is not a valid TCP port. Enter a value from 1 to 65535.
|
||||
notification.invalidSpotter="{0}" is not a valid spotter callsign.
|
||||
notification.invalidCallsign=Please enter a valid callsign.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package kst4contest.view;
|
||||
|
||||
import kst4contest.controller.RadioSlot;
|
||||
import kst4contest.model.ChatCategory;
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ChatMessage;
|
||||
@@ -97,4 +98,9 @@ public final class ComposeChatInputActions implements ChatInputActions {
|
||||
String value = application.chatcontroller.getChatPreferences().getMYQRGSecondCat().get();
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyManualQrg(final RadioSlot slot, final String value) {
|
||||
application.chatcontroller.applyManualQrg(slot, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package kst4contest.view.compose
|
||||
|
||||
import kst4contest.model.BeaconStatusSnapshot
|
||||
import kst4contest.model.BeaconStatusSnapshot.AggregateState
|
||||
import kst4contest.model.BeaconStatusSnapshot.CategoryStatus
|
||||
import kst4contest.model.BeaconStatusSnapshot.InactiveReason
|
||||
import kst4contest.view.i18n.CurrentStrings
|
||||
import kst4contest.view.i18n.Strings
|
||||
|
||||
enum class ThreadStatusVisualStyle {
|
||||
DEFAULT,
|
||||
BEACON_OFF,
|
||||
BEACON_ACTIVE,
|
||||
BEACON_FAILED,
|
||||
}
|
||||
|
||||
data class BeaconStatusPresentation(
|
||||
val label: String,
|
||||
val tooltip: String,
|
||||
val style: ThreadStatusVisualStyle,
|
||||
) {
|
||||
companion object {
|
||||
fun from(
|
||||
snapshot: BeaconStatusSnapshot,
|
||||
strings: Strings = CurrentStrings.get(),
|
||||
): BeaconStatusPresentation {
|
||||
val label = when (snapshot.aggregateState) {
|
||||
AggregateState.OFF -> "off"
|
||||
AggregateState.PARTIAL -> "1/2"
|
||||
AggregateState.ON -> "on"
|
||||
AggregateState.FAILED -> "FAILED"
|
||||
}
|
||||
val style = when (snapshot.aggregateState) {
|
||||
AggregateState.OFF -> ThreadStatusVisualStyle.BEACON_OFF
|
||||
AggregateState.PARTIAL, AggregateState.ON -> ThreadStatusVisualStyle.BEACON_ACTIVE
|
||||
AggregateState.FAILED -> ThreadStatusVisualStyle.BEACON_FAILED
|
||||
}
|
||||
val tooltip = listOf(
|
||||
strings.beaconStatusMain(describe(snapshot.main, strings)),
|
||||
strings.beaconStatusSecond(describe(snapshot.second, strings)),
|
||||
).joinToString("\n")
|
||||
return BeaconStatusPresentation(label, tooltip, style)
|
||||
}
|
||||
|
||||
private fun describe(category: CategoryStatus, strings: Strings): String {
|
||||
if (!category.isAvailable()) return strings.beaconStatusUnavailable
|
||||
if (category.isFailed()) return strings.beaconStatusFailed
|
||||
if (category.isEnabled()) return strings.beaconStatusEnabled
|
||||
return when (category.inactiveReason) {
|
||||
InactiveReason.NOT_ACTIVATED_THIS_SESSION -> strings.beaconReasonSession
|
||||
InactiveReason.USER_DISABLED -> strings.beaconReasonUser
|
||||
InactiveReason.PRIMARY_QRG_UNAVAILABLE -> strings.beaconReasonPrimaryQrg
|
||||
InactiveReason.SECONDARY_QRG_UNAVAILABLE -> strings.beaconReasonSecondaryQrg
|
||||
InactiveReason.AUTOMATIC_QRG_CHANGE -> strings.beaconReasonAutomaticQrg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,12 +163,12 @@ fun ChatInputRow(
|
||||
// profile on every keystroke.
|
||||
InlineCommittedField(
|
||||
stored = ownQrgMain.value.orEmpty(),
|
||||
onCommit = { prefs.getMYQRGFirstCat().set(it) },
|
||||
onCommit = { state.commitOwnQrg(mainCategory = true, it) },
|
||||
tooltip = kst4contest.view.i18n.CurrentStrings.get().chatOwnQrgTooltip,
|
||||
)
|
||||
InlineCommittedField(
|
||||
stored = ownQrgSecond.value.orEmpty(),
|
||||
onCommit = { prefs.getMYQRGSecondCat().set(it) },
|
||||
onCommit = { state.commitOwnQrg(mainCategory = false, it) },
|
||||
tooltip = if (so2rEnabled) {
|
||||
kst4contest.view.i18n.CurrentStrings.get().chatSecondQrgTooltipSo2r
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import kst4contest.controller.RadioSlot
|
||||
import kst4contest.model.ChatCategory
|
||||
import kst4contest.model.ChatMember
|
||||
import kst4contest.model.ChatMessage
|
||||
@@ -46,6 +47,8 @@ interface ChatInputActions {
|
||||
fun ownQrgMain(): String
|
||||
|
||||
fun ownQrgSecond(): String
|
||||
|
||||
fun applyManualQrg(slot: RadioSlot, value: String) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +153,10 @@ class ChatInputState(private val actions: ChatInputActions) {
|
||||
appendAndFocus("$qrg ")
|
||||
}
|
||||
|
||||
fun commitOwnQrg(mainCategory: Boolean, value: String) {
|
||||
actions.applyManualQrg(if (mainCategory) RadioSlot.FIRST else RadioSlot.SECOND, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the field with "/cq CALL <snippet>", resolved.
|
||||
*
|
||||
|
||||
@@ -194,6 +194,10 @@ fun buildSettingsTabs(
|
||||
setSecondBeaconEnabled = controller::setSecondBeaconEnabled,
|
||||
mainCategoryName = categoryName(controller.chatCategoryMain, kst4contest.view.i18n.CurrentStrings.get().settingsCategoryMainFallback),
|
||||
secondCategoryName = categoryName(controller.chatCategorySecondChat, kst4contest.view.i18n.CurrentStrings.get().settingsCategorySecondFallback),
|
||||
onBeaconTemplatesApplied = {
|
||||
controller.onMainBeaconTemplateApplied()
|
||||
controller.onSecondBeaconTemplateApplied()
|
||||
},
|
||||
)
|
||||
|
||||
val logSynchState = LogSynchTabState(
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -202,8 +203,20 @@ private fun ThreadStatusButtonView(button: ThreadStatusButton) {
|
||||
}
|
||||
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val background = if (button.highlighted) scheme.primary else scheme.surfaceVariant
|
||||
val foreground = if (button.highlighted) scheme.onPrimary else scheme.onSurfaceVariant
|
||||
val colors = if (button.visualStyle == ThreadStatusVisualStyle.DEFAULT) {
|
||||
ThreadButtonColors(
|
||||
background = if (button.highlighted) scheme.primary else scheme.surfaceVariant,
|
||||
border = null,
|
||||
text = if (button.highlighted) scheme.onPrimary else scheme.onSurfaceVariant,
|
||||
)
|
||||
} else {
|
||||
beaconThreadButtonColors(
|
||||
style = button.visualStyle,
|
||||
highlighted = button.highlighted,
|
||||
darkTheme = scheme.surface.luminance() < 0.5f,
|
||||
neutralBackground = scheme.surfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
TooltipArea(tooltip = { TooltipCard(button.tooltip) }) {
|
||||
val actionModifier = if (button.hasAction) {
|
||||
@@ -211,17 +224,20 @@ private fun ThreadStatusButtonView(button: ThreadStatusButton) {
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
var boxModifier = Modifier
|
||||
.defaultMinSize(minHeight = 22.dp)
|
||||
.background(background, NOTICE_SHAPE)
|
||||
.background(colors.background, NOTICE_SHAPE)
|
||||
.then(actionModifier)
|
||||
.padding(NOTICE_PADDING),
|
||||
colors.border?.let { border ->
|
||||
boxModifier = boxModifier.border(1.dp, border, NOTICE_SHAPE)
|
||||
}
|
||||
Box(
|
||||
modifier = boxModifier.padding(NOTICE_PADDING),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = button.label,
|
||||
color = foreground,
|
||||
color = colors.text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
maxLines = 1,
|
||||
)
|
||||
@@ -229,6 +245,33 @@ private fun ThreadStatusButtonView(button: ThreadStatusButton) {
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ThreadButtonColors(
|
||||
val background: Color,
|
||||
val border: Color?,
|
||||
val text: Color,
|
||||
)
|
||||
|
||||
internal fun beaconThreadButtonColors(
|
||||
style: ThreadStatusVisualStyle,
|
||||
highlighted: Boolean,
|
||||
darkTheme: Boolean,
|
||||
neutralBackground: Color,
|
||||
): ThreadButtonColors = when (style) {
|
||||
ThreadStatusVisualStyle.BEACON_ACTIVE -> if (highlighted) {
|
||||
ThreadButtonColors(Color(0xFFFFB300), Color(0xFFFFE082), Color(0xFF1B1B1B))
|
||||
} else {
|
||||
ThreadButtonColors(Color(0xFF238636), Color(0xFF56D364), Color.White)
|
||||
}
|
||||
ThreadStatusVisualStyle.BEACON_FAILED ->
|
||||
ThreadButtonColors(Color(0xFFD50000), Color(0xFFFF6B6B), Color.White)
|
||||
ThreadStatusVisualStyle.BEACON_OFF -> ThreadButtonColors(
|
||||
neutralBackground,
|
||||
null,
|
||||
if (darkTheme) Color(0xFFFF6B6B) else Color(0xFFD50000),
|
||||
)
|
||||
ThreadStatusVisualStyle.DEFAULT -> error("Default worker colours come from the theme")
|
||||
}
|
||||
|
||||
/** The tooltips in this bar carry the long text the badges had to cut. */
|
||||
@Composable
|
||||
internal fun TooltipCard(text: String) {
|
||||
|
||||
@@ -187,9 +187,11 @@ class ThreadStatusButtons {
|
||||
val button = bySource.getOrPut(sourceName) {
|
||||
ThreadStatusButton(sourceName, actions[sourceName]).also { ordered.add(it) }
|
||||
}
|
||||
val beacon = effectiveMessage.beaconStatusSnapshot?.let(BeaconStatusPresentation::from)
|
||||
button.report(
|
||||
label = "$sourceName: ${effectiveMessage.runningInformationTextDescription}",
|
||||
tooltip = effectiveMessage.runningInformation.orEmpty(),
|
||||
label = "$sourceName: ${beacon?.label ?: effectiveMessage.runningInformationTextDescription}",
|
||||
tooltip = beacon?.tooltip ?: effectiveMessage.runningInformation.orEmpty(),
|
||||
style = beacon?.style ?: ThreadStatusVisualStyle.DEFAULT,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -226,6 +228,9 @@ class ThreadStatusButton(
|
||||
var highlighted: Boolean by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var visualStyle: ThreadStatusVisualStyle by mutableStateOf(ThreadStatusVisualStyle.DEFAULT)
|
||||
private set
|
||||
|
||||
var revision: Int by mutableStateOf(0)
|
||||
private set
|
||||
|
||||
@@ -235,10 +240,16 @@ class ThreadStatusButton(
|
||||
val hasAction: Boolean
|
||||
get() = action != null
|
||||
|
||||
internal fun report(label: String, tooltip: String) {
|
||||
internal fun report(
|
||||
label: String,
|
||||
tooltip: String,
|
||||
style: ThreadStatusVisualStyle = ThreadStatusVisualStyle.DEFAULT,
|
||||
) {
|
||||
this.label = label
|
||||
this.tooltip = tooltip
|
||||
highlighted = true
|
||||
visualStyle = style
|
||||
highlighted = style == ThreadStatusVisualStyle.DEFAULT ||
|
||||
style == ThreadStatusVisualStyle.BEACON_ACTIVE
|
||||
revision++
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class BeaconTabState(
|
||||
val secondCategoryName: String,
|
||||
private val setMainBeaconEnabled: (Boolean) -> Unit = prefs::setBcn_beaconsEnabledMainCat,
|
||||
private val setSecondBeaconEnabled: (Boolean) -> Unit = prefs::setBcn_beaconsEnabledSecondCat,
|
||||
private val onBeaconTemplatesApplied: () -> Unit = {},
|
||||
) {
|
||||
|
||||
val mainBeaconEnabled = prefs.bcnBeaconsEnabledMainCatProperty()
|
||||
@@ -45,10 +46,14 @@ class BeaconTabState(
|
||||
|
||||
private var textMainCat: String by draft.setting(
|
||||
{ prefs.getBcn_beaconTextMainCat() },
|
||||
effectKey = BEACON_TEMPLATE_EFFECT,
|
||||
effect = onBeaconTemplatesApplied,
|
||||
) { prefs.setBcn_beaconTextMainCat(it) }
|
||||
|
||||
private var textSecondCat: String by draft.setting(
|
||||
{ prefs.getBcn_beaconTextSecondCat() },
|
||||
effectKey = BEACON_TEMPLATE_EFFECT,
|
||||
effect = onBeaconTemplatesApplied,
|
||||
) { prefs.setBcn_beaconTextSecondCat(it) }
|
||||
|
||||
/*
|
||||
@@ -123,4 +128,8 @@ class BeaconTabState(
|
||||
interval = minutes
|
||||
return null
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val BEACON_TEMPLATE_EFFECT = "beaconTemplates"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package kst4contest.view.compose
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class BeaconStatusColorsTest {
|
||||
private val neutral = Color(0xFF123456)
|
||||
|
||||
@Test
|
||||
fun `active and activity colours are the approved traffic colours`() {
|
||||
assertEquals(
|
||||
ThreadButtonColors(Color(0xFF238636), Color(0xFF56D364), Color.White),
|
||||
beaconThreadButtonColors(ThreadStatusVisualStyle.BEACON_ACTIVE, false, false, neutral),
|
||||
)
|
||||
assertEquals(
|
||||
ThreadButtonColors(Color(0xFFFFB300), Color(0xFFFFE082), Color(0xFF1B1B1B)),
|
||||
beaconThreadButtonColors(ThreadStatusVisualStyle.BEACON_ACTIVE, true, false, neutral),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `off uses neutral background and theme specific red text`() {
|
||||
assertEquals(Color(0xFFD50000),
|
||||
beaconThreadButtonColors(ThreadStatusVisualStyle.BEACON_OFF, false, false, neutral).text)
|
||||
assertEquals(Color(0xFFFF6B6B),
|
||||
beaconThreadButtonColors(ThreadStatusVisualStyle.BEACON_OFF, false, true, neutral).text)
|
||||
assertEquals(neutral,
|
||||
beaconThreadButtonColors(ThreadStatusVisualStyle.BEACON_OFF, false, true, neutral).background)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed stays red even when a highlight is requested`() {
|
||||
val colors = beaconThreadButtonColors(
|
||||
ThreadStatusVisualStyle.BEACON_FAILED,
|
||||
true,
|
||||
false,
|
||||
neutral,
|
||||
)
|
||||
assertEquals(ThreadButtonColors(Color(0xFFD50000), Color(0xFFFF6B6B), Color.White), colors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed suppresses the flash while active status allows it`() {
|
||||
val button = ThreadStatusButton("MyBeacon")
|
||||
button.report("MyBeacon: on", "active", ThreadStatusVisualStyle.BEACON_ACTIVE)
|
||||
assertTrue(button.highlighted)
|
||||
|
||||
button.report("MyBeacon: FAILED", "failed", ThreadStatusVisualStyle.BEACON_FAILED)
|
||||
assertFalse(button.highlighted)
|
||||
assertEquals(
|
||||
ThreadButtonColors(Color(0xFFD50000), Color(0xFFFF6B6B), Color.White),
|
||||
beaconThreadButtonColors(ThreadStatusVisualStyle.BEACON_FAILED, true, false, neutral),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package kst4contest.view.compose
|
||||
|
||||
import kst4contest.model.BeaconStatusSnapshot
|
||||
import kst4contest.model.BeaconStatusSnapshot.AggregateState
|
||||
import kst4contest.model.BeaconStatusSnapshot.CategoryStatus
|
||||
import kst4contest.model.BeaconStatusSnapshot.InactiveReason
|
||||
import kst4contest.view.i18n.LanguageStore
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.Locale
|
||||
|
||||
class BeaconStatusPresentationTest {
|
||||
@Test
|
||||
fun `partial status keeps the inactive category reason in English`() {
|
||||
val presentation = BeaconStatusPresentation.from(snapshot(AggregateState.PARTIAL))
|
||||
|
||||
assertEquals("1/2", presentation.label)
|
||||
assertTrue(presentation.tooltip.contains("Main category: active"))
|
||||
assertTrue(presentation.tooltip.contains("primary QRG missing or invalid"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the same category reasons are available in German`() {
|
||||
val language = LanguageStore({ "de" }, {}, Locale.UK)
|
||||
try {
|
||||
val presentation = BeaconStatusPresentation.from(
|
||||
snapshot(AggregateState.PARTIAL),
|
||||
language.strings,
|
||||
)
|
||||
|
||||
assertTrue(presentation.tooltip.contains("Hauptkategorie: aktiv"))
|
||||
assertTrue(presentation.tooltip.contains("Haupt-QRG fehlt oder ist ungültig"))
|
||||
assertFalse(presentation.tooltip.contains("144."))
|
||||
} finally {
|
||||
language.use("en")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed style overrides an active category`() {
|
||||
val main = CategoryStatus(true, true, true, InactiveReason.USER_DISABLED)
|
||||
val second = CategoryStatus(false, false, false, InactiveReason.NOT_ACTIVATED_THIS_SESSION)
|
||||
val presentation = BeaconStatusPresentation.from(
|
||||
BeaconStatusSnapshot(AggregateState.FAILED, main, second),
|
||||
)
|
||||
|
||||
assertEquals("FAILED", presentation.label)
|
||||
assertEquals(ThreadStatusVisualStyle.BEACON_FAILED, presentation.style)
|
||||
assertTrue(presentation.tooltip.contains("send failed"))
|
||||
}
|
||||
|
||||
private fun snapshot(state: AggregateState): BeaconStatusSnapshot = BeaconStatusSnapshot(
|
||||
state,
|
||||
CategoryStatus(true, true, false, InactiveReason.USER_DISABLED),
|
||||
CategoryStatus(true, false, false, InactiveReason.PRIMARY_QRG_UNAVAILABLE),
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import kst4contest.model.ChatCategory
|
||||
import kst4contest.model.ChatMember
|
||||
import kst4contest.model.ChatMessage
|
||||
import kst4contest.controller.RadioSlot
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertSame
|
||||
@@ -233,6 +234,20 @@ class ChatInputStateTest {
|
||||
assertEquals("432.200 ", state.value.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `manual QRG commits keep their radio source explicit`() {
|
||||
val actions = FakeActions()
|
||||
val state = ChatInputState(actions)
|
||||
|
||||
state.commitOwnQrg(mainCategory = true, "144.300")
|
||||
state.commitOwnQrg(mainCategory = false, "1296.100")
|
||||
|
||||
assertEquals(
|
||||
listOf(RadioSlot.FIRST to "144.300", RadioSlot.SECOND to "1296.100"),
|
||||
actions.manualQrgs,
|
||||
)
|
||||
}
|
||||
|
||||
// ---- the snippets ------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -407,6 +422,7 @@ class ChatInputStateTest {
|
||||
val resolvedTemplates = mutableListOf<String>()
|
||||
val recordedCqs = mutableListOf<String>()
|
||||
val recomputeReasons = mutableListOf<String>()
|
||||
val manualQrgs = mutableListOf<Pair<RadioSlot, String>>()
|
||||
|
||||
override fun panelSelection(): ChatMember? = panelSelection
|
||||
override fun tableSelection(): ChatMember? = tableSelection
|
||||
@@ -431,5 +447,6 @@ class ChatInputStateTest {
|
||||
override fun queue(message: ChatMessage) { queued += message }
|
||||
override fun ownQrgMain(): String = ownQrgMain
|
||||
override fun ownQrgSecond(): String = ownQrgSecond
|
||||
override fun applyManualQrg(slot: RadioSlot, value: String) { manualQrgs += slot to value }
|
||||
}
|
||||
}
|
||||
@@ -173,13 +173,24 @@ class BeaconTabStateTest(@TempDir private val tempDir: Path) {
|
||||
s.prefs.setBcn_beaconIntervalInMinutesMainCat(5)
|
||||
val state = s.state()
|
||||
|
||||
assertNotNull(state.commitBeaconInterval("0"))
|
||||
assertNotNull(state.commitBeaconInterval("4"))
|
||||
|
||||
assertEquals(5, s.prefs.getBcn_beaconIntervalInMinutesMainCat())
|
||||
assertEquals(0, s.timerRestarts,
|
||||
"restarting the timer per rejected keystroke would disturb the cooldown")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exactly five minutes is accepted`() {
|
||||
val s = Session()
|
||||
val state = s.state()
|
||||
|
||||
assertNull(state.commitBeaconInterval("5"))
|
||||
|
||||
assertEquals(5, s.prefs.getBcn_beaconIntervalInMinutesMainCat())
|
||||
assertEquals(5, s.prefs.getBcn_beaconIntervalInMinutesSecondCat())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an interval that is not a whole number is refused`() {
|
||||
val s = Session()
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ class RemainingTextsFollowTheLanguageTest {
|
||||
|
||||
assertEquals("Wertung", score.title(), "the column title stayed in the old language")
|
||||
assertEquals(
|
||||
"Einen ganzzahligen Bakenabstand von mindestens 1 Minute eingeben.",
|
||||
"Einen ganzzahligen Bakenabstand von mindestens 5 Minuten eingeben.",
|
||||
beacon.commitBeaconInterval("0"),
|
||||
"the refusal from the tab state stayed in the old language",
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user