Fully complete the Install From The Web window, for installing new paks

This commit is contained in:
heliguy4599
2024-02-15 03:21:57 -05:00
parent 3ae90e52ae
commit 4903d36e02
9 changed files with 383 additions and 231 deletions

View File

@@ -5,13 +5,13 @@
### Features that have all work done and will release in the next update (ready to ship)
- Remembering filters on app close
- Loading pages display the current working task, and how much is left
### Features that might release in the next update, but will release in some update
- Installing flatpaks from a search
- Edit flatpak pins
- Uninstall unused packages
- Loading pages display the current working task, and how much is left
# Features Being Considered

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -980 -140)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -980 -140)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -980 -140)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g><path d="m 10.9375 0.996094 c -0.292969 0 -0.558594 0.128906 -0.742188 0.328125 l -6.671874 6.671875 l 6.671874 6.675781 c 0.011719 0.011719 0.023438 0.023437 0.035157 0.03125 c 0.179687 0.183594 0.429687 0.292969 0.707031 0.292969 h 1 v -1 c 0 -0.273438 -0.113281 -0.523438 -0.292969 -0.707032 c -0.011719 -0.007812 -0.019531 -0.019531 -0.035156 -0.03125 l -5.257813 -5.257812 l 5.292969 -5.292969 c 0.179688 -0.183593 0.292969 -0.433593 0.292969 -0.707031 v -1 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -87,7 +87,7 @@ template $OrphansWindow: Adw.Window {
}
Label installing_status {
label: _("");
label: "";
justify: center;
styles [

View File

@@ -4,121 +4,195 @@ using Adw 1;
template $SearchInstallWindow: Adw.Window {
default-width: 500;
default-height: 450;
title: _("Install From The Web…");
title: "";
modal: true;
Adw.ToolbarView main_toolbar_view {
[top]
HeaderBar header_bar {
show-title-buttons: false;
[start]
Button cancel_button {
label: _("Cancel");
}
[end]
Button install_button {
label: _("Install");
styles [
"suggested-action"
]
}
}
[top]
Adw.Clamp {
Box {
margin-top: 6;
margin-start: 6;
margin-end: 6;
styles [
"linked"
]
[start]
SearchEntry search_entry {
hexpand: true;
placeholder-text: _("Search for Flatpaks…");
}
[start]
MenuButton remotes_dropdown {
label: _("All Remotes");
}
[start]
Button search_button {
tooltip-text: _("Search");
icon-name: "system-search-symbolic";
}
}
}
content: Adw.ToastOverlay toast_overlay {
Stack main_stack {
Overlay main_overlay {
[overlay]
ProgressBar progress_bar {
pulse-step: 0.7;
can-target: false;
styles [
"osd"
]
}
ScrolledWindow {
Adw.Clamp {
ListBox results_list_box {
margin-top: 12;
margin-bottom: 12;
margin-start: 12;
margin-end: 12;
hexpand: true;
valign: start;
selection-mode: none;
styles [
"boxed-list"
]
content:
Stack outer_stack {
Adw.NavigationView nav_view {
Adw.NavigationPage search_page {
title: _("Search Criteria");
Adw.ToastOverlay toast_overlay {
Adw.ToolbarView {
[top]
HeaderBar {
}
content:
Adw.StatusPage {
title: _("Choose a Remote to Search");
valign: start;
child:
Adw.Clamp {
ListBox remotes_list {
selection-mode: none;
styles ["boxed-list"]
}
};
};
}
}
}
Adw.NavigationPage results_page {
title: _("Results");
Adw.ToolbarView {
[top]
HeaderBar {
[start]
Button back_button {
tooltip-text: _("Back");
icon-name: "left-large-symbolic";
}
}
[bottom]
ActionBar action_bar {
revealed: false;
[center]
Button install_button {
margin-top: 6;
margin-bottom: 6;
styles[
"pill",
"suggested-action"
]
Adw.StatusPage no_results {
icon-name: "system-search-symbolic";
title: _("No Results Found");
description: _("Try a different search term");
}
Adw.ButtonContent {
label: _("Install");
icon-name: "plus-large-symbolic";
}
}
}
[top]
Adw.Clamp {
Box search_box {
margin-top: 4;
margin-start: 12;
margin-end: 12;
margin-bottom: 6;
SearchEntry search_entry {
hexpand: true;
}
Button search_button {
icon-name: "right-large-symbolic";
tooltip-text: _("Start Search");
}
styles ["linked"]
}
}
content:
Stack inner_stack {
Adw.StatusPage blank_page {
title: _("Search for Flatpaks");
icon-name: "flatpak-symbolic";
description: _("Search for Flatpaks that you want to install");
}
Adw.StatusPage blank_page {
title: _("Search for Flatpaks");
icon-name: "flatpak-symbolic";
description: _("Search for Flatpaks that you want to install");
}
Adw.StatusPage loading_page {
title: C_("Shown with a spinner while search operation is pending", "Searching");
Adw.StatusPage no_results {
icon-name: "system-search-symbolic";
title: _("No Results Found");
description: _("Try a different search term");
}
Spinner {
spinning: true;
height-request: 32;
width-request: 32;
margin-top: 0;
halign: center;
valign: center;
Box loading_page {
orientation: vertical;
spacing: 10;
margin-top: 40;
margin-bottom: 20;
halign: center;
valign: center;
Spinner {
margin-bottom: 35;
width-request: 30;
height-request: 30;
opacity: 0.5;
spinning: true;
}
Label {
label: _("Searching");
styles [
"title-1",
"title"
]
}
}
Adw.StatusPage too_many {
icon-name: "error-symbolic";
title: _("Too Many Results");
description: _("Try being more specific with your search");
}
ScrolledWindow results_scroll {
vexpand: true;
Adw.Clamp {
ListBox results_list {
margin-top: 6;
margin-bottom: 12;
margin-start: 12;
margin-end: 12;
hexpand: true;
valign: start;
selection-mode: none;
styles ["boxed-list"]
}
}
}
};
}
}
Adw.StatusPage too_many {
icon-name: "error-symbolic";
title: _("Too Many Results");
description: _("Try being more specific with your search");
}
Adw.ToolbarView installing {
[top]
HeaderBar {
}
content:
Overlay overlay {
[overlay]
ProgressBar progress_bar {
visible: false;
can-target: false;
styles ["osd"]
}
Box {
orientation: vertical;
spacing: 10;
margin-top: 40;
margin-bottom: 20;
halign: center;
valign: center;
Spinner {
margin-bottom: 35;
width-request: 30;
height-request: 30;
opacity: 0.5;
spinning: true;
}
Label {
label: _("Installing");
styles [
"title-1",
"title"
]
}
Label installing_status {
label: "";
justify: center;
styles [
"description",
"body"
]
}
}
};
}
};
}

View File

@@ -36,6 +36,13 @@ template $WarehouseWindow: Adw.ApplicationWindow {
icon-name: "selection-mode-symbolic";
tooltip-text: _("Toggle Selection Mode");
}
[end]
MenuButton install_button {
icon-name: "plus-large-symbolic";
tooltip-text: _("Install New Flatpak");
menu-model: install_menu;
}
}
[top]
@@ -137,7 +144,7 @@ template $WarehouseWindow: Adw.ApplicationWindow {
}
Label uninstalling_status {
label: _("");
label: "";
justify: center;
styles ["description", "body"]
}
@@ -237,11 +244,6 @@ template $WarehouseWindow: Adw.ApplicationWindow {
menu primary_menu {
section {
item {
label: _("Install From File…");
action: "app.install-from-file";
}
item {
label: _("Manage Leftover Data…");
action: "app.manage-data-folders";
@@ -256,11 +258,6 @@ menu primary_menu {
action: "app.show-remotes-window";
}
// item {
// label: _("Install From The Web…");
// action: "app.open-search-install";
// }
item {
label: _("_Keyboard Shortcuts");
action: "win.show-help-overlay";
@@ -292,11 +289,16 @@ menu copy_menu {
}
}
menu row_menu {
menu install_menu {
section {
item {
label: _("Open App");
//action: "win.open-app";
label: _("Install From File…");
action: "app.install-from-file";
}
item {
label: _("Install From The Web…");
action: "app.open-search-install";
}
}
}

View File

@@ -32,6 +32,7 @@ from .orphans_window import OrphansWindow
from .filter_window import FilterWindow
from .search_install_window import SearchInstallWindow
from .const import Config
from .common import myUtils
class WarehouseApplication(Adw.Application):
@@ -70,7 +71,7 @@ class WarehouseApplication(Adw.Application):
self.create_action("set-filter", self.filters_shortcut, ["<primary>t"])
self.create_action("install-from-file", self.install_from_file, ["<primary>o"])
self.create_action("open-menu", self.main_menu_shortcut, ["F10"])
# self.create_action("open-search-install", self.open_search_install, ["<primary>i"])
self.create_action("open-search-install", self.open_search_install, ["<primary>i"])
gtk_version = (
str(Gtk.MAJOR_VERSION)
@@ -99,6 +100,14 @@ class WarehouseApplication(Adw.Application):
lang=lang,
)
my_utils = myUtils(self)
total = 0
for rem in my_utils.get_host_remotes():
if my_utils.get_install_type(rem[7]) != "disabled":
total += 1
if total < 2:
self.lookup_action(f"open-search-install").set_enabled(False)
def open_search_install(self, widget, _):
SearchInstallWindow(self.props.active_window).present()

View File

@@ -4,6 +4,34 @@ import subprocess
import os
import pathlib
class RemoteRow(Adw.ActionRow):
def __init__(self, remote, **kwargs):
super().__init__(**kwargs)
my_utils = myUtils(self)
self.install_type = my_utils.get_install_type(remote[7])
if self.install_type == "disabled":
self.set_visible(False)
return
self.set_activatable(True)
self.remote = remote
if remote[1] == "-":
self.set_title(remote[0])
else:
self.set_title(remote[1])
self.set_subtitle(_("{} wide").format(self.install_type))
self.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic"))
class ResultRow(Adw.ActionRow):
def __init__(self, flatpak, **kwargs):
super().__init__(**kwargs)
my_utils = myUtils(self)
self.flatpak = flatpak
self.set_title(GLib.markup_escape_text(flatpak[0]))
self.set_subtitle(GLib.markup_escape_text(flatpak[1]))
self.check = Gtk.CheckButton()
self.check.add_css_class("selection-mode")
self.add_suffix(self.check)
self.set_activatable_widget(self.check)
@Gtk.Template(
resource_path="/io/github/flattool/Warehouse/../data/ui/search_install.ui"
@@ -13,144 +41,179 @@ class SearchInstallWindow(
): # TODO: stop execution of thread when search is changed
__gtype_name__ = "SearchInstallWindow"
results_list_box = Gtk.Template.Child()
main_stack = Gtk.Template.Child()
main_overlay = Gtk.Template.Child()
no_results = Gtk.Template.Child()
too_many = Gtk.Template.Child()
cancel_button = Gtk.Template.Child()
blank_page = Gtk.Template.Child()
loading_page = Gtk.Template.Child()
search_button = Gtk.Template.Child()
back_button = Gtk.Template.Child()
nav_view = Gtk.Template.Child()
search_page = Gtk.Template.Child()
results_page = Gtk.Template.Child()
remotes_list = Gtk.Template.Child()
search_entry = Gtk.Template.Child()
remotes_dropdown = Gtk.Template.Child()
is_debug = GLib.environ_getenv(GLib.get_environ(), "G_MESSAGES_DEBUG") == "all"
blank_page = Gtk.Template.Child()
inner_stack = Gtk.Template.Child()
outer_stack = Gtk.Template.Child()
loading_page = Gtk.Template.Child()
results_scroll = Gtk.Template.Child()
results_list = Gtk.Template.Child()
too_many = Gtk.Template.Child()
action_bar = Gtk.Template.Child()
search_button = Gtk.Template.Child()
no_results = Gtk.Template.Child()
install_button = Gtk.Template.Child()
installing = Gtk.Template.Child()
installing_status = Gtk.Template.Child()
search_box = Gtk.Template.Child()
toast_overlay = Gtk.Template.Child()
progress_bar = Gtk.Template.Child()
def key_handler(self, controller, keyval, keycode, state):
if keyval == Gdk.KEY_Escape or (keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK):
self.close()
def search_response(self, a, b):
self.results_list_box.remove_all()
print(self.search_results)
if (self.is_debug and len(self.search_results) == 5) or (
len(self.search_results) == 1 and len(self.search_results[0]) == 1
): # This is unreliable with G_DEBUG
self.main_stack.set_visible_child(self.no_results)
def reset(self):
self.results = []
self.results_list.remove_all()
self.inner_stack.set_visible_child(self.blank_page)
def check_handler(self, button, row):
if button.get_active():
self.selected.append(row.flatpak)
else:
self.selected.remove(row.flatpak)
if len(self.selected) == 0:
self.set_title(self.title)
self.action_bar.set_revealed(False)
else:
self.set_title(_("{} Selected").format(len(self.selected)))
self.action_bar.set_revealed(True)
def generate_remotes_list(self):
total = 0
for rem in self.host_remotes:
if self.my_utils.get_install_type(rem[7]) != "disabled":
total += 1
if total < 2:
self.nav_view.push(self.results_page)
self.back_button.set_visible(False)
self.back_button.set_sensitive(False)
self.search_remote = self.host_remotes[0][0]
self.install_type = self.host_remotes[0][7]
self.nav_view.connect("popped", lambda *_: self.nav_view.push(self.results_page))
self.nav_view.set_animate_transitions(False)
self.title = _("Search {}").format(self.search_remote)
self.set_title(self.title)
self.search_entry.set_placeholder_text(_("Search {}").format(self.search_remote))
self.search_entry.grab_focus()
return
if len(self.search_results) > 50:
self.main_stack.set_visible_child(self.too_many)
self.nav_view.connect("popped", lambda *_: self.set_title(""))
for remote in self.host_remotes:
row = RemoteRow(remote)
row.connect("activated", self.remote_choice)
self.remotes_list.append(row)
def generate_results_list(self):
for pak in self.results:
row = ResultRow(pak)
row.check.set_active(row.flatpak in self.selected)
row.check.connect("toggled", self.check_handler, row)
if self.search_remote in row.flatpak[5]:
self.results_list.append(row)
if self.results_list.get_row_at_index(0):
self.inner_stack.set_visible_child(self.results_scroll)
else:
self.inner_stack.set_visible_child(self.no_results)
def remote_choice(self, row):
self.reset()
self.selected = []
self.install_type = row.install_type
self.search_remote = row.remote[0]
self.search_entry.set_placeholder_text(_("Search {}").format(self.search_remote))
self.title = _("Search {}").format(self.search_remote)
self.set_title(self.title)
self.nav_view.push(self.results_page)
self.search_entry.grab_focus()
self.action_bar.set_revealed(len(self.selected) > 0)
def search_handler(self, *args):
self.reset()
self.inner_stack.set_visible_child(self.loading_page)
query = self.search_entry.get_text().strip()
if query == "":
self.inner_stack.set_visible_child(self.blank_page)
return
self.main_stack.set_visible_child(self.main_overlay)
for i in range(len(self.search_results)):
try:
print("creating row {}".format(str(i)))
row = Adw.ActionRow(
title=GLib.markup_escape_text(self.search_results[i][0]),
subtitle=self.search_results[i][2],
)
print("row {} is {}".format(str(i), self.search_results[i][0]))
check = Gtk.CheckButton()
check.add_css_class("selection-mode")
check.connect("toggled", self.on_check)
label = Gtk.Label(
label=self.search_results[i][3],
justify=Gtk.Justification.RIGHT,
wrap=True,
hexpand=True,
)
row.add_suffix(label)
row.add_suffix(check)
row.set_activatable_widget(check)
self.results_list_box.append(row)
except:
print("Could not create row")
def search_thread(*args):
command = ['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', query]
output = subprocess.run(
command, capture_output=True, text=True, env=self.new_env
).stdout.strip().split('\n')
for elm in output:
self.results.append(elm.split("\t"))
def on_check(self, button):
print(button.get_active())
def done(*args):
if len(self.results) > 50:
self.inner_stack.set_visible_child(self.too_many)
return
if ['No matches found'] in self.results:
self.inner_stack.set_visible_child(self.no_results)
return
self.generate_results_list()
def search_thread(self):
command = [
"flatpak-spawn",
"--host",
"flatpak",
"search",
"--columns=all",
self.to_search,
]
if self.remote_to_search:
command += self.remote_to_search
task = Gio.Task.new(None, None, done)
task.run_in_thread(search_thread)
output = subprocess.run(
command, capture_output=True, text=True, env=self.new_env
).stdout
lines = output.strip().split("\n")
columns = lines[0].split("\t")
data = [columns]
for line in lines[1:]:
row = line.split("\t")
data.append(row)
data = sorted(data, key=lambda item: item[0].lower())
self.search_results = data
def install_handler(self, *args):
paks = []
for pak in self.selected:
paks.append(pak[2])
self.outer_stack.set_visible_child(self.installing)
self.set_title(_("Install From The Web"))
def on_search(self, widget):
self.main_stack.set_visible_child(self.loading_page)
self.to_search = self.search_entry.get_text()
if len(self.to_search) < 1 or " " in self.to_search:
self.results_list_box.remove_all()
self.main_stack.set_visible_child(self.blank_page)
return
task = Gio.Task.new(None, None, self.search_response)
task.run_in_thread(lambda *_: self.search_thread())
def thread(*args):
self.my_utils.install_flatpak(paks, self.search_remote, self.install_type, self.progress_bar, self.installing_status)
def set_choice(self, index):
print(index)
def remotes_chooser_creator(self):
remotes_pop = Gtk.Popover()
remotes_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# remotes_pop.set_size_request(400, 1) # why?
remotes_pop.set_child(remotes_box) # don't use ScrolledWindows in popovers!
# why not just comment this code out and leave it unimplemented— it does nothing of use
for i in range(1, 3):
x = 0
height = remotes_pop.get_size(x)
all = Gtk.Button(label="all")
all.add_css_class("flat")
remotes_box.append(all)
print(x)
self.remotes_dropdown.set_popover(remotes_pop)
def done(*args):
self.parent_window.refresh_list_of_flatpaks(None, False)
self.disconnect(self.no_close_id) # Make window able to close
if self.my_utils.install_success:
self.close()
self.parent_window.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully")))
else:
self.progress_bar.set_visible(False)
self.nav_view.pop()
self.outer_stack.set_visible_child(self.nav_view)
self.toast_overlay.add_toast(Adw.Toast.new(_("Some apps didn't install")))
self.no_close_id = self.connect(
"close-request", lambda event: True
) # Make window unable to close
task = Gio.Task.new(None, None, done)
task.run_in_thread(thread)
def __init__(self, parent_window, **kwargs):
super().__init__(**kwargs)
# Create Variables
self.my_utils = myUtils(self)
self.search_results = []
self.to_search = ""
self.new_env = dict(os.environ)
self.new_env["LC_ALL"] = "C"
event_controller = Gtk.EventControllerKey()
event_controller.connect("key-pressed", self.key_handler)
self.cancel_button.connect("clicked", lambda *_: self.close())
self.host_remotes = self.my_utils.get_host_remotes()
self.parent_window = parent_window
self.results = []
self.selected = []
self.search_remote = ""
self.install_type = ""
self.title = _("Install From The Web")
self.back_button.connect("clicked", lambda *_: self.nav_view.pop())
self.search_entry.connect("activate", self.search_handler)
self.search_button.connect("clicked", self.search_handler)
self.install_button.connect("clicked", self.install_handler)
# Apply Widgets
self.add_controller(event_controller)
self.set_transient_for(parent_window)
# self.search_bar.connect_entry(self.search_entry)
self.search_entry.connect("activate", self.on_search)
self.search_button.connect("clicked", self.on_search)
self.search_entry.connect("changed", lambda *_: self.search_entry.grab_focus())
# self.search_entry.set_key_capture_widget(self.results_list_box)
self.search_entry.grab_focus()
self.host_remotes = self.my_utils.get_host_remotes()
if len(self.host_remotes) > 1:
self.remotes_chooser_creator()
self.remote_to_search = []
self.main_stack.set_visible_child(self.blank_page)
self.generate_remotes_list()
self.set_size_request(260, 230)
self.set_modal(True)
self.set_resizable(True)

View File

@@ -34,5 +34,6 @@
<file preprocess="xml-stripblanks" alias="important-small-symbolic.svg">../data/icons/important-small-symbolic.svg</file>
<file preprocess="xml-stripblanks" alias="eye-not-looking-symbolic.svg">../data/icons/eye-not-looking-symbolic.svg</file>
<file preprocess="xml-stripblanks" alias="eye-open-negative-filled-symbolic.svg">../data/icons/eye-open-negative-filled-symbolic.svg</file>
<file preprocess="xml-stripblanks" alias="left-large-symbolic.svg">../data/icons/left-large-symbolic.svg</file>
</gresource>
</gresources>

View File

@@ -67,6 +67,7 @@ class WarehouseWindow(Adw.ApplicationWindow):
no_matches = Gtk.Template.Child()
reset_filters_button = Gtk.Template.Child()
uninstalling_status = Gtk.Template.Child()
install_button = Gtk.Template.Child()
main_progress_bar = Gtk.ProgressBar(visible=False, can_target=False)
main_progress_bar.add_css_class("osd")