diff --git a/cozy/ui/book_detail_view.py b/cozy/ui/book_detail_view.py index 627cde68..ec799442 100644 --- a/cozy/ui/book_detail_view.py +++ b/cozy/ui/book_detail_view.py @@ -1,12 +1,10 @@ import logging import time -from math import pi as PI from threading import Event, Thread from typing import Callable, Final -import cairo import inject -from gi.repository import Adw, Gio, GLib, GObject, Graphene, Gtk +from gi.repository import Adw, Gio, GLib, GObject, Gtk from cozy.control.artwork_cache import ArtworkCache from cozy.model.book import Book @@ -19,58 +17,12 @@ log = logging.getLogger(__name__) ALBUM_ART_SIZE: Final[int] = 256 -PROGRESS_RING_LINE_WIDTH: Final[int] = 5 def call_in_main_thread(*args) -> None: # TODO: move this elsewhere, it might come useful GLib.MainContext.default().invoke_full(GLib.PRIORITY_DEFAULT_IDLE, *args) - -class ProgressRing(Gtk.Widget): - __gtype_name__ = "ProgressRing" - - progress = GObject.Property(type=float, default=0.0) - - def __init__(self) -> None: - super().__init__() - - self._style_manager = Adw.StyleManager() - self._style_manager.connect("notify::accent-color", self.redraw) - self.connect("notify::progress", self.redraw) - - def redraw(self, *_) -> None: - self.queue_draw() - - def do_measure(self, *_) -> tuple[int, int, int, int]: - return (40, 40, -1, -1) - - def do_snapshot(self, snapshot: Gtk.Snapshot) -> None: - size = self.get_allocated_height() - radius = (size - 8) / 2.0 - - context = snapshot.append_cairo(Graphene.Rect().init(0, 0, size, size)) - - context.arc(size / 2, size / 2, radius, 0, 2 * PI) - context.set_source_rgba(*self.get_dim_color()) - context.set_line_width(PROGRESS_RING_LINE_WIDTH) - context.stroke() - - context.arc(size / 2, size / 2, radius, -0.5 * PI, self.progress * 2 * PI - (0.5 * PI)) - context.set_source_rgb(*self.get_accent_color()) - context.set_line_width(PROGRESS_RING_LINE_WIDTH) - context.set_line_cap(cairo.LineCap.ROUND) - context.stroke() - - def get_dim_color(self) -> tuple[int, int, int, int]: - color = self.get_color() - return color.red, color.green, color.blue, 0.15 - - def get_accent_color(self) -> tuple[int, int, int]: - color = self._style_manager.get_accent_color_rgba() - return color.red, color.green, color.blue - - class ChaptersListBox(Adw.PreferencesGroup): def __init__(self, title: str): super().__init__() @@ -93,12 +45,13 @@ class BookDetailView(Adw.NavigationPage): book_label: Gtk.Label = Gtk.Template.Child() author_label: Gtk.Label = Gtk.Template.Child() total_label: Gtk.Label = Gtk.Template.Child() - remaining_label: Gtk.Label = Gtk.Template.Child() + progress_label: Gtk.Label = Gtk.Template.Child() - book_progress_ring: ProgressRing = Gtk.Template.Child() + book_progress_bar: Gtk.ProgressBar = Gtk.Template.Child() album_art: Gtk.Picture = Gtk.Template.Child() - album_art_container: Gtk.Box = Gtk.Template.Child() + album_art_container: Gtk.Stack = Gtk.Template.Child() + fallback_icon: Gtk.Image = Gtk.Template.Child() unavailable_banner: Adw.Banner = Gtk.Template.Child() @@ -148,6 +101,11 @@ def _connect_view_model(self): def _connect_widgets(self): self.play_button.connect("clicked", self._play_book_clicked) + self.connect("showing", self._refresh_page) + + def _refresh_page(self, *_): + # should probably combine with _on_book_changed later + self._on_progress_changed() def _on_book_changed(self): book = self._view_model.book @@ -165,7 +123,7 @@ def _on_book_changed(self): self._current_selected_chapter = None - self.total_label.set_visible(False) + self.total_label.set_text(_("Loading…")) self.unavailable_banner.set_revealed(False) self.book_title = book.name @@ -218,8 +176,8 @@ def _on_length_changed(self): self.total_label.set_text(self._view_model.total_text) def _on_progress_changed(self): - self.remaining_label.set_text(self._view_model.remaining_text) - self.book_progress_ring.progress = self._view_model.progress_percent + self.progress_label.set_text(self._view_model.progress_text) + self.book_progress_bar.set_fraction(self._view_model.progress_percent) def _on_lock_ui_changed(self): self.available_offline_action.set_enabled(not self._view_model.lock_ui) @@ -310,16 +268,18 @@ def _display_external_section(self): self.available_offline_action.handler_unblock_by_func(self._download_switch_changed) def _set_cover_image(self, book: Book): - self.album_art_container.set_visible(False) paintable = self._artwork_cache.get_cover_paintable( book, self.get_scale_factor(), ALBUM_ART_SIZE ) if paintable: - self.album_art_container.set_visible(True) self.album_art.set_paintable(paintable) self.album_art.set_overflow(True) + self.album_art_container.set_visible_child(self.album_art) + else: + self.fallback_icon.set_from_icon_name("book-open-variant-symbolic") + self.album_art_container.set_visible_child(self.fallback_icon) def _interrupt_chapters_jobs(self): self._chapters_job_locked = True diff --git a/cozy/ui/chapter_element.py b/cozy/ui/chapter_element.py index 26bca729..c069cd94 100644 --- a/cozy/ui/chapter_element.py +++ b/cozy/ui/chapter_element.py @@ -1,9 +1,9 @@ from gi.repository import Adw, GObject, Gtk +from os import path from cozy.control.time_format import ns_to_time from cozy.model.chapter import Chapter - @Gtk.Template.from_resource("/com/github/geigi/cozy/ui/chapter_element.ui") class ChapterElement(Adw.ActionRow): __gtype_name__ = "ChapterElement" @@ -23,6 +23,7 @@ def __init__(self, chapter: Chapter): self.set_title(self.chapter.name) self.number_label.set_text(str(self.chapter.number)) self.duration_label.set_text(ns_to_time(self.chapter.length)) + self.set_tooltip_text(path.basename(self.chapter.file)) @GObject.Signal(arg_types=(object,)) def play_pause_clicked(self, *_): ... diff --git a/cozy/view_model/book_detail_view_model.py b/cozy/view_model/book_detail_view_model.py index 0555126b..753ac9de 100644 --- a/cozy/view_model/book_detail_view_model.py +++ b/cozy/view_model/book_detail_view_model.py @@ -90,12 +90,11 @@ def total_text(self) -> str | None: return time_format.ns_to_human_readable(self._book.duration / self._book.playback_speed) @property - def remaining_text(self) -> str | None: + def progress_text(self) -> str | None: if not self._book: return None - remaining = self._book.duration - self._book.progress - return time_format.ns_to_human_readable(remaining / self._book.playback_speed) + return time_format.ns_to_human_readable(self._book.progress / self._book.playback_speed) @property def progress_percent(self) -> float | None: diff --git a/data/ui/book_detail.blp b/data/ui/book_detail.blp index 5ad01dc9..abf64920 100644 --- a/data/ui/book_detail.blp +++ b/data/ui/book_detail.blp @@ -60,9 +60,10 @@ template $BookDetail: Adw.NavigationPage { margin-end: 18; margin-top: 12; margin-bottom: 18; - halign: start; + halign: fill; + hexpand: true; - Box album_art_container { + Stack album_art_container { halign: center; width-request: 200; height-request: 200; @@ -73,6 +74,10 @@ template $BookDetail: Adw.NavigationPage { ] } + Image fallback_icon { + pixel-size: 200; + } + styles [ "card", ] @@ -80,17 +85,16 @@ template $BookDetail: Adw.NavigationPage { Box { orientation: vertical; - margin-top: 12; - margin-bottom: 12; spacing: 6; vexpand: false; + Label book_label { halign: start; label: bind template.book_title; ellipsize: end; max-width-chars: 35; - lines: 1; + lines: 2; styles [ "title-1", @@ -111,72 +115,26 @@ template $BookDetail: Adw.NavigationPage { ] } + ProgressBar book_progress_bar { + } + Box { - valign: end; - margin-top: 18; - spacing: 6; + orientation: horizontal; + hexpand: true; + halign: fill; - $ProgressRing book_progress_ring { - valign: center; + Label progress_label { + halign: start; } - Grid { + Label total_label{ + halign: end; hexpand: true; - row-spacing: 3; - column-spacing: 20; - - Label { - halign: start; - hexpand: true; - label: _("Remaining"); - - styles [ - "dim-label", - ] - - layout { - column: 0; - row: 0; - } - } - - Label remaining_label { - hexpand: true; - xalign: 0; - - layout { - column: 1; - row: 0; - } - } - - Label { - halign: start; - hexpand: true; - label: _("Total"); - - styles [ - "dim-label", - ] - - layout { - column: 0; - row: 1; - } - } - - Label total_label{ - hexpand: true; - xalign: 0; - - layout { - column: 1; - row: 1; - } - } + label: _("Loading…"); } } + Button play_button { halign: start; valign: end; @@ -243,11 +201,30 @@ template $BookDetail: Adw.NavigationPage { } menu book_menu_model { + section { + item { + action: 'app.mark_book_as_read'; + label: _("Mark as Read"); + } + + item { + action: 'app.jump_to_book_folder'; + label: _("Open in Files"); + } + } + section { item { action: 'book_overview.download'; - label: _("_Available Offline"); + label: _("Download Book"); hidden-when: "action-disabled"; } } + + section { + item { + action: 'app.remove_book'; + label: _("Delete Permanently…"); + } + } }