mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-05-09 04:13:28 -05:00
Show joining info for local and external IP addresses
Doesn't support traversal yet
This commit is contained in:
parent
3e34012148
commit
4a52be0960
|
|
@ -154,6 +154,10 @@ class NetplaySession(
|
|||
|
||||
fun startGame() = nativeStartGame()
|
||||
|
||||
fun getPort(): Int = nativeGetPort()
|
||||
|
||||
fun getExternalIpAddress(): String? = nativeGetExternalIpAddress()
|
||||
|
||||
fun consumeBootSessionData(): Long {
|
||||
return bootSessionDataPointer.also {
|
||||
bootSessionDataPointer = 0
|
||||
|
|
@ -235,6 +239,10 @@ class NetplaySession(
|
|||
|
||||
private external fun nativeStartGame()
|
||||
|
||||
private external fun nativeGetPort(): Int
|
||||
|
||||
private external fun nativeGetExternalIpAddress(): String?
|
||||
|
||||
// NetPlayUI callbacks
|
||||
|
||||
@Keep
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
|
||||
enum class JoinInfoType(@StringRes val labelId: Int) {
|
||||
EXTERNAL(R.string.netplay_address_type_external),
|
||||
LOCAL(R.string.netplay_address_type_local),
|
||||
}
|
||||
|
||||
sealed class JoinAddress {
|
||||
data object Loading : JoinAddress()
|
||||
data class Loaded(val address: String) : JoinAddress()
|
||||
data class Unknown(val retry: () -> Unit) : JoinAddress()
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
|
@ -20,15 +21,25 @@ import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
|||
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
|
||||
import org.dolphinemu.dolphinemu.model.GameFile
|
||||
import org.dolphinemu.dolphinemu.services.GameFileCacheManager
|
||||
import org.dolphinemu.dolphinemu.utils.NetworkHelper
|
||||
|
||||
class NetplayViewModel(
|
||||
private val netplaySession: NetplaySession,
|
||||
private val networkHelper: NetworkHelper,
|
||||
) : ViewModel() {
|
||||
|
||||
val launchGame = netplaySession.launchGame
|
||||
|
||||
val isHosting = netplaySession.isHosting
|
||||
|
||||
private val _joinAddresses = MutableStateFlow(
|
||||
mapOf(
|
||||
JoinInfoType.EXTERNAL to JoinAddress.Loading,
|
||||
JoinInfoType.LOCAL to getLocalIp(),
|
||||
)
|
||||
)
|
||||
val joinAddresses = _joinAddresses.asStateFlow()
|
||||
|
||||
val connectionLost = netplaySession.connectionLost
|
||||
|
||||
val players = netplaySession.players
|
||||
|
|
@ -61,6 +72,7 @@ class NetplayViewModel(
|
|||
init {
|
||||
if (netplaySession.isHosting) {
|
||||
setInitialGame()
|
||||
fetchExternalIp()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +100,24 @@ class NetplayViewModel(
|
|||
netplaySession.changeGame(gameFile)
|
||||
}
|
||||
|
||||
private fun getLocalIp(): JoinAddress {
|
||||
val localIp = networkHelper.getLocalIpString()
|
||||
?: return JoinAddress.Unknown { _joinAddresses.value += JoinInfoType.LOCAL to getLocalIp() }
|
||||
val port = netplaySession.getPort()
|
||||
return JoinAddress.Loaded("$localIp:$port")
|
||||
}
|
||||
|
||||
private fun fetchExternalIp() {
|
||||
_joinAddresses.value += JoinInfoType.EXTERNAL to JoinAddress.Loading
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val ip = netplaySession.getExternalIpAddress()
|
||||
val port = netplaySession.getPort()
|
||||
val address = if (ip != null) JoinAddress.Loaded("$ip:$port")
|
||||
else JoinAddress.Unknown { fetchExternalIp() }
|
||||
_joinAddresses.value += JoinInfoType.EXTERNAL to address
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInitialGame() {
|
||||
val game = gameFiles.value
|
||||
.find { it.getGameId() == StringSetting.NETPLAY_GAME.string }
|
||||
|
|
@ -108,10 +138,13 @@ class NetplayViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
class Factory(private val session: NetplaySession) : ViewModelProvider.Factory {
|
||||
class Factory(
|
||||
private val session: NetplaySession,
|
||||
private val networkHelper: NetworkHelper,
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return NetplayViewModel(session) as T
|
||||
return NetplayViewModel(session, networkHelper) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.features.netplay.NetplayManager
|
|||
import org.dolphinemu.dolphinemu.features.netplay.model.NetplayViewModel
|
||||
import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
||||
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
|
||||
import org.dolphinemu.dolphinemu.utils.NetworkHelper
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||
|
||||
class NetplayActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
|
@ -37,7 +38,7 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
|
|||
return
|
||||
}
|
||||
|
||||
val viewModel = ViewModelProvider(this, NetplayViewModel.Factory(session))[NetplayViewModel::class.java]
|
||||
val viewModel = ViewModelProvider(this, NetplayViewModel.Factory(session, NetworkHelper))[NetplayViewModel::class.java]
|
||||
|
||||
viewModel.launchGame
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||
|
|
@ -62,6 +63,7 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
|
|||
onMaxBufferChanged = viewModel::setMaxBuffer,
|
||||
saveTransferProgress = viewModel.saveTransferProgress.collectAsState().value,
|
||||
gameDigestProgress = viewModel.gameDigestProgress.collectAsState().value,
|
||||
joinAddresses = viewModel.joinAddresses.collectAsState().value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu.features.netplay.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -17,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -32,11 +34,17 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
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.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -45,6 +53,7 @@ import androidx.compose.material3.LinearProgressIndicator
|
|||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
|
|
@ -62,7 +71,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
|
|
@ -84,6 +92,8 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.GameDigestProgress
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.JoinAddress
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.JoinInfoType
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.NetplayMessage
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.Player
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress
|
||||
|
|
@ -92,6 +102,7 @@ import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
|
|||
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
|
||||
import org.dolphinemu.dolphinemu.ui.theme.OutlinedBox
|
||||
import org.dolphinemu.dolphinemu.ui.theme.PreviewTheme
|
||||
import org.dolphinemu.dolphinemu.ui.theme.ReadOnlyTextField
|
||||
import org.dolphinemu.dolphinemu.utils.CoilUtils
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -113,6 +124,7 @@ fun NetplayScreen(
|
|||
players: List<Player>,
|
||||
saveTransferProgress: SaveTransferProgress?,
|
||||
gameDigestProgress: GameDigestProgress?,
|
||||
joinAddresses: Map<JoinInfoType, JoinAddress>,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
|
@ -141,8 +153,10 @@ fun NetplayScreen(
|
|||
.consumeWindowInsets(innerPadding)
|
||||
.padding(innerPadding)
|
||||
|
||||
// State which must live above the landscape/portrait split.
|
||||
var showChat by rememberSaveable { mutableStateOf(false) }
|
||||
var showGamePicker by rememberSaveable { mutableStateOf(false) }
|
||||
var selectedJoinInfoType by rememberSaveable { mutableStateOf(JoinInfoType.EXTERNAL) }
|
||||
|
||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
LandscapeContent(
|
||||
|
|
@ -160,6 +174,9 @@ fun NetplayScreen(
|
|||
hostInputAuthorityEnabled = hostInputAuthorityEnabled,
|
||||
maxBuffer = maxBuffer,
|
||||
onMaxBufferChanged = onMaxBufferChanged,
|
||||
joinAddresses = joinAddresses,
|
||||
selectedJoinInfoType = selectedJoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged = { selectedJoinInfoType = it },
|
||||
modifier = modifier
|
||||
)
|
||||
} else {
|
||||
|
|
@ -178,6 +195,9 @@ fun NetplayScreen(
|
|||
hostInputAuthorityEnabled = hostInputAuthorityEnabled,
|
||||
maxBuffer = maxBuffer,
|
||||
onMaxBufferChanged = onMaxBufferChanged,
|
||||
joinAddresses = joinAddresses,
|
||||
selectedJoinInfoType = selectedJoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged = { selectedJoinInfoType = it },
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -243,6 +263,9 @@ private fun PortraitContent(
|
|||
hostInputAuthorityEnabled: Boolean,
|
||||
maxBuffer: Int,
|
||||
onMaxBufferChanged: (Int) -> Unit,
|
||||
joinAddresses: Map<JoinInfoType, JoinAddress>,
|
||||
selectedJoinInfoType: JoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -273,6 +296,9 @@ private fun PortraitContent(
|
|||
maxBuffer = maxBuffer,
|
||||
onMaxBufferChanged = onMaxBufferChanged,
|
||||
isHosting = isHosting,
|
||||
joinAddresses = joinAddresses,
|
||||
selectedJoinInfoType = selectedJoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged = onSelectedJoinInfoTypeChanged,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = DolphinTheme.scaffoldPadding),
|
||||
)
|
||||
|
|
@ -299,10 +325,14 @@ private fun LandscapeContent(
|
|||
hostInputAuthorityEnabled: Boolean,
|
||||
maxBuffer: Int,
|
||||
onMaxBufferChanged: (Int) -> Unit,
|
||||
joinAddresses: Map<JoinInfoType, JoinAddress>,
|
||||
selectedJoinInfoType: JoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(horizontal = DolphinTheme.scaffoldPadding)
|
||||
) {
|
||||
Chat(
|
||||
messages = messages,
|
||||
|
|
@ -312,9 +342,10 @@ private fun LandscapeContent(
|
|||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = DolphinTheme.scaffoldPadding)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
|
|
@ -331,8 +362,10 @@ private fun LandscapeContent(
|
|||
maxBuffer = maxBuffer,
|
||||
onMaxBufferChanged = onMaxBufferChanged,
|
||||
isHosting = isHosting,
|
||||
joinAddresses = joinAddresses,
|
||||
selectedJoinInfoType = selectedJoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged = onSelectedJoinInfoTypeChanged,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = DolphinTheme.scaffoldPadding)
|
||||
)
|
||||
|
||||
if (isHosting) {
|
||||
|
|
@ -354,6 +387,9 @@ private fun PLayersAndSettings(
|
|||
maxBuffer: Int,
|
||||
onMaxBufferChanged: (Int) -> Unit,
|
||||
isHosting: Boolean,
|
||||
joinAddresses: Map<JoinInfoType, JoinAddress>,
|
||||
selectedJoinInfoType: JoinInfoType,
|
||||
onSelectedJoinInfoTypeChanged: (JoinInfoType) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -368,6 +404,16 @@ private fun PLayersAndSettings(
|
|||
isHosting = isHosting,
|
||||
)
|
||||
|
||||
if (isHosting) {
|
||||
MenuSpacer()
|
||||
|
||||
JoinAddressSection(
|
||||
joinAddresses = joinAddresses,
|
||||
selectedType = selectedJoinInfoType,
|
||||
onSelectedTypeChanged = onSelectedJoinInfoTypeChanged,
|
||||
)
|
||||
}
|
||||
|
||||
MenuSpacer()
|
||||
|
||||
OutlinedBox(
|
||||
|
|
@ -479,6 +525,7 @@ private fun Chat(
|
|||
OutlinedBox(
|
||||
onClick = { onShowBottomSheetChanged(true) },
|
||||
label = { Text(stringResource(R.string.netplay_chat_label)) },
|
||||
fadeContentTop = true,
|
||||
modifier = modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
|
|
@ -532,26 +579,16 @@ private fun GamePicker(
|
|||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
ReadOnlyTextField(
|
||||
value = game,
|
||||
label = stringResource(R.string.netplay_game_label),
|
||||
onClick = if (isHosting) {
|
||||
{ onShowGamePickerChanged(true) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = game,
|
||||
onValueChange = {},
|
||||
label = { Text(stringResource(R.string.netplay_game_label)) },
|
||||
readOnly = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (isHosting) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.padding(top = 8.dp)
|
||||
.clip(MaterialTheme.shapes.extraSmall)
|
||||
.clickable { onShowGamePickerChanged(true) }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -609,6 +646,150 @@ private fun GameGridItem(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun JoinAddressSection(
|
||||
joinAddresses: Map<JoinInfoType, JoinAddress>,
|
||||
selectedType: JoinInfoType,
|
||||
onSelectedTypeChanged: (JoinInfoType) -> Unit,
|
||||
) {
|
||||
val address = joinAddresses[selectedType] ?: joinAddresses.values.first()
|
||||
|
||||
@Suppress("UnusedBoxWithConstraintsScope")
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
|
||||
if (maxWidth > 392.dp) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
JoinInfoDropdown(
|
||||
joinAddresses = joinAddresses,
|
||||
selectedType = selectedType,
|
||||
onSelectedTypeChanged = onSelectedTypeChanged,
|
||||
modifier = Modifier.weight(0.39f),
|
||||
)
|
||||
AddressRow(
|
||||
address = address,
|
||||
modifier = Modifier.weight(0.61f),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
JoinInfoDropdown(
|
||||
joinAddresses = joinAddresses,
|
||||
selectedType = selectedType,
|
||||
onSelectedTypeChanged = onSelectedTypeChanged,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
MenuSpacer()
|
||||
AddressRow(
|
||||
address = address,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun JoinInfoDropdown(
|
||||
joinAddresses: Map<JoinInfoType, JoinAddress>,
|
||||
selectedType: JoinInfoType,
|
||||
onSelectedTypeChanged: (JoinInfoType) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
modifier = modifier,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = stringResource(selectedType.labelId),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(R.string.netplay_host_address_label)) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
|
||||
modifier = Modifier
|
||||
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
joinAddresses.keys.forEach { type ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(type.labelId)) },
|
||||
onClick = {
|
||||
onSelectedTypeChanged(type)
|
||||
expanded = false
|
||||
},
|
||||
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddressRow(
|
||||
address: JoinAddress,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
ReadOnlyTextField(
|
||||
value = when (address) {
|
||||
is JoinAddress.Loading -> stringResource(R.string.netplay_address_loading)
|
||||
is JoinAddress.Loaded -> address.address
|
||||
is JoinAddress.Unknown -> stringResource(R.string.netplay_address_unknown)
|
||||
},
|
||||
label = stringResource(R.string.netplay_address_label),
|
||||
onClick = when (address) {
|
||||
is JoinAddress.Loaded -> {
|
||||
{
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, address.address)
|
||||
}
|
||||
context.startActivity(Intent.createChooser(intent, null))
|
||||
}
|
||||
}
|
||||
|
||||
is JoinAddress.Unknown -> address.retry
|
||||
is JoinAddress.Loading -> null
|
||||
},
|
||||
textStyle = if (address is JoinAddress.Loading) {
|
||||
LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
trailingIcon = {
|
||||
when (address) {
|
||||
is JoinAddress.Loaded -> Icon(
|
||||
imageVector = Icons.Filled.Share,
|
||||
contentDescription = stringResource(R.string.netplay_address_share),
|
||||
)
|
||||
|
||||
is JoinAddress.Unknown -> Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
contentDescription = stringResource(R.string.netplay_address_retry),
|
||||
)
|
||||
|
||||
is JoinAddress.Loading -> CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A table arranged into columns sized to wrap the largest item. Except the
|
||||
* first column which takes up the remaining space left by the other columns.
|
||||
|
|
@ -981,6 +1162,10 @@ private fun PreviewNetplayScreen() {
|
|||
onMaxBufferChanged = {},
|
||||
saveTransferProgress = null,
|
||||
gameDigestProgress = null,
|
||||
joinAddresses = mapOf(
|
||||
JoinInfoType.EXTERNAL to JoinAddress.Loaded("203.0.113.1:2626"),
|
||||
JoinInfoType.LOCAL to JoinAddress.Loaded("192.168.1.5:2626"),
|
||||
),
|
||||
// saveTransferProgress = SaveTransferProgress(
|
||||
// title = "Title",
|
||||
// totalSize = 1024L,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -28,6 +31,7 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.android.material.color.MaterialColors
|
||||
|
|
@ -205,3 +209,35 @@ fun OutlinedBox(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReadOnlyTextField(
|
||||
value: String,
|
||||
label: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
textStyle: TextStyle? = null,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
label = { Text(label) },
|
||||
trailingIcon = trailingIcon,
|
||||
textStyle = textStyle ?: LocalTextStyle.current,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (onClick != null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.padding(top = 8.dp)
|
||||
.clip(MaterialTheme.shapes.extraSmall)
|
||||
.clickable(onClick = onClick)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,4 +75,9 @@ object NetworkHelper {
|
|||
0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLocalIpString(): String? {
|
||||
return getIPv4Link()?.address?.hostAddress
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1016,4 +1016,12 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
<string name="netplay_game_digest_close">Close</string>
|
||||
<string name="netplay_host_port_label">Port</string>
|
||||
<string name="netplay_use_upnp">Forward port (UPnP)</string>
|
||||
<string name="netplay_host_address_label">Join info</string>
|
||||
<string name="netplay_address_label">Address</string>
|
||||
<string name="netplay_address_type_external">External IP</string>
|
||||
<string name="netplay_address_type_local">Local IP</string>
|
||||
<string name="netplay_address_loading">Loading…</string>
|
||||
<string name="netplay_address_unknown">Unknown</string>
|
||||
<string name="netplay_address_share">Share address</string>
|
||||
<string name="netplay_address_retry">Retry</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/NetPlayCommon.h"
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/Config/NetplaySettings.h"
|
||||
#include "Core/NetPlayClient.h"
|
||||
|
|
@ -174,6 +175,25 @@ Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeStartGame(J
|
|||
server->RequestStartGame();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeGetPort(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
if (auto* server = GetServerPointer(env, obj))
|
||||
return static_cast<jint>(server->GetPort());
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeGetExternalIpAddress(
|
||||
JNIEnv* env, jobject)
|
||||
{
|
||||
std::string ip = NetPlay::GetExternalIPAddress();
|
||||
if (ip.empty())
|
||||
return nullptr;
|
||||
return ToJString(env, ip);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeReleaseUICallbacks(JNIEnv*,
|
||||
jobject,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user