|
|
|
|
@ -13,6 +13,7 @@
|
|
|
|
|
#include "common/assert.h"
|
|
|
|
|
#include "common/log.h"
|
|
|
|
|
#include "common/string_util.h"
|
|
|
|
|
#include "common/time_helpers.h"
|
|
|
|
|
#include "common/timer.h"
|
|
|
|
|
|
|
|
|
|
#include "IconsEmoji.h"
|
|
|
|
|
@ -93,7 +94,8 @@ static void CloseLeaderboard();
|
|
|
|
|
static void DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboard);
|
|
|
|
|
static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, u32 index, bool is_self,
|
|
|
|
|
float rank_column_width, float name_column_width, float time_column_width,
|
|
|
|
|
float column_spacing);
|
|
|
|
|
float column_spacing, std::time_t current_time, const std::tm& current_tm);
|
|
|
|
|
static SmallString FormatRelativeTimestamp(std::time_t timestamp, std::time_t current_time, const std::tm& current_tm);
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
@ -1692,6 +1694,14 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
|
|
|
|
BeginMenuButtons(0, 0.0f, LAYOUT_MENU_BUTTON_X_PADDING, 8.0f, 0.0f, 4.0f);
|
|
|
|
|
ResetFocusHere();
|
|
|
|
|
|
|
|
|
|
// for drawing time popups
|
|
|
|
|
const std::time_t current_time = std::time(nullptr);
|
|
|
|
|
std::optional<std::tm> current_tm = Common::LocalTime(current_time);
|
|
|
|
|
if (!current_tm.has_value())
|
|
|
|
|
current_tm = std::tm{};
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f);
|
|
|
|
|
|
|
|
|
|
if (!s_achievements_locals.is_showing_all_leaderboard_entries)
|
|
|
|
|
{
|
|
|
|
|
if (s_achievements_locals.leaderboard_nearby_entries)
|
|
|
|
|
@ -1700,7 +1710,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
|
|
|
|
{
|
|
|
|
|
DrawLeaderboardEntry(s_achievements_locals.leaderboard_nearby_entries->entries[i], i,
|
|
|
|
|
static_cast<s32>(i) == s_achievements_locals.leaderboard_nearby_entries->user_index,
|
|
|
|
|
rank_column_width, name_column_width, time_column_width, column_spacing);
|
|
|
|
|
rank_column_width, name_column_width, time_column_width, column_spacing, current_time,
|
|
|
|
|
current_tm.value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
@ -1720,7 +1731,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
|
|
|
|
for (u32 i = 0; i < list->num_entries; i++)
|
|
|
|
|
{
|
|
|
|
|
DrawLeaderboardEntry(list->entries[i], i, static_cast<s32>(i) == list->user_index, rank_column_width,
|
|
|
|
|
name_column_width, time_column_width, column_spacing);
|
|
|
|
|
name_column_width, time_column_width, column_spacing, current_time,
|
|
|
|
|
current_tm.value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1734,6 +1746,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::PopStyleVar(2);
|
|
|
|
|
|
|
|
|
|
EndMenuButtons();
|
|
|
|
|
}
|
|
|
|
|
EndFullscreenWindow();
|
|
|
|
|
@ -1767,7 +1781,7 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
|
|
|
|
|
|
|
|
|
void FullscreenUI::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, u32 index, bool is_self,
|
|
|
|
|
float rank_column_width, float name_column_width, float time_column_width,
|
|
|
|
|
float column_spacing)
|
|
|
|
|
float column_spacing, std::time_t current_time, const std::tm& current_tm)
|
|
|
|
|
{
|
|
|
|
|
ImRect bb;
|
|
|
|
|
bool visible, hovered;
|
|
|
|
|
@ -1831,10 +1845,20 @@ void FullscreenUI::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
|
|
|
|
|
|
|
|
|
const ImRect time_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
|
|
|
|
|
|
|
|
|
const std::string submit_time =
|
|
|
|
|
Host::FormatNumber(Host::NumberFormatType::LongDateTime, static_cast<s64>(entry.submitted));
|
|
|
|
|
const SmallString relative_time =
|
|
|
|
|
FormatRelativeTimestamp(static_cast<std::time_t>(entry.submitted), current_time, current_tm);
|
|
|
|
|
RenderShadowedTextClipped(UIStyle.Font, UIStyle.MediumLargeFontSize, font_weight, time_bb.Min, time_bb.Max,
|
|
|
|
|
text_color, submit_time, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &time_bb);
|
|
|
|
|
text_color, relative_time, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &time_bb);
|
|
|
|
|
|
|
|
|
|
if (time_bb.Contains(ImGui::GetIO().MousePos) && ImGui::BeginItemTooltip())
|
|
|
|
|
{
|
|
|
|
|
const std::string submit_time =
|
|
|
|
|
Host::FormatNumber(Host::NumberFormatType::LongDateTime, static_cast<s64>(entry.submitted));
|
|
|
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
|
|
|
|
ImGui::Text(ICON_EMOJI_CLOCK_FIVE_OCLOCK " %s", submit_time.c_str());
|
|
|
|
|
ImGui::PopFont();
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pressed)
|
|
|
|
|
{
|
|
|
|
|
@ -1843,6 +1867,83 @@ void FullscreenUI::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
|
|
|
|
Host::OpenURL(url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SmallString FullscreenUI::FormatRelativeTimestamp(time_t timestamp, time_t current_time, const std::tm& current_tm)
|
|
|
|
|
{
|
|
|
|
|
const s64 diff = static_cast<s64>(current_time) - static_cast<s64>(timestamp);
|
|
|
|
|
|
|
|
|
|
constexpr s64 MINUTE = 60;
|
|
|
|
|
constexpr s64 HOUR = 60 * MINUTE;
|
|
|
|
|
constexpr s64 DAY = 24 * HOUR;
|
|
|
|
|
constexpr s64 WEEK = 7 * DAY;
|
|
|
|
|
|
|
|
|
|
if (diff < MINUTE)
|
|
|
|
|
return SmallString(TRANSLATE_SV("Achievements", "Just now"));
|
|
|
|
|
|
|
|
|
|
if (diff < HOUR)
|
|
|
|
|
{
|
|
|
|
|
const s64 minutes = diff / MINUTE;
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n minutes ago", "Relative time", static_cast<int>(minutes));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (diff < DAY)
|
|
|
|
|
{
|
|
|
|
|
const s64 hours = diff / HOUR;
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n hours ago", "Relative time", static_cast<int>(hours));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (diff < DAY * 2)
|
|
|
|
|
{
|
|
|
|
|
// Check if it's actually today vs yesterday
|
|
|
|
|
const std::optional<std::tm> timestamp_tm = Common::LocalTime(timestamp);
|
|
|
|
|
if (timestamp_tm.has_value() && timestamp_tm->tm_yday == current_tm.tm_yday &&
|
|
|
|
|
timestamp_tm->tm_year == current_tm.tm_year)
|
|
|
|
|
{
|
|
|
|
|
return SmallString(TRANSLATE_SV("Achievements", "Today"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SmallString(TRANSLATE_SV("Achievements", "Yesterday"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (diff < WEEK)
|
|
|
|
|
{
|
|
|
|
|
const s64 days = diff / DAY;
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n days ago", "Relative time", static_cast<int>(days));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::optional<std::tm> timestamp_tm = Common::LocalTime(timestamp);
|
|
|
|
|
if (!timestamp_tm.has_value())
|
|
|
|
|
return SmallString();
|
|
|
|
|
|
|
|
|
|
const int year_diff = current_tm.tm_year - timestamp_tm->tm_year;
|
|
|
|
|
const int month_diff = current_tm.tm_mon - timestamp_tm->tm_mon;
|
|
|
|
|
const int total_months = year_diff * 12 + month_diff;
|
|
|
|
|
|
|
|
|
|
if (total_months == 0)
|
|
|
|
|
{
|
|
|
|
|
// Less than a month - use weeks
|
|
|
|
|
const s64 weeks = diff / WEEK;
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n weeks ago", "Relative time", static_cast<int>(weeks));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (total_months < 12)
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n months ago", "Relative time", total_months);
|
|
|
|
|
|
|
|
|
|
// For years, adjust if we haven't reached the anniversary yet
|
|
|
|
|
int years = year_diff;
|
|
|
|
|
if (current_tm.tm_mon < timestamp_tm->tm_mon ||
|
|
|
|
|
(current_tm.tm_mon == timestamp_tm->tm_mon && current_tm.tm_mday < timestamp_tm->tm_mday))
|
|
|
|
|
{
|
|
|
|
|
years--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Edge case: less than a full year but more than 11 months
|
|
|
|
|
if (years < 1)
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n months ago", "Relative time", total_months);
|
|
|
|
|
|
|
|
|
|
return TRANSLATE_PLURAL_SSTR("Achievements", "%n years ago", "Relative time", years);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FullscreenUI::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboard)
|
|
|
|
|
{
|
|
|
|
|
SmallString title;
|
|
|
|
|
|