forked from MeloNX/MeloNX
add dlc manager
This commit is contained in:
parent
fb562c8077
commit
7a85dc2e76
@ -152,6 +152,22 @@ namespace LibRyujinx
|
|||||||
return LoadApplication(path);
|
return LoadApplication(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
|
||||||
|
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JLong titleId)
|
||||||
|
{
|
||||||
|
var list = GetDlcContentList(GetString(jEnv, pathPtr), (ulong)(long)titleId);
|
||||||
|
|
||||||
|
debug_break(4);
|
||||||
|
|
||||||
|
return CreateStringArray(jEnv, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")]
|
||||||
|
public static JStringLocalRef JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JStringLocalRef ncaPath)
|
||||||
|
{
|
||||||
|
return CreateString(jEnv, GetDlcTitleId(GetString(jEnv, pathPtr), GetString(jEnv, ncaPath)));
|
||||||
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
|
||||||
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
@ -296,6 +312,27 @@ namespace LibRyujinx
|
|||||||
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
|
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JArrayLocalRef CreateStringArray(JEnvRef jEnv, List<string> strings)
|
||||||
|
{
|
||||||
|
JEnvValue value = jEnv.Environment;
|
||||||
|
ref JNativeInterface jInterface = ref value.Functions;
|
||||||
|
IntPtr newObjectArrayPtr = jInterface.NewObjectArrayPointer;
|
||||||
|
IntPtr findClassPtr = jInterface.FindClassPointer;
|
||||||
|
IntPtr setObjectArrayElementPtr = jInterface.SetObjectArrayElementPointer;
|
||||||
|
|
||||||
|
var newObjectArray = newObjectArrayPtr.GetUnsafeDelegate<NewObjectArrayDelegate>();
|
||||||
|
var findClass = findClassPtr.GetUnsafeDelegate<FindClassDelegate>();
|
||||||
|
var setObjectArrayElement = setObjectArrayElementPtr.GetUnsafeDelegate<SetObjectArrayElementDelegate>();
|
||||||
|
var array = newObjectArray(jEnv, strings.Count, findClass(jEnv, GetCCharSequence("java/lang/String")), CreateString(jEnv, "")._value);
|
||||||
|
|
||||||
|
for (int i = 0; i < strings.Count; i++)
|
||||||
|
{
|
||||||
|
setObjectArrayElement(jEnv, array, i, CreateString(jEnv, strings[i])._value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
||||||
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,7 @@ using Ryujinx.Common.Utilities;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Ryujinx.Ui.Common.Configuration.System;
|
using Ryujinx.Ui.Common.Configuration.System;
|
||||||
using Ryujinx.Common.Logging.Targets;
|
using Ryujinx.Common.Logging.Targets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace LibRyujinx
|
namespace LibRyujinx
|
||||||
{
|
{
|
||||||
@ -545,6 +546,83 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
return gameInfo;
|
return gameInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetDlcTitleId(string path, string ncaPath)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
|
||||||
|
SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, ncaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), ncaPath);
|
||||||
|
if (nca != null)
|
||||||
|
{
|
||||||
|
return nca.Header.TitleId.ToString("X16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new Nca(SwitchDevice.VirtualFileSystem.KeySet, ncaStorage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> GetDlcContentList(string path, ulong titleId)
|
||||||
|
{
|
||||||
|
if(!File.Exists(path))
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
bool containsDownloadableContent = false;
|
||||||
|
|
||||||
|
SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
List<string> paths = new List<string>();
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
if (nca == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
|
{
|
||||||
|
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != titleId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.Add(fileEntry.FullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SwitchDevice : IDisposable
|
public class SwitchDevice : IDisposable
|
||||||
|
@ -0,0 +1,182 @@
|
|||||||
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
class Icons {
|
||||||
|
companion object{
|
||||||
|
/// Icons exported from https://www.composables.com/icons
|
||||||
|
@Composable
|
||||||
|
fun Download(): ImageVector {
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "download",
|
||||||
|
defaultWidth = 40.0.dp,
|
||||||
|
defaultHeight = 40.0.dp,
|
||||||
|
viewportWidth = 40.0f,
|
||||||
|
viewportHeight = 40.0f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black),
|
||||||
|
fillAlpha = 1f,
|
||||||
|
stroke = null,
|
||||||
|
strokeAlpha = 1f,
|
||||||
|
strokeLineWidth = 1.0f,
|
||||||
|
strokeLineCap = StrokeCap.Butt,
|
||||||
|
strokeLineJoin = StrokeJoin.Miter,
|
||||||
|
strokeLineMiter = 1f,
|
||||||
|
pathFillType = PathFillType.NonZero
|
||||||
|
) {
|
||||||
|
moveTo(20f, 26.25f)
|
||||||
|
quadToRelative(-0.25f, 0f, -0.479f, -0.083f)
|
||||||
|
quadToRelative(-0.229f, -0.084f, -0.438f, -0.292f)
|
||||||
|
lineToRelative(-6.041f, -6.083f)
|
||||||
|
quadToRelative(-0.417f, -0.375f, -0.396f, -0.917f)
|
||||||
|
quadToRelative(0.021f, -0.542f, 0.396f, -0.917f)
|
||||||
|
reflectiveQuadToRelative(0.916f, -0.396f)
|
||||||
|
quadToRelative(0.542f, -0.02f, 0.959f, 0.396f)
|
||||||
|
lineToRelative(3.791f, 3.792f)
|
||||||
|
verticalLineTo(8.292f)
|
||||||
|
quadToRelative(0f, -0.584f, 0.375f, -0.959f)
|
||||||
|
reflectiveQuadTo(20f, 6.958f)
|
||||||
|
quadToRelative(0.542f, 0f, 0.938f, 0.375f)
|
||||||
|
quadToRelative(0.395f, 0.375f, 0.395f, 0.959f)
|
||||||
|
verticalLineTo(21.75f)
|
||||||
|
lineToRelative(3.792f, -3.792f)
|
||||||
|
quadToRelative(0.375f, -0.416f, 0.917f, -0.396f)
|
||||||
|
quadToRelative(0.541f, 0.021f, 0.958f, 0.396f)
|
||||||
|
quadToRelative(0.375f, 0.375f, 0.375f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(-0.375f, 0.958f)
|
||||||
|
lineToRelative(-6.083f, 6.042f)
|
||||||
|
quadToRelative(-0.209f, 0.208f, -0.438f, 0.292f)
|
||||||
|
quadToRelative(-0.229f, 0.083f, -0.479f, 0.083f)
|
||||||
|
close()
|
||||||
|
moveTo(9.542f, 32.958f)
|
||||||
|
quadToRelative(-1.042f, 0f, -1.834f, -0.791f)
|
||||||
|
quadToRelative(-0.791f, -0.792f, -0.791f, -1.834f)
|
||||||
|
verticalLineToRelative(-4.291f)
|
||||||
|
quadToRelative(0f, -0.542f, 0.395f, -0.938f)
|
||||||
|
quadToRelative(0.396f, -0.396f, 0.938f, -0.396f)
|
||||||
|
quadToRelative(0.542f, 0f, 0.917f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(4.291f)
|
||||||
|
horizontalLineToRelative(20.916f)
|
||||||
|
verticalLineToRelative(-4.291f)
|
||||||
|
quadToRelative(0f, -0.542f, 0.375f, -0.938f)
|
||||||
|
quadToRelative(0.375f, -0.396f, 0.917f, -0.396f)
|
||||||
|
quadToRelative(0.583f, 0f, 0.958f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(4.291f)
|
||||||
|
quadToRelative(0f, 1.042f, -0.791f, 1.834f)
|
||||||
|
quadToRelative(-0.792f, 0.791f, -1.834f, 0.791f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun VideoGame(): ImageVector {
|
||||||
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "videogame_asset",
|
||||||
|
defaultWidth = 40.0.dp,
|
||||||
|
defaultHeight = 40.0.dp,
|
||||||
|
viewportWidth = 40.0f,
|
||||||
|
viewportHeight = 40.0f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
|
||||||
|
fillAlpha = 1f,
|
||||||
|
stroke = SolidColor(primaryColor),
|
||||||
|
strokeAlpha = 1f,
|
||||||
|
strokeLineWidth = 1.0f,
|
||||||
|
strokeLineCap = StrokeCap.Butt,
|
||||||
|
strokeLineJoin = StrokeJoin.Miter,
|
||||||
|
strokeLineMiter = 1f,
|
||||||
|
pathFillType = PathFillType.NonZero
|
||||||
|
) {
|
||||||
|
moveTo(6.25f, 29.792f)
|
||||||
|
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
|
||||||
|
quadToRelative(-0.771f, -0.792f, -0.771f, -1.833f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
|
||||||
|
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
|
||||||
|
horizontalLineToRelative(27.5f)
|
||||||
|
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
|
||||||
|
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
quadToRelative(0f, 1.041f, -0.771f, 1.833f)
|
||||||
|
reflectiveQuadToRelative(-1.854f, 0.792f)
|
||||||
|
close()
|
||||||
|
moveToRelative(0f, -2.625f)
|
||||||
|
horizontalLineToRelative(27.5f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
close()
|
||||||
|
moveToRelative(7.167f, -1.792f)
|
||||||
|
quadToRelative(0.541f, 0f, 0.916f, -0.375f)
|
||||||
|
reflectiveQuadToRelative(0.375f, -0.917f)
|
||||||
|
verticalLineToRelative(-2.791f)
|
||||||
|
horizontalLineToRelative(2.75f)
|
||||||
|
quadToRelative(0.584f, 0f, 0.959f, -0.375f)
|
||||||
|
reflectiveQuadToRelative(0.375f, -0.917f)
|
||||||
|
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
||||||
|
quadToRelative(-0.375f, -0.395f, -0.959f, -0.395f)
|
||||||
|
horizontalLineToRelative(-2.75f)
|
||||||
|
verticalLineToRelative(-2.75f)
|
||||||
|
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
||||||
|
quadToRelative(-0.375f, -0.396f, -0.916f, -0.396f)
|
||||||
|
quadToRelative(-0.584f, 0f, -0.959f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(-0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(2.75f)
|
||||||
|
horizontalLineToRelative(-2.75f)
|
||||||
|
quadToRelative(-0.541f, 0f, -0.937f, 0.395f)
|
||||||
|
quadTo(8f, 19.458f, 8f, 20f)
|
||||||
|
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(0.937f, 0.375f)
|
||||||
|
horizontalLineToRelative(2.75f)
|
||||||
|
verticalLineToRelative(2.791f)
|
||||||
|
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(0.938f, 0.375f)
|
||||||
|
close()
|
||||||
|
moveToRelative(11.125f, -0.5f)
|
||||||
|
quadToRelative(0.791f, 0f, 1.396f, -0.583f)
|
||||||
|
quadToRelative(0.604f, -0.584f, 0.604f, -1.375f)
|
||||||
|
quadToRelative(0f, -0.834f, -0.604f, -1.417f)
|
||||||
|
quadToRelative(-0.605f, -0.583f, -1.396f, -0.583f)
|
||||||
|
quadToRelative(-0.834f, 0f, -1.417f, 0.583f)
|
||||||
|
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
||||||
|
quadToRelative(0f, 0.833f, 0.583f, 1.417f)
|
||||||
|
quadToRelative(0.583f, 0.583f, 1.417f, 0.583f)
|
||||||
|
close()
|
||||||
|
moveToRelative(3.916f, -5.833f)
|
||||||
|
quadToRelative(0.834f, 0f, 1.417f, -0.584f)
|
||||||
|
quadToRelative(0.583f, -0.583f, 0.583f, -1.416f)
|
||||||
|
quadToRelative(0f, -0.792f, -0.583f, -1.375f)
|
||||||
|
quadToRelative(-0.583f, -0.584f, -1.417f, -0.584f)
|
||||||
|
quadToRelative(-0.791f, 0f, -1.375f, 0.584f)
|
||||||
|
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
||||||
|
quadToRelative(0f, 0.833f, 0.583f, 1.416f)
|
||||||
|
quadToRelative(0.584f, 0.584f, 1.375f, 0.584f)
|
||||||
|
close()
|
||||||
|
moveTo(6.25f, 27.167f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,4 +50,6 @@ class RyujinxNative {
|
|||||||
external fun graphicsSetSurface(surface: Long)
|
external fun graphicsSetSurface(surface: Long)
|
||||||
external fun deviceCloseEmulation()
|
external fun deviceCloseEmulation()
|
||||||
external fun deviceSignalEmulationClose()
|
external fun deviceSignalEmulationClose()
|
||||||
|
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
|
||||||
|
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<String>
|
||||||
}
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.text.intl.Locale
|
||||||
|
import androidx.compose.ui.text.toLowerCase
|
||||||
|
import com.anggrayudi.storage.SimpleStorageHelper
|
||||||
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import org.ryujinx.android.MainActivity
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class DlcViewModel(val titleId: String) {
|
||||||
|
private var storageHelper: SimpleStorageHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val UpdateRequestCode = 1002
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(item: DlcItem) {
|
||||||
|
data?.apply {
|
||||||
|
this.removeAll { it.path == item.containerPath }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(refresh: MutableState<Boolean>) {
|
||||||
|
val callBack = storageHelper.onFileSelected
|
||||||
|
|
||||||
|
storageHelper.onFileSelected = { requestCode, files ->
|
||||||
|
run {
|
||||||
|
storageHelper.onFileSelected = callBack
|
||||||
|
if (requestCode == UpdateRequestCode) {
|
||||||
|
val file = files.firstOrNull()
|
||||||
|
file?.apply {
|
||||||
|
val path = file.getAbsolutePath(storageHelper.storage.context)
|
||||||
|
if (path.isNotEmpty()) {
|
||||||
|
data?.apply {
|
||||||
|
var contents = RyujinxNative().deviceGetDlcContentList(
|
||||||
|
path,
|
||||||
|
titleId.toLong(16)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (contents.isNotEmpty()) {
|
||||||
|
val contentPath = path
|
||||||
|
val container = DlcContainerList(contentPath);
|
||||||
|
|
||||||
|
for (content in contents)
|
||||||
|
container.dlc_nca_list.add(
|
||||||
|
DlcContainer(
|
||||||
|
true,
|
||||||
|
titleId,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.add(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refresh.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storageHelper.openFilePicker(UpdateRequestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save(items: List<DlcItem>) {
|
||||||
|
data?.apply {
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
val json = gson.toJson(this)
|
||||||
|
jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
|
||||||
|
File(jsonPath).mkdirs()
|
||||||
|
File("$jsonPath/dlc.json").writeText(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getDlc(): List<DlcItem> {
|
||||||
|
var items = mutableListOf<DlcItem>()
|
||||||
|
|
||||||
|
data?.apply {
|
||||||
|
for (container in this) {
|
||||||
|
val containerPath = container.path
|
||||||
|
|
||||||
|
if (!File(containerPath).exists())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (dlc in container.dlc_nca_list) {
|
||||||
|
val enabled = remember {
|
||||||
|
mutableStateOf(dlc.enabled)
|
||||||
|
}
|
||||||
|
items.add(
|
||||||
|
DlcItem(
|
||||||
|
File(containerPath).name,
|
||||||
|
enabled,
|
||||||
|
containerPath,
|
||||||
|
dlc.fullPath,
|
||||||
|
RyujinxNative().deviceGetDlcTitleId(containerPath, dlc.fullPath)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
var data: MutableList<DlcContainerList>? = null
|
||||||
|
private var jsonPath: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
jsonPath =
|
||||||
|
MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/dlc.json"
|
||||||
|
storageHelper = MainActivity.StorageHelper!!
|
||||||
|
|
||||||
|
reloadFromDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reloadFromDisk() {
|
||||||
|
data = mutableListOf()
|
||||||
|
if (File(jsonPath).exists()) {
|
||||||
|
val gson = Gson()
|
||||||
|
val typeToken = object : TypeToken<MutableList<DlcContainerList>>() {}.type
|
||||||
|
data =
|
||||||
|
gson.fromJson<MutableList<DlcContainerList>>(File(jsonPath).readText(), typeToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DlcContainerList(
|
||||||
|
var path: String = "",
|
||||||
|
var dlc_nca_list: MutableList<DlcContainer> = mutableListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DlcContainer(
|
||||||
|
var enabled: Boolean = false,
|
||||||
|
var titleId: String = "",
|
||||||
|
var fullPath: String = "")
|
||||||
|
|
||||||
|
data class DlcItem(
|
||||||
|
var name:String = "",
|
||||||
|
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
|
||||||
|
var containerPath: String = "",
|
||||||
|
var fullPath: String = "",
|
||||||
|
var titleId: String = "")
|
@ -0,0 +1,134 @@
|
|||||||
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.ryujinx.android.viewmodels.DlcItem
|
||||||
|
import org.ryujinx.android.viewmodels.DlcViewModel
|
||||||
|
|
||||||
|
class DlcViews {
|
||||||
|
companion object {
|
||||||
|
@Composable
|
||||||
|
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>) {
|
||||||
|
val viewModel = DlcViewModel(titleId)
|
||||||
|
|
||||||
|
var dlcList = remember {
|
||||||
|
mutableListOf<DlcItem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.data?.apply {
|
||||||
|
dlcList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
var refresh = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Column {
|
||||||
|
Row(modifier = Modifier.padding(8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Text(text = "DLC for ${name}", textAlign = TextAlign.Center, modifier = Modifier.align(
|
||||||
|
Alignment.CenterVertically
|
||||||
|
))
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.add(refresh)
|
||||||
|
},
|
||||||
|
modifier = Modifier.align(
|
||||||
|
Alignment.CenterVertically
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Add,
|
||||||
|
contentDescription = "Add"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(refresh.value) {
|
||||||
|
dlcList.clear()
|
||||||
|
dlcList.addAll(viewModel.getDlc())
|
||||||
|
refresh.value = false
|
||||||
|
}
|
||||||
|
LazyColumn(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(400.dp)){
|
||||||
|
items(dlcList) { dlcItem ->
|
||||||
|
dlcItem.apply {
|
||||||
|
Row(modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = (dlcItem.isEnabled.value),
|
||||||
|
onCheckedChange = { dlcItem.isEnabled.value = it })
|
||||||
|
Text(
|
||||||
|
text = dlcItem.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.wrapContentWidth(Alignment.Start)
|
||||||
|
.fillMaxWidth(0.9f)
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.remove(dlcItem)
|
||||||
|
refresh.value = true
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Filled.Delete,
|
||||||
|
contentDescription = "remove"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.align(Alignment.End),
|
||||||
|
onClick = {
|
||||||
|
openDialog.value = false
|
||||||
|
viewModel.save(dlcList)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text("Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,6 @@ import androidx.compose.ui.platform.LocalView
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
@ -236,11 +235,12 @@ class HomeViews {
|
|||||||
showBottomSheet.value = false
|
showBottomSheet.value = false
|
||||||
},
|
},
|
||||||
sheetState = sheetState) {
|
sheetState = sheetState) {
|
||||||
val openDialog = remember { mutableStateOf(false) }
|
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
||||||
|
val openDlcDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if(openDialog.value) {
|
if(openTitleUpdateDialog.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
AlertDialog(onDismissRequest = {
|
||||||
openDialog.value = false
|
openTitleUpdateDialog.value = false
|
||||||
}) {
|
}) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -251,24 +251,43 @@ class HomeViews {
|
|||||||
) {
|
) {
|
||||||
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
TitleUpdateViews.Main(titleId, name, openDialog)
|
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(openDlcDialog.value) {
|
||||||
|
AlertDialog(onDismissRequest = {
|
||||||
|
openDlcDialog.value = false
|
||||||
|
}) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
|
DlcViews.Main(titleId, name, openDlcDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Surface(color = MaterialTheme.colorScheme.surface,
|
Surface(color = MaterialTheme.colorScheme.surface,
|
||||||
modifier = Modifier.padding(16.dp)) {
|
modifier = Modifier.padding(16.dp)) {
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
||||||
Card(
|
Card(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
openDialog.value = true
|
openTitleUpdateDialog.value = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.app_update),
|
painter = painterResource(R.drawable.app_update),
|
||||||
contentDescription = "More",
|
contentDescription = "Game Updates",
|
||||||
tint = Color.Green,
|
tint = Color.Green,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(48.dp)
|
.width(48.dp)
|
||||||
@ -281,6 +300,28 @@ class HomeViews {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
onClick = {
|
||||||
|
openDlcDialog.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Icon(
|
||||||
|
imageVector = org.ryujinx.android.Icons.Download(),
|
||||||
|
contentDescription = "Game Dlc",
|
||||||
|
tint = Color.Green,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(48.dp)
|
||||||
|
.height(48.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Text(text = "Game DLC",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,10 +348,13 @@ class HomeViews {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
launch {
|
launch {
|
||||||
showLoading.value = true
|
showLoading.value = true
|
||||||
val success = viewModel.mainViewModel?.loadGame(gameModel) ?: false
|
val success =
|
||||||
if(success) {
|
viewModel.mainViewModel?.loadGame(gameModel) ?: false
|
||||||
|
if (success) {
|
||||||
launchOnUiThread {
|
launchOnUiThread {
|
||||||
viewModel.mainViewModel?.activity?.setFullScreen(true)
|
viewModel.mainViewModel?.activity?.setFullScreen(
|
||||||
|
true
|
||||||
|
)
|
||||||
viewModel.mainViewModel?.navController?.navigate("game")
|
viewModel.mainViewModel?.navController?.navigate("game")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package org.ryujinx.android.views
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
@ -19,7 +17,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -38,12 +35,12 @@ import androidx.compose.ui.input.pointer.PointerEventType
|
|||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import org.ryujinx.android.GameController
|
import org.ryujinx.android.GameController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
|
import org.ryujinx.android.Icons
|
||||||
import org.ryujinx.android.RyujinxNative
|
import org.ryujinx.android.RyujinxNative
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.viewmodels.SettingsViewModel
|
import org.ryujinx.android.viewmodels.SettingsViewModel
|
||||||
@ -143,7 +140,7 @@ class MainView {
|
|||||||
mainViewModel.controller?.setVisible(!mainViewModel.controller!!.isVisible)
|
mainViewModel.controller?.setVisible(!mainViewModel.controller!!.isVisible)
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = rememberVideogameAsset(),
|
imageVector = Icons.VideoGame(),
|
||||||
contentDescription = "Toggle Virtual Pad"
|
contentDescription = "Toggle Virtual Pad"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -201,103 +198,6 @@ class MainView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun rememberVideogameAsset(): ImageVector {
|
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
|
||||||
return remember {
|
|
||||||
ImageVector.Builder(
|
|
||||||
name = "videogame_asset",
|
|
||||||
defaultWidth = 40.0.dp,
|
|
||||||
defaultHeight = 40.0.dp,
|
|
||||||
viewportWidth = 40.0f,
|
|
||||||
viewportHeight = 40.0f
|
|
||||||
).apply {
|
|
||||||
path(
|
|
||||||
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
|
|
||||||
fillAlpha = 1f,
|
|
||||||
stroke = SolidColor(primaryColor),
|
|
||||||
strokeAlpha = 1f,
|
|
||||||
strokeLineWidth = 1.0f,
|
|
||||||
strokeLineCap = StrokeCap.Butt,
|
|
||||||
strokeLineJoin = StrokeJoin.Miter,
|
|
||||||
strokeLineMiter = 1f,
|
|
||||||
pathFillType = PathFillType.NonZero
|
|
||||||
) {
|
|
||||||
moveTo(6.25f, 29.792f)
|
|
||||||
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
|
|
||||||
quadToRelative(-0.771f, -0.792f, -0.771f, -1.833f)
|
|
||||||
verticalLineTo(12.833f)
|
|
||||||
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
|
|
||||||
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
|
|
||||||
horizontalLineToRelative(27.5f)
|
|
||||||
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
|
|
||||||
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
|
|
||||||
verticalLineToRelative(14.334f)
|
|
||||||
quadToRelative(0f, 1.041f, -0.771f, 1.833f)
|
|
||||||
reflectiveQuadToRelative(-1.854f, 0.792f)
|
|
||||||
close()
|
|
||||||
moveToRelative(0f, -2.625f)
|
|
||||||
horizontalLineToRelative(27.5f)
|
|
||||||
verticalLineTo(12.833f)
|
|
||||||
horizontalLineTo(6.25f)
|
|
||||||
verticalLineToRelative(14.334f)
|
|
||||||
close()
|
|
||||||
moveToRelative(7.167f, -1.792f)
|
|
||||||
quadToRelative(0.541f, 0f, 0.916f, -0.375f)
|
|
||||||
reflectiveQuadToRelative(0.375f, -0.917f)
|
|
||||||
verticalLineToRelative(-2.791f)
|
|
||||||
horizontalLineToRelative(2.75f)
|
|
||||||
quadToRelative(0.584f, 0f, 0.959f, -0.375f)
|
|
||||||
reflectiveQuadToRelative(0.375f, -0.917f)
|
|
||||||
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
|
||||||
quadToRelative(-0.375f, -0.395f, -0.959f, -0.395f)
|
|
||||||
horizontalLineToRelative(-2.75f)
|
|
||||||
verticalLineToRelative(-2.75f)
|
|
||||||
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
|
||||||
quadToRelative(-0.375f, -0.396f, -0.916f, -0.396f)
|
|
||||||
quadToRelative(-0.584f, 0f, -0.959f, 0.396f)
|
|
||||||
reflectiveQuadToRelative(-0.375f, 0.938f)
|
|
||||||
verticalLineToRelative(2.75f)
|
|
||||||
horizontalLineToRelative(-2.75f)
|
|
||||||
quadToRelative(-0.541f, 0f, -0.937f, 0.395f)
|
|
||||||
quadTo(8f, 19.458f, 8f, 20f)
|
|
||||||
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
|
||||||
reflectiveQuadToRelative(0.937f, 0.375f)
|
|
||||||
horizontalLineToRelative(2.75f)
|
|
||||||
verticalLineToRelative(2.791f)
|
|
||||||
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
|
||||||
reflectiveQuadToRelative(0.938f, 0.375f)
|
|
||||||
close()
|
|
||||||
moveToRelative(11.125f, -0.5f)
|
|
||||||
quadToRelative(0.791f, 0f, 1.396f, -0.583f)
|
|
||||||
quadToRelative(0.604f, -0.584f, 0.604f, -1.375f)
|
|
||||||
quadToRelative(0f, -0.834f, -0.604f, -1.417f)
|
|
||||||
quadToRelative(-0.605f, -0.583f, -1.396f, -0.583f)
|
|
||||||
quadToRelative(-0.834f, 0f, -1.417f, 0.583f)
|
|
||||||
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
|
||||||
quadToRelative(0f, 0.833f, 0.583f, 1.417f)
|
|
||||||
quadToRelative(0.583f, 0.583f, 1.417f, 0.583f)
|
|
||||||
close()
|
|
||||||
moveToRelative(3.916f, -5.833f)
|
|
||||||
quadToRelative(0.834f, 0f, 1.417f, -0.584f)
|
|
||||||
quadToRelative(0.583f, -0.583f, 0.583f, -1.416f)
|
|
||||||
quadToRelative(0f, -0.792f, -0.583f, -1.375f)
|
|
||||||
quadToRelative(-0.583f, -0.584f, -1.417f, -0.584f)
|
|
||||||
quadToRelative(-0.791f, 0f, -1.375f, 0.584f)
|
|
||||||
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
|
||||||
quadToRelative(0f, 0.833f, 0.583f, 1.416f)
|
|
||||||
quadToRelative(0.584f, 0.584f, 1.375f, 0.584f)
|
|
||||||
close()
|
|
||||||
moveTo(6.25f, 27.167f)
|
|
||||||
verticalLineTo(12.833f)
|
|
||||||
verticalLineToRelative(14.334f)
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GameStats(mainViewModel: MainViewModel) {
|
fun GameStats(mainViewModel: MainViewModel) {
|
||||||
val fifo = remember {
|
val fifo = remember {
|
||||||
|
@ -107,7 +107,7 @@ class TitleUpdateViews {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.Add,
|
Icons.Filled.Add,
|
||||||
contentDescription = "Remove"
|
contentDescription = "Add"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user