mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-03-13 09:12:27 +01:00
android: Implement play time tacking
Co-Authored-By: Reg Tiangha <rtiangha@users.noreply.github.com>
This commit is contained in:
parent
43dbe42b29
commit
1888bceb11
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user