mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat(nav,settings): bottom nav 5+N collapsible menu + Indonesian locale
Background: 8 bottom-nav items squeeze long English labels on narrow screens. 4.5.1 introduces the 5+N pattern: 5 main tabs plus a fixed 6th "More" button that pops a second-level menu (spring bounce) with the remaining pages. Changes: - MainScreen: nav split into main (<=5, screenOrder-driven) + more (subMenuOrder); More button with popup panel + spring animation; BackHandler closes the menu before navigating back - MoreMenuPopup: bottom-end card, current page highlighted, scrim click to dismiss - UI Settings: page order card now has two zones (main menu, max 5, Settings locked last with no drag handle / more menu); move buttons between zones, drag-to-reorder within zones; new subMenuOrder pref - Defaults: main = Satellites/Passes/Radar/Map/Settings, more = Mutual/Roaming/CwDecode; old screenOrder migrates by classifying pages against the default sub menu - Hard-coded UI strings localized (Tracking/Lat/Lon/Qth/Connect/ Track/Stop/CW permission prompts) into EN/ZH/TR - NEW Indonesian locale (values-in, 181 strings + cw module strings) - user rule: every future release must update EN/ZH/TR/IN - Version 4.5.1 (451) Verified: check_strings.py OK (8 files, no bare apostrophes); :app:compileDebugKotlin BUILD SUCCESSFUL locally.
This commit is contained in:
1 parent
760a26b007
commit
38cf7a3569
19 files changed
+753
-172
No files matched your search
@@ -181,7 +181,7 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = "Microphone permission is required for CW decoding",
|
||||
text = stringResource(R.string.cw_mic_permission),
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp)
|
||||
@@ -190,7 +190,7 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||
Button(onClick = {
|
||||
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||
}) {
|
||||
Text("Grant permission")
|
||||
Text(stringResource(R.string.cw_grant_permission))
|
||||
}
|
||||
if (permanentlyDenied) {
|
||||
// 勾选了"不再询问": 引导去系统设置开启
|
||||
@@ -202,7 +202,7 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||
)
|
||||
)
|
||||
}) {
|
||||
Text("Open app settings")
|
||||
Text(stringResource(R.string.cw_open_settings))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_500">#ff6200ee</color>
|
||||
<integer name="default_font_size">18</integer>
|
||||
<string name="idle">Diam</string>
|
||||
<string name="waterfall_display">Tampilan waterfall</string>
|
||||
<string name="frequency_scale">Skala frekuensi</string>
|
||||
<string name="decoded_text">Teks terdekode</string>
|
||||
<string name="clear">Bersihkan</string>
|
||||
<string name="pause">Jeda Dekode</string>
|
||||
<string name="save_text">Simpan Teks</string>
|
||||
<string name="record_signals">Rekam Sinyal</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="help">Bantuan Daring</string>
|
||||
<string name="rate">Nilai Aplikasi Ini</string>
|
||||
<string name="premium">Dapatkan Versi Premium</string>
|
||||
<string name="tap_back_again_to_close">Ketuk Kembali lagi untuk menutup aplikasi</string>
|
||||
<string name="message_type">Tipe Pesan</string>
|
||||
<string name="cw_mic_permission">Izin mikrofon diperlukan untuk dekode CW</string>
|
||||
<string name="cw_grant_permission">Beri izin</string>
|
||||
<string name="cw_open_settings">Buka pengaturan aplikasi</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_500">#ff6200ee</color>
|
||||
<integer name="default_font_size">18</integer>
|
||||
<string name="idle">Beklemede</string>
|
||||
<string name="waterfall_display">Şelale görüntüsü</string>
|
||||
<string name="frequency_scale">Frekans ölçeği</string>
|
||||
<string name="decoded_text">Çözülen metin</string>
|
||||
<string name="clear">Temizle</string>
|
||||
<string name="pause">Çözmeyi Duraklat</string>
|
||||
<string name="save_text">Metni Kaydet</string>
|
||||
<string name="record_signals">Sinyalleri Kaydet</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="help">Çevrimiçi Yardım</string>
|
||||
<string name="rate">Bu Uygulamayı Değerlendir</string>
|
||||
<string name="premium">Premium Sürümü Al</string>
|
||||
<string name="tap_back_again_to_close">Kapatmak için Geriye tekrar dokunun</string>
|
||||
<string name="message_type">Mesaj Türü</string>
|
||||
<string name="cw_mic_permission">CW çözme için mikrofon izni gereklidir</string>
|
||||
<string name="cw_grant_permission">İzin ver</string>
|
||||
<string name="cw_open_settings">Uygulama ayarlarını aç</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_500">#ff6200ee</color>
|
||||
<integer name="default_font_size">18</integer>
|
||||
<string name="idle">空闲</string>
|
||||
<string name="waterfall_display">瀑布图</string>
|
||||
<string name="frequency_scale">频率刻度</string>
|
||||
<string name="decoded_text">解码文本</string>
|
||||
<string name="clear">清除</string>
|
||||
<string name="pause">暂停解码</string>
|
||||
<string name="save_text">保存文本</string>
|
||||
<string name="record_signals">录音信号</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="help">在线帮助</string>
|
||||
<string name="rate">评价此应用</string>
|
||||
<string name="premium">获取高级版</string>
|
||||
<string name="tap_back_again_to_close">再次按返回键退出应用</string>
|
||||
<string name="message_type">消息类型</string>
|
||||
<string name="cw_mic_permission">CW 解码需要麦克风权限</string>
|
||||
<string name="cw_grant_permission">授予权限</string>
|
||||
<string name="cw_open_settings">打开应用设置</string>
|
||||
</resources>
|
||||
@@ -16,4 +16,7 @@
|
||||
<string name="premium">Get Premium Version</string>
|
||||
<string name="tap_back_again_to_close">Tap Back again to close the app</string>
|
||||
<string name="message_type">Message Type</string>
|
||||
<string name="cw_mic_permission">Microphone permission is required for CW decoding</string>
|
||||
<string name="cw_grant_permission">Grant permission</string>
|
||||
<string name="cw_open_settings">Open app settings</string>
|
||||
</resources>
|
||||
@@ -461,19 +461,19 @@ private fun ExpandedRadioControl(
|
||||
if (!radioControl.txPanel.isConnected && !radioControl.rxPanel.isConnected) {
|
||||
CardButton(
|
||||
onClick = { onAction(RadarAction.ConnectRadios) },
|
||||
text = "Connect",
|
||||
text = stringResource(R.string.connect_radio),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
} else {
|
||||
CardButton(
|
||||
onClick = { onAction(RadarAction.DisconnectRadios) },
|
||||
text = "Disconnect",
|
||||
text = stringResource(R.string.disconnect_radio),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
CardButton(
|
||||
onClick = { onAction(RadarAction.ToggleTracking) },
|
||||
text = if (radioControl.isTracking) "Stop" else "Track",
|
||||
text = if (radioControl.isTracking) stringResource(R.string.stop_radio) else stringResource(R.string.track_radio),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
|
||||
+131
-58
@@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -96,6 +97,8 @@ import com.rtbishop.look4sat.core.presentation.R
|
||||
import com.rtbishop.look4sat.core.presentation.ScreenColumn
|
||||
import com.rtbishop.look4sat.core.presentation.TopBar
|
||||
import com.rtbishop.look4sat.core.presentation.infiniteMarquee
|
||||
import com.rtbishop.look4sat.core.presentation.defaultScreenOrder
|
||||
import com.rtbishop.look4sat.core.presentation.defaultSubMenuOrder
|
||||
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -327,9 +330,11 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
||||
UiSettingsCard(
|
||||
hiddenScreens = uiState.otherSettings.hiddenScreens,
|
||||
screenOrder = uiState.otherSettings.screenOrder,
|
||||
subMenuOrder = uiState.otherSettings.subMenuOrder,
|
||||
onToggle = { name -> onAction(SettingsAction.ToggleScreen(name)) },
|
||||
onReorder = { order -> onAction(SettingsAction.ReorderScreens(order)) },
|
||||
onResetOrder = { onAction(SettingsAction.ResetScreenOrder) }
|
||||
onResetOrder = { onAction(SettingsAction.ResetMenuOrder) },
|
||||
onUpdateMenu = { main, sub -> onAction(SettingsAction.UpdateMenuOrder(main, sub)) }
|
||||
)
|
||||
}
|
||||
item { CardCredits() }
|
||||
@@ -370,11 +375,11 @@ private fun LocationCard(
|
||||
Text(text = formatUpdateTime(updateTime = settings.stationPos.timestamp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(text = "Lat: ${settings.stationPos.latitude}°")
|
||||
Text(text = "Lon: ${settings.stationPos.longitude}°")
|
||||
Text(text = stringResource(R.string.loc_lat_text, settings.stationPos.latitude))
|
||||
Text(text = stringResource(R.string.loc_lon_text, settings.stationPos.longitude))
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(text = "Qth: ${settings.stationPos.qthLocator}")
|
||||
Text(text = stringResource(R.string.loc_qth_text, settings.stationPos.qthLocator))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
@@ -557,9 +562,11 @@ private fun OtherCard(settings: OtherSettings, onAction: (SettingsAction) -> Uni
|
||||
private fun UiSettingsCard(
|
||||
hiddenScreens: List<String>,
|
||||
screenOrder: List<String>,
|
||||
subMenuOrder: List<String>,
|
||||
onToggle: (String) -> Unit,
|
||||
onReorder: (List<String>) -> Unit,
|
||||
onResetOrder: () -> Unit
|
||||
onResetOrder: () -> Unit,
|
||||
onUpdateMenu: (List<String>, List<String>) -> Unit
|
||||
) {
|
||||
val screens = listOf(
|
||||
R.string.nav_sat to "Satellites",
|
||||
@@ -571,6 +578,25 @@ private fun UiSettingsCard(
|
||||
R.string.nav_map to "Map",
|
||||
R.string.nav_prefs to "Settings"
|
||||
) // name 必须与 Screen.screenId 一致 (R8 安全)
|
||||
// 主菜单项: 排除更多菜单项, Settings 固定最后
|
||||
val mainItems = remember(screens, screenOrder, subMenuOrder) {
|
||||
val sub = subMenuOrder.ifEmpty { defaultSubMenuOrder }
|
||||
screens.map { it.second }
|
||||
.filter { it !in sub }
|
||||
.sortedBy { name ->
|
||||
screenOrder.indexOf(name).let { idx ->
|
||||
if (idx != -1) idx else defaultScreenOrder.indexOf(name).let {
|
||||
if (it != -1) it else Int.MAX_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy { if (it == "Settings") 1 else 0 })
|
||||
}
|
||||
// 更多菜单项
|
||||
val subItems = remember(screens, subMenuOrder) {
|
||||
val sub = subMenuOrder.ifEmpty { defaultSubMenuOrder }
|
||||
sub.filter { s -> screens.any { (_, name) -> name == s } }
|
||||
}
|
||||
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
@@ -594,7 +620,45 @@ private fun UiSettingsCard(
|
||||
text = stringResource(id = R.string.prefs_ui_order_title),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
DragOrderList(screens = screens, screenOrder = screenOrder, onReorder = onReorder)
|
||||
// 主菜单区(底部栏, 最多 5 项, 设置锁定最后)
|
||||
Text(
|
||||
text = stringResource(id = R.string.prefs_ui_main_title),
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
DragOrderList(
|
||||
items = mainItems,
|
||||
labels = screens,
|
||||
locked = setOf("Settings"),
|
||||
moveLabel = stringResource(id = R.string.prefs_ui_move_out),
|
||||
moveEnabled = { name -> name != "Settings" },
|
||||
onMove = { name ->
|
||||
onUpdateMenu(
|
||||
mainItems.filter { it != name },
|
||||
(subMenuOrder.ifEmpty { defaultSubMenuOrder } + name).distinct()
|
||||
)
|
||||
},
|
||||
onReorder = { main -> onUpdateMenu(main, subMenuOrder) }
|
||||
)
|
||||
// 更多菜单区(进「更多」的页面)
|
||||
Text(
|
||||
text = stringResource(id = R.string.prefs_ui_sub_title),
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
DragOrderList(
|
||||
items = subItems,
|
||||
labels = screens,
|
||||
moveLabel = stringResource(id = R.string.prefs_ui_move_back),
|
||||
moveEnabled = { mainItems.size < 5 },
|
||||
onMove = { name ->
|
||||
onUpdateMenu(
|
||||
(screenOrder.ifEmpty { defaultScreenOrder } + name).distinct(),
|
||||
subItems.filter { it != name }
|
||||
)
|
||||
},
|
||||
onReorder = { sub -> onUpdateMenu(screenOrder, sub) }
|
||||
)
|
||||
TextButton(onClick = onResetOrder) {
|
||||
Text(text = stringResource(id = R.string.prefs_ui_order_reset))
|
||||
}
|
||||
@@ -605,25 +669,18 @@ private fun UiSettingsCard(
|
||||
/** 页面顺序拖拽列表: 每行右侧抓手, 按住上下拖动实时换位, 松手持久化 */
|
||||
@Composable
|
||||
private fun DragOrderList(
|
||||
screens: List<Pair<Int, String>>,
|
||||
screenOrder: List<String>,
|
||||
items: List<String>,
|
||||
labels: List<Pair<Int, String>>,
|
||||
locked: Set<String> = emptySet(),
|
||||
moveLabel: String? = null,
|
||||
moveEnabled: (String) -> Boolean = { true },
|
||||
onMove: ((String) -> Unit)? = null,
|
||||
onReorder: (List<String>) -> Unit
|
||||
) {
|
||||
val itemHeight = 48.dp
|
||||
val itemHeightPx = with(LocalDensity.current) { itemHeight.toPx() }
|
||||
// 按 screenOrder 排序(空 = 默认顺序); remember 以 screens 参数为 key, 持久化后重组
|
||||
val ordered = remember(screens, screenOrder) {
|
||||
screens.sortedBy { (_, name) ->
|
||||
// 未知(新页面如 CwDecode 不在旧持久化顺序里): 用默认顺序位置, 再兜底最后
|
||||
screenOrder.indexOf(name).let {
|
||||
if (it != -1) it else com.rtbishop.look4sat.core.presentation.defaultScreenOrder.indexOf(name).let {
|
||||
if (it != -1) it else Int.MAX_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val listState = rememberLazyListState()
|
||||
val items = remember(ordered) { mutableStateListOf<Pair<Int, String>>().apply { addAll(ordered) } }
|
||||
val localItems = remember(items) { mutableStateListOf<String>().apply { addAll(items) } }
|
||||
var draggingIndex by remember { mutableIntStateOf(-1) }
|
||||
var dragOffset by remember { mutableStateOf(0f) }
|
||||
|
||||
@@ -632,10 +689,11 @@ private fun DragOrderList(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(itemHeight * items.size)
|
||||
.height(itemHeight * localItems.size)
|
||||
) {
|
||||
itemsIndexed(items, key = { _, s -> s.second }) { index, screen ->
|
||||
itemsIndexed(localItems, key = { _, name -> name }) { index, name ->
|
||||
val isDragging = draggingIndex == index
|
||||
val isLocked = name in locked
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
@@ -646,46 +704,61 @@ private fun DragOrderList(
|
||||
.animateItem()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = screen.first),
|
||||
text = stringResource(id = labels.first { it.second == name }.first),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.padding(20.dp)
|
||||
.pointerInput(screen.second) {
|
||||
detectDragGestures(
|
||||
onDragStart = { draggingIndex = items.indexOf(screen) },
|
||||
onDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
dragOffset += dragAmount.y
|
||||
val currentIndex = draggingIndex
|
||||
val target = (currentIndex + (dragOffset / itemHeightPx).roundToInt())
|
||||
.coerceIn(0, items.size - 1)
|
||||
if (target != currentIndex) {
|
||||
items.add(target, items.removeAt(currentIndex))
|
||||
dragOffset += (currentIndex - target) * itemHeightPx
|
||||
draggingIndex = target
|
||||
}
|
||||
},
|
||||
onDragEnd = {
|
||||
onReorder(items.map { it.second })
|
||||
draggingIndex = -1
|
||||
dragOffset = 0f
|
||||
},
|
||||
onDragCancel = {
|
||||
draggingIndex = -1
|
||||
dragOffset = 0f
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
// 小圆点手柄(触控区 padding 20dp 居中 -> 8dp 圆点)
|
||||
if (moveLabel != null && onMove != null && moveEnabled(name)) {
|
||||
TextButton(onClick = { onMove(name) }, contentPadding = PaddingValues(horizontal = 4.dp)) {
|
||||
Text(moveLabel, fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
if (isLocked) {
|
||||
// 锁定行(设置页): 无抓手, 固定最后
|
||||
Text(
|
||||
text = stringResource(id = R.string.prefs_ui_order_locked),
|
||||
fontSize = 11.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 12.dp)
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.onSurfaceVariant, CircleShape)
|
||||
)
|
||||
.size(48.dp)
|
||||
.padding(20.dp)
|
||||
.pointerInput(name) {
|
||||
detectDragGestures(
|
||||
onDragStart = { draggingIndex = localItems.indexOf(name) },
|
||||
onDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
dragOffset += dragAmount.y
|
||||
val currentIndex = draggingIndex
|
||||
val target = (currentIndex + (dragOffset / itemHeightPx).roundToInt())
|
||||
.coerceIn(0, localItems.size - 1)
|
||||
if (target != currentIndex) {
|
||||
localItems.add(target, localItems.removeAt(currentIndex))
|
||||
dragOffset += (currentIndex - target) * itemHeightPx
|
||||
draggingIndex = target
|
||||
}
|
||||
},
|
||||
onDragEnd = {
|
||||
onReorder(localItems.toList())
|
||||
draggingIndex = -1
|
||||
dragOffset = 0f
|
||||
},
|
||||
onDragCancel = {
|
||||
draggingIndex = -1
|
||||
dragOffset = 0f
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
// 小圆点手柄(触控区 padding 20dp 居中 -> 8dp 圆点)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.onSurfaceVariant, CircleShape)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ sealed interface SettingsAction {
|
||||
data class ReorderScreens(val order: List<String>) : SettingsAction
|
||||
// UI 设置: 重置为默认顺序
|
||||
data object ResetScreenOrder : SettingsAction
|
||||
// UI 设置: 更新主菜单 + 更多菜单顺序(4.5.1 折叠菜单)
|
||||
data class UpdateMenuOrder(val mainOrder: List<String>, val subOrder: List<String>) : SettingsAction
|
||||
// UI 设置: 重置主/子菜单为默认
|
||||
data object ResetMenuOrder : SettingsAction
|
||||
|
||||
// Remote control
|
||||
data class UpdateRC(val settings: RCSettings) : SettingsAction
|
||||
|
||||
+6
@@ -128,6 +128,12 @@ class SettingsViewModel(
|
||||
}
|
||||
is SettingsAction.ReorderScreens -> settingsRepo.updateOtherSettings { it.copy(screenOrder = action.order) }
|
||||
SettingsAction.ResetScreenOrder -> settingsRepo.updateOtherSettings { it.copy(screenOrder = emptyList()) }
|
||||
is SettingsAction.UpdateMenuOrder -> settingsRepo.updateOtherSettings {
|
||||
it.copy(screenOrder = action.mainOrder, subMenuOrder = action.subOrder)
|
||||
}
|
||||
SettingsAction.ResetMenuOrder -> settingsRepo.updateOtherSettings {
|
||||
it.copy(screenOrder = emptyList(), subMenuOrder = emptyList())
|
||||
}
|
||||
// Remote control & data sources
|
||||
is SettingsAction.UpdateRC -> settingsRepo.updateRCSettings(action.settings)
|
||||
is SettingsAction.UpdateRadioControl -> settingsRepo.updateRadioControlSettings(action.settings)
|
||||
|
||||
Reference in new issue
Block a user