minor fixes, API integration

This commit is contained in:
2025-12-17 00:39:44 +01:00
parent 154837d411
commit 6b11176015
17 changed files with 287 additions and 174 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@@ -32,10 +32,10 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child>
<child>
<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>
<style>
<class name="dim-label"/>
<class name="dimmed"/>
</style>
</object>
</child>
@@ -189,7 +189,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child>
<child>
<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="margin-bottom">4</property>
</object>
@@ -740,7 +740,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</child>
<child>
<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="margin-bottom">4</property>
<property name="can-target">false</property>

View File

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

View File

@@ -12,61 +12,69 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
<child>
<object class="AdwClamp">
<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>
<property name="title-selectable">false</property>
<property name="activatable">false</property>
<child type="suffix">
<object class="GtkLabel" id="owned_frames"></object>
<object class="GtkScrolledWindow">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<property name="child">
<object class="AdwPreferencesGroup">
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<property name="title" translatable="yes">Welcome back, Tenno</property>
<style>
<class name="list-title"/>
</style>
<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>
</object>
</child>
<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>
</property>
</object>
</child>
</object>

View File

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

View File

@@ -39,7 +39,9 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
</object>
</child>
<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>
<child type="bottom">
<object class="GtkActionBar">

View File

@@ -5,20 +5,25 @@ 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';
ScrolledWindow {
hexpand: true;
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 [
'flat',
'circular',
'suggested-action'
"list-title"
]
};
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>
<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>
<object class="GtkScrolledWindow">
<property name="hexpand">true</property>
<property name="vexpand">true</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>
<class name="flat"/>
<class name="circular"/>
<class name="suggested-action"/>
<class name="list-title"/>
</style>
</object>
</property>
<property name="separate-rows">true</property>
<property name="title" translatable="yes">Wishlist</property>
<style>
<class name="list-title"/>
</style>
</child>
</object>
</child>
</object>

View File

@@ -6,7 +6,7 @@ EXISTING_FRAMES_BASIC = [
"caliban",
"chroma",
"citrine",
"cyte09",
"cyte-09",
"dagath",
"dante",
"ember",
@@ -52,7 +52,6 @@ EXISTING_FRAMES_BASIC = [
"temple",
"titania",
"trinity",
"uriel",
"valkyr",
"vauban",
"volt",
@@ -79,7 +78,6 @@ EXISTING_FRAMES_PRIME = [
"garuda_prime",
"gauss_prime",
"grendel_prime",
"gyre_prime",
"harrow_prime",
"hildryn_prime",
"hydroid_prime",
@@ -119,7 +117,6 @@ EXISTING_FRAMES_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
PRIME_WARFRAMES = 49
CURRENT_UPDATE = 41

View File

@@ -125,7 +125,7 @@ class VoidmanifestApplication(Adw.Application):
def _refresh_metadata(self):
url = 'https://api.warframestat.us/warframes'
params = 'only=name,isPrime'
params = 'only=name,isPrime,category'
resp = requests.get(url, params)
base_frames_list: list[str] = list()
prime_frames_list: list[str] = list()
@@ -135,6 +135,9 @@ class VoidmanifestApplication(Adw.Application):
else:
print('CRITICAL ERROR: Could not refresh metadata from https://api.warframestat.us/')
for warframe in resp_data:
# skip every non-warframe
if warframe['category'] != 'Warframes':
continue
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':
@@ -145,12 +148,23 @@ class VoidmanifestApplication(Adw.Application):
prime_frames_list.append(frame_id)
else:
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_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
# 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())
@@ -170,7 +184,7 @@ class VoidmanifestApplication(Adw.Application):
json.dump(profile.to_dict(), f, indent=2)
def reset_frames(self, param):
self.profile.owned_frames = {}
self.profile.owned_frames = []
button = self.checklist_page.btns_basic.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
@@ -188,7 +202,7 @@ class VoidmanifestApplication(Adw.Application):
basic_count = 0
missing_basics: list[str] = []
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
else:
missing_basics.append(basic_frame)
@@ -196,18 +210,18 @@ class VoidmanifestApplication(Adw.Application):
prime_count = 0
missing_primes: list[str] = []
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
else:
missing_primes.append(prime_frame)
# count unique
unique_frames: set[str] = set()
for owned_frame, is_owned in self.profile.owned_frames.items():
if is_owned:
_name = owned_frame.removesuffix("_prime")
_name = _name.removesuffix("_umbra")
unique_frames.add(_name)
for owned_frame in self.profile.owned_frames:
_name = owned_frame.removesuffix("_prime")
_name = _name.removesuffix("_umbra")
unique_frames.add(_name)
self.profile.unique_frames_list = list(unique_frames)
self.profile.missing_basics_count = len(missing_basics)
self.profile.missing_primes_count = len(missing_primes)
self.profile.unique_frames_owned = len(unique_frames)

View File

@@ -44,14 +44,14 @@ class ChecklistPage(Gtk.Box):
while button is not None:
if isinstance(button, Gtk.ToggleButton):
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 = button.get_next_sibling()
button = self.btns_prime.get_first_child()
while button is not None:
if isinstance(button, Gtk.ToggleButton):
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 = button.get_next_sibling()
@@ -90,7 +90,12 @@ class ChecklistPage(Gtk.Box):
if btn_id == "GtkToggleButton":
print("what")
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._calc_frames()

View File

@@ -1,6 +1,6 @@
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('Adw', '1')
@@ -12,29 +12,80 @@ from gi.repository import Gtk, Adw, GObject, GLib # noqa: E402
class HomePage(Gtk.Box):
__gtype_name__ = "HomePage"
app = None
owned_frames = Gtk.Template.Child()
owned_frames_row = Gtk.Template.Child()
owned_basics = Gtk.Template.Child()
owned_basics_row = Gtk.Template.Child()
missing_basics = Gtk.Template.Child()
missing_basics_row = Gtk.Template.Child()
owned_primes = Gtk.Template.Child()
owned_primes_row = Gtk.Template.Child()
missing_primes = Gtk.Template.Child()
missing_primes_row = Gtk.Template.Child()
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__()
parent.add_titled(self, "home", "Home")
wrapper = parent.get_page(self)
wrapper.set_icon_name("compass2-symbolic")
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()
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_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.missing_basics.set_label(str(self.app.profile.missing_basics_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")
self.btn_wishlist_add.connect('clicked', self._open_wishlist_dialogue)
self._wishlist_rows: dict[str, Adw.ActionRow] = {}
self.refresh_wishlist()
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.present(self.get_root())
@@ -52,7 +53,7 @@ class WishlistPage(Gtk.Box):
self.wishlist.add(row)
self.app.profile.wishlist.append(frame_id)
self.app.profile.wishlist = sorted(self.app.profile.wishlist)
self._refresh_wishlist
self.refresh_wishlist
def btn_remove(self, button):
self.remove_frame_from_wishlist(button.frame_id)
@@ -61,14 +62,24 @@ class WishlistPage(Gtk.Box):
if frame_id in self._wishlist_rows:
row = self._wishlist_rows.pop(frame_id)
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:
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:
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):
to_delete: list[str] = []
for frame_id in self._wishlist_rows:
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.
@dataclass
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_list: list[str] = field(default_factory=list)
missing_basics: list[str] = field(default_factory=list)
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)
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:
"""Convert to dict, excluding private fields"""
return {

View File

@@ -16,9 +16,9 @@ class FramePickerDialog(Adw.Dialog):
search_entry = 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)
self.app = app
self.missing_frames = missing_frames
self._populate_list()
@@ -26,11 +26,12 @@ class FramePickerDialog(Adw.Dialog):
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)
if frame_name not in self.app.profile.wishlist:
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()

View File

@@ -56,4 +56,16 @@ class VoidmanifestWindow(Adw.ApplicationWindow):
self.app.save_profile(self.app.profile)
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