summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2026-08-16 11:05:39 +0200
committerGitHub <noreply@github.com>2026-08-16 11:05:39 +0200
commita9c36ee2eff5b587b013c4b1584a694a126891c8 (patch)
tree36f39db506b77b130df4bc41138887cbb6b3ea82 /Source/Android
parent54070da5851e12f2d1a4389daa528e4fb81327ce (diff)
parent6877e436fbf1511bea91eeef569b94d0354771fc (diff)
Merge pull request #14735 from tom-pratt/dual-core-warning
Android: Dual core warning when hosting netplay
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt30
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt3
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt100
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt3
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml3
5 files changed, 133 insertions, 6 deletions
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 adea1a13a5..0764448824 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
@@ -11,15 +11,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.receiveAsFlow
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.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.features.netplay.NetplaySession
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
@@ -104,18 +105,37 @@ class NetplayViewModel(
private val _notAllPlayersHaveGame = Channel<Unit>(Channel.CONFLATED)
val notAllPlayersHaveGame = _notAllPlayersHaveGame.receiveAsFlow()
+ private val _dualCoreWarning = Channel<Unit>(Channel.CONFLATED)
+ val dualCoreWarning = _dualCoreWarning.receiveAsFlow()
+
fun startGame() {
- if (netplaySession.doAllPlayersHaveGame()) {
- netplaySession.startGame()
- } else {
+ if (!netplaySession.doAllPlayersHaveGame()) {
_notAllPlayersHaveGame.trySend(Unit)
+ return
+ }
+
+ if (BooleanSetting.MAIN_CPU_THREAD.boolean &&
+ !BooleanSetting.NETPLAY_SKIP_DUAL_CORE_WARNING.boolean
+ ) {
+ _dualCoreWarning.trySend(Unit)
+ return
}
+
+ confirmStartGame()
}
fun confirmStartGame() {
netplaySession.startGame()
}
+ fun setDualCoreEnabled(enabled: Boolean) {
+ BooleanSetting.MAIN_CPU_THREAD.setBoolean(NativeConfig.LAYER_BASE, enabled)
+ }
+
+ fun skipDualCoreWarning() {
+ BooleanSetting.NETPLAY_SKIP_DUAL_CORE_WARNING.setBoolean(NativeConfig.LAYER_BASE, true)
+ }
+
fun sendMessage(message: String) {
val trimmedMessage = message.trim()
if (trimmedMessage.isEmpty()) {
@@ -176,12 +196,14 @@ class NetplayViewModel(
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),
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 eb9cecec20..28e0e4ca16 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
@@ -58,6 +58,9 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
onGameSelected = viewModel::changeGame,
gameFiles = viewModel.gameFiles.collectAsState().value,
notAllPlayersHaveGame = viewModel.notAllPlayersHaveGame,
+ dualCoreWarning = viewModel.dualCoreWarning,
+ onSetDualCoreEnabled = viewModel::setDualCoreEnabled,
+ onSkipDualCoreWarning = viewModel::skipDualCoreWarning,
onConfirmStartGame = viewModel::confirmStartGame,
players = viewModel.players.collectAsState().value,
hostInputAuthorityEnabled = viewModel.hostInputAuthority.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 789a3cef17..7df8ea35ee 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
@@ -5,6 +5,7 @@ package org.dolphinemu.dolphinemu.features.netplay.ui
import android.content.Intent
import android.content.res.Configuration
import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -37,6 +38,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Remove
import androidx.compose.material.icons.filled.Share
@@ -59,6 +61,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SheetValue
+import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
@@ -72,7 +75,10 @@ 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.graphics.Color
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
@@ -87,7 +93,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import androidx.window.core.layout.WindowSizeClass
import coil.compose.AsyncImage
@@ -129,6 +134,9 @@ fun NetplayScreen(
onGameSelected: (GameFile) -> Unit,
gameFiles: List<GameFile>,
notAllPlayersHaveGame: Flow<Unit>,
+ dualCoreWarning: Flow<Unit>,
+ onSetDualCoreEnabled: (Boolean) -> Unit,
+ onSkipDualCoreWarning: () -> Unit,
onConfirmStartGame: () -> Unit,
hostInputAuthorityEnabled: Boolean,
networkMode: NetworkMode,
@@ -252,6 +260,11 @@ fun NetplayScreen(
notAllPlayersHaveGame.collect { showNotAllPlayersHaveGame = true }
}
+ var showDualCoreWarning by rememberSaveable { mutableStateOf(false) }
+ LaunchedEffect(Unit) {
+ dualCoreWarning.collect { showDualCoreWarning = true }
+ }
+
var dismissSaveTransferProgressDialog by rememberSaveable { mutableStateOf(false) }
if (saveTransferProgress == null) {
dismissSaveTransferProgressDialog = false
@@ -323,6 +336,21 @@ fun NetplayScreen(
onDismissRequest = { showNotAllPlayersHaveGame = false },
)
}
+
+ showDualCoreWarning -> {
+ DualCoreWarningDialog(
+ onSetDualCoreEnabled = onSetDualCoreEnabled,
+ onStartGame = {
+ showDualCoreWarning = false
+ onConfirmStartGame()
+ },
+ onSkipWarning = {
+ showDualCoreWarning = false
+ onSkipDualCoreWarning()
+ },
+ onDismiss = { showDualCoreWarning = false },
+ )
+ }
}
}
}
@@ -1274,6 +1302,73 @@ private fun GameDigestPlayerRow(
}
@Composable
+private fun DualCoreWarningDialog(
+ onSetDualCoreEnabled: (Boolean) -> Unit,
+ onStartGame: () -> Unit,
+ onSkipWarning: () -> Unit,
+ onDismiss: () -> Unit,
+) {
+ var dualCoreEnabled by rememberSaveable { mutableStateOf(true) }
+ AlertDialog(
+ title = { Text(stringResource(R.string.netplay_dual_core_warning_title)) },
+ text = {
+ Column {
+ Text(
+ text = stringResource(R.string.netplay_dual_core_warning_message),
+ )
+ val ripplePadding = 12.dp
+ Spacer(Modifier.height(12.dp))
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .layout { measurable, constraints ->
+ // Enlarge the ripple area beyond the dialog's padding
+ val hPx = ripplePadding.roundToPx()
+ val placeable = measurable.measure(
+ constraints.copy(
+ minWidth = constraints.maxWidth + hPx * 2,
+ maxWidth = constraints.maxWidth + hPx * 2
+ )
+ )
+ layout(constraints.maxWidth, placeable.height) {
+ placeable.place(-hPx, 0)
+ }
+ }
+ .clip(MaterialTheme.shapes.small)
+ .clickable {
+ dualCoreEnabled = !dualCoreEnabled
+ onSetDualCoreEnabled(dualCoreEnabled)
+ }
+ .padding(ripplePadding),
+ ) {
+ Text(
+ text = stringResource(R.string.dual_core),
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.weight(1f),
+ )
+ Switch(
+ checked = dualCoreEnabled,
+ onCheckedChange = null,
+ )
+ }
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = onStartGame) {
+ Text(stringResource(R.string.netplay_start))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = onSkipWarning) {
+ Text(stringResource(R.string.netplay_dont_warn_again))
+ }
+ },
+ onDismissRequest = onDismiss,
+ )
+}
+
+@Composable
private fun NetplayMessage.color(): Color {
val isDark = isSystemInDarkTheme()
return when (this) {
@@ -1361,6 +1456,9 @@ private fun PreviewNetplayScreen() {
onGameSelected = {},
gameFiles = emptyList(),
notAllPlayersHaveGame = emptyFlow(),
+ dualCoreWarning = emptyFlow(),
+ onSetDualCoreEnabled = {},
+ onSkipDualCoreWarning = {},
onConfirmStartGame = {},
hostInputAuthorityEnabled = true,
networkMode = NetworkMode.HOST_INPUT_AUTHORITY,
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt
index 68c859082c..47f05f14ca 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt
@@ -947,7 +947,8 @@ enum class BooleanSetting(
"ProgressEnabled",
false
),
- NETPLAY_USE_UPNP(Settings.FILE_DOLPHIN, Settings.SECTION_INI_NETPLAY, "UseUPNP", false);
+ NETPLAY_USE_UPNP(Settings.FILE_DOLPHIN, Settings.SECTION_INI_NETPLAY, "UseUPNP", false),
+ NETPLAY_SKIP_DUAL_CORE_WARNING(Settings.FILE_DOLPHIN, Settings.SECTION_INI_NETPLAY, "SkipDualCoreWarning", false);
override val isOverridden: Boolean
get() = NativeConfig.isOverridden(file, section, key)
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index 9d97169d95..a6419ec83b 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -1012,6 +1012,9 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="netplay_start">Start</string>
<string name="netplay_start_warning_title">Warning</string>
<string name="netplay_start_warning_not_all_players_have_game">Not all players have the game. Do you really want to start?</string>
+ <string name="netplay_dual_core_warning_title">Dual Core Enabled</string>
+ <string name="netplay_dual_core_warning_message">Dual core during netplay is not recommended. It can cause performance issues and prevent certain games from booting.</string>
+ <string name="netplay_dont_warn_again">Don\'t warn again</string>
<string name="netplay_chat_label">Chat</string>
<string name="netplay_chat_send">Send</string>
<string name="netplay_message_game_changed">Game changed to %1$s</string>