🏡 index : quine-global-labs/bazaar.git

/*
 * 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;
    }
}