forked from MeloNX/MeloNX
android - add basic user management
This commit is contained in:
parent
8e2eb5fd26
commit
ba745514a1
@ -18,6 +18,7 @@ using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
@ -95,6 +96,12 @@ namespace LibRyujinx
|
||||
return s;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")]
|
||||
public static void JniReloadFileSystem()
|
||||
{
|
||||
SwitchDevice?.ReloadFileSystem();
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInitialize")]
|
||||
public static JBoolean JniInitializeDeviceNative(JEnvRef jEnv,
|
||||
JObjectLocalRef jObj,
|
||||
@ -503,6 +510,89 @@ namespace LibRyujinx
|
||||
return ConnectGamepad(index);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
|
||||
public static JStringLocalRef JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||
{
|
||||
var userId = GetOpenedUser();
|
||||
|
||||
return CreateString(jEnv, userId);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
|
||||
public static JStringLocalRef JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
|
||||
return CreateString(jEnv, GetUserPicture(userId));
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")]
|
||||
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
var picture = GetString(jEnv, picturePtr) ?? "";
|
||||
|
||||
SetUserPicture(userId, picture);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")]
|
||||
public static JStringLocalRef JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
|
||||
return CreateString(jEnv, GetUserName(userId));
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")]
|
||||
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
var userName = GetString(jEnv, userNamePtr) ?? "";
|
||||
|
||||
SetUserName(userId, userName);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")]
|
||||
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||
{
|
||||
var users = GetAllUsers();
|
||||
|
||||
return CreateStringArray(jEnv, users.ToList());
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")]
|
||||
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr)
|
||||
{
|
||||
var userName = GetString(jEnv, userNamePtr) ?? "";
|
||||
var picture = GetString(jEnv, picturePtr) ?? "";
|
||||
|
||||
AddUser(userName, picture);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")]
|
||||
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
|
||||
DeleteUser(userId);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
|
||||
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
|
||||
OpenUser(userId);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")]
|
||||
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||
{
|
||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||
|
||||
CloseUser(userId);
|
||||
}
|
||||
|
||||
private static FileStream OpenFile(int descriptor)
|
||||
{
|
||||
var safeHandle = new SafeFileHandle(descriptor, false);
|
||||
|
@ -17,6 +17,12 @@ namespace LibRyujinx
|
||||
return InitializeDevice(true, false, SystemLanguage.AmericanEnglish, RegionCode.USA, true, true, true, false, "UTC", false);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "device_reloadFilesystem")]
|
||||
public static void ReloadFileSystem()
|
||||
{
|
||||
SwitchDevice?.ReloadFileSystem();
|
||||
}
|
||||
|
||||
public static bool InitializeDevice(bool isHostMapped,
|
||||
bool useNce,
|
||||
SystemLanguage systemLanguage,
|
||||
|
82
src/LibRyujinx/LibRyujinx.User.cs
Normal file
82
src/LibRyujinx/LibRyujinx.User.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibRyujinx
|
||||
{
|
||||
public static partial class LibRyujinx
|
||||
{
|
||||
public static string GetOpenedUser()
|
||||
{
|
||||
var lastProfile = SwitchDevice?.AccountManager.LastOpenedUser;
|
||||
|
||||
return lastProfile?.UserId.ToString() ?? "";
|
||||
}
|
||||
|
||||
public static string GetUserPicture(string userId)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
|
||||
var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid);
|
||||
|
||||
if (user == null)
|
||||
return "";
|
||||
|
||||
var pic = user.Image;
|
||||
|
||||
return pic != null ? Convert.ToBase64String(pic) : "";
|
||||
}
|
||||
|
||||
public static void SetUserPicture(string userId, string picture)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
|
||||
SwitchDevice?.AccountManager.SetUserImage(uid, Convert.FromBase64String(picture));
|
||||
}
|
||||
|
||||
public static string GetUserName(string userId)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
|
||||
var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid);
|
||||
|
||||
return user?.Name ?? "";
|
||||
}
|
||||
|
||||
public static void SetUserName(string userId, string name)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
|
||||
SwitchDevice?.AccountManager.SetUserName(uid, name);
|
||||
}
|
||||
|
||||
public static string[] GetAllUsers()
|
||||
{
|
||||
return SwitchDevice?.AccountManager.GetAllUsers().Select(x => x.UserId.ToString()).ToArray() ??
|
||||
Array.Empty<string>();
|
||||
}
|
||||
|
||||
public static void AddUser(string userName, string picture)
|
||||
{
|
||||
SwitchDevice?.AccountManager.AddUser(userName, Convert.FromBase64String(picture));
|
||||
}
|
||||
|
||||
public static void DeleteUser(string userId)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
SwitchDevice?.AccountManager.DeleteUser(uid);
|
||||
}
|
||||
|
||||
public static void OpenUser(string userId)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
SwitchDevice?.AccountManager.OpenUser(uid);
|
||||
}
|
||||
|
||||
public static void CloseUser(string userId)
|
||||
{
|
||||
var uid = new UserId(userId);
|
||||
SwitchDevice?.AccountManager.CloseUser(uid);
|
||||
}
|
||||
}
|
||||
}
|
@ -724,6 +724,12 @@ namespace LibRyujinx
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void ReloadFileSystem()
|
||||
{
|
||||
VirtualFileSystem.ReloadKeySet();
|
||||
ContentManager = new ContentManager(VirtualFileSystem);
|
||||
}
|
||||
|
||||
internal void DisposeContext()
|
||||
{
|
||||
EmulationContext?.Dispose();
|
||||
|
@ -53,4 +53,14 @@ class RyujinxNative {
|
||||
external fun deviceSignalEmulationClose()
|
||||
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
|
||||
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<String>
|
||||
external fun userGetOpenedUser() : String
|
||||
external fun userGetUserPicture(userId: String) : String
|
||||
external fun userSetUserPicture(userId: String, picture: String)
|
||||
external fun userGetUserName(userId: String) : String
|
||||
external fun userSetUserName(userId: String, userName: String)
|
||||
external fun userGetAllUsers() : Array<String>
|
||||
external fun userAddUser(username: String, picture: String)
|
||||
external fun userDeleteUser(userId: String)
|
||||
external fun userOpenUser(userId: String)
|
||||
external fun userCloseUser(userId: String)
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.ryujinx.android.views
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -11,15 +13,16 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.Person
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@ -47,7 +50,10 @@ 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.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@ -57,9 +63,11 @@ import coil.compose.AsyncImage
|
||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||
import org.ryujinx.android.MainActivity
|
||||
import org.ryujinx.android.R
|
||||
import org.ryujinx.android.RyujinxNative
|
||||
import org.ryujinx.android.viewmodels.GameModel
|
||||
import org.ryujinx.android.viewmodels.HomeViewModel
|
||||
import java.io.File
|
||||
import java.util.Base64
|
||||
import java.util.Locale
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.roundToInt
|
||||
@ -84,6 +92,10 @@ class HomeViews {
|
||||
val refresh = remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
val native = RyujinxNative()
|
||||
val user = native.userGetOpenedUser()
|
||||
val decoder = Base64.getDecoder()
|
||||
val pic = decoder.decode(native.userGetUserPicture(user))
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
@ -115,10 +127,16 @@ class HomeViews {
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
navController?.navigate("user")
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Filled.Person,
|
||||
contentDescription = "Run"
|
||||
Image(
|
||||
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size).asImageBitmap(),
|
||||
contentDescription = "user image",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.size(52.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
|
@ -16,6 +16,7 @@ class MainView {
|
||||
|
||||
NavHost(navController = navController, startDestination = "home") {
|
||||
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
||||
composable("user") { UserViews.Main(mainViewModel, navController) }
|
||||
composable("settings") {
|
||||
SettingViews.Main(
|
||||
SettingsViewModel(
|
||||
|
@ -0,0 +1,182 @@
|
||||
package org.ryujinx.android.views
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
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.clip
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import org.ryujinx.android.RyujinxNative
|
||||
import org.ryujinx.android.viewmodels.MainViewModel
|
||||
import java.util.Base64
|
||||
|
||||
class UserViews {
|
||||
companion object {
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) {
|
||||
val ryujinxNative = RyujinxNative()
|
||||
val decoder = Base64.getDecoder()
|
||||
val openedUser = remember {
|
||||
mutableStateOf(ryujinxNative.userGetOpenedUser())
|
||||
}
|
||||
|
||||
val openedUserPic = remember {
|
||||
mutableStateOf(decoder.decode(ryujinxNative.userGetUserPicture(openedUser.value)))
|
||||
}
|
||||
val openedUserName = remember {
|
||||
mutableStateOf(ryujinxNative.userGetUserName(openedUser.value))
|
||||
}
|
||||
|
||||
val userList = remember {
|
||||
mutableListOf("")
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
userList.clear()
|
||||
userList.addAll(ryujinxNative.userGetAllUsers())
|
||||
}
|
||||
|
||||
refresh()
|
||||
|
||||
Scaffold(modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(title = {
|
||||
Text(text = "Users")
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
viewModel?.navController?.popBackStack()
|
||||
}) {
|
||||
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
})
|
||||
}) { contentPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(contentPadding)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(text = "Selected user")
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Image(
|
||||
bitmap = BitmapFactory.decodeByteArray(
|
||||
openedUserPic.value,
|
||||
0,
|
||||
openedUserPic.value.size
|
||||
).asImageBitmap(),
|
||||
contentDescription = "selected image",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.size(96.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(text = openedUserName.value)
|
||||
Text(text = openedUser.value)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(text = "Available Users")
|
||||
IconButton(onClick = {
|
||||
refresh()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
contentDescription = "refresh users"
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 96.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
items(userList) { user ->
|
||||
val pic = decoder.decode(ryujinxNative.userGetUserPicture(user))
|
||||
val name = ryujinxNative.userGetUserName(user)
|
||||
Image(
|
||||
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
|
||||
.asImageBitmap(),
|
||||
contentDescription = "selected image",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(4.dp)
|
||||
.clip(CircleShape)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
ryujinxNative.userOpenUser(user)
|
||||
openedUser.value = user
|
||||
openedUserPic.value = pic
|
||||
openedUserName.value = name
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun Preview() {
|
||||
UserViews.Main()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user