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