Android: Design settings page, Contacts in Settings, server icons in the switcher
Appearance options move to their own Design page. Contacts is the first Settings entry instead of a top-bar button. The top bar and the server switcher show each server's own icon, else the pack's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,12 +73,12 @@ fun App(vm: SessionController) {
|
||||
CompositionLocalProvider(LocalIconTheme provides vm.icons, LocalWebPictures provides vm.pictures) {
|
||||
val server = current
|
||||
when {
|
||||
settingsOpen -> SettingsScreen(vm, onBack = { settingsOpen = false })
|
||||
contactsOpen -> ContactsScreen(vm, editingContact, onEdit = { uniqueId ->
|
||||
if (uniqueId == null && contactDirect) contactsOpen = false
|
||||
contactDirect = false
|
||||
editingContact = uniqueId
|
||||
}, onBack = { contactsOpen = false })
|
||||
settingsOpen -> SettingsScreen(vm, onContacts = { openContacts(null) }, onBack = { settingsOpen = false })
|
||||
server != null && !connectOpen -> {
|
||||
serverStates.SaveableStateProvider(server.id) {
|
||||
CompositionLocalProvider(LocalServerImages provides server.images) {
|
||||
@@ -89,7 +89,7 @@ fun App(vm: SessionController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> ConnectScreen(vm, snackbar, { settingsOpen = true }, { openContacts(null) }, connectAsking,
|
||||
else -> ConnectScreen(vm, snackbar, { settingsOpen = true }, connectAsking,
|
||||
onBack = if (current != null) ({ connectOpen = false }) else null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ fun ConnectScreen(
|
||||
vm: SessionController,
|
||||
snackbar: SnackbarHostState,
|
||||
onSettings: () -> Unit,
|
||||
onContacts: () -> Unit,
|
||||
onConnect: (() -> Unit) -> Unit,
|
||||
/** Back to the servers already connected; null when there are none. */
|
||||
onBack: (() -> Unit)?,
|
||||
@@ -89,7 +88,6 @@ fun ConnectScreen(
|
||||
if (onBack != null) IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") }
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onContacts) { PackIcon("CONTACT", "Contacts") }
|
||||
IconButton(onClick = onSettings) { PackIcon("SETTINGS", "Settings") }
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ts3client.android.ui
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
@@ -108,8 +109,8 @@ fun ServerScreen(
|
||||
server: ServerController,
|
||||
snackbar: SnackbarHostState,
|
||||
onSettings: () -> Unit,
|
||||
/** Opens the contacts, on the given client's page when one is named. */
|
||||
onContacts: (String?) -> Unit,
|
||||
/** Opens the given client's contact page. */
|
||||
onEditContact: (String) -> Unit,
|
||||
onEnableMicrophone: () -> Unit,
|
||||
onConnectOther: () -> Unit,
|
||||
) {
|
||||
@@ -233,7 +234,6 @@ fun ServerScreen(
|
||||
actions = {
|
||||
state.hostButton?.let { HostButtonAction(it) { url -> runCatching { uriHandler.openUri(url) } } }
|
||||
IconButton(onClick = find::show) { PackIcon("FIND", "Find") }
|
||||
IconButton(onClick = { onContacts(null) }) { PackIcon("CONTACT", "Contacts") }
|
||||
IconButton(onClick = onSettings) { PackIcon("SETTINGS", "Settings") }
|
||||
},
|
||||
)
|
||||
@@ -333,7 +333,7 @@ fun ServerScreen(
|
||||
onVolume = { server.setClientVolume(id, it) },
|
||||
onAway = { server.setAway(it); clientSheet = null },
|
||||
onAddContact = { server.addContact(id, it) },
|
||||
onEditContact = { clientSheet = null; onContacts(client.uniqueId) },
|
||||
onEditContact = { clientSheet = null; onEditContact(client.uniqueId) },
|
||||
onMove = if (server.canMoveClients) ({ clientSheet = null; moving = id }) else null,
|
||||
onMoveToOwnChannel = if (server.canMoveClients && tree.channel(state.channelId)?.clients?.none { it.id == id } == true) {
|
||||
{ server.moveClient(id, state.channelId); clientSheet = null }
|
||||
@@ -523,6 +523,16 @@ private fun PushToTalk(talking: Boolean, onPressed: (Boolean) -> Unit, modifier:
|
||||
}
|
||||
}
|
||||
|
||||
/** The server's own icon, else the pack's server icon. */
|
||||
@Composable
|
||||
private fun ServerLogo(server: ServerController, modifier: Modifier = Modifier) {
|
||||
val tree by server.tree.collectAsStateWithLifecycle()
|
||||
val version by server.images.version.collectAsStateWithLifecycle()
|
||||
val icon = remember(tree.serverIconId, version) { server.images.icon(tree.serverIconId) }
|
||||
if (icon != null) Image(icon, null, modifier.size(24.dp))
|
||||
else PackIcon("SERVER_GREEN", null, modifier)
|
||||
}
|
||||
|
||||
/** The server on screen, opening onto the others and connecting to another. */
|
||||
@Composable
|
||||
private fun ServerSwitcher(vm: SessionController, server: ServerController, state: SessionState, onConnectOther: () -> Unit) {
|
||||
@@ -531,6 +541,7 @@ private fun ServerSwitcher(vm: SessionController, server: ServerController, stat
|
||||
Box(Modifier.fillMaxWidth()) {
|
||||
Row(Modifier.fillMaxWidth().heightIn(min = 48.dp).clickable { open = true },
|
||||
verticalAlignment = Alignment.CenterVertically) {
|
||||
ServerLogo(server, Modifier.padding(end = 12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(state.title, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
if (!state.connected) {
|
||||
@@ -548,7 +559,7 @@ private fun ServerSwitcher(vm: SessionController, server: ServerController, stat
|
||||
text = { Text(otherState.title, maxLines = 1, overflow = TextOverflow.Ellipsis) },
|
||||
onClick = { open = false; vm.select(other) },
|
||||
leadingIcon = {
|
||||
PackIcon("SERVER_GREEN", null, Modifier.alpha(if (otherState.connected) 1f else 0.5f))
|
||||
ServerLogo(other, Modifier.alpha(if (otherState.connected) 1f else 0.5f))
|
||||
},
|
||||
trailingIcon = if (otherState.micActive) ({ PackIcon("ACTIVATE_MICROPHONE", "Microphone") }) else null,
|
||||
colors = if (other == server) MenuDefaults.itemColors(textColor = MaterialTheme.colorScheme.primary)
|
||||
|
||||
@@ -21,17 +21,18 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private enum class SettingsPage { SOUND, IDENTITIES, MY_TEAMSPEAK }
|
||||
private enum class SettingsPage { SOUND, DESIGN, IDENTITIES, MY_TEAMSPEAK }
|
||||
|
||||
/**
|
||||
* The settings, by category. Every change is saved and put into effect at once,
|
||||
* as there is no dialog to confirm.
|
||||
*/
|
||||
@Composable
|
||||
fun SettingsScreen(vm: SessionController, onBack: () -> Unit) {
|
||||
fun SettingsScreen(vm: SessionController, onContacts: () -> Unit, onBack: () -> Unit) {
|
||||
var page by rememberSaveable { mutableStateOf<SettingsPage?>(null) }
|
||||
when (page) {
|
||||
SettingsPage.SOUND -> return SoundSettingsScreen(vm, onBack = { page = null })
|
||||
SettingsPage.DESIGN -> return DesignScreen(vm, onBack = { page = null })
|
||||
SettingsPage.IDENTITIES -> return IdentitiesScreen(vm.identities, onBack = { page = null })
|
||||
SettingsPage.MY_TEAMSPEAK -> return MyTeamSpeakScreen(vm, onBack = { page = null })
|
||||
null -> {}
|
||||
@@ -39,16 +40,27 @@ fun SettingsScreen(vm: SessionController, onBack: () -> Unit) {
|
||||
|
||||
SubScreen("Settings", onBack) { padding ->
|
||||
Column(Modifier.padding(padding).verticalScroll(rememberScrollState())) {
|
||||
LinkRow("Contacts", "Friends and blocked users", "CONTACT", onContacts)
|
||||
LinkRow("Sound Settings", "Devices, transmission, voice activation, processing, volume", "PLAYBACK") {
|
||||
page = SettingsPage.SOUND
|
||||
}
|
||||
LinkRow("Design", "Icon pack, country flags, description pictures", "GUISETUP") {
|
||||
page = SettingsPage.DESIGN
|
||||
}
|
||||
LinkRow("Identities", "Create, import, export, security level",
|
||||
"IDENTITY_MANAGER") { page = SettingsPage.IDENTITIES }
|
||||
LinkRow("myTeamSpeak",
|
||||
if (vm.myTeamSpeak.isSignedIn) "Signed in as ${vm.myTeamSpeak.username()}"
|
||||
else "Sign in to bring over your synchronised bookmarks and identities",
|
||||
"MYTS_ACCOUNT") { page = SettingsPage.MY_TEAMSPEAK }
|
||||
Section("Appearance")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DesignScreen(vm: SessionController, onBack: () -> Unit) {
|
||||
SubScreen("Design", onBack) { padding ->
|
||||
Column(Modifier.padding(padding).verticalScroll(rememberScrollState())) {
|
||||
IconPackPicker(vm.icons)
|
||||
var flags by remember { mutableStateOf(vm.settings.countryFlags) }
|
||||
Toggle("Show country flags in the channel tree", flags) {
|
||||
|
||||
Reference in New Issue
Block a user