Skip to content

Commit

Permalink
android: Add Play Time Tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
kleidis committed Jan 19, 2025
1 parent 1f7247c commit 440a623
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ import io.github.lime3ds.android.features.settings.model.SettingsViewModel
import io.github.lime3ds.android.features.settings.model.view.InputBindingSetting
import io.github.lime3ds.android.fragments.EmulationFragment
import io.github.lime3ds.android.fragments.MessageDialogFragment
import io.github.lime3ds.android.model.Game
import io.github.lime3ds.android.utils.ControllerMappingHelper
import io.github.lime3ds.android.utils.FileBrowserHelper
import io.github.lime3ds.android.utils.EmulationLifecycleUtil
import io.github.lime3ds.android.utils.EmulationMenuSettings
import io.github.lime3ds.android.utils.PlayTimeTracker
import io.github.lime3ds.android.utils.ThemeUtil
import io.github.lime3ds.android.viewmodel.EmulationViewModel

Expand All @@ -56,6 +58,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyUtility: HotkeyUtility
private var emulationStartTime: Long = 0

private val emulationFragment: EmulationFragment
get() {
Expand Down Expand Up @@ -107,6 +110,8 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true
instance = this

emulationStartTime = System.currentTimeMillis()

applyOrientationSettings() // Check for orientation settings at startup
}

Expand Down Expand Up @@ -142,6 +147,10 @@ class EmulationActivity : AppCompatActivity() {
override fun onDestroy() {
NativeLibrary.enableAdrenoTurboMode(false)
EmulationLifecycleUtil.clear()
val sessionTime = System.currentTimeMillis() - emulationStartTime
val game = requireNotNull(intent.getParcelableExtra<Game>("game"))
PlayTimeTracker.addPlayTime(game, sessionTime)

isEmulationRunning = false
instance = null
super.onDestroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import io.github.lime3ds.android.databinding.CardGameBinding
import io.github.lime3ds.android.features.cheats.ui.CheatsFragmentDirections
import io.github.lime3ds.android.model.Game
import io.github.lime3ds.android.utils.GameIconUtils
import io.github.lime3ds.android.utils.PlayTimeTracker
import io.github.lime3ds.android.viewmodel.GamesViewModel
import io.github.lime3ds.android.features.settings.ui.SettingsActivity
import io.github.lime3ds.android.features.settings.utils.SettingsFile
Expand Down Expand Up @@ -215,6 +216,7 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = "ID: " + String.format("%016X", game.titleId)
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = "File: " + game.filename
bottomSheetView.findViewById<TextView>(R.id.about_game_playtime ).text = "Playtime: " + PlayTimeTracker.getFormattedPlayTime(game.titleId)
GameIconUtils.loadGameIcon(activity, game, bottomSheetView.findViewById(R.id.game_icon))

bottomSheetView.findViewById<MaterialButton>(R.id.about_game_play).setOnClickListener {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@

// License headers

package io.github.lime3ds.android.utils

import androidx.documentfile.provider.DocumentFile
import io.github.lime3ds.android.LimeApplication
import io.github.lime3ds.android.model.Game
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()
}

private fun getPlayTime(titleId: Long): Long {
return playTimes[titleId]?.totalPlayTimeMs ?: 0
}

fun getFormattedPlayTime(titleId: Long): String {
// Reload playtime in case of manual config file editing
loadPlayTimes()

val totalSeconds = getPlayTime(titleId) / 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(LimeApplication.appContext, PermissionsHandler.lime3dsDirectory)
val configDir = root?.findFile("config") ?: return
val playTimeFile = configDir.findFile(PLAYTIME_FILENAME) ?: return

val context = LimeApplication.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(LimeApplication.appContext, PermissionsHandler.lime3dsDirectory)
val configDir = root?.findFile("config")
?: root?.createDirectory("config")
?: return

var playTimeFile = configDir.findFile(PLAYTIME_FILENAME)
if (playTimeFile == null) {
playTimeFile = configDir.createFile("application/json", PLAYTIME_FILENAME) ?: return
}

val jsonString = Json.encodeToString(playTimes.values.toList())
val outputStream = LimeApplication.appContext.contentResolver.openOutputStream(playTimeFile.uri) ?: return
outputStream.write(jsonString.toByteArray())
outputStream.close()
} catch (e: Exception) {
Log.error("Failed to save play times: ${e.message}")
}
}
}
12 changes: 11 additions & 1 deletion src/android/app/src/main/res/layout/dialog_about_game.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textSize="20sp"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Game Title" />
Expand Down Expand Up @@ -85,6 +86,15 @@
app:layout_constraintTop_toBottomOf="@+id/about_game_id"
tools:text="Game 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
Expand Down

0 comments on commit 440a623

Please sign in to comment.