Compare commits

3 Commits
beta ... main

Author SHA1 Message Date
6b11176015 minor fixes, API integration 2025-12-17 00:39:44 +01:00
154837d411 Update README.md 2025-12-09 00:13:53 +01:00
c1acd2dfcb Update README.md 2025-12-09 00:12:02 +01:00
18 changed files with 303 additions and 176 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
subprojects/blueprint-compiler subprojects/blueprint-compiler
_build

View File

@@ -5,10 +5,10 @@ This is a companion-app for Warframe players on Linux, written in phython and GT
## Features ## Features
- Manage a persistent list of which Basic and Prime warframes you own to help you plan what to get next! - Manage a persistent list of which Basic and Prime warframes you own to help you plan what to get next!
- Wishlist missing frames and track which parts you own and where to get the missing ones!
## Planned ## Planned
- Overview of needed parts, with ability to select which are already acquired, what is needed to craft them, and where/how to optain them.
- Overview of which frames have been subsumed. - Overview of which frames have been subsumed.
- Overview of which relics to prioritize based on which frames are missing or a wishlist of priority frames. (Potentially also integrate market data?) - Overview of which relics to prioritize based on which frames are missing or a wishlist of priority frames. (Potentially also integrate market data?)
- Support for weapons. - Support for weapons.
@@ -21,7 +21,21 @@ Ideally, cloning the repo and opening with GNOME Builder will have the best resu
$ git clone https://git.valhrafnaz.gay/valhrafnaz/VoidManifest.git $ git clone https://git.valhrafnaz.gay/valhrafnaz/VoidManifest.git
``` ```
Alternatively, you can run ninja yourself. Alternatively, you can build the project yourself.
```
$ cd VoidManifest
$ meson setup builddir
$ meson compile -C builddir
```
To be able to run the executable `./builddir/src/voidmanifest`, you must install the project with meson:
```
$ meson install -C builddir
```
Note that only the flatpak version of the program is supported, all issues referencing local installations will be deleted.
## License ## License

View File

@@ -24,11 +24,11 @@ template $ChecklistPage: Box {
} }
Label { Label {
label: _("List of all Warframes available as of Dec 10 '25"); label: _("List of all Warframes available as of today");
margin-bottom: 12; margin-bottom: 12;
styles [ styles [
"dim-label", "dimmed",
] ]
} }
@@ -155,7 +155,7 @@ template $ChecklistPage: Box {
} }
ToggleButton cyte09 { ToggleButton cyte09 {
name: "cyte09"; name: "cyte-09";
label: _("Cyte-09"); label: _("Cyte-09");
margin-bottom: 4; margin-bottom: 4;
} }
@@ -622,7 +622,7 @@ template $ChecklistPage: Box {
} }
ToggleButton cyte09_prime { ToggleButton cyte09_prime {
name: "cyte09_prime"; name: "cyte-09_prime";
label: _("Cyte-09 Prime"); label: _("Cyte-09 Prime");
margin-bottom: 4; margin-bottom: 4;
can-target: false; can-target: false;

View File

@@ -32,10 +32,10 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="label" translatable="yes">List of all Warframes available as of Dec 10 '25</property> <property name="label" translatable="yes">List of all Warframes available as of today</property>
<property name="margin-bottom">12</property> <property name="margin-bottom">12</property>
<style> <style>
<class name="dim-label"/> <class name="dimmed"/>
</style> </style>
</object> </object>
</child> </child>
@@ -189,7 +189,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child> </child>
<child> <child>
<object class="GtkToggleButton" id="cyte09"> <object class="GtkToggleButton" id="cyte09">
<property name="name">cyte09</property> <property name="name">cyte-09</property>
<property name="label" translatable="yes">Cyte-09</property> <property name="label" translatable="yes">Cyte-09</property>
<property name="margin-bottom">4</property> <property name="margin-bottom">4</property>
</object> </object>
@@ -740,7 +740,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child> </child>
<child> <child>
<object class="GtkToggleButton" id="cyte09_prime"> <object class="GtkToggleButton" id="cyte09_prime">
<property name="name">cyte09_prime</property> <property name="name">cyte-09_prime</property>
<property name="label" translatable="yes">Cyte-09 Prime</property> <property name="label" translatable="yes">Cyte-09 Prime</property>
<property name="margin-bottom">4</property> <property name="margin-bottom">4</property>
<property name="can-target">false</property> <property name="can-target">false</property>

View File

@@ -5,59 +5,65 @@ template $HomePage: Box {
orientation: vertical; orientation: vertical;
baseline-position: center; baseline-position: center;
Adw.Clamp { Adw.Clamp {
Adw.PreferencesGroup { ScrolledWindow {
title: _("Welcome back, Tenno"); hexpand: true;
styles [ vexpand: true;
'list-title' child: Adw.PreferencesGroup {
] margin-start:20;
Adw.ActionRow { margin-end: 20;
title: _("Owned Unique Frames:"); title: _("Welcome back, Tenno");
title-selectable: false; styles [
activatable: false; 'list-title'
]
Adw.ExpanderRow owned_frames_row {
title: _("Owned Unique Frames:");
title-selectable: false;
activatable: false;
[suffix] [suffix]
Label owned_frames { Label owned_frames {
}
} }
} Adw.ExpanderRow owned_basics_row {
Adw.ActionRow { title: _("Owned Basic Frames:");
title: _("Owned Basic Frames:"); title-selectable: false;
title-selectable: false; activatable: false;
activatable: false;
[suffix] [suffix]
Label owned_basics { Label owned_basics {
}
} }
}
Adw.ActionRow { Adw.ExpanderRow missing_basics_row {
title: _("Missing Basic Frames:"); title: _("Missing Basic Frames:");
title-selectable: false; title-selectable: false;
activatable: false; activatable: false;
[suffix] [suffix]
Label missing_basics { Label missing_basics {
}
} }
}
Adw.ActionRow { Adw.ExpanderRow owned_primes_row {
title: _("Owned Prime Frames:"); title: _("Owned Prime Frames:");
title-selectable: false; title-selectable: false;
activatable: false; activatable: false;
[suffix] [suffix]
Label owned_primes { Label owned_primes {
}
} }
}
Adw.ActionRow { Adw.ExpanderRow missing_primes_row {
title: _("Missing Prime Frames:"); title: _("Missing Prime Frames:");
title-selectable: false; title-selectable: false;
activatable: false; activatable: false;
[suffix] [suffix]
Label missing_primes { Label missing_primes {
}
} }
} };
} }
} }
} }

View File

@@ -12,61 +12,69 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<child> <child>
<object class="AdwClamp"> <object class="AdwClamp">
<child> <child>
<object class="AdwPreferencesGroup"> <object class="GtkScrolledWindow">
<property name="title" translatable="yes">Welcome back, Tenno</property> <property name="hexpand">true</property>
<style> <property name="vexpand">true</property>
<class name="list-title"/> <property name="child">
</style> <object class="AdwPreferencesGroup">
<child> <property name="margin-start">20</property>
<object class="AdwActionRow"> <property name="margin-end">20</property>
<property name="title" translatable="yes">Owned Unique Frames:</property> <property name="title" translatable="yes">Welcome back, Tenno</property>
<property name="title-selectable">false</property> <style>
<property name="activatable">false</property> <class name="list-title"/>
<child type="suffix"> </style>
<object class="GtkLabel" id="owned_frames"></object> <child>
<object class="AdwExpanderRow" id="owned_frames_row">
<property name="title" translatable="yes">Owned Unique Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_frames"></object>
</child>
</object>
</child>
<child>
<object class="AdwExpanderRow" id="owned_basics_row">
<property name="title" translatable="yes">Owned Basic Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_basics"></object>
</child>
</object>
</child>
<child>
<object class="AdwExpanderRow" id="missing_basics_row">
<property name="title" translatable="yes">Missing Basic Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="missing_basics"></object>
</child>
</object>
</child>
<child>
<object class="AdwExpanderRow" id="owned_primes_row">
<property name="title" translatable="yes">Owned Prime Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_primes"></object>
</child>
</object>
</child>
<child>
<object class="AdwExpanderRow" id="missing_primes_row">
<property name="title" translatable="yes">Missing Prime Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="missing_primes"></object>
</child>
</object>
</child> </child>
</object> </object>
</child> </property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Owned Basic Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_basics"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Missing Basic Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="missing_basics"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Owned Prime Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_primes"></object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Missing Prime Frames:</property>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="missing_primes"></object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>

View File

@@ -35,7 +35,7 @@ template $VoidmanifestWindow: Adw.ApplicationWindow {
// Main View // Main View
content: content:
Adw.ViewStack viewstack { Adw.ViewStack viewstack {
notify::visible-child => $on_page_changed();
}; };
[bottom] [bottom]
ActionBar { ActionBar {

View File

@@ -39,7 +39,9 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</object> </object>
</child> </child>
<property name="content"> <property name="content">
<object class="AdwViewStack" id="viewstack"></object> <object class="AdwViewStack" id="viewstack">
<signal name="notify::visible-child" handler="on_page_changed"/>
</object>
</property> </property>
<child type="bottom"> <child type="bottom">
<object class="GtkActionBar"> <object class="GtkActionBar">

View File

@@ -5,20 +5,25 @@ template $WishlistPage: Gtk.Box {
orientation: vertical; orientation: vertical;
baseline-position: center; baseline-position: center;
Adw.Clamp { Adw.Clamp {
Adw.PreferencesGroup wishlist { ScrolledWindow {
header-suffix: Button btn_wishlist_add { hexpand: true;
icon-name: 'plus-circle-outline-symbolic'; vexpand: true;
Adw.PreferencesGroup wishlist {
margin-end:20;
header-suffix: Button btn_wishlist_add {
icon-name: 'plus-circle-outline-symbolic';
styles [
'flat',
'circular',
'suggested-action'
]
};
separate-rows: true;
title: _("Wishlist");
styles [ styles [
'flat', "list-title"
'circular',
'suggested-action'
] ]
}; }
separate-rows: true;
title: _("Wishlist");
styles [
"list-title"
]
} }
} }
} }

View File

@@ -12,22 +12,29 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<child> <child>
<object class="AdwClamp"> <object class="AdwClamp">
<child> <child>
<object class="AdwPreferencesGroup" id="wishlist"> <object class="GtkScrolledWindow">
<property name="header-suffix"> <property name="hexpand">true</property>
<object class="GtkButton" id="btn_wishlist_add"> <property name="vexpand">true</property>
<property name="icon-name">plus-circle-outline-symbolic</property> <child>
<object class="AdwPreferencesGroup" id="wishlist">
<property name="margin-end">20</property>
<property name="header-suffix">
<object class="GtkButton" id="btn_wishlist_add">
<property name="icon-name">plus-circle-outline-symbolic</property>
<style>
<class name="flat"/>
<class name="circular"/>
<class name="suggested-action"/>
</style>
</object>
</property>
<property name="separate-rows">true</property>
<property name="title" translatable="yes">Wishlist</property>
<style> <style>
<class name="flat"/> <class name="list-title"/>
<class name="circular"/>
<class name="suggested-action"/>
</style> </style>
</object> </object>
</property> </child>
<property name="separate-rows">true</property>
<property name="title" translatable="yes">Wishlist</property>
<style>
<class name="list-title"/>
</style>
</object> </object>
</child> </child>
</object> </object>

View File

@@ -6,7 +6,7 @@ EXISTING_FRAMES_BASIC = [
"caliban", "caliban",
"chroma", "chroma",
"citrine", "citrine",
"cyte09", "cyte-09",
"dagath", "dagath",
"dante", "dante",
"ember", "ember",
@@ -52,7 +52,6 @@ EXISTING_FRAMES_BASIC = [
"temple", "temple",
"titania", "titania",
"trinity", "trinity",
"uriel",
"valkyr", "valkyr",
"vauban", "vauban",
"volt", "volt",
@@ -79,7 +78,6 @@ EXISTING_FRAMES_PRIME = [
"garuda_prime", "garuda_prime",
"gauss_prime", "gauss_prime",
"grendel_prime", "grendel_prime",
"gyre_prime",
"harrow_prime", "harrow_prime",
"hildryn_prime", "hildryn_prime",
"hydroid_prime", "hydroid_prime",
@@ -119,7 +117,6 @@ EXISTING_FRAMES_PRIME = [
ID_TO_NAME: dict[str, str] = dict() ID_TO_NAME: dict[str, str] = dict()
NAME_TO_ID: dict[str, str] = dict() NAME_TO_ID: dict[str, str] = dict()
# warframe counts # warframe counts
# TODO Update via API/wiki
BASIC_WARFRAMES = 63 BASIC_WARFRAMES = 63
PRIME_WARFRAMES = 49 PRIME_WARFRAMES = 49
CURRENT_UPDATE = 41 CURRENT_UPDATE = 41

View File

@@ -125,7 +125,7 @@ class VoidmanifestApplication(Adw.Application):
def _refresh_metadata(self): def _refresh_metadata(self):
url = 'https://api.warframestat.us/warframes' url = 'https://api.warframestat.us/warframes'
params = 'only=name,isPrime' params = 'only=name,isPrime,category'
resp = requests.get(url, params) resp = requests.get(url, params)
base_frames_list: list[str] = list() base_frames_list: list[str] = list()
prime_frames_list: list[str] = list() prime_frames_list: list[str] = list()
@@ -135,6 +135,9 @@ class VoidmanifestApplication(Adw.Application):
else: else:
print('CRITICAL ERROR: Could not refresh metadata from https://api.warframestat.us/') print('CRITICAL ERROR: Could not refresh metadata from https://api.warframestat.us/')
for warframe in resp_data: for warframe in resp_data:
# skip every non-warframe
if warframe['category'] != 'Warframes':
continue
name = warframe['name'] name = warframe['name']
# skip these two frames since they cannot be acquired anymore and break symmetry in the list # skip these two frames since they cannot be acquired anymore and break symmetry in the list
if name == 'Excalibur Umbra Prime' or name == 'Excalibur Prime': if name == 'Excalibur Umbra Prime' or name == 'Excalibur Prime':
@@ -145,12 +148,23 @@ class VoidmanifestApplication(Adw.Application):
prime_frames_list.append(frame_id) prime_frames_list.append(frame_id)
else: else:
base_frames_list.append(frame_id) base_frames_list.append(frame_id)
# TODO REMOVE
# only needed until API is finally updated
base_frames_list.append('uriel')
prime_frames_list.append('gyre_prime')
base_frames_list = sorted(list(set(base_frames_list)))
prime_frames_list = sorted(list(set(prime_frames_list)))
EXISTING_FRAMES_BASIC = base_frames_list.copy() EXISTING_FRAMES_BASIC = base_frames_list.copy()
EXISTING_FRAMES_PRIME = prime_frames_list.copy() EXISTING_FRAMES_PRIME = prime_frames_list.copy()
BASIC_WARFRAMES = len(EXISTING_FRAMES_BASIC) BASIC_WARFRAMES = len(EXISTING_FRAMES_BASIC)
PRIME_WARFRAMES = len(EXISTING_FRAMES_PRIME) PRIME_WARFRAMES = len(EXISTING_FRAMES_PRIME)
# dictionary that translates ids to names for better responsiveness on UI # dictionary that translates ids to names for better responsiveness on UI
ID_TO_NAME[frame_id] = name ID_TO_NAME[frame_id] = name
# TODO REMOVE
# only needed until API is finally updated
ID_TO_NAME['gyre_prime'] = 'Gyre Prime'
ID_TO_NAME['uriel'] = 'Uriel'
NAME_TO_ID = dict((v,k) for k,v in ID_TO_NAME.items()) NAME_TO_ID = dict((v,k) for k,v in ID_TO_NAME.items())
@@ -170,7 +184,7 @@ class VoidmanifestApplication(Adw.Application):
json.dump(profile.to_dict(), f, indent=2) json.dump(profile.to_dict(), f, indent=2)
def reset_frames(self, param): def reset_frames(self, param):
self.profile.owned_frames = {} self.profile.owned_frames = []
button = self.checklist_page.btns_basic.get_first_child() button = self.checklist_page.btns_basic.get_first_child()
while button is not None: while button is not None:
if isinstance(button, Gtk.ToggleButton): if isinstance(button, Gtk.ToggleButton):
@@ -188,7 +202,7 @@ class VoidmanifestApplication(Adw.Application):
basic_count = 0 basic_count = 0
missing_basics: list[str] = [] missing_basics: list[str] = []
for basic_frame in EXISTING_FRAMES_BASIC: for basic_frame in EXISTING_FRAMES_BASIC:
if self.profile.owned_frames.get(basic_frame, False): if basic_frame in self.profile.owned_frames:
basic_count = basic_count + 1 basic_count = basic_count + 1
else: else:
missing_basics.append(basic_frame) missing_basics.append(basic_frame)
@@ -196,18 +210,18 @@ class VoidmanifestApplication(Adw.Application):
prime_count = 0 prime_count = 0
missing_primes: list[str] = [] missing_primes: list[str] = []
for prime_frame in EXISTING_FRAMES_PRIME: for prime_frame in EXISTING_FRAMES_PRIME:
if self.profile.owned_frames.get(prime_frame, False): if prime_frame in self.profile.owned_frames:
prime_count = prime_count + 1 prime_count = prime_count + 1
else: else:
missing_primes.append(prime_frame) missing_primes.append(prime_frame)
# count unique # count unique
unique_frames: set[str] = set() unique_frames: set[str] = set()
for owned_frame, is_owned in self.profile.owned_frames.items(): for owned_frame in self.profile.owned_frames:
if is_owned: _name = owned_frame.removesuffix("_prime")
_name = owned_frame.removesuffix("_prime") _name = _name.removesuffix("_umbra")
_name = _name.removesuffix("_umbra") unique_frames.add(_name)
unique_frames.add(_name)
self.profile.unique_frames_list = list(unique_frames)
self.profile.missing_basics_count = len(missing_basics) self.profile.missing_basics_count = len(missing_basics)
self.profile.missing_primes_count = len(missing_primes) self.profile.missing_primes_count = len(missing_primes)
self.profile.unique_frames_owned = len(unique_frames) self.profile.unique_frames_owned = len(unique_frames)

View File

@@ -44,14 +44,14 @@ class ChecklistPage(Gtk.Box):
while button is not None: while button is not None:
if isinstance(button, Gtk.ToggleButton): if isinstance(button, Gtk.ToggleButton):
btn_id = button.get_name() btn_id = button.get_name()
button.set_active(self.app.profile.owned_frames.get(btn_id, False)) button.set_active((btn_id in self.app.profile.owned_frames))
button.connect("toggled", self._on_button_toggled) button.connect("toggled", self._on_button_toggled)
button = button.get_next_sibling() button = button.get_next_sibling()
button = self.btns_prime.get_first_child() button = self.btns_prime.get_first_child()
while button is not None: while button is not None:
if isinstance(button, Gtk.ToggleButton): if isinstance(button, Gtk.ToggleButton):
btn_id = button.get_name() btn_id = button.get_name()
button.set_active(self.app.profile.owned_frames.get(btn_id, False)) button.set_active((btn_id in self.app.profile.owned_frames))
button.connect("toggled", self._on_button_toggled) button.connect("toggled", self._on_button_toggled)
button = button.get_next_sibling() button = button.get_next_sibling()
@@ -90,7 +90,12 @@ class ChecklistPage(Gtk.Box):
if btn_id == "GtkToggleButton": if btn_id == "GtkToggleButton":
print("what") print("what")
return return
self.app.profile.owned_frames[btn_id] = btn.get_active() if btn.get_active():
self.app.profile.owned_frames.append(btn_id)
if btn_id in self.app.profile.wishlist(btn_id):
self.app.profile.wishlist.remove(btn_id)
else:
self.app.profile.owned_frames.remove(btn_id)
self.app.save_profile(self.app.profile) self.app.save_profile(self.app.profile)
self._calc_frames() self._calc_frames()

View File

@@ -1,6 +1,6 @@
import gi import gi
from ..constants import BASIC_WARFRAMES, PRIME_WARFRAMES from ..constants import BASIC_WARFRAMES, PRIME_WARFRAMES, ID_TO_NAME
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
@@ -12,29 +12,80 @@ from gi.repository import Gtk, Adw, GObject, GLib # noqa: E402
class HomePage(Gtk.Box): class HomePage(Gtk.Box):
__gtype_name__ = "HomePage" __gtype_name__ = "HomePage"
app = None
owned_frames = Gtk.Template.Child() owned_frames = Gtk.Template.Child()
owned_frames_row = Gtk.Template.Child()
owned_basics = Gtk.Template.Child() owned_basics = Gtk.Template.Child()
owned_basics_row = Gtk.Template.Child()
missing_basics = Gtk.Template.Child() missing_basics = Gtk.Template.Child()
missing_basics_row = Gtk.Template.Child()
owned_primes = Gtk.Template.Child() owned_primes = Gtk.Template.Child()
owned_primes_row = Gtk.Template.Child()
missing_primes = Gtk.Template.Child() missing_primes = Gtk.Template.Child()
missing_primes_row = Gtk.Template.Child()
def __init__(self, *, parent: Adw.ViewStack): def __init__(self, *, parent: Adw.ViewStack):
"""
`parent` is the ViewStack that will host this page.
By passing it to the superclass constructor we tell GTK to
insert the newly created page into that stack automatically.
"""
super().__init__() super().__init__()
parent.add_titled(self, "home", "Home") parent.add_titled(self, "home", "Home")
wrapper = parent.get_page(self) wrapper = parent.get_page(self)
wrapper.set_icon_name("compass2-symbolic") wrapper.set_icon_name("compass2-symbolic")
self.app = self.get_root().get_application() self.app = self.get_root().get_application()
self.app.profile._on_change = self.refresh self._unique_rows: dict[str, Adw.ActionRow] = {}
self._missing_basics_rows: dict[str, Adw.ActionRow] = {}
self._missing_primes_rows: dict[str, Adw.ActionRow] = {}
self._basic_rows: dict[str, Adw.ActionRow] = {}
self._prime_rows: dict[str, Adw.ActionRow] = {}
self.refresh() self.refresh()
def refresh(self): def refresh(self):
# Prevents race condition where refresh is called before __init__
if self.app is None:
return
self.app.profile.owned_frames = sorted(list(set(self.app.profile.owned_frames)))
# set labels
self.owned_frames.set_label(str (self.app.profile.unique_frames_owned)) self.owned_frames.set_label(str (self.app.profile.unique_frames_owned))
self.owned_basics.set_label(str (BASIC_WARFRAMES - self.app.profile.missing_basics_count)) self.owned_basics.set_label(str (BASIC_WARFRAMES - self.app.profile.missing_basics_count))
self.owned_primes.set_label(str (PRIME_WARFRAMES - self.app.profile.missing_primes_count)) self.owned_primes.set_label(str (PRIME_WARFRAMES - self.app.profile.missing_primes_count))
self.missing_basics.set_label(str(self.app.profile.missing_basics_count)) self.missing_basics.set_label(str(self.app.profile.missing_basics_count))
self.missing_primes.set_label(str(self.app.profile.missing_primes_count)) self.missing_primes.set_label(str(self.app.profile.missing_primes_count))
# populate dropdowns
for frame in self.app.profile.owned_frames:
if "_prime" in frame or "_umbra" in frame:
row = Adw.ActionRow(
title=ID_TO_NAME[frame]
)
if not self._prime_rows.get(frame, False):
self._prime_rows[frame] = row
self.owned_primes_row.add_row(row)
else:
row = Adw.ActionRow(
title=ID_TO_NAME[frame]
)
if not self._basic_rows.get(frame, False):
self._basic_rows[frame] = row
self.owned_basics_row.add_row(row)
frame = frame.replace('_prime', '')
row = Adw.ActionRow(
title=ID_TO_NAME[frame]
)
if not self._unique_rows.get(frame, False):
self._unique_rows[frame] = row
self.owned_frames_row.add_row(row)
for prime in self.app.profile.missing_primes:
row = Adw.ActionRow(
title=ID_TO_NAME[prime]
)
if not self._missing_primes_rows.get(prime, False):
self._missing_primes_rows[prime] = row
self.missing_primes_row.add_row(row)
for basic in self.app.profile.missing_basics:
row = Adw.ActionRow(
title=ID_TO_NAME[basic]
)
if not self._missing_basics_rows.get(basic, False):
self._missing_basics_rows[basic] = row
self.missing_basics_row.add_row(row)

View File

@@ -25,9 +25,10 @@ class WishlistPage(Gtk.Box):
wrapper.set_icon_name("logs-symbolic") wrapper.set_icon_name("logs-symbolic")
self.btn_wishlist_add.connect('clicked', self._open_wishlist_dialogue) self.btn_wishlist_add.connect('clicked', self._open_wishlist_dialogue)
self._wishlist_rows: dict[str, Adw.ActionRow] = {} self._wishlist_rows: dict[str, Adw.ActionRow] = {}
self.refresh_wishlist()
def _open_wishlist_dialogue(self, button): def _open_wishlist_dialogue(self, button):
dialog = FramePickerDialog(missing_frames=self.app.profile.missing_basics + self.app.profile.missing_primes) dialog = FramePickerDialog(missing_frames=self.app.profile.missing_basics + self.app.profile.missing_primes, app=self.app)
dialog.connect('frame-selected', self.add_frame_to_wishlist) dialog.connect('frame-selected', self.add_frame_to_wishlist)
dialog.present(self.get_root()) dialog.present(self.get_root())
@@ -52,7 +53,7 @@ class WishlistPage(Gtk.Box):
self.wishlist.add(row) self.wishlist.add(row)
self.app.profile.wishlist.append(frame_id) self.app.profile.wishlist.append(frame_id)
self.app.profile.wishlist = sorted(self.app.profile.wishlist) self.app.profile.wishlist = sorted(self.app.profile.wishlist)
self._refresh_wishlist self.refresh_wishlist
def btn_remove(self, button): def btn_remove(self, button):
self.remove_frame_from_wishlist(button.frame_id) self.remove_frame_from_wishlist(button.frame_id)
@@ -61,14 +62,24 @@ class WishlistPage(Gtk.Box):
if frame_id in self._wishlist_rows: if frame_id in self._wishlist_rows:
row = self._wishlist_rows.pop(frame_id) row = self._wishlist_rows.pop(frame_id)
self.wishlist.remove(row) self.wishlist.remove(row)
if frame_id in self.app.profile.wishlist:
self.app.profile.wishlist.remove(frame_id)
def has_frame(self, frame_id) -> bool: def has_frame(self, frame_id) -> bool:
return frame_id in self._wishlist_rows return frame_id in self._wishlist_rows
def _refresh_wishlist(self): def refresh_wishlist(self):
# deduplicate
self.app.profile.wishlist = list(set(self.app.profile.wishlist))
for frame_id in self.app.profile.wishlist: for frame_id in self.app.profile.wishlist:
if frame_id not in self._wishlist_rows: if frame_id not in self._wishlist_rows:
self.add_frame_to_wishlist(Adw.Dialog.new(),frame_id) self.add_frame_to_wishlist(Adw.Dialog.new(),frame_id)
for frame_id in list(self._wishlist_rows): to_delete: list[str] = []
for frame_id in self._wishlist_rows:
if frame_id not in self.app.profile.wishlist: if frame_id not in self.app.profile.wishlist:
self.remove_frame_from_wishlist(frame_id) to_delete.append(frame_id)
if frame_id in self.app.profile.owned_frames:
to_delete.append(frame_id)
for frame_id in to_delete:
self.remove_frame_from_wishlist(frame_id)

View File

@@ -5,22 +5,15 @@ from .constants import BASIC_WARFRAMES, PRIME_WARFRAMES
# pseudo struct that contains the profile data that gets serialized into the .json profile. # pseudo struct that contains the profile data that gets serialized into the .json profile.
@dataclass @dataclass
class Profile: class Profile:
owned_frames: dict[str, bool] = field(default_factory=dict) owned_frames: list[str] = field(default_factory=list)
unique_frames_owned: int = 0 unique_frames_owned: int = 0
unique_frames_list: list[str] = field(default_factory=list)
missing_basics: list[str] = field(default_factory=list) missing_basics: list[str] = field(default_factory=list)
missing_basics_count: int = BASIC_WARFRAMES missing_basics_count: int = BASIC_WARFRAMES
missing_primes: list[str] = field(default_factory=list) missing_primes: list[str] = field(default_factory=list)
missing_primes_count: int = PRIME_WARFRAMES missing_primes_count: int = PRIME_WARFRAMES
wishlist: list[str] = field(default_factory=list) wishlist: list[str] = field(default_factory=list)
_on_change: Callable | None = field(default=None, repr=False, compare=False)
def __setattr__(self, name, value):
super().__setattr__(name, value)
# Notify on change (skip private attributes)
if not name.startswith('_') and hasattr(self, '_on_change') and self._on_change:
self._on_change()
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""Convert to dict, excluding private fields""" """Convert to dict, excluding private fields"""
return { return {

View File

@@ -16,9 +16,9 @@ class FramePickerDialog(Adw.Dialog):
search_entry = Gtk.Template.Child() search_entry = Gtk.Template.Child()
frame_list = Gtk.Template.Child() frame_list = Gtk.Template.Child()
def __init__(self, missing_frames: list[str], **kwargs): def __init__(self, app: Adw.ApplicationWindow, missing_frames: list[str], **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.app = app
self.missing_frames = missing_frames self.missing_frames = missing_frames
self._populate_list() self._populate_list()
@@ -26,11 +26,12 @@ class FramePickerDialog(Adw.Dialog):
def _populate_list(self): def _populate_list(self):
for frame_name in sorted(self.missing_frames): for frame_name in sorted(self.missing_frames):
row = Adw.ActionRow(title=frame_name.replace("_", " ").title(), if frame_name not in self.app.profile.wishlist:
activatable=True row = Adw.ActionRow(title=frame_name.replace("_", " ").title(),
) activatable=True
row.frame_id = frame_name )
self.frame_list.append(row) row.frame_id = frame_name
self.frame_list.append(row)
def _filter_func(self, row): def _filter_func(self, row):
search_text = self.search_entry.get_text().lower() search_text = self.search_entry.get_text().lower()

View File

@@ -56,4 +56,16 @@ class VoidmanifestWindow(Adw.ApplicationWindow):
self.app.save_profile(self.app.profile) self.app.save_profile(self.app.profile)
return False return False
@Gtk.Template.Callback()
def on_page_changed(self, stack, param):
self.app.refresh_profile()
visible_child = stack.get_visible_child()
visible_name = stack.get_visible_child_name()
match visible_name:
case 'home':
visible_child.refresh()
case 'wishlist':
visible_child.refresh_wishlist()
case _:
return