[core, android] Initial playtime implementation #2535

Open
inix wants to merge 10 commits from inix/eden:playtime-android into master
9 changed files with 132 additions and 4 deletions
Showing only changes of commit 8e38ff23f0 - Show all commits

View file

@ -154,6 +154,11 @@ u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
}
}
void PlayTimeManager::SetPlayTime(u64 program_id, u64 play_time) {
database[program_id] = play_time;
Save();
}
void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
database.erase(program_id);
Save();

View file

@ -34,6 +34,7 @@ public:
u64 GetPlayTime(u64 program_id) const;
void ResetProgramPlayTime(u64 program_id);
void SetProgramId(u64 program_id);
void SetPlayTime(u64 program_id, u64 play_time);
void Start();
void Stop();

View file

@ -3,6 +3,8 @@
#include "qt_playtime_manager.h"
namespace QtCommon::PlayTimeManager {
QString ReadablePlayTime(qulonglong time_seconds) {
if (time_seconds == 0) {
return {};
@ -17,3 +19,24 @@ QString ReadablePlayTime(qulonglong time_seconds) {
.arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
.arg(QString::fromUtf8(unit));
}
QString GetPlayTimeUnit(qulonglong time_seconds, TimeUnit unit) {
switch (unit) {
case TimeUnit::Hours: {
const qulonglong hours = time_seconds / 3600;
return QString::number(hours);
}
case TimeUnit::Minutes: {
const qulonglong minutes = (time_seconds % 3600) / 60;
return QString::number(minutes);
}
case TimeUnit::Seconds: {
const qulonglong seconds = time_seconds % 60;
return QString::number(seconds);
}
default:
return QStringLiteral("0");
}
}
crueter marked this conversation as resolved

In these cases I would recommend making them FrontendCommon methods that return std::strings. For the ReadablePlayTime you can use fmt::format like in my DataManager PR

In these cases I would recommend making them FrontendCommon methods that return std::strings. For the ReadablePlayTime you can use fmt::format like in my DataManager PR
} // namespace QtCommon::PlayTimeManager

View file

@ -1,8 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QString>
namespace QtCommon::PlayTimeManager {
enum class TimeUnit {
Hours,
Minutes,
Seconds
};
// Converts a length of time in seconds into a readable format
QString ReadablePlayTime(qulonglong time_seconds);
QString ReadablePlayTime(qulonglong time_seconds);
// Returns play time hours/minutes/seconds as a string
QString GetPlayTimeUnit(qulonglong time_seconds, TimeUnit unit);
} // namespace QtCommon::PlayTimeManager

View file

@ -557,13 +557,15 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
remove_menu->addSeparator();
QAction* remove_shader_cache = remove_menu->addAction(tr("Remove All Pipeline Caches"));
QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents"));
QMenu* play_time_menu = context_menu.addMenu(tr("Manage Play Time"));
QAction* set_play_time = play_time_menu->addAction(tr("Set Play Time Data"));
crueter marked this conversation as resolved

"Edit" would work better here

"Edit" would work better here
QAction* remove_play_time_data = play_time_menu->addAction(tr("Remove Play Time Data"));
QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
@ -629,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CustomConfiguration, path);
});
connect(set_play_time, &QAction::triggered,
[this, program_id]() { emit SetPlayTimeRequested(program_id); });
connect(remove_play_time_data, &QAction::triggered,
[this, program_id]() { emit RemovePlayTimeRequested(program_id); });
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {

View file

@ -104,6 +104,7 @@ signals:
void RemoveFileRequested(u64 program_id, QtCommon::Game::GameListRemoveTarget target,
const std::string& game_path);
void RemovePlayTimeRequested(u64 program_id);
void SetPlayTimeRequested(u64 program_id);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void VerifyIntegrityRequested(const std::string& game_path);
void CopyTIDRequested(u64 program_id);

View file

@ -241,7 +241,7 @@ public:
void setData(const QVariant& value, int role) override {
qulonglong time_seconds = value.toULongLong();
GameListItem::setData(ReadablePlayTime(time_seconds), Qt::DisplayRole);
GameListItem::setData(QtCommon::PlayTimeManager::ReadablePlayTime(time_seconds), Qt::DisplayRole);
GameListItem::setData(value, PlayTimeRole);
}

View file

@ -1574,6 +1574,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
connect(game_list, &GameList::RemovePlayTimeRequested, this,
&GMainWindow::OnGameListRemovePlayTimeData);
connect(game_list, &GameList::SetPlayTimeRequested, this,
&GMainWindow::OnGameListSetPlayTime);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::VerifyIntegrityRequested, this,
&GMainWindow::OnGameListVerifyIntegrity);
@ -2634,6 +2636,85 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, QtCommon::Game::GameListR
}
}
void GMainWindow::OnGameListSetPlayTime(u64 program_id) {
QDialog dialog(this);
dialog.setWindowTitle(tr("Set Play Time Data"));
dialog.setModal(true);
crueter marked this conversation as resolved

We can probably make this a .ui file

We can probably make this a .ui file
auto* layout = new QVBoxLayout(&dialog);
auto* input_layout = new QHBoxLayout();
auto* hours_edit = new QLineEdit(&dialog);
auto* minutes_edit = new QLineEdit(&dialog);
auto* seconds_edit = new QLineEdit(&dialog);
crueter marked this conversation as resolved

These can be spin boxes

These can be spin boxes
hours_edit->setValidator(new QIntValidator(0, 9999, hours_edit));
minutes_edit->setValidator(new QIntValidator(0, 59, minutes_edit));
seconds_edit->setValidator(new QIntValidator(0, 59, seconds_edit));
crueter marked this conversation as resolved

And if we do go with QSpinBox you can just set the range and this won't be necessary

And if we do go with QSpinBox you can just set the range and this won't be necessary
using QtCommon::PlayTimeManager::TimeUnit;
const u64 current_play_time = play_time_manager->GetPlayTime(program_id);
auto getTimeUnit = [current_play_time](TimeUnit unit) {
return QtCommon::PlayTimeManager::GetPlayTimeUnit(current_play_time, unit);
};
hours_edit->setText(getTimeUnit(TimeUnit::Hours));
minutes_edit->setText(getTimeUnit(TimeUnit::Minutes));
seconds_edit->setText(getTimeUnit(TimeUnit::Seconds));
input_layout->addWidget(new QLabel(tr("Hours:"), &dialog));
input_layout->addWidget(hours_edit);
input_layout->addWidget(new QLabel(tr("Minutes:"), &dialog));
input_layout->addWidget(minutes_edit);
input_layout->addWidget(new QLabel(tr("Seconds:"), &dialog));
input_layout->addWidget(seconds_edit);
layout->addLayout(input_layout);
auto* error_label = new QLabel(&dialog);
error_label->setStyleSheet(QStringLiteral("QLabel { color : red; }"));
error_label->setVisible(false);
layout->addWidget(error_label);
auto* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
layout->addWidget(button_box);
connect(button_box, &QDialogButtonBox::accepted, [&]() {
const int hours = hours_edit->text().toInt();
const int minutes = minutes_edit->text().toInt();
const int seconds = seconds_edit->text().toInt();
crueter marked this conversation as resolved

Theoretically, this "should" be done as text is edited rather than as it's accepted.

Theoretically, this "should" be done as text is edited rather than as it's accepted.
if (hours < 0 || hours > 9999) {
error_label->setText(tr("Hours must be between 0 and 9999."));
error_label->setVisible(true);
return;
}
if (minutes < 0 || minutes > 59) {
error_label->setText(tr("Minutes must be between 0 and 59."));
error_label->setVisible(true);
return;
}
if (seconds < 0 || seconds > 59) {
error_label->setText(tr("Seconds must be between 0 and 59."));
error_label->setVisible(true);
return;
}
u64 total_seconds = static_cast<u64>(hours) * 3600 + static_cast<u64>(minutes) * 60 + static_cast<u64>(seconds);
play_time_manager->SetPlayTime(program_id, total_seconds);
game_list->PopulateAsync(UISettings::values.game_dirs);
dialog.accept();
});
connect(button_box, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
dialog.exec();
}
void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
QMessageBox::Yes | QMessageBox::No,

View file

@ -345,6 +345,7 @@ private slots:
void OnGameListRemoveFile(u64 program_id, QtCommon::Game::GameListRemoveTarget target,
const std::string& game_path);
void OnGameListRemovePlayTimeData(u64 program_id);
void OnGameListSetPlayTime(u64 program_id);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void OnGameListVerifyIntegrity(const std::string& game_path);
void OnGameListCopyTID(u64 program_id);