add settings view

This commit is contained in:
Emmanuel Hansen 2023-07-06 20:22:48 +00:00
parent 21a78f173e
commit 7b1ce437ef
8 changed files with 551 additions and 56 deletions

View File

@ -10,6 +10,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings
import kotlin.concurrent.thread
import kotlin.math.roundToInt
@ -105,16 +106,22 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
success = _nativeRyujinx.graphicsInitializeRenderer(
nativeInterop!!.VkRequiredExtensions!!,
window
);
success = _nativeRyujinx.deviceInitialize(true, true,
)
var settings = QuickSettings(mainViewModel.activity)
success = _nativeRyujinx.deviceInitialize(
settings.isHostMapped,
settings.useNce,
SystemLanguage.AmericanEnglish.ordinal,
RegionCode.USA.ordinal,
true,
true,
true,
settings.enableVsync,
settings.enableDocked,
settings.enablePtc,
false,
"UTC",
false);
settings.ignoreMissingServices
);
success = _nativeRyujinx.deviceLoad(path)

View File

@ -0,0 +1,25 @@
package org.ryujinx.android.viewmodels
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import org.ryujinx.android.MainActivity
class QuickSettings(val activity: MainActivity) {
var ignoreMissingServices: Boolean
var enablePtc: Boolean
var enableDocked: Boolean
var enableVsync: Boolean
var useNce: Boolean
var isHostMapped: Boolean
private var sharedPref: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
init {
isHostMapped = sharedPref.getBoolean("isHostMapped", true)
useNce = sharedPref.getBoolean("useNce", true)
enableVsync = sharedPref.getBoolean("enableVsync", true)
enableDocked = sharedPref.getBoolean("enableDocked", true)
enablePtc = sharedPref.getBoolean("enablePtc", true)
ignoreMissingServices = sharedPref.getBoolean("ignoreMissingServices", false)
}
}

View File

@ -0,0 +1,53 @@
package org.ryujinx.android.viewmodels
import android.content.SharedPreferences
import androidx.compose.runtime.MutableState
import androidx.navigation.NavHostController
import androidx.preference.PreferenceManager
import org.ryujinx.android.MainActivity
class SettingsViewModel(var navController: NavHostController, val activity: MainActivity) {
private var sharedPref: SharedPreferences
init {
sharedPref = getPreferences()
}
private fun getPreferences() : SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(activity)
}
fun initializeState(isHostMapped : MutableState<Boolean>,
useNce : MutableState<Boolean>,
enableVsync : MutableState<Boolean>,
enableDocked : MutableState<Boolean>,
enablePtc : MutableState<Boolean>,
ignoreMissingServices : MutableState<Boolean>)
{
isHostMapped.value = sharedPref.getBoolean("isHostMapped", true)
useNce.value = sharedPref.getBoolean("useNce", true)
enableVsync.value = sharedPref.getBoolean("enableVsync", true)
enableDocked.value = sharedPref.getBoolean("enableDocked", true)
enablePtc.value = sharedPref.getBoolean("enablePtc", true)
ignoreMissingServices.value = sharedPref.getBoolean("ignoreMissingServices", false)
}
fun save(isHostMapped : MutableState<Boolean>,
useNce : MutableState<Boolean>,
enableVsync : MutableState<Boolean>,
enableDocked : MutableState<Boolean>,
enablePtc : MutableState<Boolean>,
ignoreMissingServices : MutableState<Boolean>){
var editor = sharedPref.edit()
editor.putBoolean("isHostMapped", isHostMapped?.value ?: true)
editor.putBoolean("useNce", useNce?.value ?: true)
editor.putBoolean("enableVsync", enableVsync?.value ?: true)
editor.putBoolean("enableDocked", enableDocked?.value ?: true)
editor.putBoolean("enablePtc", enablePtc?.value ?: true)
editor.putBoolean("ignoreMissingServices", ignoreMissingServices?.value ?: false)
editor.apply()
}
}

View File

@ -1,6 +1,7 @@
package org.ryujinx.android.views
import android.content.res.Resources
import android.view.Gravity
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
@ -21,6 +22,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.Card
@ -36,6 +38,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@ -47,10 +50,17 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogWindowProvider
import androidx.compose.ui.window.Popup
import androidx.compose.ui.zIndex
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import coil.compose.AsyncImage
import org.ryujinx.android.MainActivity
import org.ryujinx.android.R
@ -65,11 +75,21 @@ class HomeViews {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainTopBar() {
fun MainTopBar(navController: NavHostController) {
var topBarSize = remember {
mutableStateOf(0)
}
Column {
var showOptionsPopup = remember {
mutableStateOf(false)
}
TopAppBar(
modifier = Modifier
.zIndex(1f)
.padding(top = 10.dp),
.padding(top = 16.dp)
.onSizeChanged {
topBarSize.value = it.height
},
title = {
DockedSearchBar(
shape = SearchBarDefaults.inputFieldShape,
@ -93,7 +113,9 @@ class HomeViews {
},
actions = {
IconButton(
onClick = { }
onClick = {
showOptionsPopup.value = true;
}
) {
Icon(
Icons.Filled.MoreVert,
@ -102,11 +124,49 @@ class HomeViews {
}
}
)
Box {
if(showOptionsPopup.value)
{
AlertDialog(
modifier = Modifier.padding(top = (topBarSize.value / Resources.getSystem().displayMetrics.density + 10).dp,
start = 16.dp, end = 16.dp),
onDismissRequest = {
showOptionsPopup.value = false
}) {
val dialogWindowProvider = LocalView.current.parent as DialogWindowProvider
dialogWindowProvider.window.setGravity(Gravity.TOP)
Surface(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(16.dp),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
Column {
TextButton(onClick = {
navController.navigate("settings")
}, modifier = Modifier.fillMaxWidth()
.align(Alignment.Start),
) {
Icon(
Icons.Filled.Settings,
contentDescription = "Settings"
)
Text(text = "Settings", modifier = Modifier.padding(16.dp)
.align(Alignment.CenterVertically))
}
}
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Home(viewModel: HomeViewModel = HomeViewModel()) {
fun Home(viewModel: HomeViewModel = HomeViewModel(), navController: NavHostController? = null) {
val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
var showBottomSheet = remember { mutableStateOf(false) }
@ -114,7 +174,9 @@ class HomeViews {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
MainTopBar()
navController?.apply {
MainTopBar(navController)
}
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
@ -168,7 +230,7 @@ class HomeViews {
}
}
Surface(color = MaterialTheme.colorScheme.surface,
modifier = Modifier.padding(10.dp)) {
modifier = Modifier.padding(16.dp)) {
Column(modifier = Modifier.fillMaxSize()) {
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
Card(
@ -176,7 +238,7 @@ class HomeViews {
openDialog.value = true
}
) {
Column(modifier = Modifier.padding(10.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Icon(
painter = painterResource(R.drawable.app_update),
contentDescription = "More",
@ -206,7 +268,7 @@ class HomeViews {
Card(shape = MaterialTheme.shapes.medium,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.padding(16.dp)
.combinedClickable(
onClick = {
if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
@ -219,7 +281,7 @@ class HomeViews {
})) {
Row(modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween) {
Row {
if(!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000")
@ -231,7 +293,7 @@ class HomeViews {
AsyncImage(model = imageFile,
contentDescription = gameModel.titleName + " icon",
modifier = Modifier
.padding(end = 5.dp)
.padding(end = 8.dp)
.width(size.roundToInt().dp)
.height(size.roundToInt().dp))
}
@ -258,7 +320,7 @@ class HomeViews {
Icons.Filled.Add,
contentDescription = "Options",
modifier = Modifier
.padding(end = 5.dp)
.padding(end = 8.dp)
.width(size.roundToInt().dp)
.height(size.roundToInt().dp)
)

View File

@ -20,6 +20,7 @@ import androidx.navigation.compose.rememberNavController
import org.ryujinx.android.GameController
import org.ryujinx.android.GameHost
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.SettingsViewModel
class MainView {
companion object {
@ -29,8 +30,9 @@ class MainView {
mainViewModel.setNavController(navController)
NavHost(navController = navController, startDestination = "home") {
composable("home") {HomeViews.Home(mainViewModel.homeViewModel)}
composable("game") { GameView(mainViewModel)}
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
composable("game") { GameView(mainViewModel) }
composable("settings") { SettingViews.Main(SettingsViewModel(navController, mainViewModel.activity)) }
}
}
@ -61,7 +63,7 @@ class MainView {
mutableStateOf(0.0)
}
Surface(modifier = Modifier.padding(10.dp),
Surface(modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.surface.copy(0.4f)) {
Column {
var gameTimeVal = 0.0;

View File

@ -0,0 +1,346 @@
package org.ryujinx.android.views
import android.annotation.SuppressLint
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import org.ryujinx.android.viewmodels.SettingsViewModel
class SettingViews {
companion object {
const val EXPANSTION_TRANSITION_DURATION = 450
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Main(settingsViewModel: SettingsViewModel) {
var loaded = remember {
mutableStateOf(false)
}
var isHostMapped = remember {
mutableStateOf(false)
}
var useNce = remember {
mutableStateOf(false)
}
var enableVsync = remember {
mutableStateOf(false)
}
var enableDocked = remember {
mutableStateOf(false)
}
var enablePtc = remember {
mutableStateOf(false)
}
var ignoreMissingServices = remember {
mutableStateOf(false)
}
if (!loaded.value) {
settingsViewModel.initializeState(
isHostMapped,
useNce,
enableVsync, enableDocked, enablePtc, ignoreMissingServices
)
loaded.value = true
}
Scaffold(modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(title = {
Text(text = "Settings")
},
modifier = Modifier.padding(top = 16.dp),
navigationIcon = {
IconButton(onClick = {
settingsViewModel.save(
isHostMapped,
useNce,
enableVsync,
enableDocked,
enablePtc,
ignoreMissingServices
)
settingsViewModel.navController.popBackStack()
}) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
}
})
}) { contentPadding ->
Column(modifier = Modifier.padding(contentPadding)) {
BackHandler {
settingsViewModel.save(
isHostMapped,
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices
)
}
ExpandableView(onCardArrowClick = { }, title = "System") {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Use NCE",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = useNce.value, onCheckedChange = {
useNce.value = !useNce.value
})
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Is Host Mapped",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = isHostMapped.value, onCheckedChange = {
isHostMapped.value = !isHostMapped.value
})
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable VSync",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = enableVsync.value, onCheckedChange = {
enableVsync.value = !enableVsync.value
})
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable PTC",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = enablePtc.value, onCheckedChange = {
enablePtc.value = !enablePtc.value
})
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable Docked Mode",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = enableDocked.value, onCheckedChange = {
enableDocked.value = !enableDocked.value
})
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Ignore Missing Services",
modifier = Modifier.align(Alignment.CenterVertically)
)
Switch(checked = ignoreMissingServices.value, onCheckedChange = {
ignoreMissingServices.value = !ignoreMissingServices.value
})
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@SuppressLint("UnusedTransitionTargetStateParameter")
fun ExpandableView(
onCardArrowClick: () -> Unit,
title: String,
content: @Composable () -> Unit
) {
var expanded = false
var mutableExpanded = remember {
mutableStateOf(expanded)
}
val transitionState = remember {
MutableTransitionState(expanded).apply {
targetState = !mutableExpanded.value
}
}
val transition = updateTransition(transitionState, label = "transition")
val cardPaddingHorizontal by transition.animateDp({
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
}, label = "paddingTransition") {
if (mutableExpanded.value) 48.dp else 24.dp
}
val cardElevation by transition.animateDp({
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
}, label = "elevationTransition") {
if (mutableExpanded.value) 24.dp else 4.dp
}
val cardRoundedCorners by transition.animateDp({
tween(
durationMillis = EXPANSTION_TRANSITION_DURATION,
easing = FastOutSlowInEasing
)
}, label = "cornersTransition") {
if (mutableExpanded.value) 0.dp else 16.dp
}
val arrowRotationDegree by transition.animateFloat({
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
}, label = "rotationDegreeTransition") {
if (mutableExpanded.value) 0f else 180f
}
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = cardPaddingHorizontal,
vertical = 8.dp
)
) {
Column {
Card(
onClick = {
mutableExpanded.value = !mutableExpanded.value
onCardArrowClick()
}) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
CardTitle(title = title)
CardArrow(
degrees = arrowRotationDegree,
)
}
}
ExpandableContent(visible = mutableExpanded.value, content = content)
}
}
}
@Composable
fun CardArrow(
degrees: Float,
) {
Icon(
Icons.Filled.KeyboardArrowUp,
contentDescription = "Expandable Arrow",
modifier = Modifier.padding(8.dp).rotate(degrees),
)
}
@Composable
fun CardTitle(title: String) {
Text(
text = title,
modifier = Modifier
.padding(16.dp),
textAlign = TextAlign.Center,
)
}
@Composable
fun ExpandableContent(
visible: Boolean = true,
content: @Composable () -> Unit
) {
val enterTransition = remember {
expandVertically(
expandFrom = Alignment.Top,
animationSpec = tween(EXPANSTION_TRANSITION_DURATION)
) + fadeIn(
initialAlpha = 0.3f,
animationSpec = tween(EXPANSTION_TRANSITION_DURATION)
)
}
val exitTransition = remember {
shrinkVertically(
// Expand from the top.
shrinkTowards = Alignment.Top,
animationSpec = tween(EXPANSTION_TRANSITION_DURATION)
) + fadeOut(
// Fade in with the initial alpha of 0.3f.
animationSpec = tween(EXPANSTION_TRANSITION_DURATION)
)
}
AnimatedVisibility(
visible = visible,
enter = enterTransition,
exit = exitTransition
) {
Column(modifier = Modifier.padding(8.dp)) {
content()
}
}
}
}
}

View File

@ -46,7 +46,7 @@ class TitleUpdateViews {
Text(text = "Updates for ${name}", textAlign = TextAlign.Center)
Surface(
modifier = Modifier
.padding(5.dp),
.padding(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
shape = MaterialTheme.shapes.medium
) {
@ -55,7 +55,7 @@ class TitleUpdateViews {
.height(300.dp)
.fillMaxWidth()
) {
Row(modifier = Modifier.padding(5.dp)) {
Row(modifier = Modifier.padding(8.dp)) {
RadioButton(
selected = (selected.value == 0),
onClick = { selected.value = 0
@ -75,7 +75,7 @@ class TitleUpdateViews {
var index = 1
for (path in paths) {
var i = index
Row(modifier = Modifier.padding(5.dp)) {
Row(modifier = Modifier.padding(8.dp)) {
RadioButton(
selected = (selected.value == i),
onClick = { selected.value = i })
@ -115,7 +115,7 @@ class TitleUpdateViews {
}
}
Spacer(modifier = Modifier.height(15.dp))
Spacer(modifier = Modifier.height(18.dp))
TextButton(
modifier = Modifier.align(Alignment.End),
onClick = {