mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-26 15:11:47 +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.constraintlayout:constraintlayout:2.1.4'
|
||||
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 'androidx.documentfile:documentfile:1.0.1'
|
||||
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 {
|
||||
isDocked = ktSettings.GetBool("isDocked");
|
||||
usernameValue = std::move(ktSettings.GetString("usernameValue"));
|
||||
profilePictureValue = ktSettings.GetString("profilePictureValue");
|
||||
systemLanguage = ktSettings.GetInt<skyline::language::SystemLanguage>("systemLanguage");
|
||||
systemRegion = ktSettings.GetInt<skyline::region::RegionCode>("systemRegion");
|
||||
forceTripleBuffering = ktSettings.GetBool("forceTripleBuffering");
|
||||
|
@ -61,6 +61,7 @@ namespace skyline {
|
||||
// System
|
||||
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> profilePictureValue; //!< The profile picture path to be supplied to the guest
|
||||
Setting<language::SystemLanguage> systemLanguage; //!< The system language
|
||||
Setting<region::RegionCode> systemRegion; //!< The system region
|
||||
|
||||
|
@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// 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 "IProfile.h"
|
||||
|
||||
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) {}
|
||||
|
||||
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) {
|
||||
response.Push<u32>(profileImageIcon.size());
|
||||
std::shared_ptr<vfs::Backing> profileImageIcon{GetProfilePicture()};
|
||||
response.Push(static_cast<u32>(profileImageIcon->size));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result IProfile::LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
// TODO: load actual profile image
|
||||
request.outputBuf.at(0).copy_from(profileImageIcon);
|
||||
response.Push<u32>(profileImageIcon.size());
|
||||
std::shared_ptr<vfs::Backing> profileImageIcon{GetProfilePicture()};
|
||||
|
||||
profileImageIcon->Read(span(request.outputBuf.at(0)).first(profileImageIcon->size), 0);
|
||||
response.Push(static_cast<u32>(profileImageIcon->size));
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* @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(
|
||||
SFUNC(0x0, IProfile, Get),
|
||||
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
|
||||
var isDocked : Boolean = pref.isDocked
|
||||
var usernameValue : String = pref.usernameValue
|
||||
var profilePictureValue : String = pref.profilePictureValue
|
||||
var systemLanguage : Int = pref.systemLanguage
|
||||
var systemRegion : Int = pref.systemRegion
|
||||
|
||||
|
@ -25,6 +25,7 @@ class PreferenceSettings @Inject constructor(@ApplicationContext private val con
|
||||
// System
|
||||
var isDocked by sharedPreferences(context, true)
|
||||
var usernameValue by sharedPreferences(context, context.getString(R.string.username_default))
|
||||
var profilePictureValue by sharedPreferences(context, "")
|
||||
var systemLanguage 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="username">Username</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_region">System region</string>
|
||||
<!-- Settings - Keys -->
|
||||
|
@ -70,6 +70,9 @@
|
||||
app:key="username_value"
|
||||
app:limit="31"
|
||||
app:title="@string/username" />
|
||||
<emu.skyline.preference.ProfilePicturePreference
|
||||
app:key="profile_picture_value"
|
||||
app:title="@string/profile_picture" />
|
||||
<emu.skyline.preference.IntegerListPreference
|
||||
android:defaultValue="1"
|
||||
android:entries="@array/system_languages"
|
||||
|
Loading…
Reference in New Issue
Block a user