summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Android/app/src/main')
-rw-r--r--Source/Android/app/src/main/AndroidManifest.xml1
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt3
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.kt10
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupScreen.kt136
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt2
-rw-r--r--Source/Android/app/src/main/res/layout/activity_settings.xml7
-rw-r--r--Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml5
-rw-r--r--Source/Android/app/src/main/res/values/dimens.xml2
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml3
12 files changed, 168 insertions, 10 deletions
diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml
index 96876acd46..5ff6ff090d 100644
--- a/Source/Android/app/src/main/AndroidManifest.xml
+++ b/Source/Android/app/src/main/AndroidManifest.xml
@@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK"/>
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA"/>
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
<uses-permission android:name="android.permission.VIBRATE"/>
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt
index b52b7b4fa4..e0ada502cc 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt
@@ -367,6 +367,9 @@ object NativeLibrary {
external fun PauseEmulation(overrideAchievementRestrictions: Boolean)
@JvmStatic
+ external fun ResetEmulation()
+
+ @JvmStatic
external fun StopEmulation()
/**
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt
index 23ffe96cd6..2761c4b4e9 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt
@@ -525,6 +525,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
MENU_ACTION_SKYLANDERS -> showSkylanderPortalSettings()
MENU_ACTION_INFINITY_BASE -> showInfinityBaseSettings()
MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation()
+ MENU_ACTION_RESET -> emulationFragment!!.resetEmulation()
}
}
@@ -1084,6 +1085,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
const val MENU_ACTION_SKYLANDERS = 36
const val MENU_ACTION_INFINITY_BASE = 37
const val MENU_ACTION_LATCHING_CONTROLS = 38
+ const val MENU_ACTION_RESET = 39;
init {
buttonsActionsMap.apply {
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.kt
index f67a23e2da..eabfb75f82 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.kt
@@ -3,6 +3,7 @@
package org.dolphinemu.dolphinemu.adapters
import android.view.ViewGroup
+import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter
@@ -16,9 +17,16 @@ class SettingsRowPresenter : Presenter() {
val settingsCard = ImageCardView(parent.context)
settingsCard.apply {
setMainImageAdjustViewBounds(true)
- setMainImageDimensions(192, 160)
+ setMainImageDimensions(
+ resources.getDimensionPixelSize(R.dimen.tv_settings_card_width),
+ resources.getDimensionPixelSize(R.dimen.tv_settings_card_height)
+ )
isFocusable = true
isFocusableInTouchMode = true
+ findViewById<TextView>(R.id.title_text).apply {
+ minLines = 2
+ maxLines = 2
+ }
}
// Use that view to create a ViewHolder.
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupScreen.kt
index ae222e8722..8b532b22de 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupScreen.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupScreen.kt
@@ -1,9 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
-@file:OptIn(ExperimentalMaterial3Api::class)
+@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
package org.dolphinemu.dolphinemu.features.netplay.ui
+import android.Manifest
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -19,6 +24,7 @@ import androidx.compose.foundation.text.KeyboardOptions
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.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
@@ -42,13 +48,22 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LifecycleEventEffect
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.PermissionState
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+import com.google.accompanist.permissions.shouldShowRationale
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -58,6 +73,7 @@ import org.dolphinemu.dolphinemu.features.netplay.model.ConnectionType
import org.dolphinemu.dolphinemu.ui.theme.DolphinScaffold
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
import org.dolphinemu.dolphinemu.ui.theme.MenuSpacer
+import org.dolphinemu.dolphinemu.ui.theme.OutlinedBox
private data class ErrorDialogState(val message: String) {
val onDismissed = CompletableDeferred<Unit>()
@@ -87,6 +103,23 @@ fun NetplaySetupScreen(
onHostClicked: () -> Unit,
onConnectClicked: () -> Unit,
) {
+ val localNetworkPermissionState: PermissionState? =
+ if (
+ connectionType is ConnectionType.DirectConnection &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN
+ ) {
+ rememberPermissionState(Manifest.permission.ACCESS_LOCAL_NETWORK) { _ ->
+ // Try to connect even if the user denied the permission. The warning is on screen
+ // so it's up to them to determine if they are using local network and need to grant it.
+ when (connectionRole) {
+ ConnectionRole.Host -> onHostClicked()
+ ConnectionRole.Connect -> onConnectClicked()
+ }
+ }
+ } else {
+ null
+ }
+
DolphinScaffold(
title = { Text(stringResource(R.string.netplay_setup_title)) },
navigationIcon = {
@@ -99,9 +132,19 @@ fun NetplaySetupScreen(
},
floatingActionButton = {
ExtendedFloatingActionButton(
- onClick = when(connectionRole) {
- ConnectionRole.Host -> onHostClicked
- ConnectionRole.Connect -> onConnectClicked
+ onClick = {
+ if (
+ localNetworkPermissionState != null &&
+ !localNetworkPermissionState.status.isGranted &&
+ !localNetworkPermissionState.status.shouldShowRationale
+ ) {
+ localNetworkPermissionState.launchPermissionRequest()
+ } else {
+ when (connectionRole) {
+ ConnectionRole.Host -> onHostClicked()
+ ConnectionRole.Connect -> onConnectClicked()
+ }
+ }
},
) {
if (connecting) {
@@ -238,6 +281,10 @@ private fun NetplaySetupContent(
onUseUpnpChanged = onUseUpnpChanged,
)
}
+
+ LocalNetworkPermissionInfo(
+ connectionType = connectionType,
+ )
}
@Composable
@@ -374,8 +421,87 @@ private fun HostMenu(
)
}
}
+ }
+}
+
+@Composable
+private fun LocalNetworkPermissionInfo(
+ connectionType: ConnectionType,
+) {
+ if (
+ connectionType is ConnectionType.DirectConnection &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN
+ ) {
+ val context = LocalContext.current
+
+ // Android permissions do not differentiate between isGranted being false because the user
+ // has never been asked and isGranted being false because the user denied twice. We only
+ // get shouldShowRationale which, if the user keeps denying goes false, true, always false.
+ // So if the value does not change after calling launchPermissionRequest we know we are in
+ // the final denied state and launch app settings for the user to manually grant from there.
+ var previousShouldShowRationale by rememberSaveable { mutableStateOf(false) }
- MenuSpacer()
+ // Check if the permission dialog actually showed by detecting an onPause event, this allows
+ // us to detect if the dialog was dismissed with the back button which would prevent
+ // previousShouldShowRationale from toggling as described above.
+ var dialogShown by rememberSaveable { mutableStateOf(false) }
+ LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) { dialogShown = true }
+
+ lateinit var permissionState: PermissionState
+ permissionState =
+ rememberPermissionState(Manifest.permission.ACCESS_LOCAL_NETWORK) { granted ->
+ if (
+ !granted &&
+ !dialogShown &&
+ previousShouldShowRationale == permissionState.status.shouldShowRationale
+ ) {
+ context.startActivity(
+ Intent(
+ Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", context.packageName, null)
+ )
+ )
+ }
+ previousShouldShowRationale = permissionState.status.shouldShowRationale
+ }
+
+ if (!permissionState.status.isGranted) {
+ MenuSpacer()
+
+ OutlinedBox(
+ label = {
+ Icon(
+ imageVector = Icons.Default.Warning,
+ tint = MaterialTheme.colorScheme.error,
+ contentDescription = null,
+ )
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Text(
+ text = stringResource(R.string.netplay_local_network_permission_info),
+ color = MaterialTheme.colorScheme.secondary,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ TextButton(
+ onClick = {
+ previousShouldShowRationale = permissionState.status.shouldShowRationale
+ dialogShown = false
+ permissionState.launchPermissionRequest()
+ },
+ modifier = Modifier.align(Alignment.End)
+ ) {
+ Text(text = stringResource(R.string.netplay_give_permission))
+ }
+ }
+ }
+ }
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
index 210a34b23f..8a46a698ce 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -1359,7 +1359,7 @@ class SettingsFragmentPresenter(
)
sl.add(
IntSliderSetting(
- context, mem2Size, R.string.main_mem2_size, 0, 64, 128, "MB", 1
+ context, mem2Size, R.string.main_mem2_size, 0, 64, 256, "MB", 1
)
)
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
index a005995bdb..a0215d4cd8 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
@@ -165,6 +165,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
runWhenSurfaceIsValid = true
}
+ fun resetEmulation() {
+ Log.debug("[EmulationFragment] Resetting emulation.")
+ NativeLibrary.ResetEmulation()
+ }
+
fun stopEmulation() {
Log.debug("[EmulationFragment] Stopping emulation.")
NativeLibrary.StopEmulation()
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
index 6bf9c7c147..ee4bb50e3f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
@@ -76,6 +76,7 @@ class MenuFragment : Fragment(), View.OnClickListener {
button.setOnClickListener(this)
}
+ binding.menuReset.setOnClickListener(this)
binding.menuExit.setOnClickListener(this)
val title = requireArguments().getString(KEY_TITLE, null)
@@ -199,6 +200,7 @@ class MenuFragment : Fragment(), View.OnClickListener {
EmulationActivity.MENU_ACTION_CHANGE_DISC
)
buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT)
+ buttonsActionsMap.append(R.id.menu_reset, EmulationActivity.MENU_ACTION_RESET)
buttonsActionsMap.append(R.id.menu_settings, EmulationActivity.MENU_ACTION_SETTINGS)
buttonsActionsMap.append(R.id.menu_skylanders, EmulationActivity.MENU_ACTION_SKYLANDERS)
buttonsActionsMap.append(
diff --git a/Source/Android/app/src/main/res/layout/activity_settings.xml b/Source/Android/app/src/main/res/layout/activity_settings.xml
index fbecfd3886..b4226e3d9f 100644
--- a/Source/Android/app/src/main/res/layout/activity_settings.xml
+++ b/Source/Android/app/src/main/res/layout/activity_settings.xml
@@ -140,10 +140,11 @@
</com.google.android.material.appbar.MaterialToolbar>
- <View
+ <com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="@color/settings_search_outline" />
+ android:layout_height="wrap_content"
+ app:dividerColor="@color/settings_search_outline"
+ app:dividerThickness="1dp" />
</LinearLayout>
diff --git a/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml b/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml
index 446706b821..39d992061e 100644
--- a/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml
+++ b/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml
@@ -111,6 +111,11 @@
android:text="@string/emulate_infinity_base"
style="@style/InGameMenuOption" />
+ <Button
+ android:id="@+id/menu_reset"
+ style="@style/InGameMenuOption"
+ android:text="@string/emulation_reset" />
+
</LinearLayout>
</ScrollView>
diff --git a/Source/Android/app/src/main/res/values/dimens.xml b/Source/Android/app/src/main/res/values/dimens.xml
index f10f34e81a..882f891bab 100644
--- a/Source/Android/app/src/main/res/values/dimens.xml
+++ b/Source/Android/app/src/main/res/values/dimens.xml
@@ -7,6 +7,8 @@
<dimen name="spacing_fab">72dp</dimen>
<dimen name="menu_width">256dp</dimen>
<dimen name="card_width">135dp</dimen>
+ <dimen name="tv_settings_card_width">120dp</dimen>
+ <dimen name="tv_settings_card_height">80dp</dimen>
<dimen name="elevated_app_bar">3dp</dimen>
</resources>
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index 2204f4380f..f8099fd652 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -630,6 +630,7 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="emulation_screenshot">Take Screenshot</string>
<string name="emulation_savestate">Save State</string>
<string name="emulation_loadstate">Load State</string>
+ <string name="emulation_reset">Reset Emulation</string>
<string name="emulation_exit">Exit Emulation</string>
<string name="emulation_state_slot">Slot %1$d\n\n%2$s</string>
<string name="emulation_state_slot_empty">Slot %1$d\n\nEmpty</string>
@@ -1062,4 +1063,6 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="netplay_address_share">Share address</string>
<string name="netplay_address_retry">Retry</string>
<string name="netplay_transfer_progress">%1$s/%2$s MiB</string>
+ <string name="netplay_local_network_permission_info">Dolphin needs permission to connect to devices on a local network. If you only want to use netplay over the internet, the permission is not required.</string>
+ <string name="netplay_give_permission">Give permission</string>
</resources>