Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: settings UI implementation. #77

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dab915f
feat: initial m3 theme support.
MAshhal Mar 4, 2025
ae4a24f
feat: apply m3 theming to application. (Disabled Dynamic Theming for …
MAshhal Mar 4, 2025
51ab82e
feat: add utility functions commonly used in settings.
MAshhal Mar 4, 2025
445c7cb
feat: Add PreferenceIcon and PreferenceTitle components
MAshhal Mar 4, 2025
6554cd3
feat: Introduce ComposePreview for themed UI previews
MAshhal Mar 4, 2025
55545b4
feat: Add BasePreference composable for settings UI
MAshhal Mar 4, 2025
8bd38b9
feat: add support for disabling preferences
MAshhal Mar 4, 2025
98c4d59
feat: Introduce RegularPreference composable for settings
MAshhal Mar 4, 2025
68c8985
feat: re-write settings screen.
MAshhal Mar 4, 2025
d8aa0b5
feat: Introduce NavHost for navigating between destinations.
MAshhal Mar 4, 2025
3d6b6d4
Merge branch 'master' into settings_groundwork
MAshhal Mar 4, 2025
52ab20c
Merge branch 'master' into settings_groundwork
MAshhal Mar 8, 2025
18f688b
fix: remove conflicting declaration (missed when merging master into …
MAshhal Mar 8, 2025
8fc8ea0
feat: add switch preference.
MAshhal Mar 8, 2025
76be004
refactor: make switch preferences preview both light/dark mode.
MAshhal Mar 8, 2025
6884ae0
feat: add checkbox preference.
MAshhal Mar 8, 2025
8262ed0
feat: add slider preference.
MAshhal Mar 8, 2025
da38f47
refactor: make RegularPreference use its lambda-based overload instea…
MAshhal Mar 8, 2025
cf32c91
feat: add PreferenceIcon overload accepting drawable icons.
MAshhal Mar 8, 2025
7aa24b6
feat: refactor SettingsScreen and add folder icon.
MAshhal Mar 8, 2025
d590696
feat: open internal storage directory from settings.
MAshhal Mar 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion app/src/main/java/net/rpcs3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import net.rpcs3.ui.navigation.AppNavHost
import net.rpcs3.ui.theme.AppTheme
import kotlin.concurrent.thread

private const val ACTION_USB_PERMISSION = "net.rpcs3.USB_PERMISSION"

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppNavHost()
AppTheme(
dynamicColor = false
) {
AppNavHost()
}
}

RPCS3.rootDirectory = applicationContext.getExternalFilesDir(null).toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import java.io.FileNotFoundException

class AppDataDocumentProvider : DocumentsProvider() {
companion object {
private const val ROOT_ID = "root"
const val ROOT_ID = "root"
const val AUTHORITY = "net.rpcs3" + ".documents"

private val DEFAULT_ROOT_PROJECTION = arrayOf(
Root.COLUMN_ROOT_ID,
Expand Down
33 changes: 33 additions & 0 deletions app/src/main/java/net/rpcs3/ui/common/Previews.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.rpcs3.ui.common

import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import net.rpcs3.ui.theme.AppTheme


/**
* a composable function for previewing UI elements within the application's theme.
*
* this functions wraps the provided content within a Material3 `Surface` and applies the application's
* theme for consistent M3 previews, otherwise, the preview defaults to using the system theme.
*
* @param modifier The modifier to be applied to the `Surface`.
* @param content The content to be previewed.
*
* @see AppTheme
*/

@Composable
fun ComposePreview(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
AppTheme {
Surface(
modifier = modifier
) {
content()
}
}
}
249 changes: 144 additions & 105 deletions app/src/main/java/net/rpcs3/ui/navigation/AppNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.Build
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
Expand All @@ -38,33 +39,63 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.activity.compose.BackHandler
import kotlinx.coroutines.launch
import net.rpcs3.FirmwareRepository
import net.rpcs3.GameRepository
import net.rpcs3.ProgressRepository
import net.rpcs3.RPCS3
import net.rpcs3.ui.games.GamesScreen
import net.rpcs3.ui.settings.SettingsScreen
import net.rpcs3.dialogs.AlertDialogQueue
import kotlin.concurrent.thread

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun AppNavHost() {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
val navController = rememberNavController()
AlertDialogQueue.alertDialog()
NavHost(
navController = navController,
startDestination = "games"
) {
composable(
route = "games"
) {
GamesDestination(
navigateToSettings = { navController.navigate("settings") }
)
}

composable(
route = "settings"
) {
SettingsScreen(
navigateBack = navController::navigateUp
)
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GamesDestination(
navigateToSettings: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)

BackHandler(enabled = drawerState.isOpen) {
scope.launch {
drawerState.close()
}
}

val context = LocalContext.current

AlertDialogQueue.alertDialog()

val installPkgLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri: Uri? ->
Expand Down Expand Up @@ -148,117 +179,125 @@ fun AppNavHost() {
}
)

MaterialTheme {
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.verticalScroll(
rememberScrollState()
)
) {
Spacer(Modifier.height(12.dp))
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.verticalScroll(
rememberScrollState()
)
) {
Spacer(Modifier.height(12.dp))

NavigationDrawerItem(
label = {
Text(
"Firmware: " + (FirmwareRepository.version.value ?: "None")
)
},
selected = false,
icon = { Icon(Icons.Outlined.Build, contentDescription = null) },
badge = {
val progressChannel = FirmwareRepository.progressChannel
val progress = ProgressRepository.getItem(progressChannel.value)
val progressValue = progress?.value?.value
val maxValue = progress?.value?.max
Log.e("Main", "Update $progressChannel, $progress")
if (progressValue != null && maxValue != null) {
if (maxValue.longValue != 0L) {
CircularProgressIndicator(
modifier = Modifier
.width(32.dp)
.height(32.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
progress = {
progressValue.longValue.toFloat() / maxValue.longValue.toFloat()
},
)
} else {
CircularProgressIndicator(
modifier = Modifier
.width(32.dp)
.height(32.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
}
}, // Placeholder
onClick = {
if (FirmwareRepository.progressChannel.value == null) {
installFwLauncher.launch("*/*")
NavigationDrawerItem(
label = {
Text(
"Firmware: " + (FirmwareRepository.version.value ?: "None")
)
},
selected = false,
icon = { Icon(Icons.Outlined.Build, contentDescription = null) },
badge = {
val progressChannel = FirmwareRepository.progressChannel
val progress = ProgressRepository.getItem(progressChannel.value)
val progressValue = progress?.value?.value
val maxValue = progress?.value?.max
Log.e("Main", "Update $progressChannel, $progress")
if (progressValue != null && maxValue != null) {
if (maxValue.longValue != 0L) {
CircularProgressIndicator(
modifier = Modifier
.width(32.dp)
.height(32.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
progress = {
progressValue.longValue.toFloat() / maxValue.longValue.toFloat()
},
)
} else {
CircularProgressIndicator(
modifier = Modifier
.width(32.dp)
.height(32.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
}
)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
}
}, // Placeholder
onClick = {
if (FirmwareRepository.progressChannel.value == null) {
installFwLauncher.launch("*/*")
}
}
)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
}
}
) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text(
"RPCS3",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
IconButton(onClick = {
scope.launch {
if (drawerState.isClosed) {
drawerState.open()
} else {
drawerState.close()
}
}
) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text(
"RPCS3",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
IconButton(onClick = {
scope.launch {
if (drawerState.isClosed) {
drawerState.open()
} else {
drawerState.close()
}
}) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Open menu"
)
}
},
actions = {
}) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Open menu"
)
}
},
actions = {
IconButton(
onClick = dropUnlessResumed(
block = navigateToSettings
),
) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = "Open Settings"
)
}
// IconButton(onClick = { /* do something */ }) {
// Icon(
// imageVector = Icons.Filled.Search,
// contentDescription = null
// )
// }
}
)
},
floatingActionButton = {
FloatingActionButton(
onClick = { installPkgLauncher.launch("*/*") },
modifier = Modifier.padding(16.dp)
) {
Icon(Icons.Filled.Add, "Add game")
}
},
) { innerPadding -> Column(modifier = Modifier.padding(innerPadding)) { GamesScreen() } }
}
)
},
floatingActionButton = {
FloatingActionButton(
onClick = { installPkgLauncher.launch("*/*") },
modifier = Modifier.padding(16.dp)
) {
Icon(Icons.Filled.Add, "Add game")
}
},
) { innerPadding -> Column(modifier = Modifier.padding(innerPadding)) { GamesScreen() } }
}
}
Loading
Loading