diff --git a/android/app/src/main/java/net/rpcsx/next/MainActivity.kt b/android/app/src/main/java/net/rpcsx/next/MainActivity.kt
index 1caf005..84db62f 100644
--- a/android/app/src/main/java/net/rpcsx/next/MainActivity.kt
+++ b/android/app/src/main/java/net/rpcsx/next/MainActivity.kt
@@ -4,6 +4,14 @@ import expo.modules.splashscreen.SplashScreenManager
import android.os.Build
import android.os.Bundle
+import android.content.res.Configuration
+import android.view.View
+import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
@@ -21,6 +29,11 @@ class MainActivity : ReactActivity() {
SplashScreenManager.registerOnActivity(this)
// @generated end expo-splashscreen
super.onCreate(null)
+
+ val orientation = resources.configuration.orientation
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ enableImmersive()
+ }
}
/**
@@ -49,17 +62,56 @@ class MainActivity : ReactActivity() {
* where moving root activities to background instead of finishing activities.
* @see onBackPressed
*/
- override fun invokeDefaultOnBackPressed() {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- if (!moveTaskToBack(false)) {
- // For non-root activities, use the default implementation to finish them.
- super.invokeDefaultOnBackPressed()
- }
- return
- }
+ override fun invokeDefaultOnBackPressed() {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+ if (!moveTaskToBack(false)) {
+ // For non-root activities, use the default implementation to finish them.
+ super.invokeDefaultOnBackPressed()
+ }
+ return
+ }
- // Use the default back button implementation on Android S
- // because it's doing more than [Activity.moveTaskToBack] in fact.
- super.invokeDefaultOnBackPressed()
- }
+ // Use the default back button implementation on Android S
+ // because it's doing more than [Activity.moveTaskToBack] in fact.
+ super.invokeDefaultOnBackPressed()
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ enableImmersive()
+ } else {
+ disableImmersive()
+ }
+ }
+
+
+ private fun enableImmersive() {
+ with(window) {
+ WindowCompat.setDecorFitsSystemWindows(this, false)
+ val insetsController = WindowInsetsControllerCompat(this, decorView)
+ insetsController.apply {
+ hide(WindowInsetsCompat.Type.systemBars())
+ systemBarsBehavior =
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
+ attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+ }
+
+ private fun disableImmersive() {
+ with(window) {
+ val insetsController = WindowInsetsControllerCompat(this, decorView)
+ insetsController.apply {
+ hide(WindowInsetsCompat.Type.systemBars())
+ }
+ }
+ }
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ val orientation = resources.configuration.orientation
+ if (hasFocus && orientation == Configuration.ORIENTATION_LANDSCAPE) enableImmersive()
+ }
}
diff --git a/package-lock.json b/package-lock.json
index c8b6838..af78361 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
+ "@shopify/react-native-skia": "v2.0.0-next.4",
"expo": "^53.0.20",
"expo-blur": "~14.1.5",
"expo-constants": "~17.1.7",
@@ -44,9 +45,11 @@
"react-native": "0.79.6",
"react-native-device-info": "^14.0.4",
"react-native-gesture-handler": "~2.24.0",
- "react-native-reanimated": "~3.17.4",
+ "react-native-localize": "^3.6.0",
+ "react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.11.1",
+ "react-native-skia": "^0.0.1",
"react-native-web": "^0.20.0",
"react-native-webview": "13.13.5"
},
@@ -141,6 +144,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -167,13 +171,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
- "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@babel/types": "^7.28.2",
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -394,9 +398,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -525,12 +529,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
- "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.2"
+ "@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -931,9 +935,9 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
- "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
+ "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
@@ -941,7 +945,7 @@
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
- "@babel/traverse": "^7.28.3"
+ "@babel/traverse": "^7.28.4"
},
"engines": {
"node": ">=6.9.0"
@@ -1525,17 +1529,17 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
- "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
+ "@babel/generator": "^7.28.5",
"@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.3",
+ "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.28.2",
+ "@babel/types": "^7.28.5",
"debug": "^4.3.1"
},
"engines": {
@@ -1562,13 +1566,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
- "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2829,9 +2833,9 @@
}
},
"node_modules/@expo/cli/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -3134,9 +3138,9 @@
}
},
"node_modules/@expo/config-plugins/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -3218,9 +3222,9 @@
}
},
"node_modules/@expo/config/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -3307,9 +3311,9 @@
}
},
"node_modules/@expo/devcert/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -3400,9 +3404,9 @@
}
},
"node_modules/@expo/fingerprint/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -3553,9 +3557,9 @@
}
},
"node_modules/@expo/metro-config/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -4187,9 +4191,9 @@
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
@@ -4955,6 +4959,7 @@
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz",
"integrity": "sha512-uEcYWi1NV+2Qe1oELfp9b5hTYekqWATv2cuwcOAg5EvsIsUPtzFrKIasgUXLBRGb9P7yR5ifoJ+ug4u6jdqSTQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@react-navigation/core": "^7.12.4",
"escape-string-regexp": "^4.0.0",
@@ -5044,6 +5049,32 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@shopify/react-native-skia": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/@shopify/react-native-skia/-/react-native-skia-2.0.0-next.4.tgz",
+ "integrity": "sha512-NzvdgryRz6tkKMHgCChCKa3wXfN9TZhlV0/LrfIU/wKLC1uKgGXkoZgNz7Is0wwdhtao1JJJJ81fqHCGHgzk9g==",
+ "license": "MIT",
+ "dependencies": {
+ "canvaskit-wasm": "0.40.0",
+ "react-reconciler": "0.31.0"
+ },
+ "bin": {
+ "setup-skia-web": "scripts/setup-canvaskit.js"
+ },
+ "peerDependencies": {
+ "react": ">=19.0",
+ "react-native": ">=0.78",
+ "react-native-reanimated": "^3.0"
+ },
+ "peerDependenciesMeta": {
+ "react-native": {
+ "optional": true
+ },
+ "react-native-reanimated": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -5267,6 +5298,7 @@
"integrity": "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -5349,6 +5381,7 @@
"integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.40.0",
"@typescript-eslint/types": "8.40.0",
@@ -5853,6 +5886,12 @@
"@urql/core": "^5.0.0"
}
},
+ "node_modules/@webgpu/types": {
+ "version": "0.1.21",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.21.tgz",
+ "integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/@xmldom/xmldom": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
@@ -5899,6 +5938,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6666,6 +6706,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001735",
"electron-to-chromium": "^1.5.204",
@@ -6985,6 +7026,15 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/canvaskit-wasm": {
+ "version": "0.40.0",
+ "resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.40.0.tgz",
+ "integrity": "sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@webgpu/types": "0.1.21"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -7421,9 +7471,9 @@
}
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
@@ -7984,16 +8034,6 @@
"node": ">= 0.8"
}
},
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -8307,6 +8347,7 @@
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8520,6 +8561,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -8873,6 +8915,7 @@
"resolved": "https://registry.npmjs.org/expo/-/expo-53.0.20.tgz",
"integrity": "sha512-Nh+HIywVy9KxT/LtH08QcXqrxtUOA9BZhsXn3KCsAYA+kNb80M8VKN8/jfQF+I6CgeKyFKJoPNsWgI0y0VBGrA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "0.24.20",
@@ -8947,6 +8990,7 @@
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.7.tgz",
"integrity": "sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@expo/config": "~11.0.12",
"@expo/env": "~1.0.7"
@@ -9054,6 +9098,7 @@
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.3.2.tgz",
"integrity": "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fontfaceobserver": "^2.1.0"
},
@@ -9109,6 +9154,7 @@
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
"integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"expo-constants": "~17.1.7",
"invariant": "^2.2.4"
@@ -9159,9 +9205,9 @@
}
},
"node_modules/expo-modules-autolinking/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -10024,14 +10070,14 @@
}
},
"node_modules/glob": {
- "version": "11.0.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
- "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
- "license": "ISC",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
+ "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
- "minimatch": "^10.0.3",
+ "minimatch": "^10.1.1",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
@@ -10060,10 +10106,10 @@
}
},
"node_modules/glob/node_modules/minimatch": {
- "version": "10.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
- "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
- "license": "ISC",
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
+ "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
@@ -11354,18 +11400,6 @@
"integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==",
"license": "MIT"
},
- "node_modules/jiti": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
- "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -11373,9 +11407,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -12981,9 +13015,9 @@
}
},
"node_modules/node-forge": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
- "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
+ "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
@@ -14026,6 +14060,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -14066,6 +14101,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.25.0"
},
@@ -14102,6 +14138,7 @@
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.6.tgz",
"integrity": "sha512-kvIWSmf4QPfY41HC25TR285N7Fv0Pyn3DAEK8qRL9dA35usSaxsJkHfw+VqnonqJjXOaoKCEanwudRAJ60TBGA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/assets-registry": "0.79.6",
@@ -14200,11 +14237,32 @@
"react-native": "*"
}
},
+ "node_modules/react-native-localize": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.6.0.tgz",
+ "integrity": "sha512-CByn67b9dPDBA7qvAx18PpXhuLqBbdTpedobGOUcjqiGuSAEXZzwas9ziMYSShAR2AvX2Cl7ePErCpj9V39Ctw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@expo/config-plugins": "*",
+ "react": "*",
+ "react-native": "*",
+ "react-native-macos": "*"
+ },
+ "peerDependenciesMeta": {
+ "@expo/config-plugins": {
+ "optional": true
+ },
+ "react-native-macos": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-native-reanimated": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz",
"integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
"@babel/plugin-transform-class-properties": "^7.0.0-0",
@@ -14240,6 +14298,7 @@
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz",
"integrity": "sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA==",
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -14250,6 +14309,7 @@
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz",
"integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"react-freeze": "^1.0.0",
"react-native-is-edge-to-edge": "^1.1.7",
@@ -14260,11 +14320,18 @@
"react-native": "*"
}
},
+ "node_modules/react-native-skia": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-skia/-/react-native-skia-0.0.1.tgz",
+ "integrity": "sha512-etuNQDOiDBmncaw17aij6ygh9rb7P3v6Hz+moU5QcmznoeD2tXRepOJO2wSN0PzibVhMNZrBqTyA8Yg5OkHwuA==",
+ "license": "MIT"
+ },
"node_modules/react-native-web": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz",
"integrity": "sha512-OOSgrw+aON6R3hRosCau/xVxdLzbjEcsLysYedka0ZON4ZZe6n9xgeN9ZkoejhARM36oTlUgHIQqxGutEJ9Wxg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.6",
"@react-native/normalize-colors": "^0.74.1",
@@ -14297,6 +14364,7 @@
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.13.5.tgz",
"integrity": "sha512-MfC2B+woL4Hlj2WCzcb1USySKk+SteXnUKmKktOk/H/AQy5+LuVdkPKm8SknJ0/RxaxhZ48WBoTRGaqgR137hw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"escape-string-regexp": "^4.0.0",
"invariant": "2.2.4"
@@ -14438,6 +14506,21 @@
"async-limiter": "~1.0.0"
}
},
+ "node_modules/react-reconciler": {
+ "version": "0.31.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",
+ "integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.25.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -15100,6 +15183,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -16191,9 +16275,9 @@
}
},
"node_modules/sucrase/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -16749,6 +16833,7 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index 0c4d73f..28bf6f1 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,9 @@
"dev:ui": "npx expo start --dev-client",
"dev:web:server": "electron electron/build/main.js --dev",
"install:android:release": "adb install android/app/build/outputs/apk/release/app-release.apk",
- "install:android": "adb install android/app/build/outputs/apk/debug/app-debug.apk"
+ "install:android": "adb install android/app/build/outputs/apk/debug/app-debug.apk",
+ "android": "expo run:android",
+ "ios": "expo run:ios"
},
"license": "GPL-3.0-only",
"workspaces": [
@@ -63,11 +65,14 @@
"react-native": "0.79.6",
"react-native-device-info": "^14.0.4",
"react-native-gesture-handler": "~2.24.0",
- "react-native-reanimated": "~3.17.4",
+ "react-native-localize": "^3.6.0",
+ "react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.11.1",
+ "react-native-skia": "^0.0.1",
"react-native-web": "^0.20.0",
- "react-native-webview": "13.13.5"
+ "react-native-webview": "13.13.5",
+ "@shopify/react-native-skia": "v2.0.0-next.4"
},
"devDependencies": {
"@expo/metro-config": "~0.20.0",
diff --git a/rpcsx-ui/assets/images/rpcsx_bg.webp b/rpcsx-ui/assets/images/rpcsx_bg.webp
new file mode 100644
index 0000000..196cbbd
Binary files /dev/null and b/rpcsx-ui/assets/images/rpcsx_bg.webp differ
diff --git a/rpcsx-ui/src/app/component.json b/rpcsx-ui/src/app/component.json
index 23a4b2c..706beb4 100644
--- a/rpcsx-ui/src/app/component.json
+++ b/rpcsx-ui/src/app/component.json
@@ -4,12 +4,15 @@
"dependencies": [
{
"name": "settings"
- },
+ } ,
{
"name": "explorer"
},
{
"name": "fs"
+ },
+ {
+ "name": "setup"
}
]
}
\ No newline at end of file
diff --git a/rpcsx-ui/src/app/server/initialization.ts b/rpcsx-ui/src/app/server/initialization.ts
index 0f63648..054be4a 100644
--- a/rpcsx-ui/src/app/server/initialization.ts
+++ b/rpcsx-ui/src/app/server/initialization.ts
@@ -1,5 +1,6 @@
import * as bridge from '$core/bridge';
import * as explorer from '$explorer';
+import * as setup from '$setup';
import { Window } from '$core/Window';
@@ -9,12 +10,12 @@ const mainWindow: Window = {
popView: () => bridge.viewPop(),
};
-export function initialize() {
- return explorer.pushExplorerView(mainWindow, {
- filter: {
- type: 'game'
- }
- });
+export async function initialize() {
+ if (await setup.setupShouldShow({})) {
+ return setup.setInitialSetupView(mainWindow, {});
+ } else {
+ return explorer.setExplorerView(mainWindow, {
+ filter: { type: 'game' },
+ });
+ }
}
-
-
diff --git a/rpcsx-ui/src/app/server/initialization.web.ts b/rpcsx-ui/src/app/server/initialization.web.ts
index 4c63566..f5d5b61 100644
--- a/rpcsx-ui/src/app/server/initialization.web.ts
+++ b/rpcsx-ui/src/app/server/initialization.web.ts
@@ -7,6 +7,7 @@ import * as path from '$core/path';
import { fileURLToPath, pathToFileURL } from 'url';
import { Future } from '$core/Future.js';
import * as explorer from '$explorer';
+import * as setup from '$setup';
import { Window } from '$core/Window';
function toWindow(browserWindow: BrowserWindow): Window {
@@ -73,11 +74,13 @@ export async function initialize() {
uiInitializedFuture.dispose();
console.log('initialization complete');
- return explorer.pushExplorerView(toWindow(MainWindow), {
- filter: {
- type: 'game'
- }
- });
+ //if (setup.settings.getShowInitialSetupScreen()) {
+ return setup.setInitialSetupView(toWindow(MainWindow), {});
+ /*} else {
+ return explorer.setExplorerView(toWindow(MainWindow), {
+ filter: { type: 'game' },
+ });
+ }*/
};
app.on('activate', () => {
diff --git a/rpcsx-ui/src/core/renderer/FocusableText.tsx b/rpcsx-ui/src/core/renderer/FocusableText.tsx
new file mode 100644
index 0000000..3045045
--- /dev/null
+++ b/rpcsx-ui/src/core/renderer/FocusableText.tsx
@@ -0,0 +1,102 @@
+import React, { useRef } from 'react';
+import { Pressable } from 'react-native';
+import Animated, {
+ useSharedValue,
+ useAnimatedStyle,
+ withSpring,
+ withTiming,
+ cancelAnimation,
+} from 'react-native-reanimated';
+import { ThemedText } from './ThemedText';
+
+const AnimatedText = Animated.createAnimatedComponent(ThemedText);
+
+export function FocusableText({
+ children,
+ onPress,
+}: {
+ children: React.ReactNode;
+ onPress?: () => void;
+}) {
+ const scale = useSharedValue(1);
+ const opacity = useSharedValue(0.85);
+
+ const isFocused = useRef(false);
+ const isHovered = useRef(false);
+ const isActive = useRef(false);
+ const suppressHover = useRef(false);
+
+ const updateState = () => {
+ const nextActive =
+ isFocused.current ||
+ (isHovered.current && !suppressHover.current);
+
+ if (isActive.current === nextActive) return;
+ isActive.current = nextActive;
+
+ cancelAnimation(scale);
+ cancelAnimation(opacity);
+
+ if (nextActive) {
+ scale.value = withSpring(1.25, {
+ damping: 16,
+ stiffness: 180,
+ });
+ opacity.value = withTiming(1, { duration: 120 });
+ } else {
+ scale.value = withTiming(1, { duration: 180 });
+ opacity.value = withTiming(0.85, { duration: 200 });
+ }
+ };
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ transform: [{ scale: scale.value }],
+ opacity: opacity.value,
+ }));
+
+ return (
+ {
+ suppressHover.current = true;
+ isFocused.current = false;
+ isHovered.current = false;
+ updateState();
+ onPress?.();
+ setTimeout(() => {
+ suppressHover.current = false;
+ }, 120);
+ }}
+ onFocus={() => {
+ isFocused.current = true;
+ updateState();
+ }}
+ onBlur={() => {
+ isFocused.current = false;
+ updateState();
+ }}
+ onHoverIn={() => {
+ if (suppressHover.current) return;
+ isHovered.current = true;
+ updateState();
+ }}
+ onHoverOut={() => {
+ isHovered.current = false;
+ updateState();
+ }}
+ >
+
+ {children}
+
+
+ );
+}
diff --git a/rpcsx-ui/src/core/renderer/LanguageItem.tsx b/rpcsx-ui/src/core/renderer/LanguageItem.tsx
new file mode 100644
index 0000000..14be63c
--- /dev/null
+++ b/rpcsx-ui/src/core/renderer/LanguageItem.tsx
@@ -0,0 +1,115 @@
+import React, { memo, useEffect, useState } from 'react';
+import { Pressable } from 'react-native';
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withSpring,
+ withTiming,
+ interpolateColor,
+} from 'react-native-reanimated';
+import { ThemedText } from './ThemedText';
+
+const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
+
+const ITEM_WIDTH = 220;
+const ITEM_HEIGHT = 56;
+const BORDER_SIZE = 2;
+
+type Props = {
+ lang: { id: string; label: string };
+ selected: boolean;
+ onPress: () => void;
+ primaryColor: string;
+};
+
+export const LanguageItem = memo(function LanguageItem({
+ lang,
+ selected,
+ onPress,
+ primaryColor,
+}: Props) {
+ const [focused, setFocused] = useState(false);
+ const [hovered, setHovered] = useState(false);
+
+ const progress = useSharedValue(0);
+
+ useEffect(() => {
+ progress.value =
+ focused || hovered || selected
+ ? withSpring(1, { damping: 16, stiffness: 200 })
+ : withTiming(0, { duration: 160 });
+ }, [focused, hovered, selected]);
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ transform: [{ scale: 1 + progress.value * 0.06 }],
+ backgroundColor: interpolateColor(
+ progress.value,
+ [0, 1],
+ ['rgba(0,0,0,0.25)', 'rgba(77,163,255,0.16)'],
+ ),
+ }));
+
+ const borderStyle = useAnimatedStyle(() => ({
+ opacity: progress.value,
+ borderColor: primaryColor,
+ }));
+
+ return (
+ setFocused(true)}
+ onBlur={() => setFocused(false)}
+ onHoverIn={() => setHovered(true)}
+ onHoverOut={() => setHovered(false)}
+ onPress={onPress}
+ style={{
+ width: ITEM_WIDTH,
+ height: ITEM_HEIGHT,
+ margin: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+
+ {/* Border overlay */}
+
+
+
+ {lang.label}
+
+
+
+ );
+});
+
diff --git a/rpcsx-ui/src/core/renderer/RPCSXBackground.tsx b/rpcsx-ui/src/core/renderer/RPCSXBackground.tsx
new file mode 100644
index 0000000..dfbf7a1
--- /dev/null
+++ b/rpcsx-ui/src/core/renderer/RPCSXBackground.tsx
@@ -0,0 +1,199 @@
+import React, { useEffect } from "react";
+import { useWindowDimensions, StyleSheet } from "react-native";
+import { useSharedValue, withSequence, withTiming, withRepeat, useDerivedValue, Easing, } from "react-native-reanimated";
+import { Canvas, Fill, Path, Circle, BlurMask, LinearGradient, Skia, vec, Group, interpolateColors } from "@shopify/react-native-skia";
+
+const X_ICON_PATH = 'm19.27 5.2773h0.13672l0.023438 0.007812 0.027343 0.027344 0.007813 0.015625v0.050781l-0.007813 0.015625-0.027343 0.042969-0.023438 0.015625-0.0625 0.078125-0.11719 0.12891c-0.019531 0.023438-0.039062 0.042969-0.058593 0.066406-0.046875 0.054688-0.097657 0.11328-0.14844 0.16406-0.058593 0.074219-0.12109 0.14062-0.18359 0.21094-0.011719 0.015626-0.027344 0.035157-0.042969 0.050782-0.074219 0.078125-0.14453 0.15625-0.21484 0.23828-0.023437 0.023437-0.042969 0.050781-0.066406 0.078125-0.046875 0.0625-0.10156 0.125-0.15625 0.17969l-0.050781 0.050781c-0.039063 0.042969-0.074219 0.082031-0.10938 0.125-0.082031 0.10547-0.17187 0.21094-0.26562 0.30859l-0.03125 0.03125-0.007813 0.011719h-0.011719l-0.015625 0.03125c-0.011719 0.011719-0.023437 0.023438-0.035156 0.035157-0.042969 0.046874-0.085937 0.09375-0.125 0.14453-0.035156 0.039062-0.070313 0.074219-0.10938 0.11328-0.039062 0.054688-0.082031 0.10547-0.12891 0.15234-0.074219 0.078125-0.14844 0.16016-0.21484 0.24609l-0.050781 0.058593c-0.015625 0.015625-0.03125 0.035157-0.042969 0.050781-0.097656 0.10156-0.19141 0.20703-0.28125 0.31641l-0.11719 0.12891-0.078124 0.089844c-0.011719 0.011719-0.023438 0.027343-0.035157 0.042969l-0.007812 0.011718-0.09375 0.09375c-0.023438 0.023438-0.039063 0.050782-0.058594 0.074219-0.050781 0.066406-0.10547 0.12891-0.16797 0.1875l-0.085937 0.09375c-0.035157 0.039063-0.070313 0.074219-0.10156 0.11719l-0.070312 0.085938c-0.078125 0.078125-0.15625 0.16406-0.22656 0.25l-0.097656 0.10938c-0.042969 0.050781-0.085937 0.10156-0.13281 0.15234l-0.09375 0.10156-0.019531 0.027344-0.007812 0.015625c-0.050781 0.058594-0.097657 0.11719-0.15234 0.17188l-0.015625 0.015625h-0.011719l-0.007812 0.015625-0.007813 0.011719c-0.015625 0.015625-0.027344 0.03125-0.042968 0.046875-0.007813 0.007812-0.015626 0.015625-0.023438 0.027343l-0.011719 0.007813-0.082031 0.09375c-0.066406 0.082031-0.14062 0.16406-0.21484 0.24609-0.042968 0.050781-0.085937 0.10156-0.12891 0.14844l-0.09375 0.10156-0.023438 0.03125-0.007812 0.011719c-0.046875 0.054687-0.09375 0.10937-0.14453 0.16016-0.050781 0.070312-0.10938 0.13281-0.17188 0.19531l-0.09375 0.10156-0.074219 0.078125-0.078125 0.10156c-0.035156 0.035156-0.070312 0.070313-0.10156 0.10938l-0.085938 0.10156c-0.078125 0.078125-0.14844 0.16016-0.21484 0.24609l-0.050781 0.054687-0.046875 0.050781c-0.042969 0.046875-0.085938 0.09375-0.12891 0.14453l-0.011719 0.007812-0.007813 0.015625-0.007812 0.011719v0.015625l0.015625 0.015625 0.011719 0.019531 0.066406 0.074219 0.09375 0.10156 0.066406 0.078125 0.046875 0.066407c0.050781 0.050781 0.097657 0.10156 0.14062 0.15625 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046875 0.050781 0.09375 0.10156 0.13672 0.15625l0.085938 0.09375 0.074219 0.082031 0.042969 0.058594 0.078124 0.085937c0.046876 0.050782 0.09375 0.10156 0.13672 0.15625 0.019531 0.023438 0.039063 0.050782 0.058594 0.074219l0.09375 0.10156 0.066406 0.078125 0.050781 0.058593c0.046876 0.050782 0.09375 0.10156 0.13672 0.16016 0.046875 0.046875 0.09375 0.10156 0.13672 0.15625 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046875 0.050781 0.09375 0.10156 0.13672 0.16016 0.046874 0.050781 0.09375 0.10547 0.13672 0.16406 0.050782 0.050781 0.09375 0.10547 0.13672 0.16016 0.050781 0.046875 0.09375 0.097656 0.13672 0.14844 0.019531 0.027343 0.039062 0.050781 0.058594 0.074218l0.09375 0.10156 0.066406 0.078125 0.050781 0.066406c0.046875 0.050781 0.09375 0.10156 0.13672 0.15625 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046876 0.050782 0.09375 0.10547 0.13672 0.16016 0.050781 0.050782 0.09375 0.10156 0.13672 0.15625 0.050781 0.050782 0.09375 0.10547 0.13672 0.16016 0.050781 0.050781 0.09375 0.10547 0.13672 0.16016 0.050781 0.050781 0.097656 0.10156 0.14062 0.15625 0.046876 0.050781 0.09375 0.10547 0.13672 0.16016 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046875 0.050781 0.09375 0.10156 0.13672 0.15625 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046874 0.050781 0.09375 0.10547 0.13672 0.16016 0.050782 0.050781 0.09375 0.10156 0.13672 0.15625l0.085937 0.09375 0.074219 0.082031 0.042969 0.058594 0.074219 0.085937c0.050781 0.050782 0.097656 0.10547 0.14453 0.16406 0.046875 0.050781 0.09375 0.10547 0.13672 0.16016 0.046875 0.050781 0.09375 0.10156 0.13672 0.16016l0.085937 0.10156c0.011719 0.011719 0.023437 0.023438 0.035156 0.035156l0.015625 0.019532 0.015625 0.03125v0.070312l-0.007812 0.015625-0.015625 0.027344-0.007813 0.015625-0.019531 0.023437-0.03125 0.019531-0.011719 0.007813-0.015625 0.007813h-0.28125-0.67188-0.042969c-0.39844-0.003907-0.79687-0.003907-1.1914 0l-0.03125 0.007812c-0.011718-0.003906-0.027344-0.007812-0.042968-0.007812h-0.019532l-0.035156-0.015626-0.039062-0.027343-0.007813-0.007813-0.011719-0.015625-0.007812-0.015625-0.015625-0.003906c-0.050781-0.058594-0.097657-0.11328-0.14453-0.17578l-0.078126-0.085938-0.10937-0.12109-0.09375-0.10156c-0.007813-0.011719-0.015626-0.019531-0.019532-0.03125l-0.007812-0.011719c-0.027344-0.027344-0.054688-0.054687-0.082032-0.082031-0.019531-0.023437-0.042968-0.050781-0.0625-0.078125l-0.10937-0.12109-0.09375-0.10156c-0.007813-0.011719-0.015626-0.019532-0.023438-0.03125l-0.003906-0.011719c-0.027344-0.027344-0.054688-0.054687-0.082032-0.082031-0.023437-0.023438-0.042968-0.050782-0.0625-0.078125-0.050781-0.0625-0.10156-0.125-0.16016-0.17969l-0.10938-0.125c-0.078125-0.078125-0.15234-0.16016-0.22266-0.24609-0.070313-0.070313-0.13281-0.14453-0.19531-0.22266-0.023437-0.023437-0.042968-0.050781-0.066406-0.078125-0.058594-0.078125-0.125-0.14844-0.19141-0.21875l-0.023438-0.027344-0.007812-0.015624c-0.027344-0.027344-0.050781-0.050782-0.078125-0.078126-0.023437-0.027343-0.046875-0.050781-0.066406-0.078124l-0.10938-0.125-0.09375-0.10156c-0.003906-0.007813-0.011719-0.019531-0.019532-0.027344l-0.007812-0.015625-0.078125-0.078125c-0.023437-0.027343-0.046875-0.050781-0.066406-0.078125l-0.10938-0.125-0.09375-0.10156c-0.007813-0.007813-0.011719-0.019532-0.019532-0.027344l-0.007812-0.015625c-0.027344-0.027344-0.054688-0.054687-0.078125-0.078125-0.023437-0.027344-0.046875-0.054688-0.066406-0.082031-0.050781-0.0625-0.10156-0.12109-0.16016-0.17969l-0.10547-0.12109c-0.082032-0.078126-0.15625-0.16016-0.22656-0.24609-0.074219-0.078125-0.14844-0.16016-0.21484-0.24609-0.074219-0.078125-0.14453-0.16016-0.21094-0.24609-0.066406-0.066406-0.13281-0.14062-0.19531-0.21484-0.023438-0.027343-0.042969-0.050781-0.0625-0.078124-0.046876-0.058594-0.09375-0.11328-0.14453-0.16797l-0.015625-0.007813-0.015625 0.03125-0.0625 0.0625-0.015625 0.023438c-0.054687 0.066406-0.11328 0.13281-0.17188 0.19531l-0.046875 0.050782-0.11328 0.12891c-0.007812 0.007812-0.015625 0.019531-0.023438 0.027344h-0.011718l-0.015625 0.03125-0.042969 0.042968c-0.007812 0.007813-0.015625 0.019532-0.023438 0.027344l-0.011718 0.007812-0.042969 0.058594c-0.019531 0.015625-0.035156 0.03125-0.050781 0.050782-0.10156 0.10156-0.19531 0.21094-0.28125 0.32422-0.019532 0.015625-0.035156 0.035156-0.050782 0.050782-0.019531 0.019531-0.035156 0.042968-0.050781 0.0625-0.019531 0.023437-0.039062 0.046874-0.058593 0.066406-0.082032 0.082031-0.15625 0.16797-0.23047 0.25391-0.019531 0.023438-0.039063 0.046875-0.058594 0.070312-0.054687 0.070313-0.10938 0.13672-0.17188 0.19531l-0.10938 0.125-0.078125 0.085937c-0.015625 0.011719-0.027344 0.027344-0.039062 0.042969l-0.007813 0.015625c-0.054687 0.066406-0.10938 0.12891-0.17188 0.19141-0.011719 0.015626-0.027344 0.03125-0.042969 0.046876-0.011719 0.007812-0.023437 0.023437-0.035156 0.035156l-0.015625 0.019531c-0.046875 0.058594-0.097656 0.11719-0.15234 0.16797-0.007812 0.011718-0.015625 0.023437-0.019531 0.035156l-0.007813 0.007812h-0.015624l-0.007813 0.015626-0.007813 0.011718-0.042968 0.042969c-0.007813 0.011719-0.011719 0.019531-0.019532 0.03125l-0.015624 0.007813-0.042969 0.054687c-0.015625 0.019531-0.035157 0.035156-0.050781 0.050781-0.023438 0.027344-0.042969 0.054688-0.066407 0.082032l-0.042969 0.050781c-0.019531 0.023437-0.042968 0.046875-0.0625 0.070312l-0.09375 0.10938c-0.011718 0.007813-0.019531 0.019532-0.03125 0.027344l-0.007812 0.015625h-0.011719l-0.007812 0.015625-0.007813 0.011719c-0.015625 0.015625-0.027344 0.03125-0.042968 0.042969l-0.074219 0.089843-0.11328 0.12891c-0.019532 0.019531-0.039063 0.042968-0.058594 0.066406l-0.042969 0.046875c-0.023437 0.023437-0.042969 0.050781-0.066406 0.074219l-0.11328 0.12891c-0.019532 0.023437-0.039063 0.042968-0.058594 0.066406l-0.042969 0.050781c-0.023437 0.023438-0.042969 0.046875-0.066406 0.070313-0.050781 0.070312-0.10938 0.13672-0.17188 0.19531l-0.09375 0.10156-0.074219 0.085937-0.10547 0.125c-0.019531 0.019532-0.039062 0.042969-0.058594 0.0625l-0.050781 0.050782c-0.035156 0.042968-0.074219 0.085937-0.10938 0.13281l-0.042968 0.046875c-0.007813 0.011719-0.015626 0.019531-0.019532 0.03125l-0.039062 0.035156-0.011719 0.015625-0.023437 0.019532-0.027344 0.015624-0.039063 0.015626-0.0625 0.007812h-0.36328-0.12109-0.058594-0.63281-0.37891-0.011719c-0.14844-0.003906-0.30078-0.003906-0.44922 0h-0.019531l-0.042969-0.007812h-0.023437l-0.035157-0.015626-0.023437-0.015624-0.019531-0.019532-0.023438-0.035156-0.007812-0.015625v-0.09375l0.007812-0.015625c0.023438-0.03125 0.050782-0.0625 0.078125-0.09375l0.007813-0.015625c0.046875-0.042969 0.089844-0.09375 0.13281-0.14453l0.046875-0.0625c0.042969-0.054688 0.085938-0.10547 0.13281-0.15234 0.019531-0.023438 0.042968-0.046875 0.0625-0.070312 0.019531-0.027344 0.039062-0.050782 0.058594-0.074219l0.11719-0.13672 0.078125-0.085938 0.042969-0.058593c0.011719-0.011719 0.023437-0.023438 0.035156-0.035157 0.011719-0.011718 0.019531-0.023437 0.03125-0.039062l0.015625-0.019531h0.011719l0.015625-0.027344 0.015625-0.023437h0.011719l0.015624-0.027344c0.007813-0.015625 0.019532-0.027344 0.027344-0.039063l0.066406-0.070312c0.039063-0.050781 0.082032-0.097657 0.12891-0.14453 0.042969-0.054688 0.085937-0.10547 0.13281-0.15234 0.019531-0.027344 0.039063-0.054688 0.0625-0.078125 0.019531-0.023438 0.039063-0.046875 0.058594-0.074219l0.11719-0.13672 0.054688-0.0625 0.039062-0.039062 0.050782-0.0625 0.019531-0.023438 0.042969-0.050781 0.015625-0.015625 0.015625-0.027344c0.050781-0.058593 0.10547-0.12109 0.15625-0.17969l0.085937-0.10156c0.050781-0.054687 0.097657-0.10938 0.14453-0.16797 0.023438-0.027343 0.050782-0.054687 0.074219-0.085937l0.078125-0.085937c0.11719-0.14063 0.23828-0.28125 0.36328-0.41797l0.0625-0.078125 0.050781-0.058594c0.023437-0.023438 0.042969-0.046875 0.066406-0.074219 0.019531-0.027343 0.042969-0.050781 0.0625-0.078125 0.023437-0.023437 0.039063-0.046875 0.058594-0.074218l0.11719-0.13672 0.058594-0.0625 0.011719-0.015625 0.023437-0.023438 0.050782-0.0625 0.019531-0.023437 0.32031-0.36719 0.011719-0.027343c0.054688-0.0625 0.11328-0.12109 0.16797-0.18359 0.035156-0.042969 0.070312-0.089844 0.10938-0.13672l0.054687-0.0625 0.039063-0.039063 0.21484-0.25 0.015624-0.03125c0.16406-0.19531 0.33203-0.38672 0.50391-0.57812l0.066406-0.078126 0.050781-0.058593c0.019532-0.023438 0.042969-0.046875 0.0625-0.070313 0.023438-0.027344 0.042969-0.054687 0.066406-0.078125 0.019532-0.023437 0.039063-0.050781 0.058594-0.074219l0.11328-0.13672 0.066406-0.074219 0.007812-0.003906c0.039063-0.042969 0.078125-0.089844 0.11328-0.13281 0.007812-0.007812 0.015624-0.019531 0.023437-0.027344l0.007813-0.015624 0.015624-0.007813 0.003907-0.011719v-0.015625l-0.019531-0.035156c-0.023438-0.023437-0.042969-0.046875-0.066407-0.066406-0.0625-0.074219-0.12891-0.14844-0.20312-0.21484-0.023437-0.03125-0.046875-0.058594-0.070312-0.089844l-0.078125-0.085937c-0.019531-0.019532-0.035157-0.039063-0.050781-0.054688l-0.066407-0.066406c-0.007812-0.015625-0.019531-0.03125-0.027343-0.042969l-0.074219-0.082031-0.078125-0.085938c-0.023438-0.023437-0.042969-0.046874-0.066406-0.070312l-0.058594-0.058594c-0.023438-0.027344-0.046875-0.058594-0.070313-0.085937l-0.078125-0.085938c-0.019531-0.019531-0.035156-0.039062-0.050781-0.058593l-0.066406-0.066407c-0.007813-0.011719-0.019531-0.027343-0.027344-0.042969l-0.074219-0.078124-0.078125-0.085938c-0.023437-0.023438-0.042969-0.050781-0.066406-0.074219l-0.058594-0.058593c-0.023437-0.027344-0.046875-0.054688-0.070312-0.085938l-0.078125-0.085938c-0.019532-0.019531-0.035156-0.039062-0.050782-0.058593l-0.066406-0.0625c-0.007812-0.015625-0.019531-0.03125-0.027344-0.046875l-0.074218-0.078125-0.078125-0.085938c-0.023438-0.023437-0.042969-0.046875-0.066407-0.074219l-0.058593-0.054687c-0.023438-0.03125-0.046875-0.058594-0.070313-0.085937l-0.078125-0.089844c-0.019531-0.019532-0.035156-0.039063-0.050781-0.054688l-0.13281-0.14062c-0.007813-0.011719-0.019532-0.027343-0.027344-0.042969l-0.074219-0.078124-0.078125-0.085938c-0.023438-0.023438-0.042969-0.046875-0.066406-0.074219-0.035156-0.039062-0.070313-0.082031-0.10547-0.12109l-0.082031-0.085938c-0.019532-0.023438-0.042969-0.050781-0.0625-0.074219l-0.058594-0.058593c-0.023438-0.027344-0.046875-0.054688-0.070312-0.085938l-0.082032-0.085938c-0.015625-0.019531-0.03125-0.039062-0.050781-0.058593l-0.0625-0.0625c-0.011719-0.015625-0.019531-0.03125-0.03125-0.042969l-0.070313-0.082031-0.082031-0.085938c-0.019531-0.023437-0.042969-0.046875-0.0625-0.070312l-0.058593-0.058594c-0.023438-0.027344-0.046876-0.058594-0.074219-0.085937l-0.078125-0.089844c-0.015625-0.015625-0.03125-0.035156-0.050782-0.054688l-0.0625-0.066406c-0.011718-0.015625-0.019531-0.027344-0.03125-0.042969l-0.070312-0.078125-0.082031-0.089844c-0.019531-0.023437-0.042969-0.046874-0.0625-0.070312l-0.058594-0.058594c-0.023437-0.027344-0.046875-0.058594-0.074219-0.085937-0.011718-0.015625-0.027344-0.035157-0.042968-0.050781l-0.019532-0.015626c-0.011718-0.011718-0.019531-0.027343-0.03125-0.042968l-0.046875-0.050782-0.023437-0.027343v-0.015625l-0.007813-0.027344v-0.050781l0.015625-0.03125 0.015625-0.027344 0.019531-0.019531 0.027344-0.023438 0.039063-0.015625h0.41016 0.17969 1.1406 0.15234 0.16016 0.09375c0.042968 0.011719 0.085937 0.023438 0.12891 0.039063l0.074218 0.035156c0.019532 0.015625 0.039063 0.03125 0.054688 0.050781l0.074219 0.0625c0.015625 0.03125 0.042969 0.050781 0.0625 0.074219l0.066406 0.070312 0.085937 0.089844 0.085938 0.09375 0.074219 0.0625 0.085937 0.10156 0.074219 0.066406 0.085937 0.10156 0.070313 0.0625 0.085937 0.10156 0.074219 0.066406 0.085938 0.10156 0.074219 0.0625 0.085937 0.10156 0.070313 0.066407 0.085937 0.10156 0.074219 0.0625 0.085937 0.10156 0.078125 0.078126 0.066406 0.074218 0.085938 0.085938 0.09375 0.10156 0.066406 0.066406 0.070313 0.078126 0.082031 0.078124 0.09375 0.10156 0.0625 0.066406 0.074219 0.078125 0.078125 0.078125 0.09375 0.10156 0.074218 0.066406 0.085938 0.10156 0.070312 0.066407 0.089844 0.097656 0.070313 0.066406 0.078125 0.085938 0.050781 0.058593 0.066406 0.066407 0.015625 0.011719 0.011719 0.015624 0.007813 0.015626h0.015624l0.015626-0.007813c0.011718-0.015625 0.027343-0.027344 0.042968-0.042969l0.019532-0.023437 0.007812-0.011719 0.058594-0.058594c0.10938-0.11719 0.21484-0.23438 0.31641-0.35547 0.17578-0.18359 0.34766-0.37109 0.51172-0.5625 0.125-0.14453 0.25391-0.28516 0.38281-0.41797 0.082031-0.089844 0.16406-0.17578 0.24609-0.26172 0.023437-0.023438 0.042969-0.046875 0.066406-0.070312l0.085938-0.09375c0.023437-0.023438 0.042969-0.050782 0.066406-0.074219 0.10547-0.12109 0.21094-0.24219 0.32422-0.35938 0.050781-0.054688 0.10156-0.10547 0.15234-0.16016l0.070312-0.085938c0.015625-0.015625 0.03125-0.035156 0.042969-0.050781 0.0625-0.074219 0.12891-0.14453 0.19531-0.21094 0.015625-0.015625 0.035157-0.035156 0.050781-0.054687l0.015626-0.007813 0.035156-0.050781 0.16797-0.17188c0.015626-0.023437 0.03125-0.042969 0.046876-0.058594l0.058593-0.066406 0.074219-0.078125c0.007812-0.011719 0.019531-0.019531 0.027344-0.03125l0.007812-0.011719h0.015625l0.011719-0.03125 0.023438-0.019531 0.050781-0.058594c0.046875-0.054687 0.09375-0.10938 0.14453-0.16016l0.10938-0.10547 0.035156-0.046875 0.11328-0.097656 0.066407-0.058594 0.10938-0.10156c0.019531-0.015625 0.039062-0.027344 0.058594-0.042969l0.14844-0.125 0.015625-0.011719 0.40625-0.27734 0.011719-0.011718c0.44141-0.26172 0.91406-0.46484 1.4102-0.60156 0.12891-0.042969 0.25781-0.070312 0.39062-0.09375l0.019531-0.007812c0.16016-0.027344 0.3125-0.042969 0.47656-0.054688h0.015625c0.11328-0.015625 0.23828-0.023437 0.35156-0.023437z';
+
+function round4(value: number) {
+ 'worklet'
+ return Math.round(value * 10000) / 10000;
+}
+
+function round3(value: number) {
+ 'worklet'
+ return Math.round(value * 1000) / 1000;
+}
+
+export const RPCSXBackground = () => {
+ const progressValue = useSharedValue(0);
+ const { width, height } = useWindowDimensions();
+
+ useEffect(() => {
+ progressValue.value = withSequence(
+ withTiming(1, { duration: 6000, easing: Easing.bezier(0, 0.4, 0.2, 1.0) }),
+ withRepeat(
+ withSequence(
+ withTiming(0, { duration: 0 }),
+ withTiming(1, { duration: 120000, easing: Easing.linear }),
+ ),
+ -1,
+ true
+ ));
+ }, []);
+
+ const progress = useDerivedValue(() => round4(progressValue.value), [progressValue])
+
+ const particleCount = 30;
+ const particles = React.useMemo(() =>
+ Array.from({ length: particleCount }).map((_, i) => {
+ const angle = (i / particleCount) * Math.PI * 2;
+ const radius = Math.random() * (width / 2) + (height / 3);
+ return {
+ id: i,
+ startX: width / 2 + Math.cos(angle) * radius,
+ startY: height / 2 + Math.sin(angle) * radius,
+ endX: width / 2,
+ endY: height / 2,
+ size: Math.random() * 10 + 2,
+ delay: Math.random(),
+ orbitRadius: Math.random() * 150 + 50,
+ orbitSpeed: Math.random() * 2 + 1,
+ };
+ }), [width, height, particleCount]
+ );
+
+ const waveCount = 2;
+ const waves = React.useMemo(() =>
+ Array.from({ length: waveCount }).map((_, i) => ({
+ id: i,
+ amplitude: 40 + i * 15,
+ frequency: 0.008 - i * 0.002,
+ phase: i * Math.PI / 3,
+ opacity: 0.4 - i * 0.03,
+ })), [waveCount]
+ );
+
+ const crossPath = React.useMemo(() => {
+ const path = Skia.Path.MakeFromSVGString(X_ICON_PATH);
+ if (!path) return null;
+
+ const targetSize = Math.min(width, height) * 0.9;
+ const scale = targetSize / 24;
+ path.transform(Skia.Matrix().scale(scale, scale));
+ const bounds = path.getBounds();
+ path.transform(Skia.Matrix().translate(
+ width / 2 - (bounds.x + bounds.width / 2),
+ height / 2 - (bounds.y + bounds.height / 2),
+ ));
+ return path;
+ }, [width, height]);
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/rpcsx-ui/src/core/renderer/main.tsx b/rpcsx-ui/src/core/renderer/main.tsx
index 2019969..5eae306 100644
--- a/rpcsx-ui/src/core/renderer/main.tsx
+++ b/rpcsx-ui/src/core/renderer/main.tsx
@@ -67,6 +67,7 @@ export function main(
function App() {
const [renderItem, setRenderItem] = useState(viewStack.length - 1);
+ const [updateId, setUpdateId] = useState(0);
useEffect(() => {
console.log("app entered");
@@ -85,6 +86,7 @@ function App() {
setRenderItem(item);
}
}
+ setUpdateId(updateId + 1);
};
});
@@ -103,7 +105,7 @@ function App() {
return (
-
+
)
}
diff --git a/rpcsx-ui/src/core/renderer/useThemeColor.ts b/rpcsx-ui/src/core/renderer/useThemeColor.ts
index c296f4e..66fb9a6 100644
--- a/rpcsx-ui/src/core/renderer/useThemeColor.ts
+++ b/rpcsx-ui/src/core/renderer/useThemeColor.ts
@@ -7,6 +7,24 @@ export function useThemeColor(
return Colors[useColorScheme()][colorName];
}
+export function withAlpha(
+ color: string,
+ alpha: number
+) {
+ if (color.startsWith('#')) {
+ const r = parseInt(color.slice(1, 3), 16);
+ const g = parseInt(color.slice(3, 5), 16);
+ const b = parseInt(color.slice(5, 7), 16);
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+ }
+
+ if (color.startsWith('rgb(')) {
+ return color.replace('rgb(', 'rgba(').replace(')', `, ${alpha})`);
+ }
+
+ return color;
+}
+
export function useThemeColorOr(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
diff --git a/rpcsx-ui/src/setup/component.json b/rpcsx-ui/src/setup/component.json
new file mode 100644
index 0000000..69ce243
--- /dev/null
+++ b/rpcsx-ui/src/setup/component.json
@@ -0,0 +1,37 @@
+{
+ "name": "setup",
+ "version": "0.1.0",
+ "contributions": {
+ "settings": {
+ "show-initial-setup-screen": {
+ "type": "boolean",
+ "defaultValue": true
+ }
+ },
+ "methods": {
+ "should-show": {
+ "handler": "handleShouldShow",
+ "params": {},
+ "returns": {
+ "value": {
+ "type": "boolean"
+ }
+ }
+ },
+ "set-show-initial-setup-screen": {
+ "handler": "setShowInitialSetupScreen",
+ "params": {
+ "value": {
+ "type": "boolean"
+ }
+ },
+ "returns": {}
+ }
+ }
+ },
+ "dependencies": [
+ {
+ "name": "explorer"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/rpcsx-ui/src/setup/renderer/views/InitialSetup.tsx b/rpcsx-ui/src/setup/renderer/views/InitialSetup.tsx
new file mode 100644
index 0000000..c9592d8
--- /dev/null
+++ b/rpcsx-ui/src/setup/renderer/views/InitialSetup.tsx
@@ -0,0 +1,317 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import {
+ ScrollView,
+ View,
+ Text,
+} from 'react-native';
+import Animated, {
+ FadeOut,
+ SlideInRight,
+} from 'react-native-reanimated';
+import * as explorer from '$explorer';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import * as self from '$';
+import { useWindowDimensions } from 'react-native';
+import { ThemedText } from '$core/ThemedText';
+import { ThemedView } from '$core/ThemedView';
+import { LanguageItem } from '$core/LanguageItem';
+import { FocusableText } from '$core/FocusableText';
+import { useThemeColor, withAlpha } from '$core/useThemeColor'
+import { RPCSXBackground } from '$core/RPCSXBackground';
+import * as RNLocalize from 'react-native-localize';
+
+export function InitialSetup() {
+ const { width, height } = useWindowDimensions();
+ const scrollRef = useRef(null);
+ const [step, setStep] = useState(0);
+ const insets = useSafeAreaInsets();
+ const primaryColor = useThemeColor("primary");
+ const secondaryColor = useThemeColor("secondary");
+
+ const LANGUAGES = [
+ { id: 'en', label: 'English' },
+ { id: 'ja', label: '日本語' },
+ { id: 'fr', label: 'Français' },
+ { id: 'de', label: 'Deutsch' },
+ { id: 'es', label: 'Español' },
+ { id: 'it', label: 'Italiano' },
+ ];
+
+ const [language, setLanguage] = useState(null);
+
+ const handleSelect = useCallback((id: string) => {
+ setLanguage(id);
+ }, []);
+
+ useEffect(() => {
+ if (language !== null) return;
+
+ const availableIds = LANGUAGES.map(l => l.id);
+
+ const locales = RNLocalize.getLocales();
+
+ if (!locales.length) {
+ setLanguage('en');
+ return;
+ }
+
+ const deviceLang = locales[0].languageCode.toLowerCase();
+
+ const matched = availableIds.includes(deviceLang)
+ ? deviceLang
+ : 'en';
+
+ setLanguage(matched);
+ }, []);
+
+ useEffect(() => {
+ scrollRef.current?.scrollTo({
+ x: step * width,
+ animated: false,
+ });
+ }, [width]);
+
+ const steps = [
+ {
+ title: 'Welcome to RPCSX',
+ subtitle: "Let's get things set up",
+ content: 'RPCSX will help you configure everything.',
+ confirmText: 'Start (X)',
+ },
+ {
+ title: 'Choose Language',
+ subtitle: 'Select your preferred language',
+ content: 'You can change this later in settings.',
+ confirmText: 'Select (X)',
+ },
+ {
+ title: 'Scan Games',
+ subtitle: 'Find your games',
+ content: 'We will scan your folders for supported games.',
+ confirmText: 'Scan (X)',
+ },
+ {
+ title: 'Ready to Go',
+ subtitle: 'Setup complete',
+ content: 'You\'re all set. Enjoy playing!',
+ confirmText: 'Finish (X)',
+ },
+ ];
+
+ const goToStep = (index: number) => {
+ scrollRef.current?.scrollTo({
+ x: index * width,
+ animated: true,
+ });
+ setStep(index);
+ };
+
+ const next = () => {
+ if (step < steps.length - 1) {
+ goToStep(step + 1);
+ } else {
+ self.setupSetShowInitialSetupScreen({ value: false });
+ explorer.setExplorerView({
+ filter: { type: 'game' },
+ });
+ }
+ };
+
+ const back = () => {
+ if (step > 0) {
+ goToStep(step - 1);
+ }
+ };
+
+ return (
+
+
+
+ {/* Pager */}
+
+ {steps.map((s, index) =>
+ index === 1 ? (
+
+
+ {s.title}
+
+
+
+ {s.content}
+
+
+ {/* Language Grid */}
+
+ {LANGUAGES.map(lang => (
+ handleSelect(lang.id)}
+ />
+ ))}
+
+
+ ) : (
+
+
+ {s.title}
+
+
+
+ {s.subtitle}
+
+
+ ))}
+
+
+
+
+ {steps[step].content}
+
+
+
+ {/* Step indicators */}
+
+ {steps.map((_, i) => (
+
+ ))}
+
+
+ {/* Bottom buttons */}
+
+ {step > 0 && (
+
+
+ Back (O)
+
+
+ )}
+
+
+
+ {steps[step].confirmText}
+
+
+
+
+ );
+}
+
diff --git a/rpcsx-ui/src/setup/server/main.ts b/rpcsx-ui/src/setup/server/main.ts
new file mode 100644
index 0000000..afd0b3c
--- /dev/null
+++ b/rpcsx-ui/src/setup/server/main.ts
@@ -0,0 +1,9 @@
+import * as self from '$';
+
+export async function setShowInitialSetupScreen(caller: ComponentRef, value: boolean) {
+ // TODO(DH): Implement this function
+}
+
+export async function handleShouldShow() {
+ return (await self.settings.getShowInitialSetupScreen()).value;
+}