mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-29 14:04:19 +01:00
Implement full profile picture support
Extends the profile picture stub into a full-fledged implementation with the ability for users to set their profile picture in settings while having the Skyline icon as the default profile picture.
This commit is contained in:
parent
7a3d2e4a26
commit
80c0f8f04d
@ -153,6 +153,7 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
implementation 'androidx.appcompat:appcompat:1.5.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
|
implementation 'androidx.activity:activity-ktx:1.6.1'
|
||||||
implementation 'com.google.android.material:material:1.6.1'
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
|
BIN
app/src/main/assets/profile_picture.jpeg
Normal file
BIN
app/src/main/assets/profile_picture.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
@ -33,6 +33,7 @@ namespace skyline {
|
|||||||
void Update() override {
|
void Update() override {
|
||||||
isDocked = ktSettings.GetBool("isDocked");
|
isDocked = ktSettings.GetBool("isDocked");
|
||||||
usernameValue = std::move(ktSettings.GetString("usernameValue"));
|
usernameValue = std::move(ktSettings.GetString("usernameValue"));
|
||||||
|
profilePictureValue = ktSettings.GetString("profilePictureValue");
|
||||||
systemLanguage = ktSettings.GetInt<skyline::language::SystemLanguage>("systemLanguage");
|
systemLanguage = ktSettings.GetInt<skyline::language::SystemLanguage>("systemLanguage");
|
||||||
systemRegion = ktSettings.GetInt<skyline::region::RegionCode>("systemRegion");
|
systemRegion = ktSettings.GetInt<skyline::region::RegionCode>("systemRegion");
|
||||||
forceTripleBuffering = ktSettings.GetBool("forceTripleBuffering");
|
forceTripleBuffering = ktSettings.GetBool("forceTripleBuffering");
|
||||||
|
@ -61,6 +61,7 @@ namespace skyline {
|
|||||||
// System
|
// System
|
||||||
Setting<bool> isDocked; //!< If the emulated Switch should be handheld or docked
|
Setting<bool> isDocked; //!< If the emulated Switch should be handheld or docked
|
||||||
Setting<std::string> usernameValue; //!< The user name to be supplied to the guest
|
Setting<std::string> usernameValue; //!< The user name to be supplied to the guest
|
||||||
|
Setting<std::string> profilePictureValue; //!< The profile picture path to be supplied to the guest
|
||||||
Setting<language::SystemLanguage> systemLanguage; //!< The system language
|
Setting<language::SystemLanguage> systemLanguage; //!< The system language
|
||||||
Setting<region::RegionCode> systemRegion; //!< The system region
|
Setting<region::RegionCode> systemRegion; //!< The system region
|
||||||
|
|
||||||
|
@ -1,23 +1,13 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
|
#include <os.h>
|
||||||
|
#include <vfs/os_backing.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <common/settings.h>
|
#include <common/settings.h>
|
||||||
#include "IProfile.h"
|
#include "IProfile.h"
|
||||||
|
|
||||||
namespace skyline::service::account {
|
namespace skyline::service::account {
|
||||||
// Smallest JPEG file https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
|
|
||||||
constexpr std::array<u8, 107> profileImageIcon{
|
|
||||||
0xFF, 0xD8, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04,
|
|
||||||
0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0A,
|
|
||||||
0x0A, 0x09, 0x08, 0x09, 0x09, 0x0A, 0x0C, 0x0F, 0x0C, 0x0A, 0x0B, 0x0E,
|
|
||||||
0x0B, 0x09, 0x09, 0x0D, 0x11, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x11, 0x10,
|
|
||||||
0x0A, 0x0C, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0F, 0x10, 0x10, 0x10, 0xFF,
|
|
||||||
0xC9, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
|
|
||||||
0xFF, 0xCC, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xFF, 0xDA, 0x00, 0x08,
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0x3F, 0x00, 0xD2, 0xCF, 0x20, 0xFF, 0xD9
|
|
||||||
};
|
|
||||||
|
|
||||||
IProfile::IProfile(const DeviceState &state, ServiceManager &manager, const UserId &userId) : userId(userId), BaseService(state, manager) {}
|
IProfile::IProfile(const DeviceState &state, ServiceManager &manager, const UserId &userId) : userId(userId), BaseService(state, manager) {}
|
||||||
|
|
||||||
Result IProfile::Get(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result IProfile::Get(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
@ -53,14 +43,28 @@ namespace skyline::service::account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result IProfile::GetImageSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result IProfile::GetImageSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
response.Push<u32>(profileImageIcon.size());
|
std::shared_ptr<vfs::Backing> profileImageIcon{GetProfilePicture()};
|
||||||
|
response.Push(static_cast<u32>(profileImageIcon->size));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IProfile::LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result IProfile::LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
// TODO: load actual profile image
|
std::shared_ptr<vfs::Backing> profileImageIcon{GetProfilePicture()};
|
||||||
request.outputBuf.at(0).copy_from(profileImageIcon);
|
|
||||||
response.Push<u32>(profileImageIcon.size());
|
profileImageIcon->Read(span(request.outputBuf.at(0)).first(profileImageIcon->size), 0);
|
||||||
|
response.Push(static_cast<u32>(profileImageIcon->size));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<vfs::Backing> IProfile::GetProfilePicture() {
|
||||||
|
std::string profilePicturePath{*state.settings->profilePictureValue};
|
||||||
|
int fd{open((profilePicturePath).c_str(), O_RDONLY)};
|
||||||
|
if (fd < 0)
|
||||||
|
// If we can't find the profile picture then just return the placeholder profile picture
|
||||||
|
return state.os->assetFileSystem->OpenFile("profile_picture.jpeg");
|
||||||
|
else
|
||||||
|
return std::make_shared<vfs::OsBacking>(fd, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,12 @@ namespace skyline::service::account {
|
|||||||
*/
|
*/
|
||||||
Result LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
Result LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to get the user's profile picture. If not found, returns the default one
|
||||||
|
* @return A shared pointer to a Backing object of the profile picture
|
||||||
|
*/
|
||||||
|
std::shared_ptr<vfs::Backing> GetProfilePicture();
|
||||||
|
|
||||||
SERVICE_DECL(
|
SERVICE_DECL(
|
||||||
SFUNC(0x0, IProfile, Get),
|
SFUNC(0x0, IProfile, Get),
|
||||||
SFUNC(0x1, IProfile, GetBase),
|
SFUNC(0x1, IProfile, GetBase),
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
|
* Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package emu.skyline.preference
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.Preference.SummaryProvider
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.preference.R
|
||||||
|
import emu.skyline.SkylineApplication
|
||||||
|
import emu.skyline.getPublicFilesDir
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class ProfilePicturePreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
|
||||||
|
private val pickMedia = (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
|
||||||
|
val profilePictureDir = SkylineApplication.instance.getPublicFilesDir().canonicalPath + "/switch/nand/system/save/8000000000000010/su/avators"
|
||||||
|
val profilePictureName = "profile_picture.jpeg"
|
||||||
|
try {
|
||||||
|
if (uri != null) { // The user selected a picture
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(key, "$profilePictureDir/$profilePictureName").apply()
|
||||||
|
File(profilePictureDir).mkdirs()
|
||||||
|
context.applicationContext.contentResolver.let { contentResolver : ContentResolver ->
|
||||||
|
val readUriPermission : Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
contentResolver.takePersistableUriPermission(uri, readUriPermission)
|
||||||
|
contentResolver.openInputStream(uri)?.use { inputStream : InputStream ->
|
||||||
|
var bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
// Compress the picture
|
||||||
|
bitmap = Bitmap.createScaledBitmap(bitmap, 256, 256, false)
|
||||||
|
StoreBitmap(bitmap, "$profilePictureDir/$profilePictureName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // No picture was selected, clear the profile picture if one was already set
|
||||||
|
if (File("$profilePictureDir/$profilePictureName").exists()) {
|
||||||
|
File("$profilePictureDir/$profilePictureName").delete()
|
||||||
|
}
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(key, "No picture selected").apply()
|
||||||
|
}
|
||||||
|
notifyChanged()
|
||||||
|
} catch (e : Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
summaryProvider = SummaryProvider<ProfilePicturePreference> { preference ->
|
||||||
|
Uri.decode(preference.getPersistedString("No picture selected"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick() = pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a bitmap, saves it in the specified location
|
||||||
|
*/
|
||||||
|
private fun StoreBitmap(bitmap : Bitmap, filePath : String) {
|
||||||
|
try {
|
||||||
|
// Create the file where the bitmap will be stored
|
||||||
|
val file = File(filePath)
|
||||||
|
file.createNewFile()
|
||||||
|
// Store bitmap as JPEG
|
||||||
|
val outputFile = FileOutputStream(file)
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputFile)
|
||||||
|
outputFile.flush()
|
||||||
|
outputFile.close()
|
||||||
|
} catch (e : Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ class NativeSettings(context : Context, pref : PreferenceSettings) {
|
|||||||
// System
|
// System
|
||||||
var isDocked : Boolean = pref.isDocked
|
var isDocked : Boolean = pref.isDocked
|
||||||
var usernameValue : String = pref.usernameValue
|
var usernameValue : String = pref.usernameValue
|
||||||
|
var profilePictureValue : String = pref.profilePictureValue
|
||||||
var systemLanguage : Int = pref.systemLanguage
|
var systemLanguage : Int = pref.systemLanguage
|
||||||
var systemRegion : Int = pref.systemRegion
|
var systemRegion : Int = pref.systemRegion
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ class PreferenceSettings @Inject constructor(@ApplicationContext private val con
|
|||||||
// System
|
// System
|
||||||
var isDocked by sharedPreferences(context, true)
|
var isDocked by sharedPreferences(context, true)
|
||||||
var usernameValue by sharedPreferences(context, context.getString(R.string.username_default))
|
var usernameValue by sharedPreferences(context, context.getString(R.string.username_default))
|
||||||
|
var profilePictureValue by sharedPreferences(context, "")
|
||||||
var systemLanguage by sharedPreferences(context, 1)
|
var systemLanguage by sharedPreferences(context, 1)
|
||||||
var systemRegion by sharedPreferences(context, -1)
|
var systemRegion by sharedPreferences(context, -1)
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
<string name="docked_enabled">The system will emulate being in docked mode</string>
|
<string name="docked_enabled">The system will emulate being in docked mode</string>
|
||||||
<string name="username">Username</string>
|
<string name="username">Username</string>
|
||||||
<string name="username_default" translatable="false">@string/app_name</string>
|
<string name="username_default" translatable="false">@string/app_name</string>
|
||||||
|
<string name="profile_picture">Profile picture</string>
|
||||||
<string name="system_language">System language</string>
|
<string name="system_language">System language</string>
|
||||||
<string name="system_region">System region</string>
|
<string name="system_region">System region</string>
|
||||||
<!-- Settings - Keys -->
|
<!-- Settings - Keys -->
|
||||||
|
@ -70,6 +70,9 @@
|
|||||||
app:key="username_value"
|
app:key="username_value"
|
||||||
app:limit="31"
|
app:limit="31"
|
||||||
app:title="@string/username" />
|
app:title="@string/username" />
|
||||||
|
<emu.skyline.preference.ProfilePicturePreference
|
||||||
|
app:key="profile_picture_value"
|
||||||
|
app:title="@string/profile_picture" />
|
||||||
<emu.skyline.preference.IntegerListPreference
|
<emu.skyline.preference.IntegerListPreference
|
||||||
android:defaultValue="1"
|
android:defaultValue="1"
|
||||||
android:entries="@array/system_languages"
|
android:entries="@array/system_languages"
|
||||||
|
Loading…
Reference in New Issue
Block a user