finished wishlist

This commit is contained in:
2025-12-08 23:35:12 +01:00
parent 16523c2915
commit 59367bac5f
22 changed files with 356 additions and 27 deletions

View File

@@ -1,5 +1,6 @@
[Desktop Entry]
Name=Voidmanifest
Name=Void Manifest
Comment=A Warframe Companion
Exec=voidmanifest
Icon=gay.valhrafnaz.Voidmanifest
Terminal=false

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#241f31"><path d="m 6.5 4.992188 h 2 c 0.277344 0 0.5 -0.222657 0.5 -0.5 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -2 c -0.277344 0 -0.5 0.222656 -0.5 0.5 c 0 0.277343 0.222656 0.5 0.5 0.5 z m 0 2 h 2 c 0.277344 0 0.5 -0.222657 0.5 -0.5 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -2 c -0.277344 0 -0.5 0.222656 -0.5 0.5 c 0 0.277343 0.222656 0.5 0.5 0.5 z m 0 2 h 2 c 0.277344 0 0.5 -0.222657 0.5 -0.5 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 h -2 c -0.277344 0 -0.5 0.222656 -0.5 0.5 c 0 0.277343 0.222656 0.5 0.5 0.5 z m 0 0"/><path d="m 11 5.992188 h 5 v -2.96875 c 0 -1.65625 -1.34375 -3.0000005 -3 -3.0000005 s -3 1.3437505 -3 3.0000005 v 9.96875 c 0 0.5625 -0.4375 1 -1 1 s -1 -0.4375 -1 -1 v -3 h -8 v 3 c 0 1.65625 1.34375 3 3 3 h 6 v -2 h -6 c -0.5625 0 -1 -0.4375 -1 -1 v -2 l -1 1 h 6 l -1 -1 v 2 c 0 1.65625 1.34375 3 3 3 s 3 -1.34375 3 -3 v -9.96875 c 0 -0.5625 0.4375 -1 1 -1 s 1 0.4375 1 1 v 1.96875 l 1 -1 h -4 z m 0 0"/><path d="m 5 10.992188 v -8 c 0 -0.5625 0.4375 -1 1 -1 h 7 c 0.550781 0 1 -0.449219 1 -1 c 0 -0.550782 -0.449219 -1.0000005 -1 -1.0000005 h -7 c -1.65625 0 -3 1.3437505 -3 3.0000005 v 8 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 0 c -4.402344 0 -8 3.597656 -8 8 s 3.597656 8 8 8 s 8 -3.597656 8 -8 s -3.597656 -8 -8 -8 z m 0 1.980469 c 3.339844 0 6.015625 2.679687 6.015625 6.019531 s -2.675781 6.019531 -6.015625 6.019531 s -6.019531 -2.679687 -6.019531 -6.019531 s 2.679687 -6.019531 6.019531 -6.019531 z m -4 5.050781 v 2 h 8 v -2 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 476 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 0 c -4.402344 0 -8 3.597656 -8 8 s 3.597656 8 8 8 s 8 -3.597656 8 -8 s -3.597656 -8 -8 -8 z m 0 1.980469 c 3.339844 0 6.015625 2.679687 6.015625 6.019531 s -2.675781 6.019531 -6.015625 6.019531 s -6.019531 -2.679687 -6.019531 -6.019531 s 2.679687 -6.019531 6.019531 -6.019531 z m -1 2.050781 v 3 h -3 v 2 h 3 v 3 h 2 v -3 h 3 v -2 h -3 v -3 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 512 B

View File

@@ -9,8 +9,10 @@ blueprints = custom_target('blueprints',
'ui/checklist.blp',
'ui/settings.blp',
'ui/welcome.blp',
'ui/home.blp'
),
'ui/home.blp',
'ui/wishlist.blp',
'ui/framepicker.blp'
),
output: '.',
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT', '@CURRENT_SOURCE_DIRECTORY@' , '@INPUT@'],
)

View File

@@ -29,3 +29,9 @@ flowboxchild:hover {
.bold {
font-weight: bold;
}
.list-title > box > box.header {
font-size: 1.75rem;
font-weight: bold;
}

View File

@@ -0,0 +1,33 @@
using Gtk 4.0;
using Adw 1;
template $FramePickerDialog: Adw.Dialog {
content-width: 400;
content-height: 300;
child: Adw.ToolbarView {
content: Box {
orientation: vertical;
spacing: 12;
margin-top: 12;
margin-bottom: 12;
margin-start: 12;
margin-end: 12;
SearchEntry search_entry {
placeholder-text: _("Search missing warframes…");
search-changed => $on_search_changed();
}
ScrolledWindow {
vexpand: true;
ListBox frame_list {
selection-mode: single;
styles [ "boxed-list" ]
row-activated => $on_row_activated();
}
}
};
};
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<template class="FramePickerDialog" parent="AdwDialog">
<property name="content-width">400</property>
<property name="content-height">300</property>
<property name="child">
<object class="AdwToolbarView">
<property name="content">
<object class="GtkBox">
<property name="orientation">1</property>
<property name="spacing">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="placeholder-text" translatable="yes">Search missing warframes…</property>
<signal name="search-changed" handler="on_search_changed"/>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<child>
<object class="GtkListBox" id="frame_list">
<property name="selection-mode">1</property>
<style>
<class name="boxed-list"/>
</style>
<signal name="row-activated" handler="on_row_activated"/>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</template>
</interface>

View File

@@ -7,6 +7,9 @@ template $HomePage: Box {
Adw.Clamp {
Adw.PreferencesGroup {
title: _("Welcome back, Tenno");
styles [
'list-title'
]
Adw.ActionRow {
title: _("Owned Unique Frames:");
title-selectable: false;

View File

@@ -14,6 +14,9 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Welcome back, Tenno</property>
<style>
<class name="list-title"/>
</style>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Owned Unique Frames:</property>

View File

@@ -0,0 +1,24 @@
using Gtk 4.0;
using Adw 1;
template $WishlistPage: Gtk.Box {
orientation: vertical;
baseline-position: center;
Adw.Clamp {
Adw.PreferencesGroup wishlist {
header-suffix: Button btn_wishlist_add {
icon-name: 'plus-circle-outline-symbolic';
styles [
'flat',
'circular',
'suggested-action'
]
};
separate-rows: true;
title: _("Wishlist");
styles [
"list-title"
]
}
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<template class="WishlistPage" parent="GtkBox">
<property name="orientation">1</property>
<property name="baseline-position">1</property>
<child>
<object class="AdwClamp">
<child>
<object class="AdwPreferencesGroup" id="wishlist">
<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>
<class name="list-title"/>
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@@ -6,13 +6,16 @@
<file preprocess="xml-stripblanks">ui/shortcuts-dialog.ui</file>
<file preprocess="xml-stripblanks">ui/checklist.ui</file>
<file preprocess="xml-stripblanks">ui/home.ui</file>
<file preprocess="xml-stripblanks">ui/settings.ui</file>
<file preprocess="xml-stripblanks">ui/wishlist.ui</file>
<file preprocess="xml-stripblanks">ui/welcome.ui</file>
<file preprocess="xml-stripblanks">ui/framepicker.ui</file>
</gresource>
<gresource prefix="/gay/valhrafnaz/Voidmanifest/">
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/floppy-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/check-round-outline2-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/compass2-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/settings-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/logs-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/hicolor/symbolic/actions/minus-circle-outline-symbolic.svg</file>
</gresource>
</gresources>

View File

@@ -23,6 +23,18 @@
"*.a"
],
"modules" : [
{
"name": "blueprint-compiler",
"buildsystem": "meson",
"cleanup": ["*"],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
"tag": "v0.18.0"
}
]
},
{
"name" : "Voidmanifest",
"builddir" : true,
@@ -34,18 +46,7 @@
"branch" : "main"
}
]
},
{
"name": "blueprint-compiler",
"buildsystem": "meson",
"cleanup": ["*"],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
"tag": "v0.18.0"
}
]
}
}
]
}

View File

@@ -3,6 +3,13 @@
data/gay.valhrafnaz.Voidmanifest.desktop.in
data/gay.valhrafnaz.Voidmanifest.metainfo.xml.in
data/gay.valhrafnaz.Voidmanifest.gschema.xml
data/resources/ui/checklist.ui
data/resources/ui/home.ui
data/resources/ui/wishlist.ui
data/resources/ui/window.ui
src/main.py
src/pages/checklist.py
src/pages/home.py
src/pages/wishlist.py
src/window.py
src/window.ui

View File

@@ -116,6 +116,8 @@ EXISTING_FRAMES_PRIME = [
"zephyr_prime"
]
ID_TO_NAME: dict[str, str] = dict()
NAME_TO_ID: dict[str, str] = dict()
# warframe counts
# TODO Update via API/wiki
BASIC_WARFRAMES = 63

View File

@@ -21,6 +21,7 @@ import gi
import os
import json
import math
import requests
from pathlib import Path
@@ -28,7 +29,7 @@ from pathlib import Path
# local imports
from .window import VoidmanifestWindow
from .profile import Profile
from .constants import EXISTING_FRAMES_BASIC, EXISTING_FRAMES_PRIME, BASIC_WARFRAMES, PRIME_WARFRAMES, CURRENT_UPDATE
from .constants import EXISTING_FRAMES_BASIC, EXISTING_FRAMES_PRIME, BASIC_WARFRAMES, PRIME_WARFRAMES, CURRENT_UPDATE, ID_TO_NAME, NAME_TO_ID
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
@@ -77,7 +78,7 @@ class VoidmanifestApplication(Adw.Application):
self.create_action('preferences', self.on_preferences_action)
self.CONFIG_DIR = get_config_dir(self.APP_ID)
self._refresh_metadata()
self.profile = self.load_profile()
def do_activate(self):
@@ -93,10 +94,10 @@ class VoidmanifestApplication(Adw.Application):
def on_about_action(self, *args):
"""Callback for the app.about action."""
about = Adw.AboutDialog(application_name='voidmanifest',
about = Adw.AboutDialog(application_name='Void Manifest',
application_icon='gay.valhrafnaz.Voidmanifest',
developer_name='valhrafnaz',
version='0.1.0',
version='1.0.1',
developers=['valhrafnaz'],
copyright='© 2025 valhrafnaz')
# Translators: Replace "translator-credits" with your name/username, and optionally an email or URL.
@@ -122,6 +123,38 @@ class VoidmanifestApplication(Adw.Application):
if shortcuts:
self.set_accels_for_action(f"app.{name}", shortcuts)
def _refresh_metadata(self):
url = 'https://api.warframestat.us/warframes'
params = 'only=name,isPrime'
resp = requests.get(url, params)
base_frames_list: list[str] = list()
prime_frames_list: list[str] = list()
# Build id to name dict
if resp.ok:
resp_data=json.loads(resp.content)
else:
print('CRITICAL ERROR: Could not refresh metadata from https://api.warframestat.us/')
for warframe in resp_data:
name = warframe['name']
# 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':
continue
frame_id = name.replace(' ', '_').lower()
# Excalibur Umbra is a prime frame in all but name (even if there is technically a prime of it)
if warframe['isPrime'] or name == 'Excalibur Umbra':
prime_frames_list.append(frame_id)
else:
base_frames_list.append(frame_id)
EXISTING_FRAMES_BASIC = base_frames_list.copy()
EXISTING_FRAMES_PRIME = prime_frames_list.copy()
BASIC_WARFRAMES = len(EXISTING_FRAMES_BASIC)
PRIME_WARFRAMES = len(EXISTING_FRAMES_PRIME)
# dictionary that translates ids to names for better responsiveness on UI
ID_TO_NAME[frame_id] = name
NAME_TO_ID = dict((v,k) for k,v in ID_TO_NAME.items())
def load_profile(self) -> Profile:
try:
with open(self.CONFIG_FILE, 'r') as f:

View File

@@ -0,0 +1,74 @@
import gi
import json
import requests
from ..constants import EXISTING_FRAMES_BASIC, EXISTING_FRAMES_PRIME, ID_TO_NAME
from ..utils.framepicker import FramePickerDialog
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GObject, GLib # noqa: E402
@Gtk.Template(resource_path="/gay/valhrafnaz/Voidmanifest/ui/wishlist.ui")
class WishlistPage(Gtk.Box):
__gtype_name__ = "WishlistPage"
wishlist = Gtk.Template.Child()
btn_wishlist_add = Gtk.Template.Child()
def __init__(self, *, parent: Adw.ViewStack):
super().__init__()
parent.add_titled(self, "wishlist", "Wishlist")
wrapper = parent.get_page(self)
self.app = self.get_root().get_application()
wrapper.set_icon_name("logs-symbolic")
self.btn_wishlist_add.connect('clicked', self._open_wishlist_dialogue)
self._wishlist_rows: dict[str, Adw.ActionRow] = {}
def _open_wishlist_dialogue(self, button):
dialog = FramePickerDialog(missing_frames=self.app.profile.missing_basics + self.app.profile.missing_primes)
dialog.connect('frame-selected', self.add_frame_to_wishlist)
dialog.present(self.get_root())
def add_frame_to_wishlist(self, dialog, frame_id: str):
if frame_id in self._wishlist_rows:
return
row = Adw.ActionRow(
title=ID_TO_NAME[frame_id],
activatable=True
)
row.frame_id = frame_id
button = Gtk.Button(
icon_name='minus-circle-outline-symbolic'
)
button.frame_id = frame_id
button.add_css_class('flat')
button.add_css_class('destructive-action')
button.add_css_class('circular')
button.connect('clicked', self.btn_remove)
row.add_suffix(button)
self._wishlist_rows[frame_id] = row
self.wishlist.add(row)
self.app.profile.wishlist.append(frame_id)
self.app.profile.wishlist = sorted(self.app.profile.wishlist)
self._refresh_wishlist
def btn_remove(self, button):
self.remove_frame_from_wishlist(button.frame_id)
def remove_frame_from_wishlist(self, frame_id: str):
if frame_id in self._wishlist_rows:
row = self._wishlist_rows.pop(frame_id)
self.wishlist.remove(row)
def has_frame(self, frame_id) -> bool:
return frame_id in self._wishlist_rows
def _refresh_wishlist(self):
for frame_id in self.app.profile.wishlist:
if frame_id not in self._wishlist_rows:
self.add_frame_to_wishlist(Adw.Dialog.new(),frame_id)
for frame_id in list(self._wishlist_rows):
if frame_id not in self.app.profile.wishlist:
self.remove_frame_from_wishlist(frame_id)

View File

@@ -11,6 +11,7 @@ class Profile:
missing_basics_count: int = BASIC_WARFRAMES
missing_primes: list[str] = field(default_factory=list)
missing_primes_count: int = PRIME_WARFRAMES
wishlist: list[str] = field(default_factory=list)
_on_change: Callable | None = field(default=None, repr=False, compare=False)

View File

View File

@@ -0,0 +1,48 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gio, GObject, Gtk, Adw # noqa: E402
@Gtk.Template(resource_path="/gay/valhrafnaz/Voidmanifest/ui/framepicker.ui")
class FramePickerDialog(Adw.Dialog):
__gtype_name__ = "FramePickerDialog"
__gsignals__ = {
'frame-selected': (GObject.SignalFlags.RUN_FIRST, None, (str,)),
}
search_entry = Gtk.Template.Child()
frame_list = Gtk.Template.Child()
def __init__(self, missing_frames: list[str], **kwargs):
super().__init__(**kwargs)
self.missing_frames = missing_frames
self._populate_list()
self.frame_list.set_filter_func(self._filter_func)
def _populate_list(self):
for frame_name in sorted(self.missing_frames):
row = Adw.ActionRow(title=frame_name.replace("_", " ").title(),
activatable=True
)
row.frame_id = frame_name
self.frame_list.append(row)
def _filter_func(self, row):
search_text = self.search_entry.get_text().lower()
if not search_text:
return True
return search_text in row.get_title().lower()
@Gtk.Template.Callback()
def on_search_changed(self, entry):
self.frame_list.invalidate_filter()
@Gtk.Template.Callback()
def on_row_activated(self, listbox, row):
self.emit('frame-selected', row.frame_id)
self.close()

View File

@@ -19,15 +19,14 @@
import json
import os
import gi
from pathlib import Path
from .pages.home import HomePage
from .pages.settings import SettingsPage
from .pages.wishlist import WishlistPage
from .pages.checklist import ChecklistPage
from gi.repository import Adw
from gi.repository import Gtk, Gio
from gi.repository import GLib
from gi.repository import Adw, Gtk, Gio, GLib # noqa: E402
@Gtk.Template(resource_path='/gay/valhrafnaz/Voidmanifest/ui/window.ui')
class VoidmanifestWindow(Adw.ApplicationWindow):
@@ -51,7 +50,7 @@ class VoidmanifestWindow(Adw.ApplicationWindow):
def _load_page_templates(self):
self.home_page = HomePage(parent=self.viewstack)
self.checklist_page = ChecklistPage(parent=self.viewstack, window=self)
self.settings_page = SettingsPage(parent=self.viewstack)
self.wishlist_page = WishlistPage(parent=self.viewstack)
def _on_close_request(self, *args):
self.app.save_profile(self.app.profile)