* bz-other-sources-list.vala
*
* Copyright 2026 Alexander Vanhee
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
public class Bz.OtherSourceRow : Adw.PreferencesRow {
private Gtk.Button download_button;
public signal void download ();
public OtherSourceRow (Bz.FlatpakEntry entry, Bz.Repository? repository, Bz.EntryGroup entry_group) {
activatable = false;
var title_label = new Gtk.Label (repository?.title) {
halign = Gtk.Align.START,
xalign = 0,
};
title_label.add_css_class ("origin-title");
var subtitle_label = new Gtk.Label (extract_domain (repository?.url)) {
halign = Gtk.Align.START,
xalign = 0,
};
subtitle_label.add_css_class ("caption");
subtitle_label.add_css_class ("dim-label");
var pill_box = new Adw.WrapBox () {
halign = Gtk.Align.START,
valign = Gtk.Align.CENTER,
child_spacing = 4,
line_spacing = 4,
};
if (repository != null && repository.is_user)
pill_box.append (create_pill ("person-symbolic", _("User"), "grey"));
if (repository != null && repository.is_beta)
pill_box.append (create_pill ("test-symbolic", _("Beta"), "warning"));
if (entry_group.get_unique_id_is_eol (entry.unique_id))
pill_box.append (create_pill ("dialog-warning-symbolic", _("End-Of-Life"), "error"));
var title_row = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) {
halign = Gtk.Align.START,
};
title_row.append (title_label);
title_row.append (pill_box);
var text_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0) {
valign = Gtk.Align.CENTER,
hexpand = true,
margin_bottom = 2,
};
text_box.append (title_row);
text_box.append (subtitle_label);
download_button = new Gtk.Button.from_icon_name ("folder-download-symbolic") {
valign = Gtk.Align.CENTER,
tooltip_text = _("Install"),
};
download_button.add_css_class ("flat");
download_button.clicked.connect (() => download ());
var root_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12) {
margin_top = 8,
margin_bottom = 8,
margin_start = 12,
margin_end = 12,
};
root_box.append (text_box);
root_box.append (download_button);
child = root_box;
}
private string extract_domain (string? url) {
MatchInfo match_info;
if (url == null)
return "";
var regex = /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:\/\/)?([^\/]+)/;
if (regex.match (url, 0, out match_info))
return match_info.fetch (1);
return "";
}
private Gtk.Widget create_pill (string icon_name, string label_text, string color_class) {
var icon = new Gtk.Image.from_icon_name (icon_name) {
pixel_size = 12,
margin_top = 2,
margin_bottom = 1,
};
var label = new Gtk.Label (label_text);
label.add_css_class ("caption");
var pill = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4) {
valign = Gtk.Align.CENTER,
margin_top = 2,
margin_bottom = 2,
};
pill.append (icon);
pill.append (label);
pill.add_css_class ("origin-rounded-box");
pill.add_css_class ("colored");
pill.add_css_class (color_class);
return pill;
}
}
public class Bz.OtherSourcesList : Adw.Bin {
private Bz.EntryGroup? _entry_group;
public Bz.EntryGroup? entry_group {
get { return _entry_group; }
set {
if (_entry_group == value)
return;
_entry_group = value;
rebuild ();
}
}
public signal void download (Bz.Entry entry);
private Gtk.ListBox listbox;
construct {
listbox = new Gtk.ListBox () {
selection_mode = Gtk.SelectionMode.NONE,
};
listbox.add_css_class ("boxed-list");
child = listbox;
visible = false;
}
private void rebuild () {
var group = _entry_group;
var child_widget = listbox.get_first_child ();
while (child_widget != null) {
var next = child_widget.get_next_sibling ();
listbox.remove (child_widget);
child_widget = next;
}
visible = false;
if (group == null)
return;
var weak_self = GLib.WeakRef (this);
var scheduler = Dex.Scheduler.get_default ();
var fiber = scheduler.spawn (0, () => {
return populate (weak_self, group);
});
fiber.disown ();
}
private Dex.Future? populate (GLib.WeakRef weak_self, Bz.EntryGroup group) {
var self = weak_self.get () as Bz.OtherSourcesList;
if (self == null)
return null;
GLib.ListModel store;
try {
store = (GLib.ListModel) group.dup_all_into_store ().await_object ();
} catch (GLib.Error e) {
return null;
}
self = weak_self.get () as Bz.OtherSourcesList;
if (self == null || self._entry_group != group)
return null;
string? ui_entry_id = group.dup_ui_entry_id ();
var repositories = Bz.StateInfo.get_default ().repositories;
uint n_items = store.get_n_items ();
uint n_shown = 0;
for (uint i = 0; i < n_items; i++) {
var entry = (Bz.Entry) store.get_item (i);
if (ui_entry_id != null && entry.unique_id == ui_entry_id)
continue;
var flatpak_entry = entry as Bz.FlatpakEntry;
if (flatpak_entry == null)
continue;
Bz.Repository? repo = null;
if (repositories != null)
repo = entry.get_repository (repositories);
var row = new Bz.OtherSourceRow (flatpak_entry, repo, group);
row.download.connect (() => self.download (entry));
self.listbox.append (row);
n_shown++;
}
self.visible = n_shown > 0;
return null;
}
}