mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-05-09 04:13:28 -05:00
Show save transfer progress
When transferring saves from the host. Equivalent of ChunkedProgressDialog in QT.
This commit is contained in:
parent
ab6c2d0d56
commit
3572afcbbf
|
|
@ -13,7 +13,9 @@ import kotlinx.coroutines.channels.BufferOverflow
|
|||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -26,6 +28,7 @@ import kotlinx.coroutines.withContext
|
|||
import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionType
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.NetplayMessage
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.Player
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress
|
||||
|
||||
object Netplay {
|
||||
@Keep
|
||||
|
|
@ -87,6 +90,9 @@ object Netplay {
|
|||
)
|
||||
val padBuffer = _padBuffer.asSharedFlow()
|
||||
|
||||
private val _saveTransferProgress = MutableStateFlow<SaveTransferProgress?>(null)
|
||||
val saveTransferProgress = _saveTransferProgress.asStateFlow()
|
||||
|
||||
suspend fun join(): Boolean = withContext(Dispatchers.IO) {
|
||||
val scope = createSessionScope()
|
||||
|
||||
|
|
@ -140,6 +146,7 @@ object Netplay {
|
|||
_game.resetReplayCache()
|
||||
_hostInputAuthorityEnabled.resetReplayCache()
|
||||
_padBuffer.resetReplayCache()
|
||||
_saveTransferProgress.value = null
|
||||
}
|
||||
|
||||
private fun createSessionScope(): CoroutineScope {
|
||||
|
|
@ -234,6 +241,44 @@ object Netplay {
|
|||
_padBuffer.tryEmit(buffer)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onShowChunkedProgressDialog(title: String, dataSize: Long, playerIds: IntArray) {
|
||||
val players = _players.replayCache.firstOrNull()
|
||||
_saveTransferProgress.value = SaveTransferProgress(
|
||||
title = title,
|
||||
totalSize = dataSize,
|
||||
playerProgresses = playerIds.map { playerId ->
|
||||
SaveTransferProgress.PlayerProgress(
|
||||
playerId = playerId,
|
||||
name = players?.find { it.pid == playerId }?.name ?: "Invalid Player ID",
|
||||
progress = 0,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onSetChunkedProgress(playerId: Int, progress: Long) {
|
||||
val current = _saveTransferProgress.value
|
||||
_saveTransferProgress.value = current?.copy(
|
||||
playerProgresses = current.playerProgresses.map {
|
||||
if (it.playerId == playerId) {
|
||||
it.copy(progress = progress)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onHideChunkedProgressDialog() {
|
||||
_saveTransferProgress.value = null
|
||||
}
|
||||
|
||||
// Settings
|
||||
object Settings {
|
||||
@JvmStatic
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
|
|
@ -42,6 +40,8 @@ class NetplayViewModel : ViewModel() {
|
|||
private val _maxBuffer = MutableStateFlow(Netplay.Settings.getClientBufferSize())
|
||||
val maxBuffer = _maxBuffer.asStateFlow()
|
||||
|
||||
val saveTransferProgress = Netplay.saveTransferProgress
|
||||
|
||||
init {
|
||||
if (!Netplay.isClientConnected()) {
|
||||
_goBack.trySend(Unit)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
data class SaveTransferProgress(
|
||||
val title: String,
|
||||
val totalSize: Long,
|
||||
val playerProgresses: List<PlayerProgress>
|
||||
) {
|
||||
data class PlayerProgress(
|
||||
val playerId: Int,
|
||||
val name: String,
|
||||
val progress: Long,
|
||||
)
|
||||
}
|
||||
|
|
@ -52,6 +52,7 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
|
|||
hostInputAuthorityEnabled = viewModel.hostInputAuthority.collectAsState().value,
|
||||
maxBuffer = viewModel.maxBuffer.collectAsState().value,
|
||||
onMaxBufferChanged = viewModel::setMaxBuffer,
|
||||
saveTransferProgress = viewModel.saveTransferProgress.collectAsState().value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
|
|
@ -70,10 +71,12 @@ import kotlinx.coroutines.flow.emptyFlow
|
|||
import org.dolphinemu.dolphinemu.R
|
||||
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.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 java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -87,6 +90,7 @@ fun NetplayScreen(
|
|||
maxBuffer: Int,
|
||||
onMaxBufferChanged: (Int) -> Unit,
|
||||
players: List<Player>,
|
||||
saveTransferProgress: SaveTransferProgress?,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
|
@ -136,16 +140,31 @@ fun NetplayScreen(
|
|||
LaunchedEffect(Unit) {
|
||||
connectionLost.collect { showConnectionLostDialog = true }
|
||||
}
|
||||
if (showConnectionLostDialog) {
|
||||
AlertDialog(
|
||||
text = { Text(stringResource(R.string.netplay_connection_lost)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onBackClicked) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onBackClicked,
|
||||
)
|
||||
|
||||
var dismissSaveTransferProgressDialog by remember { mutableStateOf(false) }
|
||||
if (saveTransferProgress == null) {
|
||||
dismissSaveTransferProgressDialog = false
|
||||
}
|
||||
|
||||
when {
|
||||
showConnectionLostDialog -> {
|
||||
AlertDialog(
|
||||
text = { Text(stringResource(R.string.netplay_connection_lost)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onBackClicked) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onBackClicked,
|
||||
)
|
||||
}
|
||||
|
||||
saveTransferProgress != null && !dismissSaveTransferProgressDialog -> {
|
||||
SaveTransferProgressDialog(
|
||||
saveTransferProgress = saveTransferProgress,
|
||||
onDismiss = { dismissSaveTransferProgressDialog = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -519,6 +538,68 @@ private fun BufferInput(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SaveTransferProgressDialog(
|
||||
saveTransferProgress: SaveTransferProgress,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
title = { Text(saveTransferProgress.title) },
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
saveTransferProgress.playerProgresses.forEachIndexed { index, playerProgress ->
|
||||
SaveTransferProgressRow(
|
||||
playerProgress = playerProgress,
|
||||
totalSize = saveTransferProgress.totalSize,
|
||||
)
|
||||
|
||||
if (index < saveTransferProgress.playerProgresses.lastIndex) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.netplay_save_transfer_progress_close))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SaveTransferProgressRow(
|
||||
playerProgress: SaveTransferProgress.PlayerProgress,
|
||||
totalSize: Long,
|
||||
) {
|
||||
fun formatMib(bytes: Long) = String.format(Locale.US, "%.2f", bytes / 1024f / 1024f)
|
||||
val progressFraction = (playerProgress.progress.toFloat() / totalSize).coerceIn(0f, 1f)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = { progressFraction },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = playerProgress.name,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = "${formatMib(playerProgress.progress)}/${formatMib(totalSize)} MiB",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NetplayScreenPreview() {
|
||||
|
|
@ -589,5 +670,22 @@ private fun PreviewNetplayScreen() {
|
|||
hostInputAuthorityEnabled = true,
|
||||
maxBuffer = 10,
|
||||
onMaxBufferChanged = {},
|
||||
saveTransferProgress = null,
|
||||
// saveTransferProgress = SaveTransferProgress(
|
||||
// title = "Title",
|
||||
// totalSize = 1024L,
|
||||
// playerProgresses = listOf(
|
||||
// SaveTransferProgress.PlayerProgress(
|
||||
// playerId = 1,
|
||||
// name = "Player 1",
|
||||
// progress = 256,
|
||||
// ),
|
||||
// SaveTransferProgress.PlayerProgress(
|
||||
// playerId = 2,
|
||||
// name = "Player 2",
|
||||
// progress = 512,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1009,4 +1009,5 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
<string name="netplay_players_mapping">Mapping</string>
|
||||
<string name="netplay_max_buffer">Max buffer</string>
|
||||
<string name="netplay_connection_lost">Netplay connection lost</string>
|
||||
<string name="netplay_save_transfer_progress_close">Close</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ static jmethodID s_netplay_on_host_input_authority_changed;
|
|||
static jmethodID s_netplay_on_pad_buffer_changed;
|
||||
static jmethodID s_netplay_on_chat_message_received;
|
||||
static jmethodID s_netplay_update;
|
||||
static jmethodID s_netplay_on_show_chunked_progress_dialog;
|
||||
static jmethodID s_netplay_on_set_chunked_progress;
|
||||
static jmethodID s_netplay_on_hide_chunked_progress_dialog;
|
||||
|
||||
static jclass s_netplay_player_class;
|
||||
static jmethodID s_netplay_player_constructor;
|
||||
|
|
@ -309,6 +312,21 @@ jmethodID GetNetplayUpdate()
|
|||
return s_netplay_update;
|
||||
}
|
||||
|
||||
jmethodID GetNetplayOnShowChunkedProgressDialog()
|
||||
{
|
||||
return s_netplay_on_show_chunked_progress_dialog;
|
||||
}
|
||||
|
||||
jmethodID GetNetplayOnSetChunkedProgress()
|
||||
{
|
||||
return s_netplay_on_set_chunked_progress;
|
||||
}
|
||||
|
||||
jmethodID GetNetplayOnHideChunkedProgressDialog()
|
||||
{
|
||||
return s_netplay_on_hide_chunked_progress_dialog;
|
||||
}
|
||||
|
||||
jclass GetNetplayPlayerClass()
|
||||
{
|
||||
return s_netplay_player_class;
|
||||
|
|
@ -741,6 +759,12 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
s_netplay_on_chat_message_received =
|
||||
env->GetStaticMethodID(netplay_class, "onChatMessageReceived", "(Ljava/lang/String;)V");
|
||||
s_netplay_update = env->GetStaticMethodID(netplay_class, "onUpdate", "([Lorg/dolphinemu/dolphinemu/features/netplay/model/Player;)V");
|
||||
s_netplay_on_show_chunked_progress_dialog =
|
||||
env->GetStaticMethodID(netplay_class, "onShowChunkedProgressDialog", "(Ljava/lang/String;J[I)V");
|
||||
s_netplay_on_set_chunked_progress =
|
||||
env->GetStaticMethodID(netplay_class, "onSetChunkedProgress", "(IJ)V");
|
||||
s_netplay_on_hide_chunked_progress_dialog =
|
||||
env->GetStaticMethodID(netplay_class, "onHideChunkedProgressDialog", "()V");
|
||||
env->DeleteLocalRef(netplay_class);
|
||||
|
||||
const jclass netplay_player_class =
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ jmethodID GetNetplayOnHostInputAuthorityChanged();
|
|||
jmethodID GetNetplayOnPadBufferChanged();
|
||||
jmethodID GetNetplayOnChatMessageReceived();
|
||||
jmethodID GetNetplayUpdate();
|
||||
jmethodID GetNetplayOnShowChunkedProgressDialog();
|
||||
jmethodID GetNetplayOnSetChunkedProgress();
|
||||
jmethodID GetNetplayOnHideChunkedProgressDialog();
|
||||
|
||||
jclass GetNetplayPlayerClass();
|
||||
jmethodID GetNetplayPlayerConstructor();
|
||||
|
|
|
|||
|
|
@ -186,9 +186,36 @@ void NetPlayUICallbacks::ShowGameDigestDialog(const std::string&) {}
|
|||
void NetPlayUICallbacks::SetGameDigestProgress(int, int) {}
|
||||
void NetPlayUICallbacks::SetGameDigestResult(int, const std::string&) {}
|
||||
void NetPlayUICallbacks::AbortGameDigest() {}
|
||||
void NetPlayUICallbacks::ShowChunkedProgressDialog(const std::string&, u64, std::span<const int>) {}
|
||||
void NetPlayUICallbacks::HideChunkedProgressDialog() {}
|
||||
void NetPlayUICallbacks::SetChunkedProgress(int, u64) {}
|
||||
|
||||
void NetPlayUICallbacks::ShowChunkedProgressDialog(const std::string& title, u64 data_size,
|
||||
std::span<const int> players)
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
|
||||
jintArray j_players = env->NewIntArray(static_cast<jsize>(players.size()));
|
||||
env->SetIntArrayRegion(j_players, 0, static_cast<jsize>(players.size()), players.data());
|
||||
|
||||
env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
|
||||
IDCache::GetNetplayOnShowChunkedProgressDialog(),
|
||||
ToJString(env, title), static_cast<jlong>(data_size), j_players);
|
||||
env->DeleteLocalRef(j_players);
|
||||
}
|
||||
|
||||
void NetPlayUICallbacks::HideChunkedProgressDialog()
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
|
||||
IDCache::GetNetplayOnHideChunkedProgressDialog());
|
||||
}
|
||||
|
||||
void NetPlayUICallbacks::SetChunkedProgress(int pid, u64 progress)
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
|
||||
IDCache::GetNetplayOnSetChunkedProgress(),
|
||||
static_cast<jint>(pid), static_cast<jlong>(progress));
|
||||
}
|
||||
|
||||
void NetPlayUICallbacks::SetHostWiiSyncData(std::vector<u64>, std::string) {}
|
||||
|
||||
} // namespace NetPlay
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user