From 7b1ce437ef7e1021f391a13cc0dee8c86a6a2475 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 6 Jul 2023 20:22:48 +0000 Subject: [PATCH] add settings view --- .../main/java/org/ryujinx/android/GameHost.kt | 25 +- .../android/viewmodels/QuickSettings.kt | 25 ++ .../android/viewmodels/SettingsViewModel.kt | 53 +++ .../org/ryujinx/android/views/HomeViews.kt | 142 +++++-- .../org/ryujinx/android/views/MainView.kt | 8 +- .../org/ryujinx/android/views/SettingViews.kt | 346 ++++++++++++++++++ .../ryujinx/android/views/TitleUpdateViews.kt | 8 +- .../main/jniLibs/arm64-v8a/insert libs here | 0 8 files changed, 551 insertions(+), 56 deletions(-) create mode 100644 src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt create mode 100644 src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt create mode 100644 src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt create mode 100644 src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/insert libs here diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt index d9a804dfe..e741d25cf 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt @@ -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 + ) + + var settings = QuickSettings(mainViewModel.activity) + + success = _nativeRyujinx.deviceInitialize( + settings.isHostMapped, + settings.useNce, + SystemLanguage.AmericanEnglish.ordinal, + RegionCode.USA.ordinal, + settings.enableVsync, + settings.enableDocked, + settings.enablePtc, + false, + "UTC", + settings.ignoreMissingServices ); - success = _nativeRyujinx.deviceInitialize(true, true, - SystemLanguage.AmericanEnglish.ordinal, - RegionCode.USA.ordinal, - true, - true, - true, - false, - "UTC", - false); success = _nativeRyujinx.deviceLoad(path) diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt new file mode 100644 index 000000000..4d07f5a99 --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt new file mode 100644 index 000000000..63d633fd9 --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt @@ -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, + useNce : MutableState, + enableVsync : MutableState, + enableDocked : MutableState, + enablePtc : MutableState, + ignoreMissingServices : MutableState) + { + + 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, + useNce : MutableState, + enableVsync : MutableState, + enableDocked : MutableState, + enablePtc : MutableState, + ignoreMissingServices : MutableState){ + 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() + } +} \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt index ea21ecd5f..6e52bc784 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt @@ -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,48 +75,98 @@ class HomeViews { @OptIn(ExperimentalMaterial3Api::class) @Composable - fun MainTopBar() { - TopAppBar( - modifier = Modifier - .zIndex(1f) - .padding(top = 10.dp), - title = { - DockedSearchBar( - shape = SearchBarDefaults.inputFieldShape, - query = "", - onQueryChange = {}, - onSearch = {}, - active = false, - onActiveChange = {}, - leadingIcon = { - Icon( - Icons.Filled.Search, - contentDescription = "Search Games" - ) + fun MainTopBar(navController: NavHostController) { + var topBarSize = remember { + mutableStateOf(0) + } + Column { + var showOptionsPopup = remember { + mutableStateOf(false) + } + TopAppBar( + modifier = Modifier + .zIndex(1f) + .padding(top = 16.dp) + .onSizeChanged { + topBarSize.value = it.height }, - placeholder = { - Text(text = "Search Games") - } - ) { + title = { + DockedSearchBar( + shape = SearchBarDefaults.inputFieldShape, + query = "", + onQueryChange = {}, + onSearch = {}, + active = false, + onActiveChange = {}, + leadingIcon = { + Icon( + Icons.Filled.Search, + contentDescription = "Search Games" + ) + }, + placeholder = { + Text(text = "Search Games") + } + ) { + } + }, + actions = { + IconButton( + onClick = { + showOptionsPopup.value = true; + } + ) { + Icon( + Icons.Filled.MoreVert, + contentDescription = "More" + ) + } } - }, - actions = { - IconButton( - onClick = { } - ) { - Icon( - Icons.Filled.MoreVert, - contentDescription = "More" - ) + ) + 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) ) diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt index 0392cac3f..d73b90376 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt @@ -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; diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt new file mode 100644 index 000000000..d523533a4 --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt @@ -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() + } + } + } + } +} \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt index 42b9d3ce5..4dff953a8 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt @@ -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 = { diff --git a/src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/insert libs here b/src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/insert libs here new file mode 100644 index 000000000..e69de29bb