summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt38
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/JoinInfo.kt1
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt51
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/TraversalState.kt18
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt1
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt34
6 files changed, 134 insertions, 9 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
index 8af9cf9806..d8f2ce8860 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
@@ -25,11 +25,12 @@ import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import org.dolphinemu.dolphinemu.features.netplay.model.GameDigestProgress
-import org.dolphinemu.dolphinemu.model.GameFile
import org.dolphinemu.dolphinemu.features.netplay.model.NetplayMessage
import org.dolphinemu.dolphinemu.features.netplay.model.Player
import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress
+import org.dolphinemu.dolphinemu.features.netplay.model.TraversalState
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
+import org.dolphinemu.dolphinemu.model.GameFile
class NetplaySession(
private val onClosed: (NetplaySession) -> Unit,
@@ -117,6 +118,15 @@ class NetplaySession(
private val _gameDigestProgress = MutableStateFlow<GameDigestProgress?>(null)
val gameDigestProgress = _gameDigestProgress.asStateFlow()
+ private val _traversalState = MutableSharedFlow<TraversalState>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ val traversalState = _traversalState.asSharedFlow()
+
+ private val _fatalTraversalError = Channel<TraversalState.Failure>(Channel.CONFLATED)
+ val fatalTraversalError = _fatalTraversalError.receiveAsFlow()
+
suspend fun join(): Boolean = withContext(Dispatchers.IO) {
if (isClosed) throw IllegalStateException("Cannot join a closed session")
@@ -148,7 +158,7 @@ class NetplaySession(
}
fun sendMessage(message: String) {
- _chatMessages.tryEmit( "$nickName: $message")
+ _chatMessages.tryEmit("$nickName: $message")
nativeSendMessage(message)
}
@@ -162,6 +172,8 @@ class NetplaySession(
fun getExternalIpAddress(): String? = nativeGetExternalIpAddress()
+ fun reconnectTraversal() = nativeReconnectTraversal()
+
fun consumeBootSessionData(): Long {
return bootSessionDataPointer.also {
bootSessionDataPointer = 0
@@ -247,6 +259,8 @@ class NetplaySession(
private external fun nativeGetExternalIpAddress(): String?
+ private external fun nativeReconnectTraversal()
+
// NetPlayUI callbacks
@Keep
@@ -390,6 +404,26 @@ class NetplaySession(
fun onAbortGameDigest() {
_gameDigestProgress.value = null
}
+
+ @Keep
+ fun onTraversalStateChanged(
+ state: Int,
+ hostCode: String?,
+ externalAddress: String?,
+ failureReason: String?,
+ ) {
+ val traversalState = when (state) {
+ 0 -> TraversalState.Connecting
+ 1 -> TraversalState.Connected(hostCode!!, externalAddress!!)
+ 2 -> TraversalState.Failure(failureReason!!)
+ else -> return
+ }
+ _traversalState.tryEmit(traversalState)
+
+ if (failureReason == "BadHost" || failureReason == "VersionTooOld") {
+ _fatalTraversalError.trySend(TraversalState.Failure(failureReason))
+ }
+ }
}
private fun <T> Channel<T>.flush() {
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/JoinInfo.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/JoinInfo.kt
index ba350a3caf..7403f06f32 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/JoinInfo.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/JoinInfo.kt
@@ -6,6 +6,7 @@ import androidx.annotation.StringRes
import org.dolphinemu.dolphinemu.R
enum class JoinInfoType(@StringRes val labelId: Int) {
+ ROOM_ID(R.string.netplay_address_type_room_id),
EXTERNAL(R.string.netplay_address_type_external),
LOCAL(R.string.netplay_address_type_local),
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt
index 923cfc314d..4ebea7e283 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt
@@ -12,7 +12,9 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.features.netplay.NetplaySession
@@ -28,20 +30,29 @@ class NetplayViewModel(
private val networkHelper: NetworkHelper,
) : ViewModel() {
+ private val isTraversal = StringSetting.NETPLAY_TRAVERSAL_CHOICE.string == "traversal"
+
val launchGame = netplaySession.launchGame
val isHosting = netplaySession.isHosting
private val _joinAddresses = MutableStateFlow(
- mapOf(
- JoinInfoType.EXTERNAL to JoinAddress.Loading,
- JoinInfoType.LOCAL to getLocalIp(),
- )
+ buildMap {
+ if (isHosting) {
+ if (isTraversal) {
+ put(JoinInfoType.ROOM_ID, JoinAddress.Loading)
+ }
+ put(JoinInfoType.EXTERNAL, JoinAddress.Loading)
+ put(JoinInfoType.LOCAL, getLocalIp())
+ }
+ }
)
val joinAddresses = _joinAddresses.asStateFlow()
val connectionLost = netplaySession.connectionLost
+ val fatalTraversalError = netplaySession.fatalTraversalError
+
val players = netplaySession.players
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
@@ -72,7 +83,11 @@ class NetplayViewModel(
init {
if (netplaySession.isHosting) {
setInitialGame()
- fetchExternalIp()
+ if (isTraversal) {
+ collectTraversalState()
+ } else {
+ fetchExternalIp()
+ }
}
}
@@ -118,6 +133,32 @@ class NetplayViewModel(
}
}
+ private fun collectTraversalState() {
+ val retry = { netplaySession.reconnectTraversal() }
+ netplaySession.traversalState.onEach { state ->
+ when (state) {
+ is TraversalState.Connecting -> {
+ _joinAddresses.value += mapOf(
+ JoinInfoType.ROOM_ID to JoinAddress.Loading,
+ JoinInfoType.EXTERNAL to JoinAddress.Loading,
+ )
+ }
+ is TraversalState.Connected -> {
+ _joinAddresses.value += mapOf(
+ JoinInfoType.ROOM_ID to JoinAddress.Loaded(state.hostCode),
+ JoinInfoType.EXTERNAL to JoinAddress.Loaded(state.externalAddress),
+ )
+ }
+ is TraversalState.Failure -> {
+ _joinAddresses.value += mapOf(
+ JoinInfoType.ROOM_ID to JoinAddress.Unknown(retry),
+ JoinInfoType.EXTERNAL to JoinAddress.Unknown(retry),
+ )
+ }
+ }
+ }.launchIn(viewModelScope)
+ }
+
private fun setInitialGame() {
val game = gameFiles.value
.find { it.getGameId() == StringSetting.NETPLAY_GAME.string }
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/TraversalState.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/TraversalState.kt
new file mode 100644
index 0000000000..9b574e4f63
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/TraversalState.kt
@@ -0,0 +1,18 @@
+package org.dolphinemu.dolphinemu.features.netplay.model
+
+import android.content.Context
+import org.dolphinemu.dolphinemu.R
+
+sealed class TraversalState {
+ data object Connecting : TraversalState()
+
+ data class Connected(val hostCode: String, val externalAddress: String) : TraversalState()
+
+ data class Failure(val reason: String) : TraversalState() {
+ fun message(context: Context) = when (reason) {
+ "BadHost" -> context.getString(R.string.netplay_traversal_error_bad_host)
+ "VersionTooOld" -> context.getString(R.string.netplay_traversal_error_version_too_old)
+ else -> reason
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt
index a9bf0309a5..e45ba7e7c1 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt
@@ -50,6 +50,7 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
NetplayScreen(
onBackClicked = { finish() },
connectionLost = viewModel.connectionLost,
+ fatalTraversalError = viewModel.fatalTraversalError,
messages = viewModel.messages.collectAsState().value,
onSendMessage = viewModel::sendMessage,
game = viewModel.game.collectAsState().value,
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt
index eeba0f1541..71b427a8c8 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt
@@ -97,6 +97,7 @@ 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
+import org.dolphinemu.dolphinemu.features.netplay.model.TraversalState
import org.dolphinemu.dolphinemu.model.GameFile
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
@@ -112,6 +113,7 @@ fun NetplayScreen(
onBackClicked: () -> Unit,
isHosting: Boolean,
connectionLost: Flow<Unit>,
+ fatalTraversalError: Flow<TraversalState.Failure>,
messages: List<NetplayMessage>,
onSendMessage: (String) -> Unit,
game: String,
@@ -156,7 +158,9 @@ fun NetplayScreen(
// 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) }
+ var selectedJoinInfoType by rememberSaveable {
+ mutableStateOf(joinAddresses.keys.firstOrNull() ?: JoinInfoType.EXTERNAL)
+ }
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) {
LandscapeContent(
@@ -207,6 +211,11 @@ fun NetplayScreen(
connectionLost.collect { showConnectionLostDialog = true }
}
+ var traversalError by rememberSaveable { mutableStateOf<TraversalState.Failure?>(null) }
+ LaunchedEffect(Unit) {
+ fatalTraversalError.collect { traversalError = it }
+ }
+
var dismissSaveTransferProgressDialog by rememberSaveable { mutableStateOf(false) }
if (saveTransferProgress == null) {
dismissSaveTransferProgressDialog = false
@@ -217,6 +226,8 @@ fun NetplayScreen(
dismissGameDigestDialog = false
}
+ val currentTraversalError = traversalError
+
when {
showConnectionLostDialog -> {
AlertDialog(
@@ -230,6 +241,18 @@ fun NetplayScreen(
)
}
+ currentTraversalError != null -> {
+ AlertDialog(
+ text = { Text(currentTraversalError.message(LocalContext.current)) },
+ confirmButton = {
+ TextButton(onClick = onBackClicked) {
+ Text(stringResource(R.string.ok))
+ }
+ },
+ onDismissRequest = onBackClicked,
+ )
+ }
+
saveTransferProgress != null && !dismissSaveTransferProgressDialog -> {
SaveTransferProgressDialog(
saveTransferProgress = saveTransferProgress,
@@ -669,6 +692,7 @@ private fun JoinAddressSection(
modifier = Modifier.weight(0.39f),
)
AddressRow(
+ joinInfoType = selectedType,
address = address,
modifier = Modifier.weight(0.61f),
)
@@ -683,6 +707,7 @@ private fun JoinAddressSection(
)
MenuSpacer()
AddressRow(
+ joinInfoType = selectedType,
address = address,
modifier = Modifier.fillMaxWidth(),
)
@@ -737,6 +762,7 @@ private fun JoinInfoDropdown(
@Composable
private fun AddressRow(
+ joinInfoType: JoinInfoType,
address: JoinAddress,
modifier: Modifier = Modifier,
) {
@@ -748,7 +774,10 @@ private fun AddressRow(
is JoinAddress.Loaded -> address.address
is JoinAddress.Unknown -> stringResource(R.string.netplay_address_unknown)
},
- label = stringResource(R.string.netplay_address_label),
+ label = stringResource(
+ if (joinInfoType == JoinInfoType.ROOM_ID) R.string.netplay_code_label
+ else R.string.netplay_address_label
+ ),
onClick = when (address) {
is JoinAddress.Loaded -> {
{
@@ -1128,6 +1157,7 @@ private fun PreviewNetplayScreen() {
NetplayScreen(
onBackClicked = {},
connectionLost = emptyFlow(),
+ fatalTraversalError = emptyFlow(),
players = listOf(
Player(
pid = 1,