diff --git a/Source/Android/app/build.gradle.kts b/Source/Android/app/build.gradle.kts
index 0a952a0704..82196ff97b 100644
--- a/Source/Android/app/build.gradle.kts
+++ b/Source/Android/app/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.androidx.baselineprofile)
}
@@ -10,6 +11,7 @@ android {
ndkVersion = "29.0.14206865"
buildFeatures {
+ compose = true
viewBinding = true
buildConfig = true
resValues = true
@@ -150,6 +152,15 @@ dependencies {
implementation(libs.kotlinx.coroutines.android)
implementation(libs.filepicker)
+
+ // Jetpack Compose
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.androidx.compose.material.icons)
+ implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.compose.ui)
+ debugImplementation(libs.androidx.compose.ui.tooling)
+ debugImplementation(libs.androidx.compose.ui.tooling.preview)
}
fun getGitVersion(): String {
diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml
index 3218ed3ed8..e68fa9f757 100644
--- a/Source/Android/app/src/main/AndroidManifest.xml
+++ b/Source/Android/app/src/main/AndroidManifest.xml
@@ -133,6 +133,11 @@
android:label="@string/user_data_submenu"
android:theme="@style/Theme.Dolphin.Main" />
+
+
Unit,
+) {
+ Scaffold(
+ topBar = {
+ MediumTopAppBar(
+ title = { Text(stringResource(R.string.netplay_setup_title)) },
+ navigationIcon = {
+ IconButton(onClick = onBackClicked) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back"
+ )
+ }
+ },
+ )
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .consumeWindowInsets(innerPadding)
+ .padding(innerPadding)
+ .padding(horizontal = DolphinTheme.scaffoldPadding)
+ .verticalScroll(rememberScrollState()),
+ ) {
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun NetplaySetupScreenPreview() {
+ MaterialTheme {
+ NetplayScreen(onBackClicked = {})
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
index 893d6a16d4..1e1d55dbd7 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
@@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateProgressBarDialogFragment
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel
+import org.dolphinemu.dolphinemu.features.netplay.ui.NetplaySetupActivity
import org.dolphinemu.dolphinemu.fragments.AboutDialogFragment
import org.dolphinemu.dolphinemu.model.GameFileCache
import org.dolphinemu.dolphinemu.services.GameFileCacheManager
@@ -188,6 +189,11 @@ class MainPresenter(private val mainView: MainView, private val activity: Fragme
true
}
+ R.id.menu_netplay -> {
+ NetplaySetupActivity.launch(activity)
+ true
+ }
+
R.id.menu_about -> {
showAboutDialog()
false
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt
new file mode 100644
index 0000000000..58eb52b347
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/theme/DolphinTheme.kt
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.theme
+
+import android.content.Context
+import androidx.annotation.AttrRes
+import androidx.compose.foundation.isSystemInDarkTheme
+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.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import com.google.android.material.color.MaterialColors
+import androidx.appcompat.R as AppCompatR
+import com.google.android.material.R as MaterialR
+
+object DolphinTheme {
+ val scaffoldPadding = 16.dp
+}
+
+@Composable
+fun DolphinTheme(content: @Composable () -> Unit) {
+ val context = LocalContext.current
+ val isDark = isSystemInDarkTheme()
+ val colorScheme = remember(context, isDark) { context.toDolphinColorScheme(isDark) }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ content = content
+ )
+}
+
+private fun Context.toDolphinColorScheme(isDark: Boolean): ColorScheme {
+ fun attr(@AttrRes attr: Int) = Color(MaterialColors.getColor(this, attr, 0))
+
+ val background = obtainStyledAttributes(intArrayOf(android.R.attr.colorBackground)).use {
+ Color(it.getColor(0, 0))
+ }
+
+ return if (isDark) {
+ darkColorScheme(
+ primary = attr(AppCompatR.attr.colorPrimary),
+ onPrimary = attr(MaterialR.attr.colorOnPrimary),
+ primaryContainer = attr(MaterialR.attr.colorPrimaryContainer),
+ onPrimaryContainer = attr(MaterialR.attr.colorOnPrimaryContainer),
+ secondary = attr(MaterialR.attr.colorSecondary),
+ onSecondary = attr(MaterialR.attr.colorOnSecondary),
+ secondaryContainer = attr(MaterialR.attr.colorSecondaryContainer),
+ onSecondaryContainer = attr(MaterialR.attr.colorOnSecondaryContainer),
+ tertiary = attr(MaterialR.attr.colorTertiary),
+ onTertiary = attr(MaterialR.attr.colorOnTertiary),
+ tertiaryContainer = attr(MaterialR.attr.colorTertiaryContainer),
+ onTertiaryContainer = attr(MaterialR.attr.colorOnTertiaryContainer),
+ error = attr(AppCompatR.attr.colorError),
+ onError = attr(MaterialR.attr.colorOnError),
+ errorContainer = attr(MaterialR.attr.colorErrorContainer),
+ onErrorContainer = attr(MaterialR.attr.colorOnErrorContainer),
+ background = background,
+ onBackground = attr(MaterialR.attr.colorOnBackground),
+ surface = attr(MaterialR.attr.colorSurface),
+ onSurface = attr(MaterialR.attr.colorOnSurface),
+ surfaceVariant = attr(MaterialR.attr.colorSurfaceVariant),
+ onSurfaceVariant = attr(MaterialR.attr.colorOnSurfaceVariant),
+ outline = attr(MaterialR.attr.colorOutline),
+ inverseSurface = attr(MaterialR.attr.colorSurfaceInverse),
+ inverseOnSurface = attr(MaterialR.attr.colorOnSurfaceInverse),
+ inversePrimary = attr(MaterialR.attr.colorPrimaryInverse),
+ )
+ } else {
+ lightColorScheme(
+ primary = attr(AppCompatR.attr.colorPrimary),
+ onPrimary = attr(MaterialR.attr.colorOnPrimary),
+ primaryContainer = attr(MaterialR.attr.colorPrimaryContainer),
+ onPrimaryContainer = attr(MaterialR.attr.colorOnPrimaryContainer),
+ secondary = attr(MaterialR.attr.colorSecondary),
+ onSecondary = attr(MaterialR.attr.colorOnSecondary),
+ secondaryContainer = attr(MaterialR.attr.colorSecondaryContainer),
+ onSecondaryContainer = attr(MaterialR.attr.colorOnSecondaryContainer),
+ tertiary = attr(MaterialR.attr.colorTertiary),
+ onTertiary = attr(MaterialR.attr.colorOnTertiary),
+ tertiaryContainer = attr(MaterialR.attr.colorTertiaryContainer),
+ onTertiaryContainer = attr(MaterialR.attr.colorOnTertiaryContainer),
+ error = attr(AppCompatR.attr.colorError),
+ onError = attr(MaterialR.attr.colorOnError),
+ errorContainer = attr(MaterialR.attr.colorErrorContainer),
+ onErrorContainer = attr(MaterialR.attr.colorOnErrorContainer),
+ background = background,
+ onBackground = attr(MaterialR.attr.colorOnBackground),
+ surface = attr(MaterialR.attr.colorSurface),
+ onSurface = attr(MaterialR.attr.colorOnSurface),
+ surfaceVariant = attr(MaterialR.attr.colorSurfaceVariant),
+ onSurfaceVariant = attr(MaterialR.attr.colorOnSurfaceVariant),
+ outline = attr(MaterialR.attr.colorOutline),
+ inverseSurface = attr(MaterialR.attr.colorSurfaceInverse),
+ inverseOnSurface = attr(MaterialR.attr.colorOnSurfaceInverse),
+ inversePrimary = attr(MaterialR.attr.colorPrimaryInverse),
+ )
+ }
+}
diff --git a/Source/Android/app/src/main/res/menu/menu_game_grid.xml b/Source/Android/app/src/main/res/menu/menu_game_grid.xml
index 2a282198d1..c52c574c44 100644
--- a/Source/Android/app/src/main/res/menu/menu_game_grid.xml
+++ b/Source/Android/app/src/main/res/menu/menu_game_grid.xml
@@ -51,6 +51,11 @@
android:title="@string/grid_menu_online_system_update"
app:showAsAction="never"/>
+
+
- Import Wii Save
Import BootMii NAND Backup
Perform Online System Update
+ Netplay
Load Wii System Menu
Load Wii System Menu (%s)
Load vWii System Menu (%s)
@@ -980,4 +981,8 @@ It can efficiently compress both junk data and encrypted Wii data.
Log Out
Logging In
Login Failed
+
+
+ Netplay Setup
+ Nickname
diff --git a/Source/Android/build.gradle.kts b/Source/Android/build.gradle.kts
index 5682b2f130..c74d069b44 100644
--- a/Source/Android/build.gradle.kts
+++ b/Source/Android/build.gradle.kts
@@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.android.library) apply false
alias(libs.plugins.android.test) apply false
alias(libs.plugins.androidx.baselineprofile) apply false
+ alias(libs.plugins.kotlin.compose) apply false
}
buildscript {
diff --git a/Source/Android/gradle/libs.versions.toml b/Source/Android/gradle/libs.versions.toml
index 09fa7b09ca..e29e0c2e10 100644
--- a/Source/Android/gradle/libs.versions.toml
+++ b/Source/Android/gradle/libs.versions.toml
@@ -4,6 +4,7 @@ appcompat = "1.7.1"
benchmarkMacroJunit4 = "1.5.0-alpha04"
cardview = "1.0.0"
coil = "2.7.0"
+compose-bom = "2025.04.00"
constraintlayout = "2.2.1"
coreKtx = "1.18.0"
coreSplashscreen = "1.2.0"
@@ -27,9 +28,16 @@ tvprovider = "1.1.0"
uiautomator = "2.3.0"
[libraries]
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
+androidx-compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
+androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
@@ -58,4 +66,5 @@ android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
android-test = { id = "com.android.test", version.ref = "agp" }
androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmarkMacroJunit4" }
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }