diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4196221 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,57 @@ +name: Release + +on: + issue_comment: + types: [ created ] + +jobs: + build_and_release: + name: Build and release + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + if: contains(github.event.comment.body, '/release-action') + + steps: + - name: Update packages + run: | + sudo -n apt-get update + sudo -n apt-get upgrade -y git build-essential + shell: bash + - name: Update latest libnx + run: | + git config --global --add safe.directory "*" + git clone --recurse-submodules https://github.com/zdm65477730/libnx.git + cd libnx + make install -j$(nproc) + shell: bash + - name: Checkout latest code + uses: actions/checkout@v4.1.1 + with: + ref: master + clean: true + fetch-depth: 0 + fetch-tags: true + submodules: recursive + - name: Setup ENV parameters + run: | + VER_FILE=Application/Makefile + VERSION=$(awk '/^VER_MAJOR/{print $3}' $VER_FILE).$(awk '/^VER_MINOR/{print $3}' $VER_FILE).$(awk '/^VER_MICRO/{print $3}' $VER_FILE) + echo "TAG=v${VERSION}" > "${GITHUB_ENV}" + echo "RELEASE_NAME=NX-Activity-Log v${VERSION}" >> "${GITHUB_ENV}" + shell: bash + - name: Build + run: | + export DEVKITPRO=/opt/devkitpro + make -j$(nproc) + shell: bash + - name: Upload Release Asset + uses: softprops/action-gh-release@v2.0.9 + with: + name: ${{ env.RELEASE_NAME }} + tag_name: ${{ env.TAG }} + draft: false + prerelease: false + generate_release_notes: yes + make_latest: true + files: | + ./sdcard/switch/NX-Activity-Log/NX-Activity-Log.nro diff --git a/.gitmodules b/.gitmodules index 32b221c..bc7be9b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ [submodule "Translations"] path = Application/romfs/lang - url = https://github.com/tallbl0nde/NX-Activity-Log-Translations.git -[submodule "libs/json"] - path = Application/libs/json - url = https://github.com/nlohmann/json.git + url = https://github.com/zdm65477730/NX-Activity-Log-Translations.git [submodule "Application/libs/SimpleIniParser"] path = Application/libs/SimpleIniParser - url = https://git.nicholemattera.com/NicholeMattera/Simple-INI-Parser + url = https://github.com/zdm65477730/SimpleIniParser.git +[submodule "Application/libs/json"] + path = Application/libs/json + url = https://github.com/nlohmann/json.git [submodule "Application/libs/Aether"] path = Application/libs/Aether - url = https://github.com/tallbl0nde/Aether + url = https://github.com/zdm65477730/Aether diff --git a/Application/Makefile b/Application/Makefile index bb043ef..9e4f105 100644 --- a/Application/Makefile +++ b/Application/Makefile @@ -34,7 +34,7 @@ BUILD := build INCLUDES := include SOURCES := source ROMFS := romfs -LIBS := -lAether -lcurl -lnx `sdl2-config --libs` -lSDL2_ttf `freetype-config --libs` -lSDL2_gfx -lSDL2_image -lpng -ljpeg -lwebp -lSimpleIniParser +LIBS := -lAether -lcurl -lnx `sdl2-config --libs` -lSDL2_ttf `freetype-config --libs` -lSDL2_gfx -lSDL2_image -lpng -ljpeg -lwebp -lSimpleIniParser -lharfbuzz LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/libs/Aether $(CURDIR)/libs/json $(CURDIR)/libs/SimpleIniParser FORWARDER := $(ROMFS)/exefs.nsp @@ -42,14 +42,14 @@ FORWARDER := $(ROMFS)/exefs.nsp # Application version #--------------------------------------------------------------------------------- VER_MAJOR := 1 -VER_MINOR := 4 -VER_MICRO := 0 +VER_MINOR := 5 +VER_MICRO := 4 #--------------------------------------------------------------------------------- # Options for .nacp information #--------------------------------------------------------------------------------- APP_TITLE := NX Activity Log -APP_AUTHOR := tallbl0nde +APP_AUTHOR := tallbl0nde&zdm65477730 APP_VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_MICRO) ICON := icon.jpg @@ -72,7 +72,7 @@ OUTPUT := $(CURDIR)/$(TARGET) #--------------------------------------------------------------------------------- DEFINES := -D__SWITCH__ -DVER_MAJOR=$(VER_MAJOR) -DVER_MINOR=$(VER_MINOR) -DVER_MICRO=$(VER_MICRO) -DVER_STRING=\"$(VER_MAJOR).$(VER_MINOR).$(VER_MICRO)\" CFLAGS := -g -Wall -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) `freetype-config --cflags` `sdl2-config --cflags` -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++2a +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++23 #---------------------------------------------------------------------------------------------------------------------- # Definition of variables which store file locations diff --git a/Application/include/Types.hpp b/Application/include/Types.hpp index 7d8d313..0e94e58 100644 --- a/Application/include/Types.hpp +++ b/Application/include/Types.hpp @@ -19,6 +19,7 @@ enum Language { ChineseTraditional, Chinese, Korean, + Japanese, TotalLanguages // Total number of languages (only used for iterating) }; // Return string matching language diff --git a/Application/include/nx/PlayData.hpp b/Application/include/nx/PlayData.hpp index 226badb..b2102b6 100644 --- a/Application/include/nx/PlayData.hpp +++ b/Application/include/nx/PlayData.hpp @@ -24,19 +24,19 @@ namespace NX { // PlayEvents are parsed PdmPlayEvents containing only necessary information struct PlayEvent { - PlayEventType type; // Type of PlayEvent - AccountUid userID; // UserID - TitleID titleID; // TitleID - EventType eventType; // See EventType enum - u64 clockTimestamp; // Time of event - u64 steadyTimestamp; // Steady timestamp (used for calculating duration) + PlayEventType type; // Type of PlayEvent + AccountUid userID; // UserID + TitleID titleID; // TitleID + EventType eventType; // See EventType enum + u64 clockTimestamp; // Time of event + u64 steadyTimestamp; // Steady timestamp (used for calculating duration) }; // A PlaySession represents a session of play for a game. It contains the start // and end timestamps and playtime for convenience. Note that (end-start) != playtime // due to time when the game may not have been focussed. struct PlaySession { - u32 playtime; // Total playtime in seconds + u64 playtime; // Total playtime in seconds u64 startTimestamp; // Time of launch u64 endTimestamp; // Time of exit }; @@ -44,9 +44,9 @@ namespace NX { // PdmPlayStatistics but only the necessary things struct PlayStatistics { TitleID titleID; // TitleID of these stats - u32 firstPlayed; // Timestamp of first launch - u32 lastPlayed; // Timestamp of last play (exit) - u32 playtime; // Total playtime in seconds + u64 firstPlayed; // Timestamp of first launch + u64 lastPlayed; // Timestamp of last play (exit) + u64 playtime; // Total playtime in seconds u32 launches; // Total launches }; @@ -54,7 +54,7 @@ namespace NX { // only contains recent values struct RecentPlayStatistics { TitleID titleID; // TitleID of these statistics - u32 playtime; // Total playtime in seconds + u64 playtime; // Total playtime in seconds u32 launches; // Total launches }; @@ -118,10 +118,10 @@ namespace NX { // Returns all play sessions for the given title ID and user ID std::vector getPlaySessionsForUser(TitleID, AccountUid); - // Returns a RecentPlayStatistics for the given time range for all users + // Returns a RecentPlayStatistics for the given time range and user ID for all Title IDs RecentPlayStatistics * getRecentStatisticsForUser(u64, u64, AccountUid); - // Returns a RecentPlayStatistics for the given time range and user ID + // Returns a RecentPlayStatistics for the given time range, user ID and title ID RecentPlayStatistics * getRecentStatisticsForTitleAndUser(TitleID, u64, u64, AccountUid); // Returns a PlayStatistics for the given titleID and userID diff --git a/Application/include/ui/element/SortedList.hpp b/Application/include/ui/element/SortedList.hpp index 2ed18bf..8e74d45 100644 --- a/Application/include/ui/element/SortedList.hpp +++ b/Application/include/ui/element/SortedList.hpp @@ -8,11 +8,11 @@ // Struct storing information about entry used for sorting struct SortInfo { std::string name; // Name of title - unsigned long long int titleID; // Title's ID - unsigned int firstPlayed; // Timestamp of first launch - unsigned int lastPlayed; // Timestamp of last play - unsigned int playtime; // Total playtime in seconds - unsigned int launches; // Total launches + u64 titleID; // Title's ID + u64 firstPlayed; // Timestamp of first launch + u64 lastPlayed; // Timestamp of last play + u64 playtime; // Total playtime in seconds + u32 launches; // Total launches }; namespace CustomElm { diff --git a/Application/include/ui/screen/Details.hpp b/Application/include/ui/screen/Details.hpp index d0dc40c..8e9170c 100644 --- a/Application/include/ui/screen/Details.hpp +++ b/Application/include/ui/screen/Details.hpp @@ -18,10 +18,6 @@ namespace Screen { // Used to udpate prev. screen bool popped; - // Updates graph if data matching current time range - void updateGraph(); - // Updates the list of play sessions matching current time range - void updateSessions(); // Element which marks top of sessions Aether::Element * topElm; diff --git a/Application/include/ui/screen/RecentActivity.hpp b/Application/include/ui/screen/RecentActivity.hpp index 60001fd..3a98af4 100644 --- a/Application/include/ui/screen/RecentActivity.hpp +++ b/Application/include/ui/screen/RecentActivity.hpp @@ -17,10 +17,6 @@ namespace Screen { // Updates the "recent activity" part of the screen void updateActivity(); - // Updates graph if data matching current time range - void updateGraph(); - // Updates the list of games played underneath - void updateTitles(); // Element which marks top of sessions Aether::Element * topElm; diff --git a/Application/include/utils/Debug.hpp b/Application/include/utils/Debug.hpp new file mode 100644 index 0000000..8327101 --- /dev/null +++ b/Application/include/utils/Debug.hpp @@ -0,0 +1,17 @@ +#pragma once +#ifndef DEBUG_HPP +#define DEBUG_HPP + +#include +#include +#include + +//#define ENABLE_DEBUG +#define MAX_LOG_LEN 512 + +namespace Utils { + extern std::mutex mutex; + void write_log(const char *pszFmt, ...); +}; + +#endif diff --git a/Application/include/utils/Utils.hpp b/Application/include/utils/Utils.hpp index c4fb28c..5ae201c 100644 --- a/Application/include/utils/Utils.hpp +++ b/Application/include/utils/Utils.hpp @@ -27,20 +27,20 @@ namespace Utils { std::string insertVersionInString(std::string, std::string); // Format the given timestamp as 'last played' string - std::string lastPlayedToString(unsigned int); + std::string lastPlayedToString(uint64_t); // Format the given number of launches into a string std::string launchesToString(unsigned int); std::string launchesToPlayedString(unsigned int); // Format the given playtime (in seconds) into hours and minutes - std::string playtimeToString(unsigned int); + std::string playtimeToString(uint64_t); // Format the given playtime (in seconds) into 'played for' string - std::string playtimeToPlayedForString(unsigned int); + std::string playtimeToPlayedForString(uint64_t); // Format the given playtime (in seconds) into 'total playtime' string - std::string playtimeToTotalPlaytimeString(unsigned int); + std::string playtimeToTotalPlaytimeString(uint64_t); // Merges two vectors into one (for sorting) // Vector to merge into, two vectors to merge diff --git a/Application/libs/Aether b/Application/libs/Aether index d268de4..29c14b5 160000 --- a/Application/libs/Aether +++ b/Application/libs/Aether @@ -1 +1 @@ -Subproject commit d268de4b445ce4c01757b0793c709166d1f3727d +Subproject commit 29c14b5abf7dba7408ab5941a14ebb2f8396b54c diff --git a/Application/libs/SimpleIniParser b/Application/libs/SimpleIniParser index c28ea18..625d49f 160000 --- a/Application/libs/SimpleIniParser +++ b/Application/libs/SimpleIniParser @@ -1 +1 @@ -Subproject commit c28ea183a6eb7f4fe546cc0accf2a887576875db +Subproject commit 625d49fb533a7648d4996a7d292ffcd51fb2a61d diff --git a/Application/libs/json b/Application/libs/json index e7b3b40..6325839 160000 --- a/Application/libs/json +++ b/Application/libs/json @@ -1 +1 @@ -Subproject commit e7b3b40b5a95bc74b9a7f662830a27c49ffc01b4 +Subproject commit 63258397761b3dd96dd171e5a5ad5aa915834c35 diff --git a/Application/romfs/lang b/Application/romfs/lang index c8f0bff..4b80787 160000 --- a/Application/romfs/lang +++ b/Application/romfs/lang @@ -1 +1 @@ -Subproject commit c8f0bff402412cf192b7f1c8d9b836aadf0bb0d0 +Subproject commit 4b807874cd3d1ffb85513552baa873e9f18004d8 diff --git a/Application/source/Application.ExportJob.cpp b/Application/source/Application.ExportJob.cpp index 9ae171f..a48371a 100644 --- a/Application/source/Application.ExportJob.cpp +++ b/Application/source/Application.ExportJob.cpp @@ -38,7 +38,6 @@ namespace Main { // Check if played, and if not move onto next NX::RecentPlayStatistics * stats = this->app->playdata_->getRecentStatisticsForTitleAndUser(title->titleID(), std::numeric_limits::min(), std::numeric_limits::max(), user->ID()); bool recentLaunched = (stats->launches != 0); - delete stats; // Add title metadata tJson["name"] = title->name(); @@ -92,10 +91,11 @@ namespace Main { // Get all summary stats NX::PlayStatistics * stats2 = this->app->playdata_->getStatisticsForUser(title->titleID(), user->ID()); bool allLaunched = (stats2->launches != 0); - tJson["summary"]["firstPlayed"] = pdmPlayTimestampToPosix(stats2->firstPlayed); - tJson["summary"]["lastPlayed"] = pdmPlayTimestampToPosix(stats2->lastPlayed); - tJson["summary"]["playtime"] = stats2->playtime; - tJson["summary"]["launches"] = stats2->launches; + tJson["summary"]["firstPlayed"] = stats2->firstPlayed; + tJson["summary"]["lastPlayed"] = stats2->lastPlayed; + tJson["summary"]["playtime"] = stats->playtime; + tJson["summary"]["launches"] = stats->launches; + delete stats; delete stats2; // Append title if played at least once diff --git a/Application/source/Application.ImportJob.cpp b/Application/source/Application.ImportJob.cpp index 86e616e..01bd8f8 100644 --- a/Application/source/Application.ImportJob.cpp +++ b/Application/source/Application.ImportJob.cpp @@ -132,8 +132,8 @@ namespace Main { nlohmann::json s = title["summary"]; if (s["firstPlayed"] != nullptr && s["lastPlayed"] != nullptr && s["playtime"] != nullptr && s["launches"] != nullptr) { tJson["summary"] = nlohmann::json(); - tJson["summary"]["firstPlayed"] = Utils::Time::posixTimestampToPdm(s["firstPlayed"].get()); - tJson["summary"]["lastPlayed"] = Utils::Time::posixTimestampToPdm(s["lastPlayed"].get()); + tJson["summary"]["firstPlayed"] = s["firstPlayed"].get(); + tJson["summary"]["lastPlayed"] = s["lastPlayed"].get(); tJson["summary"]["playtime"] = s["playtime"]; tJson["summary"]["launches"] = s["launches"]; } diff --git a/Application/source/Config.cpp b/Application/source/Config.cpp index c3340b5..2fe5343 100644 --- a/Application/source/Config.cpp +++ b/Application/source/Config.cpp @@ -46,6 +46,8 @@ namespace Main { this->gLang_ = Chinese; } else if (option->value == "Korean") { this->gLang_ = Korean; + } else if (option->value == "Japanese") { + this->gLang_ = Japanese; } else { this->gLang_ = Default; } @@ -125,7 +127,10 @@ namespace Main { std::ifstream file("/config/NX-Activity-Log/hidden.conf"); std::string line; while (file >> line) { - this->hidden.push_back(Utils::stringToU64(line)); + uint64_t hiddenTitle = Utils::stringToU64(line); + if (hiddenTitle != 0) { + this->hidden.push_back(hiddenTitle); + } } // Read in adjustment values @@ -186,6 +191,8 @@ namespace Main { option->value = "Chinese"; } else if (this->gLang_ == Korean) { option->value = "Korean"; + } else if (this->gLang_ == Japanese) { + option->value = "Japanese"; } option = ini->findSection("general")->findFirstOption("showGraphValues"); diff --git a/Application/source/Types.cpp b/Application/source/Types.cpp index 091c6d7..72ea135 100644 --- a/Application/source/Types.cpp +++ b/Application/source/Types.cpp @@ -52,6 +52,10 @@ std::string toString(Language l) { case Language::Korean: str = "한국어"; break; + + case Language::Japanese: + str = "日本語"; + break; default: break; diff --git a/Application/source/nx/PlayData.cpp b/Application/source/nx/PlayData.cpp index 71e0595..1becee6 100644 --- a/Application/source/nx/PlayData.cpp +++ b/Application/source/nx/PlayData.cpp @@ -6,9 +6,10 @@ #include "nx/PlayData.hpp" #include "utils/NX.hpp" #include "utils/Time.hpp" +#include "utils/Debug.hpp" // Maximum number of entries to process in one iteration -#define MAX_PROCESS_ENTRIES 1000 +#define MAX_PROCESS_ENTRIES 4096 namespace NX { std::vector PlayData::getPDSessions(TitleID titleID, AccountUid userID, u64 start_ts, u64 end_ts) { @@ -186,23 +187,23 @@ namespace NX { PlayEvent * event; // Populate PlayEvent based on event type - switch (pEvents[i].playEventType) { + switch (pEvents[i].play_event_type) { case PdmPlayEventType_Account: // Ignore this event if type is 2 - if (pEvents[i].eventData.account.type == 2) { + if (pEvents[i].event_data.account.type == 2) { continue; } event = new PlayEvent; event->type = PlayEvent_Account; // UserID words are wrong way around (why Nintendo?) - event->userID.uid[0] = pEvents[i].eventData.account.uid[0]; - event->userID.uid[0] = (event->userID.uid[0] << 32) | pEvents[i].eventData.account.uid[1]; - event->userID.uid[1] = (event->userID.uid[1] << 32) | pEvents[i].eventData.account.uid[2]; - event->userID.uid[1] = (event->userID.uid[1] << 32) | pEvents[i].eventData.account.uid[3]; + event->userID.uid[0] = pEvents[i].event_data.account.uid[0]; + event->userID.uid[0] = (event->userID.uid[0] << 32) | pEvents[i].event_data.account.uid[1]; + event->userID.uid[1] = (event->userID.uid[1] << 32) | pEvents[i].event_data.account.uid[2]; + event->userID.uid[1] = (event->userID.uid[1] << 32) | pEvents[i].event_data.account.uid[3]; // Set account event type - switch (pEvents[i].eventData.account.type) { + switch (pEvents[i].event_data.account.type) { case 0: event->eventType = Account_Active; break; @@ -214,18 +215,18 @@ namespace NX { case PdmPlayEventType_Applet: // Ignore this event based on log policy - if (pEvents[i].eventData.applet.logPolicy != PdmPlayLogPolicy_All) { + if (pEvents[i].event_data.applet.log_policy != PdmPlayLogPolicy_All) { continue; } event = new PlayEvent; event->type = PlayEvent_Applet; // Join two halves of title ID - event->titleID = pEvents[i].eventData.applet.program_id[0]; - event->titleID = (event->titleID << 32) | pEvents[i].eventData.applet.program_id[1]; + event->titleID = pEvents[i].event_data.applet.program_id[0]; + event->titleID = (event->titleID << 32) | pEvents[i].event_data.applet.program_id[1]; // Set applet event type - switch (pEvents[i].eventData.applet.eventType) { + switch (pEvents[i].event_data.applet.event_type) { case PdmAppletEventType_Launch: event->eventType = Applet_Launch; break; @@ -243,7 +244,6 @@ namespace NX { break; } break; - // Do nothing for other event types default: continue; @@ -251,8 +251,8 @@ namespace NX { } // Set timestamps - event->clockTimestamp = pEvents[i].timestampUser; - event->steadyTimestamp = pEvents[i].timestampSteady; + event->clockTimestamp = pEvents[i].timestamp_user; + event->steadyTimestamp = pEvents[i].timestamp_steady; // Add PlayEvent to vector ret.first.push_back(event); @@ -263,6 +263,16 @@ namespace NX { // Free memory allocated to array delete[] pEvents; +#ifdef ENABLE_DEBUG + for (auto event : ret.first) { + if (event->userID.uid[1] == 0 && event->userID.uid[0] == 0) { + Utils::write_log("offset=%d, event: type=%u titleID=%016llx applet_type: %u clockTimestamp=%llu steadyTimestamp=%llu", offset, event->type, event->titleID, event->eventType, event->clockTimestamp, event->steadyTimestamp); + } else { + Utils::write_log("offset=%d, event: type=%u userID=%016llx_%016llx account_type: %u clockTimestamp=%llu steadyTimestamp=%llu", offset, event->type, event->userID.uid[1], event->userID.uid[0], event->eventType, event->clockTimestamp, event->steadyTimestamp); + } + } +#endif + return ret; } @@ -299,7 +309,7 @@ namespace NX { if (event["clockTimestamp"] != nullptr && event["steadyTimestamp"] != nullptr && event["type"] != nullptr) { EventType type = static_cast(event["type"]); - PlayEvent * evt = new PlayEvent; + PlayEvent *evt = new PlayEvent; evt->type = (type == Account_Active || type == Account_Inactive ? PlayEvent_Account : PlayEvent_Applet); evt->userID = {user["id"][0], user["id"][1]}; evt->titleID = title["id"]; @@ -331,16 +341,12 @@ namespace NX { // Store data if entry added if (hasEntry) { - std::vector>::iterator it = std::find_if(this->titles.begin(), this->titles.end(), [title](std::pair entry) { - return (title["id"] == entry.first); - }); - if (it == this->titles.end()) { + if (std::find_if(this->titles.begin(), this->titles.end(), [title](std::pair entry) { return (title["id"] == entry.first && title["id"] != 0); }) == this->titles.end()) { this->titles.push_back(std::make_pair(title["id"], title["name"])); } } } } - return ret; } @@ -488,10 +494,19 @@ namespace NX { PdmPlayStatistics tmp; pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(titleID, userID, false, &tmp); PlayStatistics * stats = new PlayStatistics; - stats->firstPlayed = tmp.first_timestampUser; - stats->lastPlayed = tmp.last_timestampUser; - stats->playtime = tmp.playtimeMinutes * 60; - stats->launches = tmp.totalLaunches; + if (tmp.first_timestamp_user != 0 && tmp.last_timestamp_user != 0) { + stats->firstPlayed = tmp.first_timestamp_user; + stats->lastPlayed = tmp.last_timestamp_user; + } else { + auto it = std::find_if(this->summaries.begin(), this->summaries.end(), [titleID](auto s){ return (s->titleID == titleID); }); + if (it != this->summaries.end()) { + stats->firstPlayed = (*it)->firstPlayed; + stats->lastPlayed = (*it)->lastPlayed; + } + } + + stats->playtime = tmp.playtime / 1000 / 1000 / 1000; //the unit of playtime in PdmPlayStatistics is ns + stats->launches = tmp.total_launches; return stats; } diff --git a/Application/source/ui/screen/AdjustPlaytime.cpp b/Application/source/ui/screen/AdjustPlaytime.cpp index 3de85e9..d0f8f43 100644 --- a/Application/source/ui/screen/AdjustPlaytime.cpp +++ b/Application/source/ui/screen/AdjustPlaytime.cpp @@ -117,7 +117,8 @@ namespace Screen { } size_t idx = std::distance(this->adjustments.begin(), it); - NX::PlayStatistics * stats = this->app->playdata()->getStatisticsForUser(title->titleID(), this->app->activeUser()->ID()); + NX::RecentPlayStatistics * stats = this->app->playdata()->getRecentStatisticsForTitleAndUser(title->titleID(), std::numeric_limits::min(), std::numeric_limits::max(), this->app->activeUser()->ID()); + //NX::PlayStatistics * stats = this->app->playdata()->getStatisticsForUser(title->titleID(), this->app->activeUser()->ID()); CustomElm::ListAdjust * l = new CustomElm::ListAdjust(title->name(), Utils::playtimeToPlayedForString(stats->playtime), this->getValueString(this->adjustments[idx].value)); delete stats; diff --git a/Application/source/ui/screen/AllActivity.cpp b/Application/source/ui/screen/AllActivity.cpp index cfd418f..896a167 100644 --- a/Application/source/ui/screen/AllActivity.cpp +++ b/Application/source/ui/screen/AllActivity.cpp @@ -121,7 +121,7 @@ namespace Screen { std::vector adjustments = this->app->config()->adjustmentValues(); std::vector t = this->app->titleVector(); std::vector hidden = this->app->config()->hiddenTitles(); - unsigned int totalSecs = 0; + uint64_t totalSecs = 0; for (size_t i = 0; i < t.size(); i++) { // Skip over hidden games if (std::find(hidden.begin(), hidden.end(), t[i]->titleID()) != hidden.end()) { @@ -129,7 +129,8 @@ namespace Screen { } // Get statistics and append adjustment if needed - NX::PlayStatistics * ps = this->app->playdata()->getStatisticsForUser(t[i]->titleID(), this->app->activeUser()->ID()); + NX::RecentPlayStatistics *ps = this->app->playdata()->getRecentStatisticsForTitleAndUser(t[i]->titleID(), std::numeric_limits::min(), std::numeric_limits::max(), this->app->activeUser()->ID()); + NX::PlayStatistics *ps2 = this->app->playdata()->getStatisticsForUser(t[i]->titleID(), this->app->activeUser()->ID()); std::vector::iterator it = std::find_if(adjustments.begin(), adjustments.end(), [this, t, i](AdjustmentValue val) { return (val.titleID == t[i]->titleID() && val.userID == this->app->activeUser()->ID()); }); @@ -141,12 +142,13 @@ namespace Screen { totalSecs += ps->playtime; if (ps->launches == 0) { // Add in dummy data if not launched before (due to adjustment) - ps->firstPlayed = Utils::Time::posixTimestampToPdm(Utils::Time::getTimeT(Utils::Time::getTmForCurrentTime())); - ps->lastPlayed = ps->firstPlayed; + ps2->firstPlayed = Utils::Time::getTimeT(Utils::Time::getTmForCurrentTime()); + ps2->lastPlayed = ps2->firstPlayed; ps->launches = 1; if (ps->playtime == 0) { delete ps; + delete ps2; continue; } } @@ -155,8 +157,8 @@ namespace Screen { SortInfo * si = new SortInfo; si->name = t[i]->name(); si->titleID = t[i]->titleID(); - si->firstPlayed = ps->firstPlayed; - si->lastPlayed = ps->lastPlayed; + si->firstPlayed = ps2->firstPlayed; + si->lastPlayed = ps2->lastPlayed; si->playtime = ps->playtime; si->launches = ps->launches; @@ -165,7 +167,7 @@ namespace Screen { la->setImage(t[i]->imgPtr(), t[i]->imgSize()); la->setTitle(t[i]->name()); la->setPlaytime(Utils::playtimeToPlayedForString(ps->playtime)); - la->setLeftMuted(Utils::lastPlayedToString(pdmPlayTimestampToPosix(ps->lastPlayed))); + la->setLeftMuted(Utils::lastPlayedToString(ps2->lastPlayed)); la->setRightMuted(Utils::launchesToPlayedString(ps->launches)); la->onPress([this, i](){ this->app->setActiveTitle(i); @@ -177,6 +179,9 @@ namespace Screen { la->setMutedColour(this->app->theme()->mutedText()); la->setLineColour(this->app->theme()->mutedLine()); this->list->addElement(la, si); + + delete ps; + delete ps2; } // Sort the list diff --git a/Application/source/ui/screen/Details.cpp b/Application/source/ui/screen/Details.cpp index 593a489..80b6996 100644 --- a/Application/source/ui/screen/Details.cpp +++ b/Application/source/ui/screen/Details.cpp @@ -3,6 +3,7 @@ #include "utils/Lang.hpp" #include "ui/element/ListSession.hpp" #include "utils/Utils.hpp" +#include "utils/Time.hpp" // Values for summary appearance #define SUMMARY_BOX_HEIGHT 60 @@ -96,32 +97,36 @@ namespace Screen { void Details::updateActivity() { // Check if there is any activity + update heading - struct tm t = this->app->time(); - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_min = 59; - e.tm_sec = 59; + struct tm begin = this->app->time(); + struct tm t = begin; + uint64_t totalSecs = 0; + + struct tm tm = begin; + tm.tm_min = 0; + tm.tm_sec = 0; + struct tm em = tm; + em.tm_min = 59; + em.tm_sec = 59; switch (this->app->viewPeriod()) { case ViewPeriod::Day: - e.tm_hour = 23; + em.tm_hour = 23; break; case ViewPeriod::Month: - e.tm_mday = Utils::Time::tmGetDaysInMonth(t); + em.tm_mday = Utils::Time::tmGetDaysInMonth(tm); break; case ViewPeriod::Year: - e.tm_mon = 11; - e.tm_mday = Utils::Time::tmGetDaysInMonth(t); + em.tm_mon = 11; + em.tm_mday = Utils::Time::tmGetDaysInMonth(tm); break; default: break; } - this->graphHeading->setString(Utils::Time::dateToActivityForString(t, this->app->viewPeriod())); + this->graphHeading->setString(Utils::Time::dateToActivityForString(tm, this->app->viewPeriod())); this->graphHeading->setX(this->header->x() + (this->header->w() - this->graphHeading->w())/2); - NX::RecentPlayStatistics * ps = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + NX::RecentPlayStatistics * ps = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(tm), Utils::Time::getTimeT(em), this->app->activeUser()->ID()); // Remove current sessions regardless this->list->removeElementsAfter(this->topElm); @@ -136,323 +141,286 @@ namespace Screen { this->list->setCanScroll(true); this->playHeading->setHidden(false); this->noStats->setHidden(true); - this->updateGraph(); - this->updateSessions(); - } else { - this->graph->setHidden(true); - this->graphSubheading->setHidden(true); - this->graphTotal->setHidden(true); - this->list->setShowScrollBar(false); - this->list->setCanScroll(false); - this->playHeading->setHidden(true); - this->noStats->setHidden(false); - } - - delete ps; - } - void Details::update(uint32_t dt) { - // Don't check!! - if (!this->popped) { - if (this->app->timeChanged()) { - this->updateActivity(); + // update Graph + // Setup graph columns + labels + for (unsigned int i = 0; i < this->graph->entries(); i++) { + this->graph->setLabel(i, ""); } - } - Screen::update(dt); - } - - void Details::updateGraph() { - // Setup graph columns + labels - for (unsigned int i = 0; i < this->graph->entries(); i++) { - this->graph->setLabel(i, ""); - } - struct tm tm = this->app->time(); - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: - this->graph->setFontSize(14); - this->graph->setMaximumValue(60); - this->graph->setYSteps(6); - this->graph->setValuePrecision(0); - this->graph->setNumberOfEntries(24); - if (this->app->config()->gIs24H()) { - for (int i = 0; i < 24; i += 2) { - this->graph->setLabel(i, std::to_string(i)); + t.tm_min = 0; + t.tm_sec = 0; + struct tm e = t; + e.tm_min = 59; + e.tm_sec = 59; + // Read playtime and set graph values + switch (this->app->viewPeriod()) { + case ViewPeriod::Day: { + this->graph->setFontSize(14); + this->graph->setMaximumValue(60); + this->graph->setYSteps(6); + this->graph->setValuePrecision(0); + this->graph->setNumberOfEntries(24); + if (this->app->config()->gIs24H()) { + for (int i = 0; i < 24; i += 2) { + this->graph->setLabel(i, std::to_string(i)); + } + } else { + for (int i = 0; i < 24; i += 3) { + this->graph->setLabel(i, Utils::format12H(i)); + } } - } else { - for (int i = 0; i < 24; i += 3) { - this->graph->setLabel(i, Utils::format12H(i)); + for (size_t i = 0; i < this->graph->entries(); i++) { + t.tm_hour = i; + e.tm_hour = i; + NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + totalSecs += s->playtime; + double val = s->playtime/60.0; + this->graph->setValue(i, val); + delete s; } + this->graphSubheading->setString("common.playtimeMinutes"_lang); + break; } - break; - - case ViewPeriod::Month: { - unsigned int c = Utils::Time::tmGetDaysInMonth(tm); - this->graph->setFontSize(13); - this->graph->setValuePrecision(1); - this->graph->setNumberOfEntries(c); - for (unsigned int i = 0; i < c; i+=3) { - this->graph->setLabel(i, std::to_string(i + 1) + Utils::Time::getDateSuffix(i + 1)); - } - break; - } - - case ViewPeriod::Year: - this->graph->setFontSize(16); - this->graph->setValuePrecision(1); - this->graph->setNumberOfEntries(12); - for (int i = 0; i < 12; i++) { - this->graph->setLabel(i, Utils::Time::getShortMonthString(i)); - } - break; - - default: - break; - } - - // Read playtime and set graph values - struct tm t = tm; - unsigned int totalSecs = 0; - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: { - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_min = 59; - e.tm_sec = 59; - for (size_t i = 0; i < this->graph->entries(); i++) { - t.tm_hour = i; - e.tm_hour = i; - NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); - totalSecs += s->playtime; - double val = s->playtime/60.0; - this->graph->setValue(i, val); - delete s; - } - break; - } - - case ViewPeriod::Month: { - t.tm_hour = 0; - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_hour = 23; - e.tm_min = 59; - e.tm_sec = 59; - unsigned int max = 0; - for (size_t i = 0; i < this->graph->entries(); i++) { - t.tm_mday = i + 1; - e.tm_mday = i + 1; - NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); - totalSecs += s->playtime; - if (s->playtime > max) { - max = s->playtime; + case ViewPeriod::Month: { + unsigned int c = Utils::Time::tmGetDaysInMonth(tm); + this->graph->setFontSize(13); + this->graph->setValuePrecision(1); + this->graph->setNumberOfEntries(c); + for (unsigned int i = 0; i < c; i+=3) { + this->graph->setLabel(i, std::to_string(i + 1) + Utils::Time::getDateSuffix(i + 1)); } - double val = s->playtime/60/60.0; - this->graph->setValue(i, val); - delete s; - } - max /= 60.0; - max /= 60.0; - if (max <= 2) { - this->graph->setMaximumValue(max + 2 - max%2); - this->graph->setYSteps(2); - } else { - this->graph->setMaximumValue(max + 5 - max%5); - this->graph->setYSteps(5); - } - break; - } - - case ViewPeriod::Year: { - t.tm_mday = 1; - t.tm_hour = 0; - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_hour = 23; - e.tm_min = 59; - e.tm_sec = 59; - unsigned int max = 0; - for (size_t i = 0; i < this->graph->entries(); i++) { - t.tm_mon = i; - e.tm_mon = i; - e.tm_mday = Utils::Time::tmGetDaysInMonth(t); - NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); - totalSecs += s->playtime; - if (s->playtime > max) { - max = s->playtime; + t.tm_hour = 0; + e = t; + e.tm_hour = 23; + uint64_t max = 0; + for (size_t i = 0; i < this->graph->entries(); i++) { + t.tm_mday = i + 1; + e.tm_mday = i + 1; + NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + totalSecs += s->playtime; + if (s->playtime > max) { + max = s->playtime; + } + double val = s->playtime/60/60.0; + this->graph->setValue(i, val); + delete s; + } + max /= 60.0; + max /= 60.0; + if (max <= 2) { + this->graph->setMaximumValue(max + 2 - max%2); + this->graph->setYSteps(2); + } else { + this->graph->setMaximumValue(max + 5 - max%5); + this->graph->setYSteps(5); } - double val = s->playtime/60/60.0; - this->graph->setValue(i, val); - delete s; + this->graphSubheading->setString("common.playtimeHours"_lang); + break; } - max /= 60.0; - max /= 60.0; - if (max <= 2) { - this->graph->setMaximumValue(max + 2 - max%2); - this->graph->setYSteps(2); - } else { - this->graph->setMaximumValue(max + 5 - max%5); - this->graph->setYSteps(5); + case ViewPeriod::Year: { + this->graph->setFontSize(16); + this->graph->setValuePrecision(1); + this->graph->setNumberOfEntries(12); + for (int i = 0; i < 12; i++) { + this->graph->setLabel(i, Utils::Time::getShortMonthString(i)); + } + t.tm_mday = 1; + t.tm_hour = 0; + e = t; + e.tm_hour = 23; + uint64_t max = 0; + for (size_t i = 0; i < this->graph->entries(); i++) { + t.tm_mon = i; + e.tm_mon = i; + e.tm_mday = Utils::Time::tmGetDaysInMonth(t); + NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + totalSecs += s->playtime; + if (s->playtime > max) { + max = s->playtime; + } + double val = s->playtime/60/60.0; + this->graph->setValue(i, val); + delete s; + } + max /= 60.0; + max /= 60.0; + if (max <= 2) { + this->graph->setMaximumValue(max + 2 - max%2); + this->graph->setYSteps(2); + } else { + this->graph->setMaximumValue(max + 5 - max%5); + this->graph->setYSteps(5); + } + this->graphSubheading->setString("common.playtimeHours"_lang); + break; } - break; + default: + break; } - default: - break; - } - - // Set headings etc... - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: - this->graphSubheading->setString("common.playtimeMinutes"_lang); - break; - - case ViewPeriod::Month: - case ViewPeriod::Year: - this->graphSubheading->setString("common.playtimeHours"_lang); - break; - - default: - break; - } - - this->graphSubheading->setX(this->header->x() + (this->header->w() - this->graphSubheading->w())/2); - this->graphTotalSub->setString(Utils::playtimeToString(totalSecs)); - int w = this->graphTotalHead->w() + this->graphTotalSub->w(); - this->graphTotalHead->setX(this->graphTotal->x()); - this->graphTotalSub->setX(this->graphTotalHead->x() + this->graphTotalHead->w()); - this->graphTotalHead->setX(this->graphTotalHead->x() + (this->graphTotal->w() - w)/2); - this->graphTotalSub->setX(this->graphTotalSub->x() + (this->graphTotal->w() - w)/2); - } - - void Details::updateSessions() { - // Get relevant play stats - NX::PlayStatistics * ps = this->app->playdata()->getStatisticsForUser(this->app->activeTitle()->titleID(), this->app->activeUser()->ID()); - std::vector stats = this->app->playdata()->getPlaySessionsForUser(this->app->activeTitle()->titleID(), this->app->activeUser()->ID()); - - // Get start and end timestamps for date - char c = ' '; - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: - c = 'D'; - break; - - case ViewPeriod::Month: - c = 'M'; - break; - - case ViewPeriod::Year: - c = 'Y'; - break; - - default: - break; - } - unsigned int s = Utils::Time::getTimeT(this->app->time()); - // Minus one second so end time is 11:59pm and not 12:00am next day - unsigned int e = Utils::Time::getTimeT(Utils::Time::increaseTm(this->app->time(), c)) - 1; - - // Add sessions to list - for (size_t i = 0; i < stats.size(); i++) { - // Only add session if start or end is within the current time period - if (stats[i].startTimestamp > e || stats[i].endTimestamp < s) { - continue; + this->graphSubheading->setX(this->header->x() + (this->header->w() - this->graphSubheading->w())/2); + this->graphTotalSub->setString(Utils::playtimeToString(totalSecs)); + int w = this->graphTotalHead->w() + this->graphTotalSub->w(); + this->graphTotalHead->setX(this->graphTotal->x()); + this->graphTotalSub->setX(this->graphTotalHead->x() + this->graphTotalHead->w()); + this->graphTotalHead->setX(this->graphTotalHead->x() + (this->graphTotal->w() - w)/2); + this->graphTotalSub->setX(this->graphTotalSub->x() + (this->graphTotal->w() - w)/2); + + // update sessions + // Get relevant play stats + NX::RecentPlayStatistics *pss = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), std::numeric_limits::min(), std::numeric_limits::max(), this->app->activeUser()->ID()); + std::vector stats = this->app->playdata()->getPlaySessionsForUser(this->app->activeTitle()->titleID(), this->app->activeUser()->ID()); + + t = begin; + t.tm_min = 0; + t.tm_sec = 0; + // Minus one second so end time is 11:59pm and not 12:00am next day + time_t start_time = 0; + time_t end_time = 0; + switch (this->app->viewPeriod()) { + case ViewPeriod::Day: + t.tm_hour = 0; + start_time = Utils::Time::getTimeT(t); + end_time = Utils::Time::getTimeT(Utils::Time::increaseTm(t, 'D')) - 1; + break; + case ViewPeriod::Month: + t.tm_mday = 1; + t.tm_hour = 0; + start_time = Utils::Time::getTimeT(t); + end_time = Utils::Time::getTimeT(Utils::Time::increaseTm(t, 'M')) - 1; + break; + case ViewPeriod::Year: + t.tm_mon = 0; + t.tm_mday = 1; + t.tm_hour = 0; + start_time = Utils::Time::getTimeT(t); + end_time = Utils::Time::getTimeT(Utils::Time::increaseTm(t, 'Y')) - 1; + break; + default: + break; } - // Create element - CustomElm::ListSession * ls = new CustomElm::ListSession(); - NX::PlaySession ses = stats[i]; - ls->onPress([this, ses](){ - this->setupSessionBreakdown(ses); - }); - - // Defaults for a session within the range - unsigned int playtime = stats[i].playtime; - struct tm sTm = Utils::Time::getTm(stats[i].startTimestamp); - struct tm eTm = Utils::Time::getTm(stats[i].endTimestamp); - - // If started before range set start as start of range - bool outRange = false; - if (stats[i].startTimestamp < s) { - outRange = true; - sTm = Utils::Time::getTm(s); - } + // Add sessions to list + for (size_t i = 0; i < stats.size(); i++) { + // Only add session if start or end is within the current time period + if (stats[i].startTimestamp > static_cast(end_time) || stats[i].endTimestamp < static_cast(start_time)) { + continue; + } - // If finished after range set end as end of range - if (stats[i].endTimestamp > e) { - outRange = true; - eTm = Utils::Time::getTm(e); - } + // Create element + CustomElm::ListSession * ls = new CustomElm::ListSession(); + NX::PlaySession ses = stats[i]; + ls->onPress([this, ses](){ + this->setupSessionBreakdown(ses); + }); + + // Defaults for a session within the range + uint64_t playtime = stats[i].playtime; + struct tm sTm = Utils::Time::getTm(stats[i].startTimestamp); + struct tm eTm = Utils::Time::getTm(stats[i].endTimestamp); + + // If started before range set start as start of range + bool outRange = false; + if (stats[i].startTimestamp < static_cast(start_time)) { + outRange = true; + sTm = Utils::Time::getTm(start_time); + } - // Get playtime if range is altered - if (outRange) { - NX::RecentPlayStatistics * rps = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(sTm), Utils::Time::getTimeT(eTm), this->app->activeUser()->ID()); - playtime = rps->playtime; - delete rps; - } + // If finished after range set end as end of range + if (stats[i].endTimestamp > static_cast(end_time)) { + outRange = true; + eTm = Utils::Time::getTm(end_time); + } - // Set timestamp string - std::string first = ""; - std::string last = ""; - struct tm nTm = Utils::Time::getTmForCurrentTime(); - switch (this->app->viewPeriod()) { - case ViewPeriod::Year: - case ViewPeriod::Month: - first = Utils::Time::tmToDate(sTm, sTm.tm_year != nTm.tm_year) + " "; - // Show date on end timestamp if not the same - if (sTm.tm_mday != eTm.tm_mday) { - last = Utils::Time::tmToDate(eTm, eTm.tm_year != nTm.tm_year) + " "; - } + // Get playtime if range is altered + if (outRange) { + NX::RecentPlayStatistics * rps = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), Utils::Time::getTimeT(sTm), Utils::Time::getTimeT(eTm), this->app->activeUser()->ID()); + playtime = rps->playtime; + delete rps; + } - case ViewPeriod::Day: - if (this->app->config()->gIs24H()) { - first += Utils::Time::tmToString(sTm, "%R", 5); - last += Utils::Time::tmToString(eTm, "%R", 5); - } else { - std::string tmp = Utils::Time::tmToString(sTm, "%I:%M", 5); - if (tmp[0] == '0') { - tmp.erase(0, 1); + // Set timestamp string + std::string first = ""; + std::string last = ""; + struct tm nTm = Utils::Time::getTmForCurrentTime(); + switch (this->app->viewPeriod()) { + case ViewPeriod::Year: + case ViewPeriod::Month: + first = Utils::Time::tmToDate(sTm, sTm.tm_year != nTm.tm_year) + " "; + // Show date on end timestamp if not the same + if (sTm.tm_mday != eTm.tm_mday) { + last = Utils::Time::tmToDate(eTm, eTm.tm_year != nTm.tm_year) + " "; } - first += tmp + Utils::Time::getAMPM(sTm.tm_hour); - tmp = Utils::Time::tmToString(eTm, "%I:%M", 5); - if (tmp[0] == '0') { - tmp.erase(0, 1); + case ViewPeriod::Day: + if (this->app->config()->gIs24H()) { + first += Utils::Time::tmToString(sTm, "%R", 5); + last += Utils::Time::tmToString(eTm, "%R", 5); + } else { + std::string tmp = Utils::Time::tmToString(sTm, "%I:%M", 5); + if (tmp[0] == '0') { + tmp.erase(0, 1); + } + first += tmp + Utils::Time::getAMPM(sTm.tm_hour); + + tmp = Utils::Time::tmToString(eTm, "%I:%M", 5); + if (tmp[0] == '0') { + tmp.erase(0, 1); + } + last += tmp + Utils::Time::getAMPM(eTm.tm_hour); } - last += tmp + Utils::Time::getAMPM(eTm.tm_hour); - } - break; + break; - default: - break; - } - ls->setTimeString(first + " - " + last + (outRange ? "*" : "")); - ls->setPlaytimeString(Utils::playtimeToString(playtime)); + default: + break; + } + ls->setTimeString(first + " - " + last + (outRange ? "*" : "")); + ls->setPlaytimeString(Utils::playtimeToString(playtime)); + + // Add percentage of total playtime + std::string str; + double percent = 100 * ((double)playtime / ((pss->playtime == 0 || pss->playtime < playtime) ? playtime : ps->playtime)); + percent = Utils::roundToDecimalPlace(percent, 2); + if (percent < 0.01) { + str = "< 0.01%"; + } else { + str = Utils::truncateToDecimalPlace(std::to_string(percent), 2) + "%"; + } + ls->setPercentageString(str); - // Add percentage of total playtime - std::string str; - double percent = 100 * ((double)playtime / ((ps->playtime == 0) ? playtime : ps->playtime)); - percent = Utils::roundToDecimalPlace(percent, 2); - if (percent < 0.01) { - str = "< 0.01%"; - } else { - str = Utils::truncateToDecimalPlace(std::to_string(percent), 2) + "%"; + ls->setLineColour(this->app->theme()->mutedLine()); + ls->setPercentageColour(this->app->theme()->mutedText()); + ls->setPlaytimeColour(this->app->theme()->accent()); + ls->setTimeColour(this->app->theme()->text()); + this->list->addElement(ls); } - ls->setPercentageString(str); - ls->setLineColour(this->app->theme()->mutedLine()); - ls->setPercentageColour(this->app->theme()->mutedText()); - ls->setPlaytimeColour(this->app->theme()->accent()); - ls->setTimeColour(this->app->theme()->text()); - this->list->addElement(ls); + delete pss; + } else { + this->graph->setHidden(true); + this->graphSubheading->setHidden(true); + this->graphTotal->setHidden(true); + this->list->setShowScrollBar(false); + this->list->setCanScroll(false); + this->playHeading->setHidden(true); + this->noStats->setHidden(false); } delete ps; } + void Details::update(uint32_t dt) { + // Don't check!! + if (!this->popped) { + if (this->app->timeChanged()) { + this->updateActivity(); + } + } + + Screen::update(dt); + } + void Details::setupSessionHelp() { this->msgbox->emptyBody(); @@ -676,7 +644,8 @@ namespace Screen { // Get statistics and append adjustment if needed std::vector adjustments = this->app->config()->adjustmentValues(); - NX::PlayStatistics * ps = this->app->playdata()->getStatisticsForUser(this->app->activeTitle()->titleID(), this->app->activeUser()->ID()); + NX::PlayStatistics * pss = this->app->playdata()->getStatisticsForUser(this->app->activeTitle()->titleID(), this->app->activeUser()->ID()); + NX::RecentPlayStatistics *ps = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->activeTitle()->titleID(), std::numeric_limits::min(), std::numeric_limits::max(), this->app->activeUser()->ID()); std::vector::iterator it = std::find_if(adjustments.begin(), adjustments.end(), [this](AdjustmentValue val) { return (val.titleID == this->app->activeTitle()->titleID() && val.userID == this->app->activeUser()->ID()); }); @@ -686,8 +655,8 @@ namespace Screen { if (ps->launches == 0) { // Add in dummy data if not launched before (due to adjustment) - ps->firstPlayed = Utils::Time::posixTimestampToPdm(Utils::Time::getTimeT(Utils::Time::getTmForCurrentTime())); - ps->lastPlayed = ps->firstPlayed; + pss->firstPlayed = Utils::Time::getTimeT(Utils::Time::getTmForCurrentTime()); + pss->lastPlayed = pss->firstPlayed; ps->launches = 1; } @@ -706,15 +675,16 @@ namespace Screen { this->timeplayed->setX(this->timeplayed->x() - this->timeplayed->w()/2); this->addElement(this->timeplayed); - this->firstplayed = new Aether::Text(1070, 490, Utils::Time::timestampToString(pdmPlayTimestampToPosix(ps->firstPlayed)), 20); + this->firstplayed = new Aether::Text(1070, 490, Utils::Time::timestampToString(pss->firstPlayed), 20); this->firstplayed->setColour(this->app->theme()->accent()); this->firstplayed->setX(this->firstplayed->x() - this->firstplayed->w()/2); this->addElement(this->firstplayed); - this->lastplayed = new Aether::Text(1070, 580, Utils::Time::timestampToString(pdmPlayTimestampToPosix(ps->lastPlayed)), 20); + this->lastplayed = new Aether::Text(1070, 580, Utils::Time::timestampToString(pss->lastPlayed), 20); this->lastplayed->setColour(this->app->theme()->accent()); this->lastplayed->setX(this->lastplayed->x() - this->lastplayed->w()/2); this->addElement(this->lastplayed); + delete pss; delete ps; // Show update icon if needbe diff --git a/Application/source/ui/screen/RecentActivity.cpp b/Application/source/ui/screen/RecentActivity.cpp index a09fe39..e16dca3 100644 --- a/Application/source/ui/screen/RecentActivity.cpp +++ b/Application/source/ui/screen/RecentActivity.cpp @@ -68,32 +68,36 @@ namespace Screen { void RecentActivity::updateActivity() { // Check if there is any activity + update heading - struct tm t = this->app->time(); - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_min = 59; - e.tm_sec = 59; + struct tm begin = this->app->time(); + struct tm t = begin; + uint64_t totalSecs = 0; + + struct tm tm = begin; + tm.tm_min = 0; + tm.tm_sec = 0; + struct tm em = tm; + em.tm_min = 59; + em.tm_sec = 59; switch (this->app->viewPeriod()) { case ViewPeriod::Day: - e.tm_hour = 23; + em.tm_hour = 23; break; case ViewPeriod::Month: - e.tm_mday = Utils::Time::tmGetDaysInMonth(t); + em.tm_mday = Utils::Time::tmGetDaysInMonth(tm); break; case ViewPeriod::Year: - e.tm_mon = 11; - e.tm_mday = Utils::Time::tmGetDaysInMonth(t); + em.tm_mon = 11; + em.tm_mday = Utils::Time::tmGetDaysInMonth(tm); break; default: break; } - this->graphHeading->setString(Utils::Time::dateToActivityForString(t, this->app->viewPeriod())); + this->graphHeading->setString(Utils::Time::dateToActivityForString(tm, this->app->viewPeriod())); this->graphHeading->setX(this->header->x() + (this->header->w() - this->graphHeading->w())/2); - NX::RecentPlayStatistics * ps = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + NX::RecentPlayStatistics *ps = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(tm), Utils::Time::getTimeT(em), this->app->activeUser()->ID()); // Remove current sessions regardless this->list->removeElementsAfter(this->topElm); @@ -109,235 +113,186 @@ namespace Screen { this->list->setShowScrollBar(true); this->list->setCanScroll(true); this->noStats->setHidden(true); - this->updateGraph(); - this->updateTitles(); - } else { - this->gameHeading->setHidden(true); - this->graph->setHidden(true); - this->graphSubheading->setHidden(true); - this->hours->setString("common.totalPlaytime.0min"_lang); - this->hours->setX(1215 - this->hours->w()); - this->list->setShowScrollBar(false); - this->list->setCanScroll(false); - this->noStats->setHidden(false); - } - - delete ps; - } - - void RecentActivity::update(uint32_t dt) { - if (this->app->timeChanged()) { - this->updateActivity(); - } - Screen::update(dt); - } - void RecentActivity::updateGraph() { - // Setup graph columns + labels - for (unsigned int i = 0; i < this->graph->entries(); i++) { - this->graph->setLabel(i, ""); - } - struct tm tm = this->app->time(); - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: - this->graph->setFontSize(14); - this->graph->setMaximumValue(60); - this->graph->setYSteps(6); - this->graph->setValuePrecision(0); - this->graph->setNumberOfEntries(24); - if (this->app->config()->gIs24H()) { - for (int i = 0; i < 24; i += 2) { - this->graph->setLabel(i, std::to_string(i)); + // update Graph + // Setup graph columns + labels + for (unsigned int i = 0; i < this->graph->entries(); i++) + this->graph->setLabel(i, ""); + + t.tm_min = 0; + t.tm_sec = 0; + struct tm e = t; + e.tm_min = 59; + e.tm_sec = 59; + // Read playtime and set graph values + switch (this->app->viewPeriod()) { + case ViewPeriod::Day: { + this->graph->setFontSize(14); + this->graph->setMaximumValue(60); + this->graph->setYSteps(6); + this->graph->setValuePrecision(0); + this->graph->setNumberOfEntries(24); + if (this->app->config()->gIs24H()) { + for (int i = 0; i < 24; i += 2) { + this->graph->setLabel(i, std::to_string(i)); + } + } else { + for (int i = 0; i < 24; i += 3) { + this->graph->setLabel(i, Utils::format12H(i)); + } } - } else { - for (int i = 0; i < 24; i += 3) { - this->graph->setLabel(i, Utils::format12H(i)); + for (size_t i = 0; i < this->graph->entries(); i++) { + t.tm_hour = i; + e.tm_hour = i; + NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + totalSecs += s->playtime; + double val = s->playtime/60.0; + this->graph->setValue(i, val); + delete s; } - } - break; - - case ViewPeriod::Month: { - unsigned int c = Utils::Time::tmGetDaysInMonth(tm); - this->graph->setFontSize(13); - this->graph->setValuePrecision(1); - this->graph->setNumberOfEntries(c); - for (unsigned int i = 0; i < c; i+=3) { - this->graph->setLabel(i, std::to_string(i + 1) + Utils::Time::getDateSuffix(i + 1)); - } - break; - } - - case ViewPeriod::Year: - this->graph->setFontSize(16); - this->graph->setValuePrecision(1); - this->graph->setNumberOfEntries(12); - for (int i = 0; i < 12; i++) { - this->graph->setLabel(i, Utils::Time::getShortMonthString(i)); - } - break; - - default: - break; - } + break; - // Read playtime and set graph values - struct tm t = tm; - unsigned int totalSecs = 0; - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: { - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_min = 59; - e.tm_sec = 59; - for (size_t i = 0; i < this->graph->entries(); i++) { - t.tm_hour = i; - e.tm_hour = i; - NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); - totalSecs += s->playtime; - double val = s->playtime/60.0; - this->graph->setValue(i, val); - delete s; } - break; - } - - case ViewPeriod::Month: { - t.tm_hour = 0; - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_hour = 23; - e.tm_min = 59; - e.tm_sec = 59; - unsigned int max = 0; - for (size_t i = 0; i < this->graph->entries(); i++) { - t.tm_mday = i + 1; - e.tm_mday = i + 1; - NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); - totalSecs += s->playtime; - if (s->playtime > max) { - max = s->playtime; + case ViewPeriod::Month: { + unsigned int n = Utils::Time::tmGetDaysInMonth(t); + this->graph->setFontSize(13); + this->graph->setValuePrecision(1); + this->graph->setNumberOfEntries(n); + for (unsigned int i = 0; i < n; i+=3) { + this->graph->setLabel(i, std::to_string(i + 1) + Utils::Time::getDateSuffix(i + 1)); } - double val = s->playtime/60/60.0; - this->graph->setValue(i, val); - delete s; - } - max /= 60.0; - max /= 60.0; - if (max <= 2) { - this->graph->setMaximumValue(max + 2 - max%2); - this->graph->setYSteps(2); - } else { - this->graph->setMaximumValue(max + 5 - max%5); - this->graph->setYSteps(5); - } - break; - } - - case ViewPeriod::Year: { - t.tm_mday = 1; - t.tm_hour = 0; - t.tm_min = 0; - t.tm_sec = 0; - struct tm e = t; - e.tm_hour = 23; - e.tm_min = 59; - e.tm_sec = 59; - unsigned int max = 0; - for (size_t i = 0; i < this->graph->entries(); i++) { - t.tm_mon = i; - e.tm_mon = i; - e.tm_mday = Utils::Time::tmGetDaysInMonth(t); - NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); - totalSecs += s->playtime; - if (s->playtime > max) { - max = s->playtime; + t.tm_hour = 0; + e = t; + e.tm_hour = 23; + uint64_t max = 0; + for (size_t i = 0; i < this->graph->entries(); i++) { + t.tm_mday = i + 1; + e.tm_mday = i + 1; + NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + totalSecs += s->playtime; + if (s->playtime > max) { + max = s->playtime; + } + double val = s->playtime/60/60.0; + this->graph->setValue(i, val); + delete s; + } + max /= 60.0; + max /= 60.0; + if (max <= 2) { + this->graph->setMaximumValue(max + 2 - max%2); + this->graph->setYSteps(2); + } else { + this->graph->setMaximumValue(max + 5 - max%5); + this->graph->setYSteps(5); } - double val = s->playtime/60/60.0; - this->graph->setValue(i, val); - delete s; + break; } - max /= 60.0; - max /= 60.0; - if (max <= 2) { - this->graph->setMaximumValue(max + 2 - max%2); - this->graph->setYSteps(2); - } else { - this->graph->setMaximumValue(max + 5 - max%5); - this->graph->setYSteps(5); + case ViewPeriod::Year: { + this->graph->setFontSize(16); + this->graph->setValuePrecision(1); + this->graph->setNumberOfEntries(12); + for (int i = 0; i < 12; i++) { + this->graph->setLabel(i, Utils::Time::getShortMonthString(i)); + } + t.tm_mday = 1; + t.tm_hour = 0; + e = t; + e.tm_hour = 23; + uint64_t max = 0; + for (size_t i = 0; i < this->graph->entries(); i++) { + t.tm_mon = i; + e.tm_mon = i; + e.tm_mday = Utils::Time::tmGetDaysInMonth(t); + NX::RecentPlayStatistics * s = this->app->playdata()->getRecentStatisticsForUser(Utils::Time::getTimeT(t), Utils::Time::getTimeT(e), this->app->activeUser()->ID()); + totalSecs += s->playtime; + if (s->playtime > max) { + max = s->playtime; + } + double val = s->playtime/60/60.0; + this->graph->setValue(i, val); + delete s; + } + max /= 60.0; + max /= 60.0; + if (max <= 2) { + this->graph->setMaximumValue(max + 2 - max%2); + this->graph->setYSteps(2); + } else { + this->graph->setMaximumValue(max + 5 - max%5); + this->graph->setYSteps(5); + } + break; } - break; + default: + break; } - default: - break; - } - - // Set headings - if (this->app->viewPeriod() == ViewPeriod::Day) { - this->graphSubheading->setString("common.playtimeMinutes"_lang); - } else { - this->graphSubheading->setString("common.playtimeHours"_lang); - } - this->graphSubheading->setX(this->header->x() + (this->header->w() - this->graphSubheading->w())/2); - } - - void RecentActivity::updateTitles() { - // Get start and end timestamps for date - char c = ' '; - switch (this->app->viewPeriod()) { - case ViewPeriod::Day: - c = 'D'; - break; - - case ViewPeriod::Month: - c = 'M'; - break; - - case ViewPeriod::Year: - c = 'Y'; - break; - - default: - break; - } - unsigned int s = Utils::Time::getTimeT(this->app->time()); - // Minus one second so end time is 11:59pm and not 12:00am next day - unsigned int e = Utils::Time::getTimeT(Utils::Time::increaseTm(this->app->time(), c)) - 1; - - // Get stats - unsigned int totalSecs = 0; - std::vector > stats; - std::vector hidden = this->app->config()->hiddenTitles(); - for (size_t i = 0; i < this->app->titleVector().size(); i++) { - // Skip over hidden games - if (std::find(hidden.begin(), hidden.end(), this->app->titleVector()[i]->titleID()) != hidden.end()) { - continue; + // Set graph headings + if (this->app->viewPeriod() == ViewPeriod::Day) { + this->graphSubheading->setString("common.playtimeMinutes"_lang); + } else { + this->graphSubheading->setString("common.playtimeHours"_lang); + } + this->graphSubheading->setX(this->header->x() + (this->header->w() - this->graphSubheading->w())/2); + + // update Titles + t = begin; + t.tm_min = 0; + t.tm_sec = 0; + // Minus one second so end time is 11:59pm and not 12:00am next day + time_t end_time = 0; + switch (this->app->viewPeriod()) { + case ViewPeriod::Day: + t.tm_hour = 0; + end_time = Utils::Time::getTimeT(Utils::Time::increaseTm(t, 'D')) - 1; + break; + case ViewPeriod::Month: + t.tm_mday = 1; + t.tm_hour = 0; + end_time = Utils::Time::getTimeT(Utils::Time::increaseTm(t, 'M')) - 1; + break; + case ViewPeriod::Year: + t.tm_mon = 0; + t.tm_mday = 1; + t.tm_hour = 0; + end_time = Utils::Time::getTimeT(Utils::Time::increaseTm(t, 'Y')) - 1; + break; + default: + break; } - std::pair stat; - stat.first = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->titleVector()[i]->titleID(), s, e, this->app->activeUser()->ID()); - stat.second = i; - stats.push_back(stat); - } + // Get stats + std::vector > stats; + for (size_t i = 0; i < this->app->titleVector().size(); i++) { + std::pair stat; + stat.first = this->app->playdata()->getRecentStatisticsForTitleAndUser(this->app->titleVector()[i]->titleID(), Utils::Time::getTimeT(begin), end_time, this->app->activeUser()->ID()); + stat.second = i; + stats.push_back(stat); + } - // Sort to have most played first - std::sort(stats.begin(), stats.end(), [](const std::pair lhs, const std::pair rhs) { - return lhs.first->playtime > rhs.first->playtime; - }); + // Sort to have most played first + std::sort(stats.begin(), stats.end(), [](const std::pair lhs, const std::pair rhs) { + return lhs.first->playtime > rhs.first->playtime; + }); + + // Add to list + bool isHidden = false; + std::vector hidden = this->app->config()->hiddenTitles(); + for (auto stat : stats) { + isHidden = std::find(hidden.begin(), hidden.end(), this->app->titleVector()[stat.second]->titleID()) != hidden.end(); + // Only show games that have actually been played and skip over hidden games + if (stat.first->launches == 0 || isHidden) { + delete stat.first; + continue; + } - // Add to list - for (size_t i = 0; i < stats.size(); i++) { - // Only show games that have actually been played - if (stats[i].first->launches > 0) { - totalSecs += stats[i].first->playtime; CustomElm::ListActivity * la = new CustomElm::ListActivity(); - la->setImage(this->app->titleVector()[stats[i].second]->imgPtr(), this->app->titleVector()[stats[i].second]->imgSize()); - la->setTitle(this->app->titleVector()[stats[i].second]->name()); - la->setPlaytime(Utils::playtimeToPlayedForString(stats[i].first->playtime)); - la->setLeftMuted(Utils::launchesToPlayedString(stats[i].first->launches)); - unsigned int j = stats[i].second; + la->setImage(this->app->titleVector()[stat.second]->imgPtr(), this->app->titleVector()[stat.second]->imgSize()); + la->setTitle(this->app->titleVector()[stat.second]->name()); + la->setPlaytime(Utils::playtimeToPlayedForString(stat.first->playtime)); + la->setLeftMuted(Utils::launchesToPlayedString(stat.first->launches)); + unsigned int j = stat.second; la->onPress([this, j](){ this->app->setActiveTitle(j); this->app->pushScreen(); @@ -348,17 +303,34 @@ namespace Screen { la->setMutedColour(this->app->theme()->mutedText()); la->setLineColour(this->app->theme()->mutedLine()); this->list->addElement(la); + + // Can delete each pointer after it's accessed + delete stat.first; } - // Can delete each pointer after it's accessed - delete stats[i].first; + // Update playtime string + this->hours->setString(Utils::playtimeToTotalPlaytimeString(totalSecs)); + this->hours->setX(1215 - this->hours->w()); + } else { + this->gameHeading->setHidden(true); + this->graph->setHidden(true); + this->graphSubheading->setHidden(true); + this->hours->setString("common.totalPlaytime.0min"_lang); + this->hours->setX(1215 - this->hours->w()); + this->list->setShowScrollBar(false); + this->list->setCanScroll(false); + this->noStats->setHidden(false); } - // Update playtime string - this->hours->setString(Utils::playtimeToTotalPlaytimeString(totalSecs)); - this->hours->setX(1215 - this->hours->w()); + delete ps; } + void RecentActivity::update(uint32_t dt) { + if (this->app->timeChanged()) { + this->updateActivity(); + } + Screen::update(dt); + } void RecentActivity::onLoad() { // Create heading using user's name diff --git a/Application/source/ui/screen/Settings.cpp b/Application/source/ui/screen/Settings.cpp index ef37656..8c7a525 100644 --- a/Application/source/ui/screen/Settings.cpp +++ b/Application/source/ui/screen/Settings.cpp @@ -95,7 +95,7 @@ namespace Screen { } void Settings::prepareMessageBox() { - delete this->msgbox; + //delete this->msgbox; this->msgbox = new Aether::MessageBox(); this->msgbox->setLineColour(this->app->theme()->mutedLine()); this->msgbox->setRectangleColour(this->app->theme()->altBG()); @@ -244,6 +244,18 @@ namespace Screen { this->msgbox->addLeftButton("common.buttonHint.cancel"_lang, [this]() { this->msgbox->close(); }); + // Add message box body + int bw, bh; + this->msgbox->getBodySize(&bw, &bh); + Aether::Element * body = new Aether::Element(0, 0, bw, bh); + Aether::TextBlock * tb = new Aether::TextBlock(50, 40, "settings.importExport.confirmDelete"_lang, 24, bw - 100); + tb->setColour(this->app->theme()->text()); + body->addElement(tb); + tb = new Aether::TextBlock(50, tb->y() + tb->h() + 20, "settings.importExport.confirmDeleteBody"_lang, 20, bw - 100); + tb->setColour(this->app->theme()->mutedText()); + body->addElement(tb); + this->msgbox->setBodySize(bw, tb->y() + tb->h() + 40); + this->msgbox->setBody(body); this->msgbox->addRightButton("common.delete"_lang, [this](){ // Delete file std::filesystem::remove("/switch/NX-Activity-Log/importedData.json"); @@ -258,18 +270,6 @@ namespace Screen { this->setupGenericMessageOverlay("settings.importExport.deleteSuccessful"_lang); }); - // Add message box body - int bw, bh; - this->msgbox->getBodySize(&bw, &bh); - Aether::Element * body = new Aether::Element(0, 0, bw, bh); - Aether::TextBlock * tb = new Aether::TextBlock(50, 40, "settings.importExport.confirmDelete"_lang, 24, bw - 100); - tb->setColour(this->app->theme()->text()); - body->addElement(tb); - tb = new Aether::TextBlock(50, tb->y() + tb->h() + 20, "settings.importExport.confirmDeleteBody"_lang, 20, bw - 100); - tb->setColour(this->app->theme()->mutedText()); - body->addElement(tb); - this->msgbox->setBodySize(bw, tb->y() + tb->h() + 40); - this->msgbox->setBody(body); this->app->addOverlay(this->msgbox); } @@ -639,4 +639,4 @@ namespace Screen { delete this->popuplist; delete this->progressbox; } -}; +}; \ No newline at end of file diff --git a/Application/source/utils/Debug.cpp b/Application/source/utils/Debug.cpp new file mode 100644 index 0000000..8ee245d --- /dev/null +++ b/Application/source/utils/Debug.cpp @@ -0,0 +1,31 @@ + +#include +#include +#include +#include "utils/Debug.hpp" + +namespace Utils { + std::mutex mutex; + void write_log(const char *pszFmt, ...) { +#ifdef ENABLE_DEBUG + if (NULL == pszFmt || 0 == pszFmt[0]) return; + + std::time_t currentTime = std::time(nullptr); + std::tm* time = std::localtime(¤tTime); + char timestamp[30]; + strftime(timestamp, sizeof(timestamp), "[%Y-%m-%d %H:%M:%S] ", time); + + char logstr[MAX_LOG_LEN] = { 0 }; + va_list args; + va_start(args, pszFmt); + snprintf(logstr, MAX_LOG_LEN, pszFmt, args); + va_end(args); + + std::lock_guard lock(Utils::mutex); + std::ofstream file("/switch/NX-Activity-Log/debug.log", std::ios::app); + if (file.is_open()) { + file << std::string(timestamp) + std::string(logstr) << std::endl; + } +#endif + } +}; diff --git a/Application/source/utils/Lang.cpp b/Application/source/utils/Lang.cpp index cb70f17..6cca74b 100644 --- a/Application/source/utils/Lang.cpp +++ b/Application/source/utils/Lang.cpp @@ -80,6 +80,10 @@ namespace Utils::Lang { case Korean: path = "romfs:/lang/ko.json"; break; + + case Japanese: + path = "romfs:/lang/ja.json"; + break; default: break; diff --git a/Application/source/utils/NX.cpp b/Application/source/utils/NX.cpp index e18cf69..12cbaa5 100644 --- a/Application/source/utils/NX.cpp +++ b/Application/source/utils/NX.cpp @@ -1,7 +1,9 @@ #include "utils/NX.hpp" +#include +#include // Maximum number of titles to read using pdm -#define MAX_TITLES 2000 +#define MAX_TITLES 4096 // Comparison of AccountUids bool operator == (const AccountUid &a, const AccountUid &b) { @@ -144,15 +146,14 @@ namespace Utils::NX { std::vector<::NX::Title *> getTitleObjects(std::vector<::NX::User *> u) { Result rc; - // Get ALL played titles for ALL users // (this doesn't include installed games that haven't been played) std::vector playedIDs; - for (unsigned short i = 0; i < u.size(); i++) { + for (auto user : u) { s32 playedTotal = 0; TitleID tmpID = 0; PdmAccountPlayEvent *userPlayEvents = new PdmAccountPlayEvent[MAX_TITLES]; - rc = pdmqryQueryAccountPlayEvent(0, u[i]->ID(), userPlayEvents, MAX_TITLES, &playedTotal); + rc = pdmqryQueryAccountPlayEvent(0, user->ID(), userPlayEvents, MAX_TITLES, &playedTotal); if (R_FAILED(rc) || playedTotal == 0) { delete[] userPlayEvents; continue; @@ -160,16 +161,8 @@ namespace Utils::NX { // Push back ID if not already in the vector for (s32 j = 0; j < playedTotal; j++) { - bool found = false; tmpID = (static_cast(userPlayEvents[j].application_id[0]) << 32) | userPlayEvents[j].application_id[1]; - for (size_t k = 0; k < playedIDs.size(); k++) { - if (playedIDs[k] == tmpID) { - found = true; - break; - } - } - - if (!found) { + if (std::find_if(playedIDs.begin(), playedIDs.end(), [tmpID](auto id){ return (id == tmpID && tmpID != 0); }) == playedIDs.end()) { playedIDs.push_back(tmpID); } } @@ -196,17 +189,10 @@ namespace Utils::NX { // Create Title objects from IDs std::vector<::NX::Title *> titles; - for (size_t i = 0; i < playedIDs.size(); i++) { + for (auto playedID : playedIDs) { // Loop over installed titles to determine if installed or not - bool installed = false; - for (size_t j = 0; j < installedIDs.size(); j++) { - if (installedIDs[j] == playedIDs[i]) { - installed = true; - break; - } - } - - titles.push_back(new ::NX::Title(playedIDs[i], installed)); + bool installed = std::find_if(installedIDs.begin(), installedIDs.end(), [playedID](auto id) { return id == playedID; }) != installedIDs.end(); + titles.push_back(new ::NX::Title(playedID, installed)); } return titles; diff --git a/Application/source/utils/Time.cpp b/Application/source/utils/Time.cpp index 1921a5f..32e74fc 100644 --- a/Application/source/utils/Time.cpp +++ b/Application/source/utils/Time.cpp @@ -141,7 +141,7 @@ namespace Utils::Time { std::string tmToDate(struct tm t, bool b) { std::string str; if (b) { - str = std::regex_replace("common.dateFormatYear"_lang, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4)); + str = std::regex_replace("common.dateFormatYear"_lang, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4) + "common.yearSuffix"_lang); } else { str = "common.dateFormat"_lang; } @@ -157,7 +157,7 @@ namespace Utils::Time { case ViewPeriod::Day: { std::string str; if (t.tm_year != getTmForCurrentTime().tm_year) { - str = std::regex_replace("common.activityFor.dayYear"_lang, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4)); + str = std::regex_replace("common.activityFor.dayYear"_lang, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4) + "common.yearSuffix"_lang); } else { str = "common.activityFor.day"_lang; } @@ -169,12 +169,12 @@ namespace Utils::Time { case ViewPeriod::Month: { std::string str = std::regex_replace("common.activityFor.month"_lang, std::regex("\\$\\[m]"), getMonthString(t.tm_mon)); - return std::regex_replace(str, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4)); + return std::regex_replace(str, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4) + "common.yearSuffix"_lang); break; } case ViewPeriod::Year: - return std::regex_replace("common.activityFor.year"_lang, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4)); + return std::regex_replace("common.activityFor.year"_lang, std::regex("\\$\\[y]"), tmToString(t, "%Y", 4) + "common.yearSuffix"_lang); break; default: diff --git a/Application/source/utils/UpdateUtils.cpp b/Application/source/utils/UpdateUtils.cpp index 7e75565..699069d 100644 --- a/Application/source/utils/UpdateUtils.cpp +++ b/Application/source/utils/UpdateUtils.cpp @@ -8,7 +8,7 @@ // Flag indicating an update is available #define AVAILABLE_FILE "/config/NX-Activity-Log/update.flag" // URL to query release data -#define GITHUB_API_URL "https://api.github.com/repos/tallbl0nde/NX-Activity-Log/releases/latest" +#define GITHUB_API_URL "https://api.github.com/repos/zdm65477730/NX-Activity-Log/releases/latest" // File storing timestamp of last check #define TIMESTAMP_FILE "/config/NX-Activity-Log/update.time" // Path of downloaded .nro diff --git a/Application/source/utils/Utils.cpp b/Application/source/utils/Utils.cpp index 4e35bcd..87a578f 100644 --- a/Application/source/utils/Utils.cpp +++ b/Application/source/utils/Utils.cpp @@ -59,10 +59,10 @@ namespace Utils { return std::regex_replace(str, std::regex("\\$\\[v]"), ver); } - std::string lastPlayedToString(unsigned int t) { + std::string lastPlayedToString(uint64_t t) { struct tm now = Utils::Time::getTmForCurrentTime(); struct tm ts = Utils::Time::getTm(t); - int diff = Utils::Time::getTimeT(now) - Utils::Time::getTimeT(ts); + int64_t diff = Utils::Time::getTimeT(now) - Utils::Time::getTimeT(ts); // In the future if (diff < 0) { @@ -105,7 +105,7 @@ namespace Utils { std::string str; // Show year if not within the same year if (now.tm_year != ts.tm_year) { - str = std::regex_replace("common.lastPlayed.dateYear"_lang, std::regex("\\$\\[y]"), Utils::Time::tmToString(ts, "%Y", 4)); + str = std::regex_replace("common.lastPlayed.dateYear"_lang, std::regex("\\$\\[y]"), Utils::Time::tmToString(ts, "%Y", 4) + "common.yearSuffix"_lang); } else { str = "common.lastPlayed.date"_lang; } @@ -130,7 +130,7 @@ namespace Utils { return "common.timesPlayed.once"_lang; } - std::string playtimeToString(unsigned int s) { + std::string playtimeToString(uint64_t s) { if (s == 0) { return "common.playtime.0sec"_lang; } else if (s == 1) { @@ -139,7 +139,7 @@ namespace Utils { return std::regex_replace("common.playtime.secs"_lang, std::regex("\\$\\[s]"), std::to_string(s)); } - unsigned int h = s/3600; + uint64_t h = s/3600; unsigned int m = (s/60)%60; s = s%60; @@ -182,12 +182,12 @@ namespace Utils { return std::regex_replace(str, std::regex("\\$\\[m]"), std::to_string(m)); } - std::string playtimeToPlayedForString(unsigned int s) { + std::string playtimeToPlayedForString(uint64_t s) { if (s < 60) { return "common.playedFor.0min"_lang; } - unsigned int h = s/3600; + uint64_t h = s/3600; unsigned int m = (s/60)%60; s = s%60; @@ -230,14 +230,14 @@ namespace Utils { return std::regex_replace(str, std::regex("\\$\\[m]"), std::to_string(m)); } - std::string playtimeToTotalPlaytimeString(unsigned int s) { + std::string playtimeToTotalPlaytimeString(uint64_t s) { if (s == 0) { return "common.totalPlaytime.0sec"_lang; } else if (s < 60) { return "common.totalPlaytime.0min"_lang; } - unsigned int h = s/3600; + uint64_t h = s/3600; unsigned int m = (s/60)%60; s = s%60; diff --git a/Forwarder/exefs.json b/Forwarder/exefs.json index 64d558b..5530734 100644 --- a/Forwarder/exefs.json +++ b/Forwarder/exefs.json @@ -153,11 +153,12 @@ "value": 512 }, { - "type": "debug_flags", - "value": { - "allow_debug": true, - "force_debug": true - } + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } } ] } diff --git a/Forwarder/source/main.c b/Forwarder/source/main.c index 568ec4f..39a3436 100644 --- a/Forwarder/source/main.c +++ b/Forwarder/source/main.c @@ -446,7 +446,7 @@ void loadNro(void) g_smCloseWorkaround = true; } - extern NORETURN void nroEntrypointTrampoline(u64 entries_ptr, u64 handle, u64 entrypoint); + extern NX_NORETURN void nroEntrypointTrampoline(u64 entries_ptr, u64 handle, u64 entrypoint); nroEntrypointTrampoline((u64) entries, -1, entrypoint); } diff --git a/README.md b/README.md index 3a8e32d..25f188d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **NX Activity Log** is a homebrew application for the Nintendo Switch which displays more precise information about your play activity. -[Download](https://github.com/tallbl0nde/NX-Activity-Log/releases) +[Download](https://github.com/zdm65477730/NX-Activity-Log/releases) Curious about what's next? See my to-do list on [Trello](https://trello.com/b/HaJ1THGq/nx-activity-log) @@ -62,7 +62,7 @@ _Note: The data shown in Recent Activity and Details may be slightly inaccurate If you are seeing wildly incorrect values/believe the playtime shown is incorrect within Recent Activity or the Details screen, please do the following: -1. Download and run the .nro from [here](https://github.com/tallbl0nde/PlayEventParser/releases) +1. Download and run the .nro from [here](https://github.com/zdm65477730/PlayEventParser/releases) 2. Leave it run; if it appears to be frozen leave it for up to a minute! 3. Once it is done there should be a playlog.txt at the root of your SD card. 4. Create an issue with the following: @@ -81,7 +81,7 @@ _Note: If the data shown under All Activity is incorrect there is nothing I can ## Translations -If you'd like to translate the app or fix an issue with a translation, please make a pull request to [this repo](https://github.com/tallbl0nde/NX-Activity-Log-Translations)! I'll add all the relevant code here (if need be) once I see the request :) +If you'd like to translate the app or fix an issue with a translation, please make a pull request to [this repo](https://github.com/zdm65477730/NX-Activity-Log-Translations)! I'll add all the relevant code here (if need be) once I see the request :) ## Credits