diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/activities/EmulationActivity.kt b/src/android/app/src/main/java/io/github/lime3ds/android/activities/EmulationActivity.kt index aef0bbe95..d2568e228 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/activities/EmulationActivity.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Lime3DS Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -95,7 +95,13 @@ class EmulationActivity : AppCompatActivity() { windowManager.defaultDisplay.rotation ) - EmulationLifecycleUtil.addShutdownHook(hook = { this.finish() }) + EmulationLifecycleUtil.addShutdownHook(hook = { + if (intent.getBooleanExtra("launched_from_shortcut", false)) { + finishAffinity() + } else { + this.finish() + } + }) isEmulationRunning = true instance = this diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/adapters/GameAdapter.kt b/src/android/app/src/main/java/io/github/lime3ds/android/adapters/GameAdapter.kt index aaf443d6f..b0efaba9f 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/adapters/GameAdapter.kt @@ -14,6 +14,10 @@ import android.content.Context import android.widget.TextView import android.widget.ImageView 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.documentfile.provider.DocumentFile import androidx.lifecycle.ViewModelProvider @@ -23,6 +27,10 @@ import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import android.graphics.drawable.Icon +import kotlinx.coroutines.launch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CoroutineScope import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -214,6 +222,24 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater: view.findNavController().navigate(action) } + bottomSheetView.findViewById(R.id.game_shortcut).setOnClickListener { + val shortcutManager = activity.getSystemService(ShortcutManager::class.java) + + CoroutineScope(Dispatchers.IO).launch { + val bitmap = (bottomSheetView.findViewById(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(R.id.cheats).setOnClickListener { val action = CheatsFragmentDirections.actionGlobalCheatsFragment(holder.game.titleId) view.findNavController().navigate(action) diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/fragments/GamesFragment.kt b/src/android/app/src/main/java/io/github/lime3ds/android/fragments/GamesFragment.kt index 7e7631a8d..91912a777 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/fragments/GamesFragment.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/fragments/GamesFragment.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Lime3DS Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -60,6 +60,7 @@ class GamesFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { homeViewModel.setNavigationVisibility(visible = true, animated = true) homeViewModel.setStatusBarShadeVisibility(visible = true) + val inflater = LayoutInflater.from(requireContext()) binding.gridGames.apply { diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/model/Game.kt b/src/android/app/src/main/java/io/github/lime3ds/android/model/Game.kt index 55a6fdfe4..0f13dc197 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/model/Game.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/model/Game.kt @@ -1,13 +1,17 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Lime3DS Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. package io.github.lime3ds.android.model import android.os.Parcelable +import android.content.Intent +import android.net.Uri import java.util.HashSet import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable +import io.github.lime3ds.android.LimeApplication +import io.github.lime3ds.android.activities.EmulationActivity @Parcelize @Serializable @@ -27,6 +31,16 @@ class Game( val keyAddedToLibraryTime get() = "${filename}_AddedToLibraryTime" val keyLastPlayedTime get() = "${filename}_LastPlayed" + val launchIntent: Intent + get() = Intent(LimeApplication.appContext, EmulationActivity::class.java).apply { + action = Intent.ACTION_VIEW + data = if (isInstalled) { + LimeApplication.documentsTree.getUri(path) + } else { + Uri.parse(path) + } + } + override fun equals(other: Any?): Boolean { if (other !is Game) { return false diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/utils/DocumentsTree.kt b/src/android/app/src/main/java/io/github/lime3ds/android/utils/DocumentsTree.kt index 59c3a6ae0..0b30da1de 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/utils/DocumentsTree.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Lime3DS Emulator Project // Licensed under GPLv2 or any later version // 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 fun isDirectory(filepath: String): Boolean { val node = resolvePath(filepath) ?: return false diff --git a/src/android/app/src/main/res/drawable/ic_shortcut.xml b/src/android/app/src/main/res/drawable/ic_shortcut.xml new file mode 100644 index 000000000..fb353d127 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_shortcut.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/android/app/src/main/res/layout/dialog_about_game.xml b/src/android/app/src/main/res/layout/dialog_about_game.xml index 696dab2c1..fe4719161 100644 --- a/src/android/app/src/main/res/layout/dialog_about_game.xml +++ b/src/android/app/src/main/res/layout/dialog_about_game.xml @@ -109,6 +109,17 @@ android:focusedByDefault="true" android:text="@string/play" app:icon="@drawable/ic_play" /> + + + - \ No newline at end of file + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index ddb874645..49f4a1716 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -456,6 +456,7 @@ Play + Shortcut Cheats