forked from MeloNX/MeloNX
add settings view
This commit is contained in:
parent
21a78f173e
commit
7b1ce437ef
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user