mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-03-13 09:12:27 +01:00
Merge 1888bceb112726af492b2adee19ef21ed891d287 into 42d77cd720eb42845c2afb77c6d7157e02c8c325
This commit is contained in:
commit
b1d11eb954
@ -45,6 +45,9 @@ import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
|||||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||||
import org.citra.citra_emu.utils.ThemeUtil
|
import org.citra.citra_emu.utils.ThemeUtil
|
||||||
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
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() {
|
class EmulationActivity : AppCompatActivity() {
|
||||||
private val preferences: SharedPreferences
|
private val preferences: SharedPreferences
|
||||||
@ -66,6 +69,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private var isEmulationRunning: Boolean = false
|
private var isEmulationRunning: Boolean = false
|
||||||
|
|
||||||
|
private var emulationStartTime: Long = 0
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ThemeUtil.setTheme(this)
|
ThemeUtil.setTheme(this)
|
||||||
|
|
||||||
@ -105,6 +110,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
isEmulationRunning = true
|
isEmulationRunning = true
|
||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
|
emulationStartTime = System.currentTimeMillis()
|
||||||
|
|
||||||
applyOrientationSettings() // Check for orientation settings at startup
|
applyOrientationSettings() // Check for orientation settings at startup
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +146,17 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
EmulationLifecycleUtil.clear()
|
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
|
isEmulationRunning = false
|
||||||
instance = null
|
instance = null
|
||||||
super.onDestroy()
|
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.model.Game
|
||||||
import org.citra.citra_emu.utils.GameIconUtils
|
import org.citra.citra_emu.utils.GameIconUtils
|
||||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||||
|
import org.citra.citra_emu.utils.PlayTimeTracker
|
||||||
|
|
||||||
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater) :
|
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater) :
|
||||||
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||||
@ -221,6 +222,12 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
|
|||||||
view.findNavController().navigate(action)
|
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 {
|
bottomSheetView.findViewById<MaterialButton>(R.id.game_shortcut).setOnClickListener {
|
||||||
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
|
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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textSize="20sp"
|
android:textSize="15sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:textStyle="bold" app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Application Title" />
|
tools:text="Application Title" />
|
||||||
|
|
||||||
@ -85,6 +85,15 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/about_game_id"
|
app:layout_constraintTop_toBottomOf="@+id/about_game_id"
|
||||||
tools:text="Application Filename" />
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
Loading…
x
Reference in New Issue
Block a user