mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-07-09 21:26:54 -05:00
Netplay setup UI
Only for connecting, no hosting yet.
This commit is contained in:
parent
c716c32ce8
commit
f10022f2dc
|
|
@ -118,6 +118,12 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xannotation-default-target=param-property")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
baselineProfile(project(":benchmark"))
|
||||
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
|
||||
sealed class ConnectionRole(
|
||||
@StringRes val labelId: Int,
|
||||
) {
|
||||
object Connect : ConnectionRole(R.string.netplay_connection_role_connect)
|
||||
object Host : ConnectionRole(R.string.netplay_connection_role_host)
|
||||
|
||||
companion object {
|
||||
val all: List<ConnectionRole>
|
||||
get() = listOf(Connect, Host)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
|
||||
sealed class ConnectionType(
|
||||
@StringRes val labelId: Int,
|
||||
) {
|
||||
object DirectConnection : ConnectionType(
|
||||
labelId = R.string.netplay_connection_type_direct_connection,
|
||||
)
|
||||
|
||||
object TraversalServer : ConnectionType(
|
||||
labelId = R.string.netplay_connection_type_traversal_server,
|
||||
)
|
||||
|
||||
companion object {
|
||||
val all: List<ConnectionType>
|
||||
get() = listOf(DirectConnection, TraversalServer)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class NetplaySetupViewModel : ViewModel() {
|
||||
private val _connectionRole = MutableStateFlow<ConnectionRole>(ConnectionRole.Connect)
|
||||
val connectionRole = _connectionRole.asStateFlow()
|
||||
|
||||
private val _nickname = MutableStateFlow("")
|
||||
val nickname = _nickname.asStateFlow()
|
||||
|
||||
private val _connectionType = MutableStateFlow<ConnectionType>(ConnectionType.DirectConnection)
|
||||
val connectionType = _connectionType.asStateFlow()
|
||||
|
||||
private val _ipAddress = MutableStateFlow("")
|
||||
val ipAddress = _ipAddress.asStateFlow()
|
||||
|
||||
private val _hostCode = MutableStateFlow("")
|
||||
val hostCode = _hostCode.asStateFlow()
|
||||
|
||||
private val _connectPort = MutableStateFlow(0.toString())
|
||||
val connectPort = _connectPort.asStateFlow()
|
||||
|
||||
fun setConnectionRole(connectionRole: ConnectionRole) {
|
||||
_connectionRole.value = connectionRole
|
||||
}
|
||||
|
||||
fun setNickname(nickname: String) {
|
||||
_nickname.value = nickname
|
||||
}
|
||||
|
||||
fun setConnectionType(connectionType: ConnectionType) {
|
||||
_connectionType.value = connectionType
|
||||
}
|
||||
|
||||
fun setIpAddress(ipAddress: String) {
|
||||
if (ipAddress.all { it.isDigit() || it == '.' }) {
|
||||
_ipAddress.value = ipAddress
|
||||
}
|
||||
}
|
||||
|
||||
fun setHostCode(hostCode: String) {
|
||||
_hostCode.value = hostCode
|
||||
}
|
||||
|
||||
fun setConnectPort(port: String) {
|
||||
if (port.all { it.isDigit() }) {
|
||||
_connectPort.value = port
|
||||
}
|
||||
}
|
||||
|
||||
fun connect() {
|
||||
}
|
||||
}
|
||||
|
|
@ -12,39 +12,77 @@ import androidx.activity.enableEdgeToEdge
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SecondaryTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionRole
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionType
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.NetplaySetupViewModel
|
||||
import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
||||
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
|
||||
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||
|
||||
class NetplaySetupActivity : AppCompatActivity(), ThemeProvider {
|
||||
override var themeId: Int = 0
|
||||
private lateinit var viewModel: NetplaySetupViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeHelper.setTheme(this)
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProvider(this)[NetplaySetupViewModel::class.java]
|
||||
|
||||
setContent {
|
||||
DolphinTheme {
|
||||
NetplaySetupScreen(
|
||||
onBackClicked = { finish() },
|
||||
nickname = viewModel.nickname.collectAsState().value,
|
||||
onNicknameChanged = viewModel::setNickname,
|
||||
connectionType = viewModel.connectionType.collectAsState().value,
|
||||
onConnectionTypeChanged = viewModel::setConnectionType,
|
||||
connectionRole = viewModel.connectionRole.collectAsState().value,
|
||||
onConnectionRoleChanged = viewModel::setConnectionRole,
|
||||
ipAddress = viewModel.ipAddress.collectAsState().value,
|
||||
onIpAddressChanged = viewModel::setIpAddress,
|
||||
connectPort = viewModel.connectPort.collectAsState().value,
|
||||
onConnectPortChanged = viewModel::setConnectPort,
|
||||
hostCode = viewModel.hostCode.collectAsState().value,
|
||||
onHostCodeChanged = viewModel::setHostCode,
|
||||
onConnectClicked = viewModel::connect,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +109,19 @@ class NetplaySetupActivity : AppCompatActivity(), ThemeProvider {
|
|||
@Composable
|
||||
private fun NetplaySetupScreen(
|
||||
onBackClicked: () -> Unit,
|
||||
connectionRole: ConnectionRole,
|
||||
onConnectionRoleChanged: (ConnectionRole) -> Unit,
|
||||
nickname: String,
|
||||
onNicknameChanged: (String) -> Unit,
|
||||
connectionType: ConnectionType,
|
||||
onConnectionTypeChanged: (ConnectionType) -> Unit,
|
||||
ipAddress: String,
|
||||
onIpAddressChanged: (String) -> Unit,
|
||||
connectPort: String,
|
||||
onConnectPortChanged: (String) -> Unit,
|
||||
hostCode: String,
|
||||
onHostCodeChanged: (String) -> Unit,
|
||||
onConnectClicked: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
|
@ -85,23 +136,206 @@ private fun NetplaySetupScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = onConnectClicked,
|
||||
) {
|
||||
Text(stringResource(connectionRole.labelId))
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.consumeWindowInsets(innerPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = DolphinTheme.scaffoldPadding)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
SecondaryTabRow(selectedTabIndex = ConnectionRole.all.indexOf(connectionRole)) {
|
||||
ConnectionRole.all.forEach { role ->
|
||||
Tab(
|
||||
selected = connectionRole == role,
|
||||
onClick = { onConnectionRoleChanged(role) },
|
||||
text = { Text(stringResource(role.labelId)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MenuSpacer()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = DolphinTheme.scaffoldPadding)
|
||||
) {
|
||||
NetplaySetupContent(
|
||||
nickname = nickname,
|
||||
onNicknameChanged = onNicknameChanged,
|
||||
connectionType = connectionType,
|
||||
onConnectionTypeChanged = onConnectionTypeChanged,
|
||||
ipAddress = ipAddress,
|
||||
onIpAddressChanged = onIpAddressChanged,
|
||||
hostCode = hostCode,
|
||||
onHostCodeChanged = onHostCodeChanged,
|
||||
connectPort = connectPort,
|
||||
onConnectPortChanged = onConnectPortChanged,
|
||||
connectionRole = connectionRole,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetplaySetupContent(
|
||||
nickname: String,
|
||||
onNicknameChanged: (String) -> Unit,
|
||||
connectionType: ConnectionType,
|
||||
onConnectionTypeChanged: (ConnectionType) -> Unit,
|
||||
ipAddress: String,
|
||||
onIpAddressChanged: (String) -> Unit,
|
||||
hostCode: String,
|
||||
onHostCodeChanged: (String) -> Unit,
|
||||
connectPort: String,
|
||||
onConnectPortChanged: (String) -> Unit,
|
||||
connectionRole: ConnectionRole,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = nickname,
|
||||
onValueChange = onNicknameChanged,
|
||||
label = { Text(stringResource(R.string.netplay_nickname_label)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
MenuSpacer()
|
||||
|
||||
ConnectionTypePicker(
|
||||
connectionType = connectionType,
|
||||
onConnectionTypeChanged = onConnectionTypeChanged,
|
||||
)
|
||||
|
||||
MenuSpacer()
|
||||
|
||||
when (connectionRole) {
|
||||
ConnectionRole.Connect -> ConnectMenu(
|
||||
connectionType = connectionType,
|
||||
ipAddress = ipAddress,
|
||||
onIpAddressChanged = onIpAddressChanged,
|
||||
hostCode = hostCode,
|
||||
onHostCodeChanged = onHostCodeChanged,
|
||||
port = connectPort,
|
||||
onPortChanged = onConnectPortChanged,
|
||||
)
|
||||
|
||||
ConnectionRole.Host -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConnectionTypePicker(
|
||||
connectionType: ConnectionType,
|
||||
onConnectionTypeChanged: (ConnectionType) -> Unit,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
) {
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
ConnectionType.all.forEach { connectionType ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(connectionType.labelId)) },
|
||||
onClick = {
|
||||
onConnectionTypeChanged(connectionType)
|
||||
expanded = false
|
||||
},
|
||||
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = stringResource(connectionType.labelId),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.netplay_connection_type)) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
|
||||
modifier = Modifier
|
||||
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConnectMenu(
|
||||
connectionType: ConnectionType,
|
||||
ipAddress: String,
|
||||
onIpAddressChanged: (String) -> Unit,
|
||||
hostCode: String,
|
||||
onHostCodeChanged: (String) -> Unit,
|
||||
port: String,
|
||||
onPortChanged: (String) -> Unit,
|
||||
) {
|
||||
when (connectionType) {
|
||||
ConnectionType.DirectConnection -> {
|
||||
OutlinedTextField(
|
||||
value = ipAddress,
|
||||
onValueChange = onIpAddressChanged,
|
||||
label = { Text(stringResource(R.string.netplay_ip_address_label)) },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
MenuSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = port,
|
||||
onValueChange = onPortChanged,
|
||||
label = { Text(stringResource(R.string.netplay_port_label)) },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
ConnectionType.TraversalServer -> OutlinedTextField(
|
||||
value = hostCode,
|
||||
onValueChange = onHostCodeChanged,
|
||||
label = { Text(stringResource(R.string.netplay_host_code_label)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NetplaySetupScreenPreview() {
|
||||
MaterialTheme {
|
||||
NetplayScreen(onBackClicked = {})
|
||||
NetplaySetupScreen(
|
||||
onBackClicked = {},
|
||||
nickname = "Preview nickname",
|
||||
onNicknameChanged = {},
|
||||
connectionType = ConnectionType.DirectConnection,
|
||||
onConnectionTypeChanged = {},
|
||||
connectionRole = ConnectionRole.Connect,
|
||||
onConnectionRoleChanged = {},
|
||||
ipAddress = "127.0.0.1",
|
||||
onIpAddressChanged = {},
|
||||
connectPort = "2626",
|
||||
onConnectPortChanged = {},
|
||||
hostCode = "",
|
||||
onHostCodeChanged = {},
|
||||
onConnectClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ package org.dolphinemu.dolphinemu.ui.theme
|
|||
import android.content.Context
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -101,3 +104,6 @@ private fun Context.toDolphinColorScheme(isDark: Boolean): ColorScheme {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MenuSpacer() = Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
|
|||
|
|
@ -985,4 +985,12 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
<!-- Netplay -->
|
||||
<string name="netplay_setup_title">Netplay Setup</string>
|
||||
<string name="netplay_nickname_label">Nickname</string>
|
||||
<string name="netplay_connection_type">Connection type</string>
|
||||
<string name="netplay_connection_type_direct_connection">Direct connection</string>
|
||||
<string name="netplay_connection_type_traversal_server">Traversal server</string>
|
||||
<string name="netplay_connection_role_connect">Connect</string>
|
||||
<string name="netplay_connection_role_host">Host</string>
|
||||
<string name="netplay_ip_address_label">IP address</string>
|
||||
<string name="netplay_host_code_label">Host code</string>
|
||||
<string name="netplay_port_label">Port</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user