diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 7ce64e4..1a6623f 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -17,10 +17,10 @@ jobs:
- name: Cache cargo registry and target
uses: Swatinem/rust-cache@v2
- - name: Install GTK dependencies
+ - name: Install GTK and gettext dependencies
run: |
sudo apt-get update
- sudo apt-get install -y libgtk-4-dev libadwaita-1-dev
+ sudo apt-get install -y libgtk-4-dev libadwaita-1-dev gettext
- name: Check formatting
run: cargo fmt --check
diff --git a/assets/org.samrewritten.SamRewritten.gschema.xml b/assets/org.samrewritten.SamRewritten.gschema.xml
index 0213147..4e0c3be 100644
--- a/assets/org.samrewritten.SamRewritten.gschema.xml
+++ b/assets/org.samrewritten.SamRewritten.gschema.xml
@@ -85,6 +85,18 @@
How the achievement view commits toggles. Valid values are 'autocommit' (each switch commits immediately), 'deferred' (toggles build an ordered queue that the user fires later), and 'copytiming' (replay a friend's unlock cadence).
+
+ 'global-percentage'
+ Achievement Order
+ How achievements are sorted. Valid values are 'steam-default', 'alphabetical', 'global-percentage', and 'unlock-date'.
+
+
+
+ 'steam-default'
+ Stat Order
+ How stats are sorted. Valid values are 'steam-default' and 'alphabetical'.
+
+
5
Copy Timing Maximum Gap (Minutes)
@@ -157,4 +169,4 @@
Whether games you placed in Steam's own Hidden collection are left out of the games list.
-
\ No newline at end of file
+
diff --git a/po/fr.po b/po/fr.po
index cfbb9a2..f24a569 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -2,114 +2,154 @@ msgid ""
msgstr ""
"Project-Id-Version: SamRewritten\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-09-02 21:40+0200\n"
+"POT-Creation-Date: 2026-09-03 21:30+0200\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#: src/gui_frontend/ui_components.rs:159 src/gui_frontend/app_view.rs:62
+#: src/gui_frontend/ui_components.rs:160 src/gui_frontend/app_view.rs:62
#: src/gui_frontend/app_list_view/mod.rs:421
msgid "Loading..."
msgstr "Chargement..."
-#: src/gui_frontend/ui_components.rs:190
+#: src/gui_frontend/ui_components.rs:191
msgid "Select all visible apps"
msgstr "Sélectionner les applications visibles"
-#: src/gui_frontend/ui_components.rs:194
+#: src/gui_frontend/ui_components.rs:195
msgid "Deselect all apps"
msgstr "Tout désélectionner"
-#: src/gui_frontend/ui_components.rs:198
+#: src/gui_frontend/ui_components.rs:199
msgid "Unlock all in selection"
msgstr "Tout déverrouiller dans la sélection"
-#: src/gui_frontend/ui_components.rs:202
+#: src/gui_frontend/ui_components.rs:203
msgid "Reset all in selection"
msgstr "Tout réinitialiser dans la sélection"
-#: src/gui_frontend/ui_components.rs:206
+#: src/gui_frontend/ui_components.rs:207
#: src/gui_frontend/app_list_view/progress_actions.rs:116
msgid "Export selected apps progress"
msgstr "Exporter la progression sélection"
-#: src/gui_frontend/ui_components.rs:210
+#: src/gui_frontend/ui_components.rs:211
msgid "Import progress..."
msgstr "Importer progression..."
-#: src/gui_frontend/ui_components.rs:216
+#: src/gui_frontend/ui_components.rs:217
msgid "Appear in-game while managing"
msgstr "Apparaître in-game pendant l'édition"
-#: src/gui_frontend/ui_components.rs:222
+#: src/gui_frontend/ui_components.rs:223
msgid "Refresh app list"
msgstr "Actualiser la liste des apps"
-#: src/gui_frontend/ui_components.rs:226
+#: src/gui_frontend/ui_components.rs:227
msgid "Refresh achievement counts"
msgstr "Actualiser le décompte de succès"
-#: src/gui_frontend/ui_components.rs:229 src/gui_frontend/ui_components.rs:413
+#: src/gui_frontend/ui_components.rs:230 src/gui_frontend/ui_components.rs:471
msgid "About"
msgstr "À propos"
-#: src/gui_frontend/ui_components.rs:233
+#: src/gui_frontend/ui_components.rs:234
msgid "Change Steam folder…"
msgstr "Changer le dossier Steam…"
-#: src/gui_frontend/ui_components.rs:240
+#: src/gui_frontend/ui_components.rs:241
msgid "Bulk process"
msgstr "Traitement par lot"
-#: src/gui_frontend/ui_components.rs:246
+#: src/gui_frontend/ui_components.rs:247
msgid "System"
msgstr "Système"
-#: src/gui_frontend/ui_components.rs:247
+#: src/gui_frontend/ui_components.rs:248
msgid "Light"
msgstr "Clair"
-#: src/gui_frontend/ui_components.rs:248
+#: src/gui_frontend/ui_components.rs:249
msgid "Dark"
msgstr "Sombre"
-#: src/gui_frontend/ui_components.rs:257
+#: src/gui_frontend/ui_components.rs:258
msgid "Disable animations"
msgstr "Désactiver les animations"
-#: src/gui_frontend/ui_components.rs:261
+#: src/gui_frontend/ui_components.rs:262
msgid "Appearance"
msgstr "Apparence"
-#: src/gui_frontend/ui_components.rs:267
+#: src/gui_frontend/ui_components.rs:268
msgid "System default"
msgstr "Système par défaut"
-#: src/gui_frontend/ui_components.rs:270
+#: src/gui_frontend/ui_components.rs:271
msgid "Language"
msgstr "Langue"
-#: src/gui_frontend/ui_components.rs:387
+#: src/gui_frontend/ui_components.rs:388
msgid "Game default"
msgstr "Langue du jeu"
-#: src/gui_frontend/ui_components.rs:406
+#: src/gui_frontend/ui_components.rs:411
msgid "Refresh achievements & stats"
msgstr "Actualiser les succès et statistiques"
-#: src/gui_frontend/ui_components.rs:410
+#: src/gui_frontend/ui_components.rs:415
msgid "Reset everything"
msgstr "Tout réinitialiser"
-#: src/gui_frontend/ui_components.rs:418
+#: src/gui_frontend/ui_components.rs:435
msgid "Achievement language"
msgstr "Langue des succès"
+#: src/gui_frontend/ui_components.rs:440
+msgid "Achievement order"
+msgstr "Tri des succès"
+
+#: src/gui_frontend/ui_components.rs:443 src/gui_frontend/ui_components.rs:465
+msgid "Steam default"
+msgstr "Par défaut Steam"
+
+#: src/gui_frontend/ui_components.rs:444 src/gui_frontend/ui_components.rs:466
+msgid "Alphabetically"
+msgstr "Alphabétique"
+
+#: src/gui_frontend/ui_components.rs:445
+msgid "Global percentage"
+msgstr "Pourcentage global"
+
+#: src/gui_frontend/ui_components.rs:446
+msgid "Unlock date"
+msgstr "Date de déblocage"
+
+#: src/gui_frontend/ui_components.rs:451
+msgid "Achievement state"
+msgstr "Filtre des succès"
+
+#: src/gui_frontend/ui_components.rs:454
+msgid "All"
+msgstr "Tous"
+
+#: src/gui_frontend/ui_components.rs:455
+msgid "Locked"
+msgstr "Verrouillés"
+
+#: src/gui_frontend/ui_components.rs:456
+msgid "Unlocked"
+msgstr "Déverrouillés"
+
+#: src/gui_frontend/ui_components.rs:462
+msgid "Stat order"
+msgstr "Tri des stats"
+
#: src/gui_frontend/dialogs.rs:89 src/gui_frontend/dialogs.rs:162
#: src/gui_frontend/dialogs.rs:185 src/gui_frontend/dialogs.rs:233
-#: src/gui_frontend/unlock_scheduler.rs:185
+#: src/gui_frontend/unlock_scheduler.rs:217
#: src/gui_frontend/app_list_view/progress_actions.rs:43
msgid "OK"
msgstr "OK"
@@ -293,11 +333,11 @@ msgstr "Succès"
msgid "Stats"
msgstr "Statistiques"
-#: src/gui_frontend/stat_view.rs:102
+#: src/gui_frontend/stat_view.rs:166
msgid "Increment only"
msgstr "Incrément uniquement"
-#: src/gui_frontend/stat_view.rs:107
+#: src/gui_frontend/stat_view.rs:171
msgid "This statistic is protected."
msgstr "Cette statistique est protégée."
@@ -305,19 +345,19 @@ msgstr "Cette statistique est protégée."
msgid "Stop and go back"
msgstr "Arrêter et revenir"
-#: src/gui_frontend/achievement_manual_view/mod.rs:57
+#: src/gui_frontend/achievement_manual_view/mod.rs:58
msgid "No achievements staged"
msgstr "Aucun succès préparé"
-#: src/gui_frontend/achievement_manual_view/mod.rs:58
+#: src/gui_frontend/achievement_manual_view/mod.rs:59
msgid "{count} staged"
msgstr "{count} préparé(s)"
-#: src/gui_frontend/achievement_manual_view/mod.rs:96
+#: src/gui_frontend/achievement_manual_view/mod.rs:97
msgid "Already at or above target ({progress})"
msgstr "Déjà à la cible ou au-dessus ({progress})"
-#: src/gui_frontend/achievement_manual_view/mod.rs:102
+#: src/gui_frontend/achievement_manual_view/mod.rs:103
msgid "Auto-fill {count} achievement(s) ({progress})"
msgstr "Remplir automatiquement {count} succès ({progress})"
@@ -383,7 +423,7 @@ msgid "Spacing"
msgstr "Espacement"
#: src/gui_frontend/achievement_manual_view/copy_controls.rs:38
-#: src/gui_frontend/achievement_manual_view/copy_mode.rs:234
+#: src/gui_frontend/achievement_manual_view/copy_mode.rs:238
msgid "Choose a friend to copy timing from"
msgstr "Choisir un ami pour lequel copier le minutage"
@@ -404,11 +444,11 @@ msgstr "Écart max"
msgid "First delay"
msgstr "Délai initial"
-#: src/gui_frontend/achievement_manual_view/copy_mode.rs:101
+#: src/gui_frontend/achievement_manual_view/copy_mode.rs:105
msgid "No friend loaded"
msgstr "Aucun ami chargé"
-#: src/gui_frontend/achievement_manual_view/copy_mode.rs:111
+#: src/gui_frontend/achievement_manual_view/copy_mode.rs:115
msgid "{count} achievements over {time}"
msgstr "{count} succès sur {time}"
@@ -449,148 +489,148 @@ msgid "Try again"
msgstr "Réessayer"
#: src/gui_frontend/app_list_view/mod.rs:464
-#: src/gui_frontend/app_list_view/mod.rs:1752
+#: src/gui_frontend/app_list_view/mod.rs:1769
msgid "Name or AppId (Ctrl+K)"
msgstr "Nom ou AppId (Ctrl+K)"
-#: src/gui_frontend/app_list_view/mod.rs:480
+#: src/gui_frontend/app_list_view/mod.rs:499
msgid "Show or hide the sidebar"
msgstr "Afficher ou masquer la barre latérale"
-#: src/gui_frontend/app_list_view/mod.rs:1747
+#: src/gui_frontend/app_list_view/mod.rs:1764
msgid "Achievement or stat..."
msgstr "Succès ou statistique..."
-#: src/gui_frontend/app_list_view/mod.rs:1866
-#: src/gui_frontend/app_list_view/settings_bindings.rs:333
+#: src/gui_frontend/app_list_view/mod.rs:1883
+#: src/gui_frontend/app_list_view/settings_bindings.rs:328
msgid "Could not change the in-game setting"
msgstr "Impossible de changer l'option in-game"
-#: src/gui_frontend/app_list_view/mod.rs:1868
-#: src/gui_frontend/app_list_view/settings_bindings.rs:334
+#: src/gui_frontend/app_list_view/mod.rs:1885
+#: src/gui_frontend/app_list_view/settings_bindings.rs:329
msgid "The change could not be applied. Restart SamRewritten and try again."
msgstr ""
"Le changement n'a pas pu être appliqué. Redémarrez SamRewritten et réessayez."
-#: src/gui_frontend/app_list_view/sidebar.rs:58
+#: src/gui_frontend/app_list_view/sidebar.rs:57
msgid "Hide with no achievements"
msgstr "Masquer sans succès"
-#: src/gui_frontend/app_list_view/sidebar.rs:64
+#: src/gui_frontend/app_list_view/sidebar.rs:63
msgid "Hide at 100%"
msgstr "Masquer à 100 %"
-#: src/gui_frontend/app_list_view/sidebar.rs:70
+#: src/gui_frontend/app_list_view/sidebar.rs:69
msgid "Hide at 0%"
msgstr "Masquer à 0 %"
-#: src/gui_frontend/app_list_view/sidebar.rs:76
+#: src/gui_frontend/app_list_view/sidebar.rs:75
msgid "Hide never launched"
msgstr "Masquer jamais lancés"
-#: src/gui_frontend/app_list_view/sidebar.rs:82
+#: src/gui_frontend/app_list_view/sidebar.rs:81
msgid "Only currently idling"
msgstr "Uniquement in-game"
-#: src/gui_frontend/app_list_view/sidebar.rs:88
+#: src/gui_frontend/app_list_view/sidebar.rs:87
msgid "Hide hidden in Steam"
msgstr "Masquer les masqués Steam"
-#: src/gui_frontend/app_list_view/sidebar.rs:94
+#: src/gui_frontend/app_list_view/sidebar.rs:93
msgid "Show junk"
msgstr "Afficher superflus"
-#: src/gui_frontend/app_list_view/sidebar.rs:108
+#: src/gui_frontend/app_list_view/sidebar.rs:107
msgid "App ID"
msgstr "App ID"
-#: src/gui_frontend/app_list_view/sidebar.rs:113
+#: src/gui_frontend/app_list_view/sidebar.rs:112
msgid "Name"
msgstr "Nom"
-#: src/gui_frontend/app_list_view/sidebar.rs:118
+#: src/gui_frontend/app_list_view/sidebar.rs:117
msgid "Last played"
msgstr "Lancé récemment"
-#: src/gui_frontend/app_list_view/sidebar.rs:123
+#: src/gui_frontend/app_list_view/sidebar.rs:122
msgid "Playtime"
msgstr "Temps de jeu"
-#: src/gui_frontend/app_list_view/sidebar.rs:128
+#: src/gui_frontend/app_list_view/sidebar.rs:127
msgid "Completion"
msgstr "Complétion"
-#: src/gui_frontend/app_list_view/sidebar.rs:133
+#: src/gui_frontend/app_list_view/sidebar.rs:132
msgid "Achievements left"
msgstr "Succès restants"
-#: src/gui_frontend/app_list_view/sidebar.rs:183
+#: src/gui_frontend/app_list_view/sidebar.rs:180
msgid "Filters on a search term, which SamRewritten cannot reproduce exactly."
msgstr ""
"Filtre selon un terme de recherche, ce que SamRewritten ne peut pas "
"reproduire exactement."
-#: src/gui_frontend/app_list_view/sidebar.rs:186
+#: src/gui_frontend/app_list_view/sidebar.rs:183
msgid "Uses a Steam filter SamRewritten cannot reproduce exactly."
msgstr ""
"Utilise un filtre Steam que SamRewritten ne peut pas reproduire exactement."
-#: src/gui_frontend/app_list_view/sidebar.rs:189
+#: src/gui_frontend/app_list_view/sidebar.rs:186
msgid ""
"Needs information from Steam that could not be read. Refresh to try again."
msgstr ""
"Nécessite des informations de Steam qui n'ont pas pu être lues. Actualisez "
"pour réessayer."
-#: src/gui_frontend/app_list_view/sidebar.rs:192
+#: src/gui_frontend/app_list_view/sidebar.rs:189
msgid ""
"Needs your friends' games, which Steam can only tell us when it is online."
msgstr ""
"Nécessite les jeux de vos amis, que Steam ne peut indiquer qu'en ligne."
-#: src/gui_frontend/app_list_view/sidebar.rs:201
+#: src/gui_frontend/app_list_view/sidebar.rs:198
msgid "Favorites"
msgstr "Favoris"
-#: src/gui_frontend/app_list_view/sidebar.rs:228
+#: src/gui_frontend/app_list_view/sidebar.rs:225
msgid "View profile"
msgstr "Voir le profil"
-#: src/gui_frontend/app_list_view/sidebar.rs:283
+#: src/gui_frontend/app_list_view/sidebar.rs:280
#: src/gui_frontend/profile_view/mod.rs:484
msgid "Steam user"
msgstr "Utilisateur Steam"
-#: src/gui_frontend/app_list_view/sidebar.rs:305
+#: src/gui_frontend/app_list_view/sidebar.rs:300
msgid "Steam is offline. What needs its servers is turned off."
msgstr "Steam est hors ligne. Ce qui dépend de ses serveurs est désactivé."
-#: src/gui_frontend/app_list_view/sidebar.rs:316
+#: src/gui_frontend/app_list_view/sidebar.rs:311
msgid "Fetching completion…"
msgstr "Chargement de la complétion…"
-#: src/gui_frontend/app_list_view/sidebar.rs:322
+#: src/gui_frontend/app_list_view/sidebar.rs:317
msgid "Click to cancel"
msgstr "Cliquez pour annuler"
-#: src/gui_frontend/app_list_view/sidebar.rs:353
+#: src/gui_frontend/app_list_view/sidebar.rs:348
msgid "Filters"
msgstr "Filtres"
-#: src/gui_frontend/app_list_view/sidebar.rs:364
+#: src/gui_frontend/app_list_view/sidebar.rs:359
msgid "Steam collection"
msgstr "Collection Steam"
-#: src/gui_frontend/app_list_view/sidebar.rs:365
-#: src/gui_frontend/app_list_view/sidebar.rs:588
+#: src/gui_frontend/app_list_view/sidebar.rs:360
+#: src/gui_frontend/app_list_view/sidebar.rs:580
msgid "All games"
msgstr "Tous les jeux"
-#: src/gui_frontend/app_list_view/sidebar.rs:448
+#: src/gui_frontend/app_list_view/sidebar.rs:442
msgid "Sort by"
msgstr "Trier par"
-#: src/gui_frontend/app_list_view/sidebar.rs:513
+#: src/gui_frontend/app_list_view/sidebar.rs:505
msgid "Reset filters"
msgstr "Réinitialiser les filtres"
@@ -1360,9 +1400,6 @@ msgstr "Déjà déverrouillé."
#~ msgid "Hide with no unlocks"
#~ msgstr "Masquer sans succès obtenu"
-#~ msgid "Alphabetical"
-#~ msgstr "Alphabétique"
-
#~ msgid "Recently played"
#~ msgstr "Lancé récemment"
diff --git a/po/samrewritten.pot b/po/samrewritten.pot
index 9778208..c2914d9 100644
--- a/po/samrewritten.pot
+++ b/po/samrewritten.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: SamRewritten\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-09-02 21:40+0200\n"
+"POT-Creation-Date: 2026-09-03 21:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,107 +17,147 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: src/gui_frontend/ui_components.rs:159 src/gui_frontend/app_view.rs:62
+#: src/gui_frontend/ui_components.rs:160 src/gui_frontend/app_view.rs:62
#: src/gui_frontend/app_list_view/mod.rs:421
msgid "Loading..."
msgstr ""
-#: src/gui_frontend/ui_components.rs:190
+#: src/gui_frontend/ui_components.rs:191
msgid "Select all visible apps"
msgstr ""
-#: src/gui_frontend/ui_components.rs:194
+#: src/gui_frontend/ui_components.rs:195
msgid "Deselect all apps"
msgstr ""
-#: src/gui_frontend/ui_components.rs:198
+#: src/gui_frontend/ui_components.rs:199
msgid "Unlock all in selection"
msgstr ""
-#: src/gui_frontend/ui_components.rs:202
+#: src/gui_frontend/ui_components.rs:203
msgid "Reset all in selection"
msgstr ""
-#: src/gui_frontend/ui_components.rs:206
+#: src/gui_frontend/ui_components.rs:207
#: src/gui_frontend/app_list_view/progress_actions.rs:116
msgid "Export selected apps progress"
msgstr ""
-#: src/gui_frontend/ui_components.rs:210
+#: src/gui_frontend/ui_components.rs:211
msgid "Import progress..."
msgstr ""
-#: src/gui_frontend/ui_components.rs:216
+#: src/gui_frontend/ui_components.rs:217
msgid "Appear in-game while managing"
msgstr ""
-#: src/gui_frontend/ui_components.rs:222
+#: src/gui_frontend/ui_components.rs:223
msgid "Refresh app list"
msgstr ""
-#: src/gui_frontend/ui_components.rs:226
+#: src/gui_frontend/ui_components.rs:227
msgid "Refresh achievement counts"
msgstr ""
-#: src/gui_frontend/ui_components.rs:229 src/gui_frontend/ui_components.rs:413
+#: src/gui_frontend/ui_components.rs:230 src/gui_frontend/ui_components.rs:471
msgid "About"
msgstr ""
-#: src/gui_frontend/ui_components.rs:233
+#: src/gui_frontend/ui_components.rs:234
msgid "Change Steam folder…"
msgstr ""
-#: src/gui_frontend/ui_components.rs:240
+#: src/gui_frontend/ui_components.rs:241
msgid "Bulk process"
msgstr ""
-#: src/gui_frontend/ui_components.rs:246
+#: src/gui_frontend/ui_components.rs:247
msgid "System"
msgstr ""
-#: src/gui_frontend/ui_components.rs:247
+#: src/gui_frontend/ui_components.rs:248
msgid "Light"
msgstr ""
-#: src/gui_frontend/ui_components.rs:248
+#: src/gui_frontend/ui_components.rs:249
msgid "Dark"
msgstr ""
-#: src/gui_frontend/ui_components.rs:257
+#: src/gui_frontend/ui_components.rs:258
msgid "Disable animations"
msgstr ""
-#: src/gui_frontend/ui_components.rs:261
+#: src/gui_frontend/ui_components.rs:262
msgid "Appearance"
msgstr ""
-#: src/gui_frontend/ui_components.rs:267
+#: src/gui_frontend/ui_components.rs:268
msgid "System default"
msgstr ""
-#: src/gui_frontend/ui_components.rs:270
+#: src/gui_frontend/ui_components.rs:271
msgid "Language"
msgstr ""
-#: src/gui_frontend/ui_components.rs:387
+#: src/gui_frontend/ui_components.rs:388
msgid "Game default"
msgstr ""
-#: src/gui_frontend/ui_components.rs:406
+#: src/gui_frontend/ui_components.rs:411
msgid "Refresh achievements & stats"
msgstr ""
-#: src/gui_frontend/ui_components.rs:410
+#: src/gui_frontend/ui_components.rs:415
msgid "Reset everything"
msgstr ""
-#: src/gui_frontend/ui_components.rs:418
+#: src/gui_frontend/ui_components.rs:435
msgid "Achievement language"
msgstr ""
+#: src/gui_frontend/ui_components.rs:440
+msgid "Achievement order"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:443 src/gui_frontend/ui_components.rs:465
+msgid "Steam default"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:444 src/gui_frontend/ui_components.rs:466
+msgid "Alphabetically"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:445
+msgid "Global percentage"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:446
+msgid "Unlock date"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:451
+msgid "Achievement state"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:454
+msgid "All"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:455
+msgid "Locked"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:456
+msgid "Unlocked"
+msgstr ""
+
+#: src/gui_frontend/ui_components.rs:462
+msgid "Stat order"
+msgstr ""
+
#: src/gui_frontend/dialogs.rs:89 src/gui_frontend/dialogs.rs:162
#: src/gui_frontend/dialogs.rs:185 src/gui_frontend/dialogs.rs:233
-#: src/gui_frontend/unlock_scheduler.rs:185
+#: src/gui_frontend/unlock_scheduler.rs:217
#: src/gui_frontend/app_list_view/progress_actions.rs:43
msgid "OK"
msgstr ""
@@ -274,11 +314,11 @@ msgstr ""
msgid "Stats"
msgstr ""
-#: src/gui_frontend/stat_view.rs:102
+#: src/gui_frontend/stat_view.rs:166
msgid "Increment only"
msgstr ""
-#: src/gui_frontend/stat_view.rs:107
+#: src/gui_frontend/stat_view.rs:171
msgid "This statistic is protected."
msgstr ""
@@ -286,19 +326,19 @@ msgstr ""
msgid "Stop and go back"
msgstr ""
-#: src/gui_frontend/achievement_manual_view/mod.rs:57
+#: src/gui_frontend/achievement_manual_view/mod.rs:58
msgid "No achievements staged"
msgstr ""
-#: src/gui_frontend/achievement_manual_view/mod.rs:58
+#: src/gui_frontend/achievement_manual_view/mod.rs:59
msgid "{count} staged"
msgstr ""
-#: src/gui_frontend/achievement_manual_view/mod.rs:96
+#: src/gui_frontend/achievement_manual_view/mod.rs:97
msgid "Already at or above target ({progress})"
msgstr ""
-#: src/gui_frontend/achievement_manual_view/mod.rs:102
+#: src/gui_frontend/achievement_manual_view/mod.rs:103
msgid "Auto-fill {count} achievement(s) ({progress})"
msgstr ""
@@ -364,7 +404,7 @@ msgid "Spacing"
msgstr ""
#: src/gui_frontend/achievement_manual_view/copy_controls.rs:38
-#: src/gui_frontend/achievement_manual_view/copy_mode.rs:234
+#: src/gui_frontend/achievement_manual_view/copy_mode.rs:238
msgid "Choose a friend to copy timing from"
msgstr ""
@@ -385,11 +425,11 @@ msgstr ""
msgid "First delay"
msgstr ""
-#: src/gui_frontend/achievement_manual_view/copy_mode.rs:101
+#: src/gui_frontend/achievement_manual_view/copy_mode.rs:105
msgid "No friend loaded"
msgstr ""
-#: src/gui_frontend/achievement_manual_view/copy_mode.rs:111
+#: src/gui_frontend/achievement_manual_view/copy_mode.rs:115
msgid "{count} achievements over {time}"
msgstr ""
@@ -430,141 +470,141 @@ msgid "Try again"
msgstr ""
#: src/gui_frontend/app_list_view/mod.rs:464
-#: src/gui_frontend/app_list_view/mod.rs:1752
+#: src/gui_frontend/app_list_view/mod.rs:1769
msgid "Name or AppId (Ctrl+K)"
msgstr ""
-#: src/gui_frontend/app_list_view/mod.rs:480
+#: src/gui_frontend/app_list_view/mod.rs:499
msgid "Show or hide the sidebar"
msgstr ""
-#: src/gui_frontend/app_list_view/mod.rs:1747
+#: src/gui_frontend/app_list_view/mod.rs:1764
msgid "Achievement or stat..."
msgstr ""
-#: src/gui_frontend/app_list_view/mod.rs:1866
-#: src/gui_frontend/app_list_view/settings_bindings.rs:333
+#: src/gui_frontend/app_list_view/mod.rs:1883
+#: src/gui_frontend/app_list_view/settings_bindings.rs:328
msgid "Could not change the in-game setting"
msgstr ""
-#: src/gui_frontend/app_list_view/mod.rs:1868
-#: src/gui_frontend/app_list_view/settings_bindings.rs:334
+#: src/gui_frontend/app_list_view/mod.rs:1885
+#: src/gui_frontend/app_list_view/settings_bindings.rs:329
msgid "The change could not be applied. Restart SamRewritten and try again."
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:58
+#: src/gui_frontend/app_list_view/sidebar.rs:57
msgid "Hide with no achievements"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:64
+#: src/gui_frontend/app_list_view/sidebar.rs:63
msgid "Hide at 100%"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:70
+#: src/gui_frontend/app_list_view/sidebar.rs:69
msgid "Hide at 0%"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:76
+#: src/gui_frontend/app_list_view/sidebar.rs:75
msgid "Hide never launched"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:82
+#: src/gui_frontend/app_list_view/sidebar.rs:81
msgid "Only currently idling"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:88
+#: src/gui_frontend/app_list_view/sidebar.rs:87
msgid "Hide hidden in Steam"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:94
+#: src/gui_frontend/app_list_view/sidebar.rs:93
msgid "Show junk"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:108
+#: src/gui_frontend/app_list_view/sidebar.rs:107
msgid "App ID"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:113
+#: src/gui_frontend/app_list_view/sidebar.rs:112
msgid "Name"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:118
+#: src/gui_frontend/app_list_view/sidebar.rs:117
msgid "Last played"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:123
+#: src/gui_frontend/app_list_view/sidebar.rs:122
msgid "Playtime"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:128
+#: src/gui_frontend/app_list_view/sidebar.rs:127
msgid "Completion"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:133
+#: src/gui_frontend/app_list_view/sidebar.rs:132
msgid "Achievements left"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:183
+#: src/gui_frontend/app_list_view/sidebar.rs:180
msgid "Filters on a search term, which SamRewritten cannot reproduce exactly."
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:186
+#: src/gui_frontend/app_list_view/sidebar.rs:183
msgid "Uses a Steam filter SamRewritten cannot reproduce exactly."
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:189
+#: src/gui_frontend/app_list_view/sidebar.rs:186
msgid ""
"Needs information from Steam that could not be read. Refresh to try again."
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:192
+#: src/gui_frontend/app_list_view/sidebar.rs:189
msgid ""
"Needs your friends' games, which Steam can only tell us when it is online."
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:201
+#: src/gui_frontend/app_list_view/sidebar.rs:198
msgid "Favorites"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:228
+#: src/gui_frontend/app_list_view/sidebar.rs:225
msgid "View profile"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:283
+#: src/gui_frontend/app_list_view/sidebar.rs:280
#: src/gui_frontend/profile_view/mod.rs:484
msgid "Steam user"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:305
+#: src/gui_frontend/app_list_view/sidebar.rs:300
msgid "Steam is offline. What needs its servers is turned off."
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:316
+#: src/gui_frontend/app_list_view/sidebar.rs:311
msgid "Fetching completion…"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:322
+#: src/gui_frontend/app_list_view/sidebar.rs:317
msgid "Click to cancel"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:353
+#: src/gui_frontend/app_list_view/sidebar.rs:348
msgid "Filters"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:364
+#: src/gui_frontend/app_list_view/sidebar.rs:359
msgid "Steam collection"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:365
-#: src/gui_frontend/app_list_view/sidebar.rs:588
+#: src/gui_frontend/app_list_view/sidebar.rs:360
+#: src/gui_frontend/app_list_view/sidebar.rs:580
msgid "All games"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:448
+#: src/gui_frontend/app_list_view/sidebar.rs:442
msgid "Sort by"
msgstr ""
-#: src/gui_frontend/app_list_view/sidebar.rs:513
+#: src/gui_frontend/app_list_view/sidebar.rs:505
msgid "Reset filters"
msgstr ""
diff --git a/src/gui_frontend/achievement_manual_view/copy_mode.rs b/src/gui_frontend/achievement_manual_view/copy_mode.rs
index efd2ff6..e20df9a 100644
--- a/src/gui_frontend/achievement_manual_view/copy_mode.rs
+++ b/src/gui_frontend/achievement_manual_view/copy_mode.rs
@@ -23,7 +23,9 @@ use crate::gui_frontend::gobjects::mode_state::{GUnlockModeState, MODE_COPY_TIMI
use crate::gui_frontend::i18n::tr;
use crate::gui_frontend::request::{GetFriendUnlockTimes, GetFriends, GetUserAvatar, Request};
use crate::gui_frontend::unlock_queue::UnlockQueue;
-use crate::gui_frontend::unlock_scheduler::{compute_copy_timing_ms, run_timed_unlock};
+use crate::gui_frontend::unlock_scheduler::{
+ AchievementModelUpdates, compute_copy_timing_ms, run_timed_unlock,
+};
use crate::gui_frontend::widgets::shimmer_image::ShimmerImage;
use crate::utils::action_journal::Op;
use crate::utils::format::format_seconds_to_hh_mm_ss;
@@ -72,6 +74,7 @@ pub(super) fn install_copy_mode(
raw_model: &ListStore,
timed_raw_model: &ListStore,
cancelled_task: &Arc,
+ model_updates: &AchievementModelUpdates,
achievement_views_stack: &Stack,
application: &MainApplication,
) {
@@ -82,6 +85,7 @@ pub(super) fn install_copy_mode(
let raw_model = raw_model.clone();
let timed_raw_model = timed_raw_model.clone();
let cancelled_task = cancelled_task.clone();
+ let model_updates = model_updates.clone();
let achievement_views_stack = achievement_views_stack.clone();
let application = application.clone();
@@ -389,6 +393,8 @@ pub(super) fn install_copy_mode(
#[strong]
cancelled_task,
#[strong]
+ model_updates,
+ #[strong]
timed_raw_model,
#[weak(rename_to = raw_model)]
raw_model,
@@ -425,6 +431,8 @@ pub(super) fn install_copy_mode(
timed_raw_model,
#[strong]
cancelled_task,
+ #[strong]
+ model_updates,
async move {
run_timed_unlock(
app_id_val,
@@ -433,6 +441,7 @@ pub(super) fn install_copy_mode(
times_ms,
timed_raw_model,
cancelled_task,
+ model_updates,
)
.await;
}
diff --git a/src/gui_frontend/achievement_manual_view/mod.rs b/src/gui_frontend/achievement_manual_view/mod.rs
index 6fa4646..af4d57f 100644
--- a/src/gui_frontend/achievement_manual_view/mod.rs
+++ b/src/gui_frontend/achievement_manual_view/mod.rs
@@ -30,7 +30,8 @@ use crate::gui_frontend::gsettings::get_settings;
use crate::gui_frontend::i18n::tr;
use crate::gui_frontend::unlock_queue::{UnlockQueue, resolve_target_count};
use crate::gui_frontend::unlock_scheduler::{
- SPACING_EVEN, SPACING_RANDOM, compute_unlock_times_ms, run_timed_unlock, unlock_all_immediately,
+ AchievementModelUpdates, SPACING_EVEN, SPACING_RANDOM, compute_unlock_times_ms,
+ run_timed_unlock, unlock_all_immediately,
};
use crate::utils::action_journal::Op;
use crate::utils::format::format_achievement_progress;
@@ -111,6 +112,7 @@ pub fn create_achievements_manual_view(
app_id: &Rc>>,
app_unlocked_achievements_count: &Rc>,
filtered_model: &NoSelection,
+ model_updates: &AchievementModelUpdates,
raw_model: &ListStore,
timed_raw_model: &ListStore,
achievement_views_stack: &Stack,
@@ -118,6 +120,7 @@ pub fn create_achievements_manual_view(
application: &MainApplication,
) -> (Frame, Arc) {
let settings = get_settings();
+ let model_updates = model_updates.clone();
let mode_state = Rc::new(GUnlockModeState::default());
let initial_mode = settings.string("unlock-mode").to_string();
@@ -337,6 +340,8 @@ pub fn create_achievements_manual_view(
cancelled_task,
#[strong]
timed_raw_model,
+ #[strong]
+ model_updates,
#[weak]
application,
#[weak(rename_to = raw_model)]
@@ -386,6 +391,8 @@ pub fn create_achievements_manual_view(
timed_raw_model,
#[strong]
cancelled_task,
+ #[strong]
+ model_updates,
async move {
run_timed_unlock(
app_id_val,
@@ -394,6 +401,7 @@ pub fn create_achievements_manual_view(
times_ms,
timed_raw_model,
cancelled_task,
+ model_updates,
)
.await;
}
@@ -413,6 +421,7 @@ pub fn create_achievements_manual_view(
raw_model,
timed_raw_model,
&cancelled_task,
+ &model_updates,
achievement_views_stack,
application,
);
@@ -430,6 +439,7 @@ pub fn create_achievements_manual_view(
&header.queue_label,
&cancelled_task,
&update_autofill,
+ &model_updates,
);
let app_achievements_list_view = ListView::builder()
diff --git a/src/gui_frontend/achievement_manual_view/row_factory.rs b/src/gui_frontend/achievement_manual_view/row_factory.rs
index ddd649b..30b32b2 100644
--- a/src/gui_frontend/achievement_manual_view/row_factory.rs
+++ b/src/gui_frontend/achievement_manual_view/row_factory.rs
@@ -19,6 +19,7 @@ use crate::gui_frontend::gobjects::achievement::GAchievementObject;
use crate::gui_frontend::gobjects::mode_state::GUnlockModeState;
use crate::gui_frontend::request::{Request, SetAchievement};
use crate::gui_frontend::unlock_queue::UnlockQueue;
+use crate::gui_frontend::unlock_scheduler::AchievementModelUpdates;
use crate::gui_frontend::widgets::achievement_row::AchievementRow;
use crate::utils::action_journal::{Batch, Change, Op};
use crate::utils::format::format_achievement_progress;
@@ -44,6 +45,7 @@ pub(super) fn install_row_factory(
queue_label: &Label,
cancelled_task: &Arc,
update_autofill: &Rc,
+ model_updates: &AchievementModelUpdates,
) {
factory.connect_setup(clone!(
#[strong]
@@ -58,6 +60,8 @@ pub(super) fn install_row_factory(
cancelled_task,
#[strong]
update_autofill,
+ #[strong]
+ model_updates,
#[weak]
raw_model,
#[weak]
@@ -93,6 +97,8 @@ pub(super) fn install_row_factory(
raw_model,
#[weak]
start_button,
+ #[strong]
+ model_updates,
move |switch| {
let Some(achievement_object) =
list_item.item().and_downcast::()
@@ -143,12 +149,21 @@ pub(super) fn install_row_factory(
achievement_object,
#[weak]
start_button,
+ #[strong]
+ model_updates,
async move {
let result = handle.await.expect("spawn_blocking task panicked");
// Steam accepting the call and then failing to
// store it, which is a failure like any other.
match result {
Ok(true) => {
+ achievement_object.set_unlock_time_seconds(if unlocked {
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map_or(0, |duration| duration.as_secs())
+ } else {
+ 0
+ });
Batch::new(Op::ManualToggle, app_id_val, "").record(vec![
Change::Achievement {
id: achievement_object.id(),
@@ -180,6 +195,7 @@ pub(super) fn install_row_factory(
}
}
switch.set_sensitive(true);
+ model_updates.changed();
}
));
}
diff --git a/src/gui_frontend/achievement_view.rs b/src/gui_frontend/achievement_view.rs
index 2c074ad..fbd38b6 100644
--- a/src/gui_frontend/achievement_view.rs
+++ b/src/gui_frontend/achievement_view.rs
@@ -17,11 +17,14 @@ use crate::gui_frontend::MainApplication;
use crate::gui_frontend::achievement_automatic_view::create_achievements_automatic_view;
use crate::gui_frontend::achievement_manual_view::create_achievements_manual_view;
use crate::gui_frontend::gobjects::achievement::GAchievementObject;
-use gtk::gio::ListStore;
+use crate::gui_frontend::gsettings::get_settings;
+use crate::gui_frontend::unlock_scheduler::AchievementModelUpdates;
+use gtk::gio::{ListStore, SimpleAction};
+use gtk::glib;
use gtk::prelude::*;
use gtk::{
- CustomSorter, FilterListModel, Label, NoSelection, SortListModel, Stack, StackTransitionType,
- StringFilter, StringFilterMatchMode,
+ CustomFilter, CustomSorter, FilterChange, FilterListModel, Label, NoSelection, SortListModel,
+ SorterChange, Stack, StackTransitionType, StringFilter, StringFilterMatchMode,
};
use std::cell::Cell;
use std::cmp::Ordering;
@@ -29,12 +32,43 @@ use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
+#[derive(Clone, Copy, Default)]
+enum AchievementOrder {
+ SteamDefault,
+ Alphabetical,
+ #[default]
+ GlobalPercentage,
+ UnlockDate,
+}
+
+impl AchievementOrder {
+ fn from_action_target(value: &str) -> Option {
+ match value {
+ "steam-default" => Some(Self::SteamDefault),
+ "alphabetical" => Some(Self::Alphabetical),
+ "global-percentage" => Some(Self::GlobalPercentage),
+ "unlock-date" => Some(Self::UnlockDate),
+ _ => None,
+ }
+ }
+
+ fn as_action_target(self) -> &'static str {
+ match self {
+ Self::SteamDefault => "steam-default",
+ Self::Alphabetical => "alphabetical",
+ Self::GlobalPercentage => "global-percentage",
+ Self::UnlockDate => "unlock-date",
+ }
+ }
+}
+
pub fn create_achievements_view(
app_id: Rc>>,
app_unlocked_achievements_count: Rc| >,
application: &MainApplication,
app_achievement_count_value: &Label,
) -> (Stack, ListStore, StringFilter, Arc) {
+ let settings = get_settings();
let app_achievements_model = ListStore::new::();
let app_timed_achievements_model = ListStore::new::();
@@ -47,27 +81,126 @@ pub fn create_achievements_view(
.model(&app_achievements_model)
.filter(&app_achievement_string_filter)
.build();
+ // None shows all achievements; Some(false) and Some(true) show only
+ // locked and unlocked achievements respectively.
+ let achievement_status = Rc::new(Cell::new(None));
+ let achievement_status_filter = CustomFilter::new({
+ let achievement_status = Rc::clone(&achievement_status);
+ move |object| {
+ let achievement = object.downcast_ref::().unwrap();
+ achievement_status
+ .get()
+ .is_none_or(|unlocked| achievement.is_achieved() == unlocked)
+ }
+ });
+ let app_achievement_status_filter_model = FilterListModel::builder()
+ .model(&app_achievement_filter_model)
+ .filter(&achievement_status_filter)
+ .build();
let app_achievement_timed_filter_model = FilterListModel::builder()
.model(&app_timed_achievements_model)
.filter(&app_achievement_string_filter)
.build();
- let global_achieved_percent_sorter = CustomSorter::new(move |obj1, obj2| {
- let achievement1 = obj1.downcast_ref::().unwrap();
- let achievement2 = obj2.downcast_ref::().unwrap();
-
- let percent1 = achievement1.global_achieved_percent();
- let percent2 = achievement2.global_achieved_percent();
-
- percent2
- .partial_cmp(&percent1)
- .unwrap_or(Ordering::Equal)
- .into()
+ let initial_order = AchievementOrder::from_action_target(&settings.string("achievement-order"))
+ .unwrap_or_default();
+ let achievement_order = Rc::new(Cell::new(initial_order));
+ let achievement_sorter = CustomSorter::new({
+ let achievement_order = Rc::clone(&achievement_order);
+ move |obj1, obj2| {
+ let achievement1 = obj1.downcast_ref::().unwrap();
+ let achievement2 = obj2.downcast_ref::().unwrap();
+ match achievement_order.get() {
+ AchievementOrder::SteamDefault => Ordering::Equal.into(),
+ AchievementOrder::Alphabetical => achievement1
+ .name()
+ .to_lowercase()
+ .cmp(&achievement2.name().to_lowercase())
+ .into(),
+ AchievementOrder::UnlockDate => achievement2
+ .unlock_time_seconds()
+ .cmp(&achievement1.unlock_time_seconds())
+ .into(),
+ AchievementOrder::GlobalPercentage => achievement2
+ .global_achieved_percent()
+ .partial_cmp(&achievement1.global_achieved_percent())
+ .unwrap_or(Ordering::Equal)
+ .into(),
+ }
+ }
});
let app_achievement_sort_model = SortListModel::builder()
- .model(&app_achievement_filter_model)
- .sorter(&global_achieved_percent_sorter)
+ .model(&app_achievement_status_filter_model)
+ .sorter(&achievement_sorter)
.build();
+ if matches!(initial_order, AchievementOrder::SteamDefault) {
+ app_achievement_sort_model.set_sorter(None::<&CustomSorter>);
+ }
+ let achievement_model_updates =
+ AchievementModelUpdates::new(&achievement_status_filter, &achievement_sorter);
+
+ let order_action = SimpleAction::new_stateful(
+ "achievement-order",
+ Some(&String::static_variant_type()),
+ &initial_order.as_action_target().to_variant(),
+ );
+ order_action.connect_activate(glib::clone!(
+ #[strong]
+ achievement_order,
+ #[strong]
+ achievement_sorter,
+ #[strong]
+ settings,
+ #[weak(rename_to = sort_model)]
+ app_achievement_sort_model,
+ move |action, target| {
+ let Some(value) = target.and_then(|target| target.str()) else {
+ return;
+ };
+ let Some(order) = AchievementOrder::from_action_target(value) else {
+ return;
+ };
+ action.set_state(&value.to_variant());
+ if let Err(e) = settings.set_string("achievement-order", value) {
+ eprintln!("[CLIENT] Error saving achievement order: {e:?}");
+ }
+ achievement_order.set(order);
+ if matches!(order, AchievementOrder::SteamDefault) {
+ sort_model.set_sorter(None::<&CustomSorter>);
+ } else {
+ sort_model.set_sorter(Some(&achievement_sorter));
+ achievement_sorter.changed(SorterChange::Different);
+ }
+ }
+ ));
+ application.add_action(&order_action);
+
+ let state_action = SimpleAction::new_stateful(
+ "achievement-state",
+ Some(&String::static_variant_type()),
+ &"all".to_variant(),
+ );
+ state_action.connect_activate(glib::clone!(
+ #[strong]
+ achievement_status,
+ #[weak]
+ achievement_status_filter,
+ move |action, target| {
+ let Some(value) = target.and_then(|target| target.str()) else {
+ return;
+ };
+ let state = match value {
+ "all" => None,
+ "locked" => Some(false),
+ "unlocked" => Some(true),
+ _ => return,
+ };
+ action.set_state(&value.to_variant());
+ achievement_status.set(state);
+ achievement_status_filter.changed(FilterChange::Different);
+ }
+ ));
+ application.add_action(&state_action);
let app_achievement_selection_model = NoSelection::new(Option::::None);
app_achievement_selection_model.set_model(Some(&app_achievement_sort_model));
@@ -81,6 +214,7 @@ pub fn create_achievements_view(
&app_id,
&app_unlocked_achievements_count,
&app_achievement_selection_model,
+ &achievement_model_updates,
&app_achievements_model,
&app_timed_achievements_model,
&achievement_views_stack,
@@ -100,3 +234,13 @@ pub fn create_achievements_view(
cancel_timed_unlock,
)
}
+
+#[cfg(test)]
+mod tests {
+ use super::AchievementOrder;
+
+ #[test]
+ fn achievement_order_rejects_unknown_action_targets() {
+ assert!(AchievementOrder::from_action_target("unknown").is_none());
+ }
+}
diff --git a/src/gui_frontend/app_list_view/mod.rs b/src/gui_frontend/app_list_view/mod.rs
index 581a971..d03a0ae 100644
--- a/src/gui_frontend/app_list_view/mod.rs
+++ b/src/gui_frontend/app_list_view/mod.rs
@@ -43,8 +43,8 @@ use crate::gui_frontend::request::{
AppProgress, GetCollections, GetRunningApps, LaunchApp, Request, SetStealthMode, StopApp,
};
use crate::gui_frontend::ui_components::{
- create_context_menu_button, set_context_popover_to_app_list_context,
- set_context_popover_to_profile_context,
+ create_context_menu_button, set_app_details_view_options,
+ set_context_popover_to_app_list_context, set_context_popover_to_profile_context,
};
use crate::gui_frontend::widgets::steam_app_card::{CARD_HEIGHT, CARD_MIN_WIDTH, SteamAppCard};
use crate::utils::action_journal;
@@ -393,8 +393,8 @@ pub fn create_main_ui(
app_stack,
app_shimmer_image,
app_label,
- _app_achievements_button,
- _app_stats_button,
+ app_achievements_button,
+ app_stats_button,
app_achievement_count_value,
app_stats_count_value,
app_type_value,
@@ -475,6 +475,25 @@ pub fn create_main_ui(
context_menu_button_loading_progress_label,
context_menu_button_info_label,
) = create_context_menu_button();
+ app_achievements_button.connect_clicked(clone!(
+ #[weak]
+ menu_model,
+ move |_| set_app_details_view_options(&menu_model, true)
+ ));
+ app_stats_button.connect_clicked(clone!(
+ #[weak]
+ menu_model,
+ move |_| set_app_details_view_options(&menu_model, false)
+ ));
+ app_stack.connect_visible_child_name_notify(clone!(
+ #[weak]
+ menu_model,
+ move |stack| match stack.visible_child_name().as_deref() {
+ Some("achievements") => set_app_details_view_options(&menu_model, true),
+ Some("stats") => set_app_details_view_options(&menu_model, false),
+ _ => {}
+ }
+ ));
let sidebar_button = ToggleButton::builder()
.icon_name("sidebar-show-symbolic")
.tooltip_text(tr("Show or hide the sidebar").as_str())
diff --git a/src/gui_frontend/app_view.rs b/src/gui_frontend/app_view.rs
index 1315f3d..160338b 100644
--- a/src/gui_frontend/app_view.rs
+++ b/src/gui_frontend/app_view.rs
@@ -233,7 +233,8 @@ pub fn create_app_view(
&app_achievement_count_value,
);
- let (app_stat_scrolled_window, app_stat_model, app_stat_string_filter) = create_stats_view();
+ let (app_stat_scrolled_window, app_stat_model, app_stat_string_filter) =
+ create_stats_view(application);
let app_stack = Stack::builder()
.transition_type(StackTransitionType::SlideLeftRight)
diff --git a/src/gui_frontend/gobjects/achievement.rs b/src/gui_frontend/gobjects/achievement.rs
index f148b91..aa2ef4b 100644
--- a/src/gui_frontend/gobjects/achievement.rs
+++ b/src/gui_frontend/gobjects/achievement.rs
@@ -15,6 +15,7 @@
use glib::Object;
use gtk::glib;
+use std::time::UNIX_EPOCH;
use crate::backend::stat_definitions::AchievementInfo;
@@ -26,6 +27,10 @@ impl GAchievementObject {
pub fn new(info: AchievementInfo) -> Self {
let global_achieved_percent = info.global_achieved_percent.unwrap_or(0.0);
let global_achieved_percent_ok = info.global_achieved_percent.is_some();
+ let unlock_time_seconds = info
+ .unlock_time
+ .and_then(|time| time.duration_since(UNIX_EPOCH).ok())
+ .map_or(0, |duration| duration.as_secs());
Object::builder()
.property("search-text", format!("{} {}", info.name, info.description))
@@ -37,6 +42,7 @@ impl GAchievementObject {
"unlock-time",
info.unlock_time.map(|time| format!("{time:#?}")),
)
+ .property("unlock-time-seconds", unlock_time_seconds)
.property("icon-normal", info.icon_normal)
.property("icon-locked", info.icon_locked)
.property("permission", info.permission)
@@ -76,6 +82,9 @@ mod imp {
#[property(get, set)]
unlock_time: RefCell | | | |