diff options
24 files changed, 703 insertions, 9 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index e2c5b6acd..07f1b4842 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
| @@ -252,7 +252,7 @@ object NativeLibrary { | |||
| 252 | 252 | ||
| 253 | external fun reloadKeys(): Boolean | 253 | external fun reloadKeys(): Boolean |
| 254 | 254 | ||
| 255 | external fun initializeEmulation() | 255 | external fun initializeSystem() |
| 256 | 256 | ||
| 257 | external fun defaultCPUCore(): Int | 257 | external fun defaultCPUCore(): Int |
| 258 | 258 | ||
| @@ -506,6 +506,36 @@ object NativeLibrary { | |||
| 506 | external fun initializeEmptyUserDirectory() | 506 | external fun initializeEmptyUserDirectory() |
| 507 | 507 | ||
| 508 | /** | 508 | /** |
| 509 | * Gets the launch path for a given applet. It is the caller's responsibility to also | ||
| 510 | * set the system's current applet ID before trying to launch the nca given by this function. | ||
| 511 | * | ||
| 512 | * @param id The applet entry ID | ||
| 513 | * @return The applet's launch path | ||
| 514 | */ | ||
| 515 | external fun getAppletLaunchPath(id: Long): String | ||
| 516 | |||
| 517 | /** | ||
| 518 | * Sets the system's current applet ID before launching. | ||
| 519 | * | ||
| 520 | * @param appletId One of the ids in the Service::AM::Applets::AppletId enum | ||
| 521 | */ | ||
| 522 | external fun setCurrentAppletId(appletId: Int) | ||
| 523 | |||
| 524 | /** | ||
| 525 | * Sets the cabinet mode for launching the cabinet applet. | ||
| 526 | * | ||
| 527 | * @param cabinetMode One of the modes that corresponds to the enum in Service::NFP::CabinetMode | ||
| 528 | */ | ||
| 529 | external fun setCabinetMode(cabinetMode: Int) | ||
| 530 | |||
| 531 | /** | ||
| 532 | * Checks whether NAND contents are available and valid. | ||
| 533 | * | ||
| 534 | * @return 'true' if firmware is available | ||
| 535 | */ | ||
| 536 | external fun isFirmwareAvailable(): Boolean | ||
| 537 | |||
| 538 | /** | ||
| 509 | * Button type for use in onTouchEvent | 539 | * Button type for use in onTouchEvent |
| 510 | */ | 540 | */ |
| 511 | object ButtonType { | 541 | object ButtonType { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt new file mode 100644 index 000000000..a21a705c1 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt | |||
| @@ -0,0 +1,90 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.view.LayoutInflater | ||
| 7 | import android.view.View | ||
| 8 | import android.view.ViewGroup | ||
| 9 | import android.widget.Toast | ||
| 10 | import androidx.core.content.res.ResourcesCompat | ||
| 11 | import androidx.fragment.app.FragmentActivity | ||
| 12 | import androidx.navigation.findNavController | ||
| 13 | import androidx.recyclerview.widget.RecyclerView | ||
| 14 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 15 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 16 | import org.yuzu.yuzu_emu.R | ||
| 17 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 18 | import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding | ||
| 19 | import org.yuzu.yuzu_emu.model.Applet | ||
| 20 | import org.yuzu.yuzu_emu.model.AppletInfo | ||
| 21 | import org.yuzu.yuzu_emu.model.Game | ||
| 22 | |||
| 23 | class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) : | ||
| 24 | RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(), | ||
| 25 | View.OnClickListener { | ||
| 26 | |||
| 27 | override fun onCreateViewHolder( | ||
| 28 | parent: ViewGroup, | ||
| 29 | viewType: Int | ||
| 30 | ): AppletAdapter.AppletViewHolder { | ||
| 31 | CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
| 32 | .apply { root.setOnClickListener(this@AppletAdapter) } | ||
| 33 | .also { return AppletViewHolder(it) } | ||
| 34 | } | ||
| 35 | |||
| 36 | override fun onBindViewHolder(holder: AppletViewHolder, position: Int) = | ||
| 37 | holder.bind(applets[position]) | ||
| 38 | |||
| 39 | override fun getItemCount(): Int = applets.size | ||
| 40 | |||
| 41 | override fun onClick(view: View) { | ||
| 42 | val applet = (view.tag as AppletViewHolder).applet | ||
| 43 | val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId) | ||
| 44 | if (appletPath.isEmpty()) { | ||
| 45 | Toast.makeText( | ||
| 46 | YuzuApplication.appContext, | ||
| 47 | R.string.applets_error_applet, | ||
| 48 | Toast.LENGTH_SHORT | ||
| 49 | ).show() | ||
| 50 | return | ||
| 51 | } | ||
| 52 | |||
| 53 | if (applet.appletInfo == AppletInfo.Cabinet) { | ||
| 54 | view.findNavController() | ||
| 55 | .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment) | ||
| 56 | return | ||
| 57 | } | ||
| 58 | |||
| 59 | NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId) | ||
| 60 | val appletGame = Game( | ||
| 61 | title = YuzuApplication.appContext.getString(applet.titleId), | ||
| 62 | path = appletPath | ||
| 63 | ) | ||
| 64 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) | ||
| 65 | view.findNavController().navigate(action) | ||
| 66 | } | ||
| 67 | |||
| 68 | inner class AppletViewHolder(val binding: CardAppletOptionBinding) : | ||
| 69 | RecyclerView.ViewHolder(binding.root) { | ||
| 70 | lateinit var applet: Applet | ||
| 71 | |||
| 72 | init { | ||
| 73 | itemView.tag = this | ||
| 74 | } | ||
| 75 | |||
| 76 | fun bind(applet: Applet) { | ||
| 77 | this.applet = applet | ||
| 78 | |||
| 79 | binding.title.setText(applet.titleId) | ||
| 80 | binding.description.setText(applet.descriptionId) | ||
| 81 | binding.icon.setImageDrawable( | ||
| 82 | ResourcesCompat.getDrawable( | ||
| 83 | binding.icon.context.resources, | ||
| 84 | applet.iconId, | ||
| 85 | binding.icon.context.theme | ||
| 86 | ) | ||
| 87 | ) | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt new file mode 100644 index 000000000..e7b7c0f2f --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.adapters | ||
| 5 | |||
| 6 | import android.view.LayoutInflater | ||
| 7 | import android.view.View | ||
| 8 | import android.view.ViewGroup | ||
| 9 | import androidx.core.content.res.ResourcesCompat | ||
| 10 | import androidx.fragment.app.Fragment | ||
| 11 | import androidx.navigation.fragment.findNavController | ||
| 12 | import androidx.recyclerview.widget.RecyclerView | ||
| 13 | import org.yuzu.yuzu_emu.HomeNavigationDirections | ||
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 15 | import org.yuzu.yuzu_emu.R | ||
| 16 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 17 | import org.yuzu.yuzu_emu.databinding.DialogListItemBinding | ||
| 18 | import org.yuzu.yuzu_emu.model.CabinetMode | ||
| 19 | import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder | ||
| 20 | import org.yuzu.yuzu_emu.model.AppletInfo | ||
| 21 | import org.yuzu.yuzu_emu.model.Game | ||
| 22 | |||
| 23 | class CabinetLauncherDialogAdapter(val fragment: Fragment) : | ||
| 24 | RecyclerView.Adapter<CabinetModeViewHolder>(), | ||
| 25 | View.OnClickListener { | ||
| 26 | private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size) | ||
| 27 | |||
| 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder { | ||
| 29 | DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
| 30 | .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) } | ||
| 31 | .also { return CabinetModeViewHolder(it) } | ||
| 32 | } | ||
| 33 | |||
| 34 | override fun getItemCount(): Int = cabinetModes.size | ||
| 35 | |||
| 36 | override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) = | ||
| 37 | holder.bind(cabinetModes[position]) | ||
| 38 | |||
| 39 | override fun onClick(view: View) { | ||
| 40 | val mode = (view.tag as CabinetModeViewHolder).cabinetMode | ||
| 41 | val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId) | ||
| 42 | NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId) | ||
| 43 | NativeLibrary.setCabinetMode(mode.id) | ||
| 44 | val appletGame = Game( | ||
| 45 | title = YuzuApplication.appContext.getString(R.string.cabinet_applet), | ||
| 46 | path = appletPath | ||
| 47 | ) | ||
| 48 | val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame) | ||
| 49 | fragment.findNavController().navigate(action) | ||
| 50 | } | ||
| 51 | |||
| 52 | inner class CabinetModeViewHolder(val binding: DialogListItemBinding) : | ||
| 53 | RecyclerView.ViewHolder(binding.root) { | ||
| 54 | lateinit var cabinetMode: CabinetMode | ||
| 55 | |||
| 56 | init { | ||
| 57 | itemView.tag = this | ||
| 58 | } | ||
| 59 | |||
| 60 | fun bind(cabinetMode: CabinetMode) { | ||
| 61 | this.cabinetMode = cabinetMode | ||
| 62 | binding.icon.setImageDrawable( | ||
| 63 | ResourcesCompat.getDrawable( | ||
| 64 | binding.icon.context.resources, | ||
| 65 | cabinetMode.iconId, | ||
| 66 | binding.icon.context.theme | ||
| 67 | ) | ||
| 68 | ) | ||
| 69 | binding.title.setText(cabinetMode.titleId) | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt new file mode 100644 index 000000000..1f66b440d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt | |||
| @@ -0,0 +1,113 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.os.Bundle | ||
| 7 | import android.view.LayoutInflater | ||
| 8 | import android.view.View | ||
| 9 | import android.view.ViewGroup | ||
| 10 | import androidx.core.view.ViewCompat | ||
| 11 | import androidx.core.view.WindowInsetsCompat | ||
| 12 | import androidx.core.view.updatePadding | ||
| 13 | import androidx.fragment.app.Fragment | ||
| 14 | import androidx.fragment.app.activityViewModels | ||
| 15 | import androidx.navigation.findNavController | ||
| 16 | import androidx.recyclerview.widget.GridLayoutManager | ||
| 17 | import com.google.android.material.transition.MaterialSharedAxis | ||
| 18 | import org.yuzu.yuzu_emu.R | ||
| 19 | import org.yuzu.yuzu_emu.adapters.AppletAdapter | ||
| 20 | import org.yuzu.yuzu_emu.databinding.FragmentAppletLauncherBinding | ||
| 21 | import org.yuzu.yuzu_emu.model.Applet | ||
| 22 | import org.yuzu.yuzu_emu.model.AppletInfo | ||
| 23 | import org.yuzu.yuzu_emu.model.HomeViewModel | ||
| 24 | |||
| 25 | class AppletLauncherFragment : Fragment() { | ||
| 26 | private var _binding: FragmentAppletLauncherBinding? = null | ||
| 27 | private val binding get() = _binding!! | ||
| 28 | |||
| 29 | private val homeViewModel: HomeViewModel by activityViewModels() | ||
| 30 | |||
| 31 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 32 | super.onCreate(savedInstanceState) | ||
| 33 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
| 34 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 35 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
| 36 | } | ||
| 37 | |||
| 38 | override fun onCreateView( | ||
| 39 | inflater: LayoutInflater, | ||
| 40 | container: ViewGroup?, | ||
| 41 | savedInstanceState: Bundle? | ||
| 42 | ): View { | ||
| 43 | _binding = FragmentAppletLauncherBinding.inflate(inflater) | ||
| 44 | return binding.root | ||
| 45 | } | ||
| 46 | |||
| 47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| 48 | super.onViewCreated(view, savedInstanceState) | ||
| 49 | homeViewModel.setNavigationVisibility(visible = false, animated = true) | ||
| 50 | homeViewModel.setStatusBarShadeVisibility(visible = false) | ||
| 51 | |||
| 52 | binding.toolbarApplets.setNavigationOnClickListener { | ||
| 53 | binding.root.findNavController().popBackStack() | ||
| 54 | } | ||
| 55 | |||
| 56 | val applets = listOf( | ||
| 57 | Applet( | ||
| 58 | R.string.album_applet, | ||
| 59 | R.string.album_applet_description, | ||
| 60 | R.drawable.ic_album, | ||
| 61 | AppletInfo.PhotoViewer | ||
| 62 | ), | ||
| 63 | Applet( | ||
| 64 | R.string.cabinet_applet, | ||
| 65 | R.string.cabinet_applet_description, | ||
| 66 | R.drawable.ic_nfc, | ||
| 67 | AppletInfo.Cabinet | ||
| 68 | ), | ||
| 69 | Applet( | ||
| 70 | R.string.mii_edit_applet, | ||
| 71 | R.string.mii_edit_applet_description, | ||
| 72 | R.drawable.ic_mii, | ||
| 73 | AppletInfo.MiiEdit | ||
| 74 | ) | ||
| 75 | ) | ||
| 76 | |||
| 77 | binding.listApplets.apply { | ||
| 78 | layoutManager = GridLayoutManager( | ||
| 79 | requireContext(), | ||
| 80 | resources.getInteger(R.integer.grid_columns) | ||
| 81 | ) | ||
| 82 | adapter = AppletAdapter(requireActivity(), applets) | ||
| 83 | } | ||
| 84 | |||
| 85 | setInsets() | ||
| 86 | } | ||
| 87 | |||
| 88 | private fun setInsets() = | ||
| 89 | ViewCompat.setOnApplyWindowInsetsListener( | ||
| 90 | binding.root | ||
| 91 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 92 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| 93 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
| 94 | |||
| 95 | val leftInsets = barInsets.left + cutoutInsets.left | ||
| 96 | val rightInsets = barInsets.right + cutoutInsets.right | ||
| 97 | |||
| 98 | val mlpAppBar = binding.toolbarApplets.layoutParams as ViewGroup.MarginLayoutParams | ||
| 99 | mlpAppBar.leftMargin = leftInsets | ||
| 100 | mlpAppBar.rightMargin = rightInsets | ||
| 101 | binding.toolbarApplets.layoutParams = mlpAppBar | ||
| 102 | |||
| 103 | val mlpListApplets = | ||
| 104 | binding.listApplets.layoutParams as ViewGroup.MarginLayoutParams | ||
| 105 | mlpListApplets.leftMargin = leftInsets | ||
| 106 | mlpListApplets.rightMargin = rightInsets | ||
| 107 | binding.listApplets.layoutParams = mlpListApplets | ||
| 108 | |||
| 109 | binding.listApplets.updatePadding(bottom = barInsets.bottom) | ||
| 110 | |||
| 111 | windowInsets | ||
| 112 | } | ||
| 113 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt new file mode 100644 index 000000000..5933677fd --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.os.Bundle | ||
| 8 | import android.view.LayoutInflater | ||
| 9 | import android.view.View | ||
| 10 | import android.view.ViewGroup | ||
| 11 | import androidx.fragment.app.DialogFragment | ||
| 12 | import androidx.recyclerview.widget.LinearLayoutManager | ||
| 13 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 14 | import org.yuzu.yuzu_emu.R | ||
| 15 | import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter | ||
| 16 | import org.yuzu.yuzu_emu.databinding.DialogListBinding | ||
| 17 | |||
| 18 | class CabinetLauncherDialogFragment : DialogFragment() { | ||
| 19 | private lateinit var binding: DialogListBinding | ||
| 20 | |||
| 21 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 22 | binding = DialogListBinding.inflate(layoutInflater) | ||
| 23 | binding.dialogList.apply { | ||
| 24 | layoutManager = LinearLayoutManager(requireContext()) | ||
| 25 | adapter = CabinetLauncherDialogAdapter(this@CabinetLauncherDialogFragment) | ||
| 26 | } | ||
| 27 | |||
| 28 | return MaterialAlertDialogBuilder(requireContext()) | ||
| 29 | .setTitle(R.string.cabinet_launcher) | ||
| 30 | .setView(binding.root) | ||
| 31 | .create() | ||
| 32 | } | ||
| 33 | |||
| 34 | override fun onCreateView( | ||
| 35 | inflater: LayoutInflater, | ||
| 36 | container: ViewGroup?, | ||
| 37 | savedInstanceState: Bundle? | ||
| 38 | ): View { | ||
| 39 | return binding.root | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index f273c880a..6e19fc6c0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
| @@ -30,6 +30,7 @@ import androidx.recyclerview.widget.GridLayoutManager | |||
| 30 | import com.google.android.material.transition.MaterialSharedAxis | 30 | import com.google.android.material.transition.MaterialSharedAxis |
| 31 | import org.yuzu.yuzu_emu.BuildConfig | 31 | import org.yuzu.yuzu_emu.BuildConfig |
| 32 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 32 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
| 33 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 33 | import org.yuzu.yuzu_emu.R | 34 | import org.yuzu.yuzu_emu.R |
| 34 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter | 35 | import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter |
| 35 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding | 36 | import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding |
| @@ -133,6 +134,20 @@ class HomeSettingsFragment : Fragment() { | |||
| 133 | ) | 134 | ) |
| 134 | add( | 135 | add( |
| 135 | HomeSetting( | 136 | HomeSetting( |
| 137 | R.string.applets, | ||
| 138 | R.string.applets_description, | ||
| 139 | R.drawable.ic_applet, | ||
| 140 | { | ||
| 141 | binding.root.findNavController() | ||
| 142 | .navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment) | ||
| 143 | }, | ||
| 144 | { NativeLibrary.isFirmwareAvailable() }, | ||
| 145 | R.string.applets_error_firmware, | ||
| 146 | R.string.applets_error_description | ||
| 147 | ) | ||
| 148 | ) | ||
| 149 | add( | ||
| 150 | HomeSetting( | ||
| 136 | R.string.select_games_folder, | 151 | R.string.select_games_folder, |
| 137 | R.string.select_games_folder_description, | 152 | R.string.select_games_folder_description, |
| 138 | R.drawable.ic_add, | 153 | R.drawable.ic_add, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index 541b22f47..a6183d19e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt | |||
| @@ -8,6 +8,7 @@ import android.content.DialogInterface | |||
| 8 | import android.content.Intent | 8 | import android.content.Intent |
| 9 | import android.net.Uri | 9 | import android.net.Uri |
| 10 | import android.os.Bundle | 10 | import android.os.Bundle |
| 11 | import android.text.Html | ||
| 11 | import androidx.fragment.app.DialogFragment | 12 | import androidx.fragment.app.DialogFragment |
| 12 | import androidx.fragment.app.FragmentActivity | 13 | import androidx.fragment.app.FragmentActivity |
| 13 | import androidx.fragment.app.activityViewModels | 14 | import androidx.fragment.app.activityViewModels |
| @@ -32,7 +33,9 @@ class MessageDialogFragment : DialogFragment() { | |||
| 32 | if (titleId != 0) dialog.setTitle(titleId) | 33 | if (titleId != 0) dialog.setTitle(titleId) |
| 33 | if (titleString.isNotEmpty()) dialog.setTitle(titleString) | 34 | if (titleString.isNotEmpty()) dialog.setTitle(titleString) |
| 34 | 35 | ||
| 35 | if (descriptionId != 0) dialog.setMessage(descriptionId) | 36 | if (descriptionId != 0) { |
| 37 | dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY)) | ||
| 38 | } | ||
| 36 | if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) | 39 | if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) |
| 37 | 40 | ||
| 38 | if (helpLinkId != 0) { | 41 | if (helpLinkId != 0) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt new file mode 100644 index 000000000..8677674a3 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.model | ||
| 5 | |||
| 6 | import androidx.annotation.DrawableRes | ||
| 7 | import androidx.annotation.StringRes | ||
| 8 | import org.yuzu.yuzu_emu.R | ||
| 9 | |||
| 10 | data class Applet( | ||
| 11 | @StringRes val titleId: Int, | ||
| 12 | @StringRes val descriptionId: Int, | ||
| 13 | @DrawableRes val iconId: Int, | ||
| 14 | val appletInfo: AppletInfo, | ||
| 15 | val cabinetMode: CabinetMode = CabinetMode.None | ||
| 16 | ) | ||
| 17 | |||
| 18 | // Combination of Common::AM::Applets::AppletId enum and the entry id | ||
| 19 | enum class AppletInfo(val appletId: Int, val entryId: Long = 0) { | ||
| 20 | None(0x00), | ||
| 21 | Application(0x01), | ||
| 22 | OverlayDisplay(0x02), | ||
| 23 | QLaunch(0x03), | ||
| 24 | Starter(0x04), | ||
| 25 | Auth(0x0A), | ||
| 26 | Cabinet(0x0B, 0x0100000000001002), | ||
| 27 | Controller(0x0C), | ||
| 28 | DataErase(0x0D), | ||
| 29 | Error(0x0E), | ||
| 30 | NetConnect(0x0F), | ||
| 31 | ProfileSelect(0x10), | ||
| 32 | SoftwareKeyboard(0x11), | ||
| 33 | MiiEdit(0x12, 0x0100000000001009), | ||
| 34 | Web(0x13), | ||
| 35 | Shop(0x14), | ||
| 36 | PhotoViewer(0x015, 0x010000000000100D), | ||
| 37 | Settings(0x16), | ||
| 38 | OfflineWeb(0x17), | ||
| 39 | LoginShare(0x18), | ||
| 40 | WebAuth(0x19), | ||
| 41 | MyPage(0x1A) | ||
| 42 | } | ||
| 43 | |||
| 44 | // Matches enum in Service::NFP::CabinetMode with extra metadata | ||
| 45 | enum class CabinetMode( | ||
| 46 | val id: Int, | ||
| 47 | @StringRes val titleId: Int = 0, | ||
| 48 | @DrawableRes val iconId: Int = 0 | ||
| 49 | ) { | ||
| 50 | None(-1), | ||
| 51 | StartNicknameAndOwnerSettings(0, R.string.cabinet_nickname_and_owner, R.drawable.ic_edit), | ||
| 52 | StartGameDataEraser(1, R.string.cabinet_game_data_eraser, R.drawable.ic_refresh), | ||
| 53 | StartRestorer(2, R.string.cabinet_restorer, R.drawable.ic_restore), | ||
| 54 | StartFormatter(3, R.string.cabinet_formatter, R.drawable.ic_clear) | ||
| 55 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index b43978fce..de84b2adb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt | |||
| @@ -11,12 +11,12 @@ import kotlinx.serialization.Serializable | |||
| 11 | @Parcelize | 11 | @Parcelize |
| 12 | @Serializable | 12 | @Serializable |
| 13 | class Game( | 13 | class Game( |
| 14 | val title: String, | 14 | val title: String = "", |
| 15 | val path: String, | 15 | val path: String, |
| 16 | val programId: String, | 16 | val programId: String = "", |
| 17 | val developer: String, | 17 | val developer: String = "", |
| 18 | val version: String, | 18 | val version: String = "", |
| 19 | val isHomebrew: Boolean | 19 | val isHomebrew: Boolean = false |
| 20 | ) : Parcelable { | 20 | ) : Parcelable { |
| 21 | val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" | 21 | val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" |
| 22 | val keyLastPlayedTime get() = "${programId}_LastPlayed" | 22 | val keyLastPlayedTime get() = "${programId}_LastPlayed" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 233aa4101..ba1177426 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
| @@ -403,6 +403,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 403 | } else { | 403 | } else { |
| 404 | firmwarePath.deleteRecursively() | 404 | firmwarePath.deleteRecursively() |
| 405 | cacheFirmwareDir.copyRecursively(firmwarePath, true) | 405 | cacheFirmwareDir.copyRecursively(firmwarePath, true) |
| 406 | NativeLibrary.initializeSystem() | ||
| 406 | getString(R.string.save_file_imported_success) | 407 | getString(R.string.save_file_imported_success) |
| 407 | } | 408 | } |
| 408 | } catch (e: Exception) { | 409 | } catch (e: Exception) { |
| @@ -648,7 +649,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 648 | } | 649 | } |
| 649 | 650 | ||
| 650 | // Reinitialize relevant data | 651 | // Reinitialize relevant data |
| 651 | NativeLibrary.initializeEmulation() | 652 | NativeLibrary.initializeSystem() |
| 652 | gamesViewModel.reloadGames(false) | 653 | gamesViewModel.reloadGames(false) |
| 653 | 654 | ||
| 654 | return@newInstance getString(R.string.user_data_import_success) | 655 | return@newInstance getString(R.string.user_data_import_success) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 3c9f6bad0..79a07f7ef 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt | |||
| @@ -15,7 +15,7 @@ object DirectoryInitialization { | |||
| 15 | fun start() { | 15 | fun start() { |
| 16 | if (!areDirectoriesReady) { | 16 | if (!areDirectoriesReady) { |
| 17 | initializeInternalStorage() | 17 | initializeInternalStorage() |
| 18 | NativeLibrary.initializeEmulation() | 18 | NativeLibrary.initializeSystem() |
| 19 | areDirectoriesReady = true | 19 | areDirectoriesReady = true |
| 20 | } | 20 | } |
| 21 | } | 21 | } |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 686b73588..f7931a89d 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -755,4 +755,49 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* | |||
| 755 | } | 755 | } |
| 756 | } | 756 | } |
| 757 | 757 | ||
| 758 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz, | ||
| 759 | jlong jid) { | ||
| 760 | auto bis_system = | ||
| 761 | EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); | ||
| 762 | if (!bis_system) { | ||
| 763 | return ToJString(env, ""); | ||
| 764 | } | ||
| 765 | |||
| 766 | auto applet_nca = | ||
| 767 | bis_system->GetEntry(static_cast<u64>(jid), FileSys::ContentRecordType::Program); | ||
| 768 | if (!applet_nca) { | ||
| 769 | return ToJString(env, ""); | ||
| 770 | } | ||
| 771 | |||
| 772 | return ToJString(env, applet_nca->GetFullPath()); | ||
| 773 | } | ||
| 774 | |||
| 775 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentAppletId(JNIEnv* env, jclass clazz, | ||
| 776 | jint jappletId) { | ||
| 777 | EmulationSession::GetInstance().System().GetAppletManager().SetCurrentAppletId( | ||
| 778 | static_cast<Service::AM::Applets::AppletId>(jappletId)); | ||
| 779 | } | ||
| 780 | |||
| 781 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass clazz, | ||
| 782 | jint jcabinetMode) { | ||
| 783 | EmulationSession::GetInstance().System().GetAppletManager().SetCabinetMode( | ||
| 784 | static_cast<Service::NFP::CabinetMode>(jcabinetMode)); | ||
| 785 | } | ||
| 786 | |||
| 787 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) { | ||
| 788 | auto bis_system = | ||
| 789 | EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); | ||
| 790 | if (!bis_system) { | ||
| 791 | return false; | ||
| 792 | } | ||
| 793 | |||
| 794 | // Query an applet to see if it's available | ||
| 795 | auto applet_nca = | ||
| 796 | bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program); | ||
| 797 | if (!applet_nca) { | ||
| 798 | return false; | ||
| 799 | } | ||
| 800 | return true; | ||
| 801 | } | ||
| 802 | |||
| 758 | } // extern "C" | 803 | } // extern "C" |
diff --git a/src/android/app/src/main/res/drawable/ic_album.xml b/src/android/app/src/main/res/drawable/ic_album.xml new file mode 100644 index 000000000..f2b63813f --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_album.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_applet.xml b/src/android/app/src/main/res/drawable/ic_applet.xml new file mode 100644 index 000000000..b154e6f56 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_applet.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M17,16l-4,-4V8.82C14.16,8.4 15,7.3 15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6c0,1.3 0.84,2.4 2,2.82V12l-4,4H3v5h5v-3.05l4,-4.2 4,4.2V21h5v-5h-4z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_edit.xml b/src/android/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000..ac22ce8a5 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_edit.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_mii.xml b/src/android/app/src/main/res/drawable/ic_mii.xml new file mode 100644 index 000000000..1271ec401 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_mii.xml | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M9,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" /> | ||
| 9 | <path | ||
| 10 | android:fillColor="?attr/colorControlNormal" | ||
| 11 | android:pathData="M20.77,8.58l-0.92,2.01c0.09,0.46 0.15,0.93 0.15,1.41 0,4.41 -3.59,8 -8,8s-8,-3.59 -8,-8c0,-0.05 0.01,-0.1 0,-0.14 2.6,-0.98 4.69,-2.99 5.74,-5.55C11.58,8.56 14.37,10 17.5,10c0.45,0 0.89,-0.04 1.33,-0.1l-0.6,-1.32 -0.88,-1.93 -1.93,-0.88 -2.79,-1.27 2.79,-1.27 0.71,-0.32C14.87,2.33 13.47,2 12,2 6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10c0,-1.47 -0.33,-2.87 -0.9,-4.13l-0.33,0.71z" /> | ||
| 12 | <path | ||
| 13 | android:fillColor="?attr/colorControlNormal" | ||
| 14 | android:pathData="M15,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" /> | ||
| 15 | <path | ||
| 16 | android:fillColor="?attr/colorControlNormal" | ||
| 17 | android:pathData="M20.6,5.6L19.5,8l-1.1,-2.4L16,4.5l2.4,-1.1L19.5,1l1.1,2.4L23,4.5z" /> | ||
| 18 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_refresh.xml b/src/android/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 000000000..d0d87ecc2 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_refresh.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_restore.xml b/src/android/app/src/main/res/drawable/ic_restore.xml new file mode 100644 index 000000000..d6d9d4017 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_restore.xml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | android:width="24dp" | ||
| 3 | android:height="24dp" | ||
| 4 | android:viewportWidth="24" | ||
| 5 | android:viewportHeight="24"> | ||
| 6 | <path | ||
| 7 | android:fillColor="?attr/colorControlNormal" | ||
| 8 | android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" /> | ||
| 9 | </vector> | ||
diff --git a/src/android/app/src/main/res/layout/card_applet_option.xml b/src/android/app/src/main/res/layout/card_applet_option.xml new file mode 100644 index 000000000..19fbec9f1 --- /dev/null +++ b/src/android/app/src/main/res/layout/card_applet_option.xml | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | style="?attr/materialCardViewOutlinedStyle" | ||
| 6 | android:layout_width="match_parent" | ||
| 7 | android:layout_height="wrap_content" | ||
| 8 | android:layout_marginHorizontal="16dp" | ||
| 9 | android:layout_marginVertical="12dp" | ||
| 10 | android:background="?attr/selectableItemBackground" | ||
| 11 | android:clickable="true" | ||
| 12 | android:focusable="true"> | ||
| 13 | |||
| 14 | <LinearLayout | ||
| 15 | android:layout_width="match_parent" | ||
| 16 | android:layout_height="wrap_content" | ||
| 17 | android:orientation="horizontal" | ||
| 18 | android:layout_gravity="center" | ||
| 19 | android:padding="24dp"> | ||
| 20 | |||
| 21 | <ImageView | ||
| 22 | android:id="@+id/icon" | ||
| 23 | android:layout_width="24dp" | ||
| 24 | android:layout_height="24dp" | ||
| 25 | android:layout_marginEnd="20dp" | ||
| 26 | android:layout_gravity="center_vertical" | ||
| 27 | app:tint="?attr/colorOnSurface" /> | ||
| 28 | |||
| 29 | <LinearLayout | ||
| 30 | android:layout_width="0dp" | ||
| 31 | android:layout_height="wrap_content" | ||
| 32 | android:layout_weight="1" | ||
| 33 | android:orientation="vertical" | ||
| 34 | android:layout_gravity="center_vertical"> | ||
| 35 | |||
| 36 | <com.google.android.material.textview.MaterialTextView | ||
| 37 | android:id="@+id/title" | ||
| 38 | style="@style/TextAppearance.Material3.TitleMedium" | ||
| 39 | android:layout_width="match_parent" | ||
| 40 | android:layout_height="wrap_content" | ||
| 41 | android:textAlignment="viewStart" | ||
| 42 | tools:text="@string/applets" /> | ||
| 43 | |||
| 44 | <com.google.android.material.textview.MaterialTextView | ||
| 45 | android:id="@+id/description" | ||
| 46 | style="@style/TextAppearance.Material3.BodyMedium" | ||
| 47 | android:layout_width="match_parent" | ||
| 48 | android:layout_height="wrap_content" | ||
| 49 | android:layout_marginTop="6dp" | ||
| 50 | android:textAlignment="viewStart" | ||
| 51 | tools:text="@string/applets_description" /> | ||
| 52 | |||
| 53 | </LinearLayout> | ||
| 54 | |||
| 55 | </LinearLayout> | ||
| 56 | |||
| 57 | </com.google.android.material.card.MaterialCardView> | ||
diff --git a/src/android/app/src/main/res/layout/dialog_list.xml b/src/android/app/src/main/res/layout/dialog_list.xml new file mode 100644 index 000000000..7de2b2c3a --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_list.xml | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | android:layout_width="match_parent" | ||
| 4 | android:layout_height="wrap_content"> | ||
| 5 | |||
| 6 | <androidx.recyclerview.widget.RecyclerView | ||
| 7 | android:id="@+id/dialog_list" | ||
| 8 | android:layout_width="match_parent" | ||
| 9 | android:layout_height="wrap_content" | ||
| 10 | android:clipToPadding="false" | ||
| 11 | android:fadeScrollbars="false" | ||
| 12 | android:paddingVertical="12dp" | ||
| 13 | android:scrollbars="vertical" /> | ||
| 14 | |||
| 15 | </androidx.appcompat.widget.LinearLayoutCompat> | ||
diff --git a/src/android/app/src/main/res/layout/dialog_list_item.xml b/src/android/app/src/main/res/layout/dialog_list_item.xml new file mode 100644 index 000000000..39f3558ff --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_list_item.xml | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:tools="http://schemas.android.com/tools" | ||
| 4 | android:layout_width="match_parent" | ||
| 5 | android:layout_height="wrap_content" | ||
| 6 | android:background="?attr/selectableItemBackground" | ||
| 7 | android:clickable="true" | ||
| 8 | android:focusable="true" | ||
| 9 | android:orientation="horizontal" | ||
| 10 | android:paddingHorizontal="24dp" | ||
| 11 | android:paddingVertical="16dp"> | ||
| 12 | |||
| 13 | <ImageView | ||
| 14 | android:id="@+id/icon" | ||
| 15 | android:layout_width="20dp" | ||
| 16 | android:layout_height="20dp" | ||
| 17 | android:layout_gravity="center" | ||
| 18 | tools:src="@drawable/ic_nfc" /> | ||
| 19 | |||
| 20 | <com.google.android.material.textview.MaterialTextView | ||
| 21 | android:id="@+id/title" | ||
| 22 | style="@style/TextAppearance.Material3.BodyMedium" | ||
| 23 | android:layout_width="match_parent" | ||
| 24 | android:layout_height="wrap_content" | ||
| 25 | android:layout_marginStart="16dp" | ||
| 26 | android:layout_gravity="center_vertical|start" | ||
| 27 | android:textAlignment="viewStart" | ||
| 28 | tools:text="List option" /> | ||
| 29 | |||
| 30 | </LinearLayout> | ||
diff --git a/src/android/app/src/main/res/layout/fragment_applet_launcher.xml b/src/android/app/src/main/res/layout/fragment_applet_launcher.xml new file mode 100644 index 000000000..fe8fae40f --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_applet_launcher.xml | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | android:id="@+id/coordinator_applets" | ||
| 5 | android:layout_width="match_parent" | ||
| 6 | android:layout_height="match_parent" | ||
| 7 | android:background="?attr/colorSurface"> | ||
| 8 | |||
| 9 | <com.google.android.material.appbar.AppBarLayout | ||
| 10 | android:id="@+id/appbar_applets" | ||
| 11 | android:layout_width="match_parent" | ||
| 12 | android:layout_height="wrap_content" | ||
| 13 | android:fitsSystemWindows="true"> | ||
| 14 | |||
| 15 | <com.google.android.material.appbar.MaterialToolbar | ||
| 16 | android:id="@+id/toolbar_applets" | ||
| 17 | android:layout_width="match_parent" | ||
| 18 | android:layout_height="?attr/actionBarSize" | ||
| 19 | app:navigationIcon="@drawable/ic_back" | ||
| 20 | app:title="@string/applets" /> | ||
| 21 | |||
| 22 | </com.google.android.material.appbar.AppBarLayout> | ||
| 23 | |||
| 24 | <androidx.recyclerview.widget.RecyclerView | ||
| 25 | android:id="@+id/list_applets" | ||
| 26 | android:layout_width="match_parent" | ||
| 27 | android:layout_height="match_parent" | ||
| 28 | android:clipToPadding="false" | ||
| 29 | app:layout_behavior="@string/appbar_scrolling_view_behavior" /> | ||
| 30 | |||
| 31 | </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 82749359d..6d4c1f86d 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml | |||
| @@ -25,6 +25,9 @@ | |||
| 25 | <action | 25 | <action |
| 26 | android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment" | 26 | android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment" |
| 27 | app:destination="@id/driverManagerFragment" /> | 27 | app:destination="@id/driverManagerFragment" /> |
| 28 | <action | ||
| 29 | android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment" | ||
| 30 | app:destination="@id/appletLauncherFragment" /> | ||
| 28 | </fragment> | 31 | </fragment> |
| 29 | 32 | ||
| 30 | <fragment | 33 | <fragment |
| @@ -102,5 +105,17 @@ | |||
| 102 | android:id="@+id/driverManagerFragment" | 105 | android:id="@+id/driverManagerFragment" |
| 103 | android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment" | 106 | android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment" |
| 104 | android:label="DriverManagerFragment" /> | 107 | android:label="DriverManagerFragment" /> |
| 108 | <fragment | ||
| 109 | android:id="@+id/appletLauncherFragment" | ||
| 110 | android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment" | ||
| 111 | android:label="AppletLauncherFragment" > | ||
| 112 | <action | ||
| 113 | android:id="@+id/action_appletLauncherFragment_to_cabinetLauncherDialogFragment" | ||
| 114 | app:destination="@id/cabinetLauncherDialogFragment" /> | ||
| 115 | </fragment> | ||
| 116 | <dialog | ||
| 117 | android:id="@+id/cabinetLauncherDialogFragment" | ||
| 118 | android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment" | ||
| 119 | android:label="CabinetLauncherDialogFragment" /> | ||
| 105 | 120 | ||
| 106 | </navigation> | 121 | </navigation> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 9e4854221..b92978140 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -124,6 +124,24 @@ | |||
| 124 | <string name="share_save_file">Share save file</string> | 124 | <string name="share_save_file">Share save file</string> |
| 125 | <string name="export_save_failed">Failed to export save</string> | 125 | <string name="export_save_failed">Failed to export save</string> |
| 126 | 126 | ||
| 127 | <!-- Applet launcher strings --> | ||
| 128 | <string name="applets">Applet launcher</string> | ||
| 129 | <string name="applets_description">Launch system applets using installed firmware</string> | ||
| 130 | <string name="applets_error_firmware">Firmware not installed</string> | ||
| 131 | <string name="applets_error_applet">Applet not available</string> | ||
| 132 | <string name="applets_error_description"><![CDATA[Please ensure your <a href="https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file and <a href="https://yuzu-emu.org/help/quickstart/#dumping-system-firmware">firmware</a> are installed and try again.]]></string> | ||
| 133 | <string name="album_applet">Album</string> | ||
| 134 | <string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</string> | ||
| 135 | <string name="mii_edit_applet">Mii edit</string> | ||
| 136 | <string name="mii_edit_applet_description">View and edit Miis with the system editor</string> | ||
| 137 | <string name="cabinet_applet">Cabinet</string> | ||
| 138 | <string name="cabinet_applet_description">Edit and delete data stored on amiibo</string> | ||
| 139 | <string name="cabinet_launcher">Cabinet launcher</string> | ||
| 140 | <string name="cabinet_nickname_and_owner">Nickname and owner settings</string> | ||
| 141 | <string name="cabinet_game_data_eraser">Game data eraser</string> | ||
| 142 | <string name="cabinet_restorer">Restorer</string> | ||
| 143 | <string name="cabinet_formatter">Formatter</string> | ||
| 144 | |||
| 127 | <!-- About screen strings --> | 145 | <!-- About screen strings --> |
| 128 | <string name="gaia_is_not_real">Gaia isn\'t real</string> | 146 | <string name="gaia_is_not_real">Gaia isn\'t real</string> |
| 129 | <string name="copied_to_clipboard">Copied to clipboard</string> | 147 | <string name="copied_to_clipboard">Copied to clipboard</string> |