mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-07 07:29:58 +01:00
android: Implement play time tracking (#813)
* android: Implement play time tacking Co-Authored-By: Reg Tiangha <rtiangha@users.noreply.github.com> * Moved playtime manager from `citra_qt` to `common` * Reimplemented Android play time to use existing logic from desktop * Updated license headers * When getting current game ID fails, silently error rather than crashing * playTimeManagerStart: Check that `play_time_manager` is initialized before using it --------- Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com> Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com>
This commit is contained in:
parent
06ed6f3b6d
commit
7c278f9701
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -58,6 +58,7 @@ class CitraApplication : Application() {
|
|||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
logDeviceInfo()
|
logDeviceInfo()
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
NativeLibrary.playTimeManagerInit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logDeviceInfo() {
|
fun logDeviceInfo() {
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.citra.citra_emu.activities.EmulationActivity
|
import org.citra.citra_emu.activities.EmulationActivity
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
|
||||||
import org.citra.citra_emu.utils.FileUtil
|
import org.citra.citra_emu.utils.FileUtil
|
||||||
import org.citra.citra_emu.utils.Log
|
import org.citra.citra_emu.utils.Log
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
@ -190,6 +189,12 @@ object NativeLibrary {
|
|||||||
|
|
||||||
external fun disableTemporaryFrameLimit()
|
external fun disableTemporaryFrameLimit()
|
||||||
|
|
||||||
|
external fun playTimeManagerInit()
|
||||||
|
external fun playTimeManagerStart(titleId: Long)
|
||||||
|
external fun playTimeManagerStop()
|
||||||
|
external fun playTimeManagerGetPlayTime(titleId: Long): Long
|
||||||
|
external fun playTimeManagerGetCurrentTitleId(): Long
|
||||||
|
|
||||||
private var coreErrorAlertResult = false
|
private var coreErrorAlertResult = false
|
||||||
private val coreErrorAlertLock = Object()
|
private val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ package org.citra.citra_emu.activities
|
|||||||
|
|
||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@ -21,6 +20,7 @@ import android.widget.Toast
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.os.BundleCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
@ -40,12 +40,13 @@ import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
|||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
||||||
import org.citra.citra_emu.fragments.EmulationFragment
|
import org.citra.citra_emu.fragments.EmulationFragment
|
||||||
import org.citra.citra_emu.fragments.MessageDialogFragment
|
import org.citra.citra_emu.fragments.MessageDialogFragment
|
||||||
|
import org.citra.citra_emu.model.Game
|
||||||
import org.citra.citra_emu.utils.ControllerMappingHelper
|
import org.citra.citra_emu.utils.ControllerMappingHelper
|
||||||
import org.citra.citra_emu.utils.FileBrowserHelper
|
import org.citra.citra_emu.utils.FileBrowserHelper
|
||||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
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.Log
|
||||||
import org.citra.citra_emu.utils.ThemeUtil
|
import org.citra.citra_emu.utils.ThemeUtil
|
||||||
import org.citra.citra_emu.utils.TurboHelper
|
|
||||||
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
import org.citra.citra_emu.viewmodel.EmulationViewModel
|
||||||
|
|
||||||
class EmulationActivity : AppCompatActivity() {
|
class EmulationActivity : AppCompatActivity() {
|
||||||
@ -110,6 +111,20 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
applyOrientationSettings() // Check for orientation settings at startup
|
applyOrientationSettings() // Check for orientation settings at startup
|
||||||
|
|
||||||
|
val game = try {
|
||||||
|
intent.extras?.let { extras ->
|
||||||
|
BundleCompat.getParcelable(extras, "game", Game::class.java)
|
||||||
|
} ?: run {
|
||||||
|
Log.error("[EmulationActivity] Missing game data in intent extras")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.error("[EmulationActivity] Failed to retrieve game data: ${e.message}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.playTimeManagerStart(game.titleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// On some devices, the system bars will not disappear on first boot or after some
|
// On some devices, the system bars will not disappear on first boot or after some
|
||||||
@ -143,6 +158,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
EmulationLifecycleUtil.clear()
|
EmulationLifecycleUtil.clear()
|
||||||
|
NativeLibrary.playTimeManagerStop()
|
||||||
isEmulationRunning = false
|
isEmulationRunning = false
|
||||||
instance = null
|
instance = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|||||||
@ -45,6 +45,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.citra.citra_emu.HomeNavigationDirections
|
import org.citra.citra_emu.HomeNavigationDirections
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
|
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
|
||||||
import org.citra.citra_emu.databinding.CardGameBinding
|
import org.citra.citra_emu.databinding.CardGameBinding
|
||||||
@ -351,6 +352,24 @@ 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 {
|
||||||
|
val playTimeSeconds = NativeLibrary.playTimeManagerGetPlayTime(game.titleId)
|
||||||
|
|
||||||
|
val hours = playTimeSeconds / 3600
|
||||||
|
val minutes = (playTimeSeconds % 3600) / 60
|
||||||
|
val seconds = playTimeSeconds % 60
|
||||||
|
|
||||||
|
val readablePlayTime = when {
|
||||||
|
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
|
||||||
|
minutes > 0 -> "${minutes}m ${seconds}s"
|
||||||
|
else -> "${seconds}s"
|
||||||
|
}
|
||||||
|
|
||||||
|
append("Playtime: ")
|
||||||
|
append(readablePlayTime)
|
||||||
|
}
|
||||||
|
|
||||||
bottomSheetView.findViewById<MaterialButton>(R.id.game_shortcut).setOnClickListener {
|
bottomSheetView.findViewById<MaterialButton>(R.id.game_shortcut).setOnClickListener {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
|||||||
@ -355,7 +355,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_exit -> {
|
R.id.menu_exit -> {
|
||||||
NativeLibrary.pauseEmulation()
|
emulationState.pause()
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.emulation_close_game)
|
.setTitle(R.string.emulation_close_game)
|
||||||
.setMessage(R.string.emulation_close_game_message)
|
.setMessage(R.string.emulation_close_game_message)
|
||||||
@ -363,9 +363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
EmulationLifecycleUtil.closeGame()
|
EmulationLifecycleUtil.closeGame()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
|
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
|
||||||
NativeLibrary.unPauseEmulation()
|
emulationState.unpause()
|
||||||
}
|
}
|
||||||
.setOnCancelListener { NativeLibrary.unPauseEmulation() }
|
.setOnCancelListener { emulationState.unpause() }
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -470,7 +470,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
Choreographer.getInstance().postFrameCallback(this)
|
Choreographer.getInstance().postFrameCallback(this)
|
||||||
if (NativeLibrary.isRunning()) {
|
if (NativeLibrary.isRunning()) {
|
||||||
NativeLibrary.unPauseEmulation()
|
emulationState.pause()
|
||||||
|
|
||||||
// If the overlay is enabled, we need to update the position if changed
|
// If the overlay is enabled, we need to update the position if changed
|
||||||
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
|
||||||
@ -1395,6 +1395,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
// Release the surface before pausing, since emulation has to be running for that.
|
// Release the surface before pausing, since emulation has to be running for that.
|
||||||
NativeLibrary.surfaceDestroyed()
|
NativeLibrary.surfaceDestroyed()
|
||||||
NativeLibrary.pauseEmulation()
|
NativeLibrary.pauseEmulation()
|
||||||
|
NativeLibrary.playTimeManagerStop()
|
||||||
} else {
|
} else {
|
||||||
Log.warning("[EmulationFragment] Pause called while already paused.")
|
Log.warning("[EmulationFragment] Pause called while already paused.")
|
||||||
}
|
}
|
||||||
@ -1407,6 +1408,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
Log.debug("[EmulationFragment] Unpausing emulation.")
|
Log.debug("[EmulationFragment] Unpausing emulation.")
|
||||||
|
|
||||||
NativeLibrary.unPauseEmulation()
|
NativeLibrary.unPauseEmulation()
|
||||||
|
NativeLibrary.playTimeManagerStart(NativeLibrary.playTimeManagerGetCurrentTitleId())
|
||||||
} else {
|
} else {
|
||||||
Log.warning("[EmulationFragment] Unpause called while already running.")
|
Log.warning("[EmulationFragment] Unpause called while already running.")
|
||||||
}
|
}
|
||||||
@ -1473,7 +1475,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
|
|
||||||
State.PAUSED -> {
|
State.PAUSED -> {
|
||||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||||
NativeLibrary.unPauseEmulation()
|
unpause()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
@ -71,6 +72,9 @@ ANativeWindow* s_surf;
|
|||||||
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
||||||
std::unique_ptr<EmuWindow_Android> window;
|
std::unique_ptr<EmuWindow_Android> window;
|
||||||
|
|
||||||
|
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
|
||||||
|
jlong ptm_current_title_id = std::numeric_limits<jlong>::max(); // Arbitrary default value
|
||||||
|
|
||||||
std::atomic<bool> stop_run{true};
|
std::atomic<bool> stop_run{true};
|
||||||
std::atomic<bool> pause_emulation{false};
|
std::atomic<bool> pause_emulation{false};
|
||||||
|
|
||||||
@ -795,4 +799,31 @@ void Java_org_citra_citra_1emu_NativeLibrary_disableTemporaryFrameLimit(JNIEnv*
|
|||||||
Settings::is_temporary_frame_limit = false;
|
Settings::is_temporary_frame_limit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerInit(JNIEnv* env, jobject obj) {
|
||||||
|
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerStart(JNIEnv* env, jobject obj,
|
||||||
|
jlong title_id) {
|
||||||
|
ptm_current_title_id = title_id;
|
||||||
|
if (play_time_manager) {
|
||||||
|
play_time_manager->SetProgramId(static_cast<u64>(title_id));
|
||||||
|
play_time_manager->Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerStop(JNIEnv* env, jobject obj) {
|
||||||
|
play_time_manager->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
jlong Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerGetPlayTime(JNIEnv* env, jobject obj,
|
||||||
|
jlong title_id) {
|
||||||
|
return static_cast<jlong>(play_time_manager->GetPlayTime(title_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
jlong Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerGetCurrentTitleId(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
return ptm_current_title_id;
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -171,8 +171,6 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
|
|||||||
multiplayer/state.cpp
|
multiplayer/state.cpp
|
||||||
multiplayer/state.h
|
multiplayer/state.h
|
||||||
multiplayer/validation.h
|
multiplayer/validation.h
|
||||||
play_time_manager.cpp
|
|
||||||
play_time_manager.h
|
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
uisettings.cpp
|
uisettings.cpp
|
||||||
uisettings.h
|
uisettings.h
|
||||||
|
|||||||
@ -67,9 +67,9 @@
|
|||||||
#include "citra_qt/movie/movie_play_dialog.h"
|
#include "citra_qt/movie/movie_play_dialog.h"
|
||||||
#include "citra_qt/movie/movie_record_dialog.h"
|
#include "citra_qt/movie/movie_record_dialog.h"
|
||||||
#include "citra_qt/multiplayer/state.h"
|
#include "citra_qt/multiplayer/state.h"
|
||||||
#include "citra_qt/play_time_manager.h"
|
|
||||||
#include "citra_qt/qt_image_interface.h"
|
#include "citra_qt/qt_image_interface.h"
|
||||||
#include "citra_qt/uisettings.h"
|
#include "citra_qt/uisettings.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
#ifdef ENABLE_QT_UPDATE_CHECKER
|
#ifdef ENABLE_QT_UPDATE_CHECKER
|
||||||
#include "citra_qt/update_checker.h"
|
#include "citra_qt/update_checker.h"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "citra_qt/compatibility_list.h"
|
#include "citra_qt/compatibility_list.h"
|
||||||
#include "citra_qt/play_time_manager.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
#include "uisettings.h"
|
#include "uisettings.h"
|
||||||
|
|
||||||
namespace Service::FS {
|
namespace Service::FS {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// Copyright 2015 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -18,11 +17,11 @@
|
|||||||
#include <QStandardItem>
|
#include <QStandardItem>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "citra_qt/play_time_manager.h"
|
|
||||||
#include "citra_qt/uisettings.h"
|
#include "citra_qt/uisettings.h"
|
||||||
#include "citra_qt/util/util.h"
|
#include "citra_qt/util/util.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/loader/smdh.h"
|
#include "core/loader/smdh.h"
|
||||||
|
|
||||||
@ -382,7 +381,7 @@ public:
|
|||||||
|
|
||||||
void setData(const QVariant& value, int role) override {
|
void setData(const QVariant& value, int role) override {
|
||||||
qulonglong time_seconds = value.toULongLong();
|
qulonglong time_seconds = value.toULongLong();
|
||||||
GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
|
GameListItem::setData(ReadableDuration(time_seconds), Qt::DisplayRole);
|
||||||
GameListItem::setData(value, PlayTimeRole);
|
GameListItem::setData(value, PlayTimeRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
// Copyright 2018 yuzu emulator team
|
// Copyright 2018 yuzu emulator team
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
@ -13,8 +17,8 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include "citra_qt/compatibility_list.h"
|
#include "citra_qt/compatibility_list.h"
|
||||||
#include "citra_qt/play_time_manager.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
|
|
||||||
namespace Service::FS {
|
namespace Service::FS {
|
||||||
enum class MediaType : u32;
|
enum class MediaType : u32;
|
||||||
|
|||||||
@ -36,6 +36,21 @@ QString ReadableByteSize(qulonglong size) {
|
|||||||
.arg(QString::fromUtf8(units[digit_groups]));
|
.arg(QString::fromUtf8(units[digit_groups]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ReadableDuration(qulonglong time_seconds) {
|
||||||
|
if (time_seconds == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
|
||||||
|
const auto time_hours = static_cast<double>(time_seconds) / 3600;
|
||||||
|
const bool is_minutes = time_minutes < 60;
|
||||||
|
const char* unit = is_minutes ? "m" : "h";
|
||||||
|
const auto value = is_minutes ? time_minutes : time_hours;
|
||||||
|
|
||||||
|
return QStringLiteral("%L1 %2")
|
||||||
|
.arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
|
||||||
|
.arg(QString::fromUtf8(unit));
|
||||||
|
}
|
||||||
|
|
||||||
QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
||||||
QPixmap circle_pixmap(16, 16);
|
QPixmap circle_pixmap(16, 16);
|
||||||
circle_pixmap.fill(Qt::transparent);
|
circle_pixmap.fill(Qt::transparent);
|
||||||
|
|||||||
@ -15,6 +15,9 @@ QFont GetMonospaceFont();
|
|||||||
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
||||||
QString ReadableByteSize(qulonglong size);
|
QString ReadableByteSize(qulonglong size);
|
||||||
|
|
||||||
|
// Converts a length of time in seconds into a readable format
|
||||||
|
QString ReadableDuration(qulonglong time_seconds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circle pixmap from a specified color
|
* Creates a circle pixmap from a specified color
|
||||||
* @param color The color the pixmap shall have
|
* @param color The color the pixmap shall have
|
||||||
|
|||||||
@ -114,6 +114,8 @@ add_library(citra_common STATIC
|
|||||||
microprofileui.h
|
microprofileui.h
|
||||||
param_package.cpp
|
param_package.cpp
|
||||||
param_package.h
|
param_package.h
|
||||||
|
play_time_manager.cpp
|
||||||
|
play_time_manager.h
|
||||||
polyfill_thread.h
|
polyfill_thread.h
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
quaternion.h
|
quaternion.h
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "citra_qt/play_time_manager.h"
|
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
|
||||||
@ -140,19 +141,4 @@ void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
|
|||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ReadablePlayTime(qulonglong time_seconds) {
|
|
||||||
if (time_seconds == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
|
|
||||||
const auto time_hours = static_cast<double>(time_seconds) / 3600;
|
|
||||||
const bool is_minutes = time_minutes < 60;
|
|
||||||
const char* unit = is_minutes ? "m" : "h";
|
|
||||||
const auto value = is_minutes ? time_minutes : time_hours;
|
|
||||||
|
|
||||||
return QStringLiteral("%L1 %2")
|
|
||||||
.arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
|
|
||||||
.arg(QString::fromUtf8(unit));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace PlayTime
|
} // namespace PlayTime
|
||||||
@ -1,10 +1,9 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
@ -40,6 +39,4 @@ private:
|
|||||||
std::jthread play_time_thread;
|
std::jthread play_time_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString ReadablePlayTime(qulonglong time_seconds);
|
|
||||||
|
|
||||||
} // namespace PlayTime
|
} // namespace PlayTime
|
||||||
Loading…
x
Reference in New Issue
Block a user