android: Add game shortcuts to about game dialog (#313)

Adapted from https://github.com/mandarine3ds/mandarine/pull/47

Co-authored-by: Charles Lombardo <clombardo169@gmail.com>
Co-authored-by: Ishan09811 <156402647+Ishan09811@users.noreply.github.com>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
Kleidis 2024-10-13 14:49:53 +02:00 committed by OpenSauce04
parent cc2146a880
commit 083390901b
8 changed files with 81 additions and 6 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -95,7 +95,13 @@ class EmulationActivity : AppCompatActivity() {
windowManager.defaultDisplay.rotation windowManager.defaultDisplay.rotation
) )
EmulationLifecycleUtil.addShutdownHook(hook = { this.finish() }) EmulationLifecycleUtil.addShutdownHook(hook = {
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
finishAffinity()
} else {
this.finish()
}
})
isEmulationRunning = true isEmulationRunning = true
instance = this instance = this

View File

@ -4,6 +4,7 @@
package org.citra.citra_emu.adapters package org.citra.citra_emu.adapters
import android.graphics.drawable.Icon
import android.net.Uri import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.text.TextUtils import android.text.TextUtils
@ -14,6 +15,10 @@ import android.content.Context
import android.widget.TextView import android.widget.TextView
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -28,6 +33,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope
import org.citra.citra_emu.HomeNavigationDirections import org.citra.citra_emu.HomeNavigationDirections
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R import org.citra.citra_emu.R
@ -213,6 +221,24 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
view.findNavController().navigate(action) view.findNavController().navigate(action)
} }
bottomSheetView.findViewById<MaterialButton>(R.id.game_shortcut).setOnClickListener {
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
CoroutineScope(Dispatchers.IO).launch {
val bitmap = (bottomSheetView.findViewById<ImageView>(R.id.game_icon).drawable as BitmapDrawable).bitmap
val icon = Icon.createWithBitmap(bitmap)
val shortcut = ShortcutInfo.Builder(context, game.title)
.setShortLabel(game.title)
.setIcon(icon)
.setIntent(game.launchIntent.apply {
putExtra("launched_from_shortcut", true)
})
.build()
shortcutManager.requestPinShortcut(shortcut, null)
}
}
bottomSheetView.findViewById<MaterialButton>(R.id.cheats).setOnClickListener { bottomSheetView.findViewById<MaterialButton>(R.id.cheats).setOnClickListener {
val action = CheatsFragmentDirections.actionGlobalCheatsFragment(holder.game.titleId) val action = CheatsFragmentDirections.actionGlobalCheatsFragment(holder.game.titleId)
view.findNavController().navigate(action) view.findNavController().navigate(action)

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -60,6 +60,7 @@ class GamesFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = true) homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = true) homeViewModel.setStatusBarShadeVisibility(visible = true)
val inflater = LayoutInflater.from(requireContext()) val inflater = LayoutInflater.from(requireContext())
binding.gridGames.apply { binding.gridGames.apply {

View File

@ -1,13 +1,17 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.model package org.citra.citra_emu.model
import android.os.Parcelable import android.os.Parcelable
import android.content.Intent
import android.net.Uri
import java.util.HashSet import java.util.HashSet
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.activities.EmulationActivity
@Parcelize @Parcelize
@Serializable @Serializable
@ -27,6 +31,16 @@ class Game(
val keyAddedToLibraryTime get() = "${filename}_AddedToLibraryTime" val keyAddedToLibraryTime get() = "${filename}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${filename}_LastPlayed" val keyLastPlayedTime get() = "${filename}_LastPlayed"
val launchIntent: Intent
get() = Intent(CitraApplication.appContext, EmulationActivity::class.java).apply {
action = Intent.ACTION_VIEW
data = if (isInstalled) {
CitraApplication.documentsTree.getUri(path)
} else {
Uri.parse(path)
}
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is Game) { if (other !is Game) {
return false return false

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -100,6 +100,12 @@ class DocumentsTree {
} }
} }
@Synchronized
fun getUri(filepath: String): Uri {
val node = resolvePath(filepath) ?: return Uri.EMPTY
return node.uri ?: return Uri.EMPTY
}
@Synchronized @Synchronized
fun isDirectory(filepath: String): Boolean { fun isDirectory(filepath: String): Boolean {
val node = resolvePath(filepath) ?: return false val node = resolvePath(filepath) ?: return false

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,280L680,280L680,240L280,240L280,720L680,720L680,680L760,680L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM686,520L480,520Q480,520 480,520Q480,520 480,520L480,640L400,640L400,520Q400,487 423.5,463.5Q447,440 480,440L686,440L624,376L680,320L840,480L680,640L624,584L686,520Z"/>
</vector>

View File

@ -109,6 +109,17 @@
android:focusedByDefault="true" android:focusedByDefault="true"
android:text="@string/play" android:text="@string/play"
app:icon="@drawable/ic_play" /> app:icon="@drawable/ic_play" />
<com.google.android.material.button.MaterialButton
android:id="@+id/game_shortcut"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/shortcut"
app:icon="@drawable/ic_shortcut"
app:iconGravity="textStart" />
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -458,6 +458,7 @@
<!-- About Game Dialog --> <!-- About Game Dialog -->
<string name="play">Play</string> <string name="play">Play</string>
<string name="shortcut">Shortcut</string>
<!-- Cheats --> <!-- Cheats -->
<string name="cheats">Cheats</string> <string name="cheats">Cheats</string>