Talk group switcher on main screen, auto-connect on launch

- Tapping the TG badge opens a group list; picking a group saves it and
  reconnects to the server with the new group
- Groups configured in settings (comma-separated), defaults 9,11,12,13
- Auto-connect at app launch to the default group from settings
This commit is contained in:
ua1zbe
2026-08-14 09:34:26 +03:00
parent abb6f254bc
commit bff0d29ee8
5 changed files with 118 additions and 31 deletions

View File

@@ -105,38 +105,18 @@ class MainActivity : Activity(), SvxService.Listener {
startActivity(Intent(this, SettingsActivity::class.java))
}
lblTg.setOnClickListener { showGroupsDialog() }
connectZone.setOnClickListener {
val s = svc
if (connected && s != null) {
s.disconnect()
if (connected) {
svc?.disconnect()
} else {
settings = Prefs.load(this)
if (settings.host.isBlank() || settings.callsign.isBlank()) {
toast(getString(R.string.toast_need_settings))
return@setOnClickListener
}
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), REQ_MIC)
return@setOnClickListener
}
if (Build.VERSION.SDK_INT >= 33 &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQ_NOTIF)
}
startForegroundService(Intent(this, SvxService::class.java))
bindService(Intent(this, SvxService::class.java), svcConn, Context.BIND_AUTO_CREATE)
mainHandler.postDelayed({
svc?.connect(
ReflectorClient.Settings(
host = settings.host,
port = settings.port,
callsign = settings.callsign,
authKey = settings.authKey,
talkGroup = settings.talkGroup
)
)
}, 300)
requestPermissionsIfNeeded()
connect()
}
}
@@ -167,10 +147,23 @@ class MainActivity : Activity(), SvxService.Listener {
refreshUi()
}
private var autoConnectAttempted = false
override fun onResume() {
super.onResume()
settings = Prefs.load(this)
refreshUi()
if (!autoConnectAttempted &&
settings.host.isNotBlank() &&
settings.callsign.isNotBlank()
) {
autoConnectAttempted = true
requestPermissionsIfNeeded()
connect()
}
if (pendingUpdate != null && updateDialog == null && canInstall()) {
startUpdate()
}
}
override fun onDestroy() {
@@ -311,6 +304,62 @@ class MainActivity : Activity(), SvxService.Listener {
pttButton.enabled2 = connected
}
private fun requestPermissionsIfNeeded() {
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), REQ_MIC)
}
if (Build.VERSION.SDK_INT >= 33 &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQ_NOTIF)
}
}
/** Запустить сервис и подключиться с текущими настройками. */
private fun connect() {
startForegroundService(Intent(this, SvxService::class.java))
bindService(Intent(this, SvxService::class.java), svcConn, Context.BIND_AUTO_CREATE)
mainHandler.postDelayed({
svc?.connect(
ReflectorClient.Settings(
host = settings.host,
port = settings.port,
callsign = settings.callsign,
authKey = settings.authKey,
talkGroup = settings.talkGroup
)
)
}, 300)
}
/** Переключиться на другую группу: пересохранить и переподключиться. */
private fun switchGroup(tg: Long) {
settings = settings.copy(talkGroup = tg)
Prefs.save(this, settings)
refreshUi()
svc?.disconnect()
mainHandler.postDelayed({ connect() }, 500)
}
private fun showGroupsDialog() {
val groups = (settings.groups + settings.talkGroup).distinct()
if (groups.isEmpty()) {
toast(getString(R.string.toast_groups_empty))
return
}
val current = settings.talkGroup
AlertDialog.Builder(this)
.setTitle(R.string.select_tg)
.setItems(groups.map { tg ->
val label = getString(R.string.tg_badge, tg)
if (tg == current) "$label" else label
}.toTypedArray()) { _, which ->
val tg = groups[which]
if (tg != current) switchGroup(tg)
}
.show()
}
private fun checkForUpdates() {
Thread {
val version = try {

View File

@@ -15,6 +15,7 @@ class SettingsActivity : Activity() {
private lateinit var inputCallsign: EditText
private lateinit var inputAuthKey: EditText
private lateinit var inputTg: EditText
private lateinit var inputGroups: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -25,6 +26,7 @@ class SettingsActivity : Activity() {
inputCallsign = findViewById(R.id.input_callsign)
inputAuthKey = findViewById(R.id.input_auth_key)
inputTg = findViewById(R.id.input_tg)
inputGroups = findViewById(R.id.input_groups)
val s = Prefs.load(this)
inputHost.setText(s.host)
@@ -32,6 +34,7 @@ class SettingsActivity : Activity() {
inputCallsign.setText(s.callsign)
inputAuthKey.setText(s.authKey)
inputTg.setText(s.talkGroup.toString())
inputGroups.setText(s.groups.joinToString(", "))
findViewById<Button>(R.id.btn_save).setOnClickListener {
val host = inputHost.text.toString().trim()
@@ -39,6 +42,10 @@ class SettingsActivity : Activity() {
val callsign = inputCallsign.text.toString().trim()
val authKey = inputAuthKey.text.toString()
val tg = inputTg.text.toString().trim().toLongOrNull() ?: 11
val groups = inputGroups.text.toString()
.split(',')
.mapNotNull { it.trim().toLongOrNull() }
.distinct()
if (host.isBlank() || callsign.isBlank()) {
toast(getString(R.string.toast_settings_empty))
@@ -52,7 +59,8 @@ class SettingsActivity : Activity() {
port = port.coerceIn(1, 65535),
callsign = callsign,
authKey = authKey,
talkGroup = tg.coerceAtLeast(0)
talkGroup = tg.coerceAtLeast(0),
groups = groups
)
)
toast(getString(R.string.saved))

View File

@@ -8,7 +8,8 @@ data class ConnectionSettings(
val port: Int,
val callsign: String,
val authKey: String,
val talkGroup: Long
val talkGroup: Long,
val groups: List<Long> = emptyList()
)
object Prefs {
@@ -21,7 +22,12 @@ object Prefs {
port = sp.getInt("port", 5300),
callsign = sp.getString("callsign", "") ?: "",
authKey = sp.getString("auth_key", "") ?: "",
talkGroup = sp.getLong("tg", 11)
talkGroup = sp.getLong("tg", 11),
groups = (sp.getString("groups", "") ?: "")
.split(',')
.mapNotNull { it.trim().toLongOrNull() }
.distinct()
.ifEmpty { listOf(9L, 11L, 12L, 13L) }
)
}
@@ -33,6 +39,7 @@ object Prefs {
.putString("callsign", s.callsign.trim())
.putString("auth_key", s.authKey)
.putLong("tg", s.talkGroup)
.putString("groups", s.groups.distinct().joinToString(","))
.apply()
}
}

View File

@@ -110,7 +110,7 @@
android:id="@+id/input_tg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_marginBottom="16dp"
android:inputType="number"
android:textColor="@color/text_primary"
android:textColorHint="@color/text_secondary"
@@ -119,6 +119,26 @@
android:padding="12dp"
android:hint="11" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pref_groups"
android:textColor="@color/text_primary"
android:textSize="15sp" />
<EditText
android:id="@+id/input_groups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:inputType="text"
android:textColor="@color/text_primary"
android:textColorHint="@color/text_secondary"
android:textSize="16sp"
android:background="@drawable/bg_input"
android:padding="12dp"
android:hint="9, 11, 12, 13" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"

View File

@@ -9,6 +9,9 @@
<string name="pref_callsign">Позывной</string>
<string name="pref_auth_key">Пароль (AUTH_KEY)</string>
<string name="pref_tg">Группа вещания (TG)</string>
<string name="pref_groups">Группы для переключения (через запятую)</string>
<string name="select_tg">Выбор группы</string>
<string name="toast_groups_empty">Добавьте группы в настройках</string>
<string name="btn_save">Сохранить</string>
<string name="app_version">SVXhomyak — v1.0.2</string>
<string name="saved">Сохранено</string>
@@ -26,7 +29,7 @@
<string name="meter_mic">MIC</string>
<string name="meter_idle">---</string>
<string name="meter_db">-%1$d dB</string>
<string name="tg_badge">TG%1$d</string>
<string name="tg_badge">TG%1$d</string>
<string name="talker_last_none">Последний: —</string>
<string name="status_connecting">Подключение…</string>
<string name="status_connecting_ssl">Проверка ключа…</string>