Merge 1888bceb112726af492b2adee19ef21ed891d287 into 42d77cd720eb42845c2afb77c6d7157e02c8c325

This commit is contained in:
Kleidis 2025-03-13 02:36:02 -03:00 committed by GitHub
commit b1d11eb954
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 138 additions and 2 deletions

View File

@ -45,6 +45,9 @@ import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.ThemeUtil
import org.citra.citra_emu.viewmodel.EmulationViewModel
import androidx.core.os.BundleCompat
import org.citra.citra_emu.utils.PlayTimeTracker
import org.citra.citra_emu.model.Game
class EmulationActivity : AppCompatActivity() {
private val preferences: SharedPreferences
@ -66,6 +69,8 @@ class EmulationActivity : AppCompatActivity() {
private var isEmulationRunning: Boolean = false
private var emulationStartTime: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
ThemeUtil.setTheme(this)
@ -105,6 +110,8 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true
instance = this
emulationStartTime = System.currentTimeMillis()
applyOrientationSettings() // Check for orientation settings at startup
}
@ -139,6 +146,17 @@ class EmulationActivity : AppCompatActivity() {
override fun onDestroy() {
EmulationLifecycleUtil.clear()
val sessionTime = System.currentTimeMillis() - emulationStartTime
val game = try {
intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
} ?: throw IllegalStateException("Missing game data in intent extras")
} catch (e: Exception) {
throw IllegalStateException("Failed to retrieve game data: ${e.message}", e)
}
PlayTimeTracker.addPlayTime(game, sessionTime)
isEmulationRunning = false
instance = null
super.onDestroy()

View File

@ -47,6 +47,7 @@ import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.utils.PlayTimeTracker
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
@ -221,6 +222,12 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
view.findNavController().navigate(action)
}
bottomSheetView.findViewById<TextView>(R.id.about_game_playtime).text =
buildString {
append("Playtime: ")
append(PlayTimeTracker.getPlayTime(game.titleId))
}
bottomSheetView.findViewById<MaterialButton>(R.id.game_shortcut).setOnClickListener {
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)

View File

@ -0,0 +1,102 @@
// Copyright 2025 Mandarine Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.utils
import androidx.documentfile.provider.DocumentFile
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.CitraApplication
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.BufferedReader
import java.io.InputStreamReader
@Serializable
data class PlayTimeData(
val titleId: Long,
val title: String,
var totalPlayTimeMs: Long = 0
)
object PlayTimeTracker {
private const val PLAYTIME_FILENAME = "playtime.json"
private val playTimes = mutableMapOf<Long, PlayTimeData>()
init {
loadPlayTimes()
}
fun addPlayTime(game: Game, sessionTimeMs: Long) {
val data = playTimes.getOrPut(game.titleId) {
PlayTimeData(game.titleId, game.title)
}
data.totalPlayTimeMs += sessionTimeMs
savePlayTimes()
}
fun getPlayTime(titleId: Long): String {
// Reload playtime in case of manual file editing
loadPlayTimes()
val totalSeconds = (playTimes[titleId]?.totalPlayTimeMs ?: 0) / 1000
val hours = totalSeconds / 3600
val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
return when {
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
minutes > 0 -> "${minutes}m ${seconds}s"
else -> "${seconds}s"
}
}
private fun loadPlayTimes() {
try {
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, PermissionsHandler.citraDirectory)
val logDir = root?.findFile("log") ?: return
val playTimeFile = logDir.findFile(PLAYTIME_FILENAME) ?: return
val context = CitraApplication.appContext
val inputStream = context.contentResolver.openInputStream(playTimeFile.uri) ?: return
val reader = BufferedReader(InputStreamReader(inputStream))
val jsonString = reader.readText()
val loadedData = Json.decodeFromString<List<PlayTimeData>>(jsonString)
playTimes.clear()
loadedData.forEach { playTimes[it.titleId] = it }
reader.close()
} catch (e: Exception) {
Log.error("Failed to load play times: ${e.message}")
}
}
private fun savePlayTimes() {
try {
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, PermissionsHandler.citraDirectory)
val logDir = root?.findFile("log")
?: root?.createDirectory("log")
?: return
var playTimeFile = logDir.findFile(PLAYTIME_FILENAME)
if (playTimeFile == null) {
playTimeFile = logDir.createFile("application/json", PLAYTIME_FILENAME) ?: return
}
val jsonString = Json.encodeToString(playTimes.values.toList())
val outputStream = CitraApplication.appContext.contentResolver.openOutputStream(playTimeFile.uri) ?: return
outputStream.write(jsonString.toByteArray())
outputStream.close()
} catch (e: Exception) {
Log.error("Failed to save play times: ${e.message}")
}
}
// Will be used on a later PR
fun deletePlayTime(titleId: Long) {
playTimes.remove(titleId)
savePlayTimes()
}
}

View File

@ -44,8 +44,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
android:textSize="15sp"
android:textStyle="bold" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Application Title" />
@ -85,6 +85,15 @@
app:layout_constraintTop_toBottomOf="@+id/about_game_id"
tools:text="Application Filename" />
<TextView
android:id="@+id/about_game_playtime"
style="?attr/textAppearanceBodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/about_game_title"
app:layout_constraintTop_toBottomOf="@+id/about_game_filename"
tools:text="Game Playtime" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout