diff --git a/src/dialogs/add_manual_dialog.py b/src/dialogs/add_manual_dialog.py index d78d531..d81d300 100644 --- a/src/dialogs/add_manual_dialog.py +++ b/src/dialogs/add_manual_dialog.py @@ -27,9 +27,9 @@ @Gtk.Template(resource_path=shared.PREFIX + '/ui/dialogs/add_manual.ui') -class AddManualDialog(Adw.Window): +class AddManualDialog(Adw.Dialog): """ - This class represents the window to manually add content to the db. + This class represents the dialog to manually add content to the db. Properties: edit_mode (bool): whether or not the window is in add/edit mode @@ -39,7 +39,7 @@ class AddManualDialog(Adw.Window): get_season(title: str, poster: str, episodes: list): returns the tuple with the provided data or an empty tuple Signals: - edit-saved(SeriesModel or MovieMovel): emited when the user clicks the save button + edit-saved(SeriesModel or MovieModel): emited when the user clicks the save button """ __gtype_name__ = 'AddManualDialog' @@ -74,12 +74,10 @@ class AddManualDialog(Adw.Window): seasons: list = [] def __init__(self, - parent: Gtk.Window, edit_mode: bool = False, content: MovieModel | SeasonModel | None = None ): super().__init__() - self.set_transient_for(parent) self.edit_mode = edit_mode self._content = content @@ -293,7 +291,7 @@ def _on_season_add_btn_clicked(self, user_data: object | None) -> None: dialog = EditSeasonDialog(self, title=_( 'Season {num}').format(num=len(self.seasons)+1)) dialog.connect('edit-saved', self._on_edit_saved) - dialog.present() + dialog.present(dialog.parent) def _on_edit_saved(self, source: Gtk.Widget, title: str, poster_uri: str, episodes: List[tuple]) -> None: """ @@ -370,8 +368,7 @@ def _on_add_done(self, activity: BackgroundActivity): """Callback to complete async activity""" - self.get_ancestor(Adw.Window).get_transient_for( - ).activate_action('win.refresh', None) + self.get_ancestor(Adw.ApplicationWindow).activate_action('win.refresh', None) activity.end() def _save_movie(self, poster_uri: str) -> None: diff --git a/src/dialogs/add_tmdb_dialog.py b/src/dialogs/add_tmdb_dialog.py index 8578842..7ea14b2 100644 --- a/src/dialogs/add_tmdb_dialog.py +++ b/src/dialogs/add_tmdb_dialog.py @@ -12,9 +12,9 @@ @Gtk.Template(resource_path=shared.PREFIX + '/ui/dialogs/add_tmdb.ui') -class AddTMDBDialog(Adw.Window): +class AddTMDBDialog(Adw.Dialog): """ - This class represents the window used to search for movies and tv-series on TMDB. + This class represents the dialog used to search for movies and tv-series on TMDB. Properties: None @@ -32,9 +32,8 @@ class AddTMDBDialog(Adw.Window): _stack = Gtk.Template.Child() _model = Gtk.Template.Child() - def __init__(self, parent: Gtk.Window): + def __init__(self): super().__init__() - self.set_transient_for(parent) @Gtk.Template.Callback('_on_searchentry_search_changed') def _on_searchentry_search_changed(self, user_data: object | None) -> None: diff --git a/src/dialogs/edit_season_dialog.py b/src/dialogs/edit_season_dialog.py index 9fe3e9f..74fd394 100644 --- a/src/dialogs/edit_season_dialog.py +++ b/src/dialogs/edit_season_dialog.py @@ -13,7 +13,7 @@ @Gtk.Template(resource_path=shared.PREFIX + '/ui/dialogs/edit_season.ui') -class EditSeasonDialog(Adw.Window): +class EditSeasonDialog(Adw.Dialog): """ This class represents the window to edit a season. @@ -46,7 +46,7 @@ def __init__(self, episodes: List[tuple] | None = None): super().__init__() - self.set_transient_for(parent) + self.parent = parent self._title = title self._poster_uri = poster_uri diff --git a/src/pages/details_page.py b/src/pages/details_page.py index 896a765..0245bcc 100644 --- a/src/pages/details_page.py +++ b/src/pages/details_page.py @@ -73,6 +73,8 @@ class DetailsView(Adw.NavigationPage): _additional_info_box = Gtk.Template.Child() _flow_box = Gtk.Template.Child() _loading_lbl = Gtk.Template.Child() + + mobile = True def __init__(self, content: MovieModel | SeriesModel): super().__init__() @@ -85,11 +87,49 @@ def __init__(self, content: MovieModel | SeriesModel): else: self.content = local.get_series_by_id(content.id) logging.info( - f'Loading info [{"movie" if type(content) is MovieModel else "TV Serie"}] {self.content.title}') + f'Loading info [{"movie" if type(content) is MovieModel else "TV Serie"}] {self.content.title}') # type: ignore self.set_title(self.content.title) # type: ignore self._view_stack.set_visible_child_name('loading') + + if shared.schema.get_int('win-width') >= 550: + self.mobile = False + else: + self.mobile = True + self._populate_data() + + @Gtk.Template.Callback() + def _on_breakpoint_applied(self, breakpoint: Adw.Breakpoint) -> None: + """ + Callback for the "applied" signal. + Sets the orientation of the page based on the breakpoint. + + Args: + breakpoint (Adw.Breakpoint): the breakpoint that was applied + + Returns: + None + """ + + self.mobile = True + self._build_seasons_group() + + @Gtk.Template.Callback() + def _on_breakpoint_unapplied(self, breakpoint: Adw.Breakpoint) -> None: + """ + Callback for the "unapplied" signal. + Sets the orientation of the page based on the breakpoint. + + Args: + breakpoint (Adw.Breakpoint): the breakpoint that was unapplied + + Returns: + None + """ + + self.mobile = False + self._build_seasons_group() def _populate_data(self) -> None: """ @@ -108,8 +148,7 @@ def _populate_data(self) -> None: if not Adw.StyleManager.get_default().get_high_contrast(): self._background_picture.set_file(Gio.File.new_for_uri( self.content.backdrop_path)) # type: ignore - # type: ignore - with Image.open(self.content.backdrop_path[7:]) as image: + with Image.open(self.content.backdrop_path[7:]) as image: # type: ignore stat = ImageStat.Stat(image.convert('L')) luminance = [ @@ -206,6 +245,7 @@ def _build_seasons_group(self) -> None: list_box.remove_all() self._episode_rows = [] + for season in self.content.seasons: # type: ignore season_row = Adw.ExpanderRow(title=season.title, subtitle=ngettext('{num} Episode'.format(num=season.episodes_number), @@ -223,23 +263,31 @@ def _build_seasons_group(self) -> None: season_row.add_prefix(poster) button = Gtk.Button(valign=Gtk.Align.CENTER) - btn_content = Adw.ButtonContent() - - if all(episode.watched for episode in season.episodes): - btn_content.set_label(_('Watched')) - btn_content.set_icon_name('check-plain') + + if not self.mobile: + btn_content = Adw.ButtonContent() + + if all(episode.watched for episode in season.episodes): + btn_content.set_label(_('Watched')) + btn_content.set_icon_name('check-plain') + else: + btn_content.set_label(_('Mark as Watched')) + btn_content.set_icon_name('watchlist') + + button.set_child(btn_content) + season_row.add_suffix(button) else: - btn_content.set_label(_('Mark as Watched')) - btn_content.set_icon_name('watchlist') - - button.set_child(btn_content) - season_row.add_suffix(button) + if all(episode.watched for episode in season.episodes): + button.set_icon_name('check-plain') + else: + button.set_icon_name('watchlist') + season_row.add_suffix(button) tmp = [] for episode in season.episodes: - episode_row = EpisodeRow(episode) + episode_row = EpisodeRow(episode, small_controls=self.mobile) episode_row.connect( - 'watched-clicked', self._on_episode_watch_clicked, (btn_content, season)) + 'watched-clicked', self._on_episode_watch_clicked, (button, season)) season_row.add_row(episode_row) tmp.append(episode_row) @@ -247,18 +295,18 @@ def _build_seasons_group(self) -> None: self._episode_rows.append((season, tmp)) button.connect('clicked', self._on_season_watched_clicked, - (btn_content, season, self._episode_rows)) + (button, season, self._episode_rows)) def _on_episode_watch_clicked(self, source: Gtk.Widget, - data: Tuple[Adw.ButtonContent, SeasonModel]) -> None: + data: Tuple[Gtk.Button, SeasonModel]) -> None: """ Callback for "watched-clicked" signal. Called after an episode is (un)marked as watched, checks and updates, if needed, the watched button for the corresponding season. Args: source (Gtk.Widget): caller widget - data(tuple[Adw.ButtonContent, SeasonModel]): tuple with the Adw.ButtonContent to change and the SeasonModel + data(tuple[Gtk.Button, SeasonModel]): tuple with the Gtk.Button to change and the SeasonModel parent of the changed episode Returns: @@ -267,35 +315,41 @@ def _on_episode_watch_clicked(self, self.content = local.get_series_by_id(self.content.id) # type: ignore - btn_content = data[0] season_idx = 0 for idx, season in enumerate(self.content.seasons): # type: ignore if season == data[1]: season_idx = idx - - if all(episode.watched for episode in self.content.seasons[season_idx].episodes): - btn_content.set_label(_('Watched')) - btn_content.set_icon_name('check-plain') + + if not self.mobile: + btn_content = data[0] + if all(episode.watched for episode in self.content.seasons[season_idx].episodes): # type: ignore + btn_content.set_label(_('Watched')) + btn_content.set_icon_name('check-plain') + else: + btn_content.set_label(_('Mark as Watched')) + btn_content.set_icon_name('watchlist') else: - btn_content.set_label(_('Mark as Watched')) - btn_content.set_icon_name('watchlist') + if all(episode.watched for episode in self.content.seasons[season_idx].episodes): # type: ignore + data[0].set_icon_name('check-plain') + else: + data[0].set_icon_name('watchlist') # Update season status - local.mark_watched_series(self.content.id, all( + local.mark_watched_series(self.content.id, all( # type: ignore episode.watched for season in self.content.seasons for episode in season.episodes)) # type: ignore self.activate_action('win.refresh', None) def _on_season_watched_clicked(self, source: Gtk.Widget, - data: Tuple[Adw.ButtonContent, SeasonModel, List[Tuple[SeasonModel, List[EpisodeRow]]]]) -> None: + data: Tuple[Gtk.Button, SeasonModel, List[Tuple[SeasonModel, List[EpisodeRow]]]]) -> None: """ Callback for "clicked" signal. Marks a whole season as (un)watched, making changes in the db and updating the ui. Args: source (Gtk.Widget): caller widget - data (Tuple[Adw.ButtonContent, SeasonModel, List[Tuple[SeasonModel, List[EpisodeRow]]]]): tuple with the - Adw.ButtonContent to change, the SeasonModel of the modified season, and a list of tuples of all + data (Tuple[Gtk.Button, SeasonModel, List[Tuple[SeasonModel, List[EpisodeRow]]]]): tuple with the + Gtk.Button to change, the SeasonModel of the modified season, and a list of tuples of all SeasonModels and associated EpisodeRows. Returns: @@ -304,7 +358,6 @@ def _on_season_watched_clicked(self, self.content = local.get_series_by_id(self.content.id) # type: ignore - btn_content = data[0] season_idx = 0 for idx, season in enumerate(self.content.seasons): # type: ignore if season == data[1]: @@ -316,8 +369,7 @@ def _on_season_watched_clicked(self, episode_rows = item[1] # Make changes in db - # type: ignore - for episode in self.content.seasons[season_idx].episodes: + for episode in self.content.seasons[season_idx].episodes: # type: ignore local.mark_watched_episode(episode.id, not all( episode.watched for episode in self.content.seasons[season_idx].episodes)) # type: ignore @@ -327,12 +379,20 @@ def _on_season_watched_clicked(self, not all(episode.watched for episode in self.content.seasons[season_idx].episodes)) # type: ignore # Update season expander - if not all(episode.watched for episode in self.content.seasons[season_idx].episodes): - btn_content.set_label(_('Watched')) - btn_content.set_icon_name('check-plain') + if not self.mobile: + btn_content = data[0] + + if not all(episode.watched for episode in self.content.seasons[season_idx].episodes): # type: ignore + btn_content.set_label(_('Watched')) + btn_content.set_icon_name('check-plain') + else: + btn_content.set_label(_('Mark as Watched')) + btn_content.set_icon_name('watchlist') else: - btn_content.set_label(_('Mark as Watched')) - btn_content.set_icon_name('watchlist') + if all(episode.watched for episode in self.content.seasons[season_idx].episodes): # type: ignore + data[0].set_icon_name('check-plain') + else: + data[0].set_icon_name('watchlist') # Update season status local.mark_watched_series(self.content.id, not all( # type: ignore @@ -368,7 +428,7 @@ def _build_flow_box(self) -> None: label.add_css_class('heading') box.append(label) # type: ignore - box.append(Gtk.Label(label=self.content.original_language.name)) + box.append(Gtk.Label(label=self.content.original_language.name, lines=2, wrap=True)) self._flow_box.append(box) if self.content.original_title: # type: ignore @@ -377,7 +437,7 @@ def _build_flow_box(self) -> None: label.add_css_class('heading') box.append(label) # type: ignore - box.append(Gtk.Label(label=self.content.original_title)) + box.append(Gtk.Label(label=self.content.original_title, lines=2, wrap=True)) self._flow_box.append(box) # Movie specific @@ -488,10 +548,9 @@ def _on_edit_btn_clicked(self, user_data: object | None) -> None: logging.info( f'Editing [{"movie" if type(self.content) is MovieModel else "TV Serie"}] {self.content.title}') - dialog = AddManualDialog(self.get_ancestor( - Gtk.Window), True, self.content) + dialog = AddManualDialog(edit_mode=True, content=self.content) dialog.connect('edit-saved', self._on_edit_saved) - dialog.present() + dialog.present(self) def _on_edit_saved(self, source: Gtk.Widget, content: MovieModel | SeriesModel) -> None: """ diff --git a/src/ui/dialogs/add_manual.blp b/src/ui/dialogs/add_manual.blp index 2024a2c..9c5957b 100644 --- a/src/ui/dialogs/add_manual.blp +++ b/src/ui/dialogs/add_manual.blp @@ -1,26 +1,15 @@ // Copyright (C) 2022 - 2023 Alessandro Iepure -// +// // SPDX-License-Identifier: GPL-3.0-or-later - using Gtk 4.0; using Adw 1; -template $AddManualDialog: Adw.Window { - modal: true; - default-height: 670; - default-width: 650; - +template $AddManualDialog: Adw.Dialog { + content-height: 670; + content-width: 650; map => $_on_map(); - ShortcutController { - Shortcut { - trigger: "Escape"; - action: "action(window.close)"; - } - } - Adw.ToolbarView { - [top] Adw.HeaderBar { show-end-title-buttons: false; @@ -41,11 +30,12 @@ template $AddManualDialog: Adw.Window { orientation: horizontal; valign: center; - styles ["linked"] + styles [ + "linked" + ] ToggleButton _movies_btn { label: C_("Category", "Movie"); - toggled => $_on_movies_btn_toggled(); } @@ -66,86 +56,76 @@ template $AddManualDialog: Adw.Window { Button _save_btn { label: _("Save"); sensitive: false; - clicked => $_on_done_btn_clicked(); - styles ["suggested-action"] + + styles [ + "suggested-action" + ] } } content: ScrolledWindow { vexpand: true; - propagate-natural-width: true; + hexpand: true; Box { orientation: vertical; - vexpand: true; margin-start: 20; margin-end: 20; margin-bottom: 20; - Box { - orientation: horizontal; - margin-top: 20; + $ImageSelector _poster {} + + Adw.PreferencesGroup { margin-bottom: 20; - spacing: 20; + title: _("General"); - $ImageSelector _poster {} + Adw.EntryRow _title_entry { + title: _("Title (required)"); + changed => $_on_title_changed(); + } + + Adw.ActionRow { + title: _("Release Date"); + activatable-widget: _release_date_menu_btn; - Box { - orientation: vertical; - spacing: 6; - valign: start; - width-request: 410; + MenuButton _release_date_menu_btn { + valign: center; - Adw.PreferencesGroup { - title: _("General"); + popover: Popover { + Calendar _calendar { + day-selected => $_on_calendar_day_selected(); + } + }; - Adw.EntryRow _title_entry { - title: _("Title (required)"); + styles [ + "flat" + ] + } + } - changed => $_on_title_changed(); - } + Adw.EntryRow _genres_entry { + title: _("Genres (comma separated)"); + } - Adw.ActionRow { - title: _("Release Date"); - activatable-widget: _release_date_menu_btn; + Adw.SpinRow _runtime_spinrow { + visible: bind _movies_btn.active; + title: _("Runtime (minutes)"); - MenuButton _release_date_menu_btn { - valign: center; - popover: Popover { - Calendar _calendar { - day-selected => $_on_calendar_day_selected(); - } - }; + adjustment: Adjustment { + lower: 0; + upper: 900; + step-increment: 1; + }; + } - styles ["flat"] - } - } - - Adw.EntryRow _genres_entry { - title: _("Genres (comma separated)"); - } - - Adw.SpinRow _runtime_spinrow { - visible: bind _movies_btn.active; - title: _("Runtime (minutes)"); - adjustment: Adjustment { - lower: 0; - upper: 900; - step-increment: 1; - }; - } - - Adw.EntryRow _tagline_entry { - title: _("Tagline"); - } - - Adw.EntryRow _creator_entry { - visible: bind _series_btn.active; - title: _("Created by"); - } + Adw.EntryRow _tagline_entry { + title: _("Tagline"); + } - } + Adw.EntryRow _creator_entry { + visible: bind _series_btn.active; + title: _("Created by"); } } @@ -161,7 +141,9 @@ template $AddManualDialog: Adw.Window { left-margin: 12; wrap-mode: word; - styles ["card"] + styles [ + "card" + ] } } @@ -178,6 +160,7 @@ template $AddManualDialog: Adw.Window { } clicked => $_on_season_add_btn_clicked(); + styles ["accent"] } } @@ -190,6 +173,7 @@ template $AddManualDialog: Adw.Window { Adw.ComboRow _original_language_comborow { title: _("Original Language"); + model: StringList _language_model {}; } @@ -200,6 +184,7 @@ template $AddManualDialog: Adw.Window { Adw.SpinRow _budget_spinrow { visible: bind _movies_btn.active; title: _("Budget (US$)"); + adjustment: Adjustment { lower: 0; upper: 9999999999999999999; @@ -210,6 +195,7 @@ template $AddManualDialog: Adw.Window { Adw.SpinRow _revenue_spinrow { visible: bind _movies_btn.active; title: _("Revenue (US$)"); + adjustment: Adjustment { lower: 0; upper: 9999999999999999999; diff --git a/src/ui/dialogs/add_tmdb.blp b/src/ui/dialogs/add_tmdb.blp index 2b6549d..f64286b 100644 --- a/src/ui/dialogs/add_tmdb.blp +++ b/src/ui/dialogs/add_tmdb.blp @@ -6,13 +6,10 @@ using Gtk 4.0; using Gio 2.0; using Adw 1; -template $AddTMDBDialog: Adw.Window { - modal: true; +template $AddTMDBDialog: Adw.Dialog { focus-widget: _search_entry; - width-request: 350; - height-request: 550; - default-width: 700; - default-height: 550; + content-width: 700; + content-height: 550; ShortcutController { Shortcut { diff --git a/src/ui/dialogs/edit_season.blp b/src/ui/dialogs/edit_season.blp index d517b92..38c7b7d 100644 --- a/src/ui/dialogs/edit_season.blp +++ b/src/ui/dialogs/edit_season.blp @@ -1,30 +1,19 @@ // Copyright (C) 2022 - 2023 Alessandro Iepure -// +// // SPDX-License-Identifier: GPL-3.0-or-later - using Gtk 4.0; using Adw 1; -template $EditSeasonDialog: Adw.Window { - modal: true; - default-width: 600; - default-height: 640; - +template $EditSeasonDialog: Adw.Dialog { + content-width: 600; + content-height: 640; map => $_on_map(); - ShortcutController { - Shortcut { - trigger: "Escape"; - action: "action(window.close)"; - } - } - Adw.NavigationView _navigation_view { Adw.NavigationPage { title: _("Edit Season"); - child: Adw.ToolbarView { - + child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; @@ -40,39 +29,31 @@ template $EditSeasonDialog: Adw.Window { Button _save_btn { label: _("Save"); sensitive: false; - clicked => $_on_save_btn_clicked(); - styles ["suggested-action"] + + styles [ + "suggested-action" + ] } } content: ScrolledWindow { - vexpand: true; - propagate-natural-width: true; Box { orientation: vertical; - vexpand: true; margin-start: 20; margin-end: 20; margin-bottom: 20; - Box { - orientation: horizontal; - margin-top: 20; - margin-bottom: 20; - spacing: 20; - - $ImageSelector _poster {} + $ImageSelector _poster {} - Adw.PreferencesGroup { - title: _("General"); - - Adw.EntryRow _title_entry { - title: _("Title (required)"); + Adw.PreferencesGroup { + margin-bottom: 20; + title: _("General"); - changed => $_on_title_entry_changed(); - } + Adw.EntryRow _title_entry { + title: _("Title (required)"); + changed => $_on_title_entry_changed(); } } @@ -88,6 +69,7 @@ template $EditSeasonDialog: Adw.Window { } clicked => $_on_add_btn_clicked(); + styles ["accent"] } } } diff --git a/src/ui/pages/details_page.blp b/src/ui/pages/details_page.blp index b39eaaf..b7b32e2 100644 --- a/src/ui/pages/details_page.blp +++ b/src/ui/pages/details_page.blp @@ -1,7 +1,6 @@ // Copyright (C) 2023 Alessandro Iepure -// +// // SPDX-License-Identifier: GPL-3.0-or-later - using Gtk 4.0; using Adw 1; @@ -9,6 +8,7 @@ template $DetailsView: Adw.NavigationPage { child: Adw.ViewStack _view_stack { Adw.ViewStackPage { name: "filled"; + child: Overlay { Picture _background_picture { content-fit: fill; @@ -17,10 +17,11 @@ template $DetailsView: Adw.NavigationPage { [overlay] Adw.ToolbarView { - [top] Adw.HeaderBar { - styles ["flat"] + styles [ + "flat" + ] [end] MenuButton _menu_btn { @@ -30,226 +31,296 @@ template $DetailsView: Adw.NavigationPage { } } - content: ScrolledWindow { - child: Box { - orientation: vertical; - - Box { - orientation: horizontal; - margin-start: 50; - margin-top: 25; - margin-bottom: 50; - margin-end: 50; - spacing: 50; + content: Adw.BreakpointBin { + width-request: 360; + height-request: 200; + + Adw.Breakpoint _breakpoint { + condition ("max-width: 550sp") + setters { + main-details.orientation: vertical; + main-details.margin-start: 0; + main-details.margin-end: 0; + main-details.margin-bottom: 0; + main-details.spacing: 0; + _poster_picture.halign: center; + _poster_picture.margin-bottom: 20; + details.margin-start: 20; + details.margin-end: 20; + _seasons_box.margin-start: 20; + _seasons_box.margin-end: 20; + _additional_info_box.margin-start: 20; + _additional_info_box.margin-end: 20; + } - Picture _poster_picture { - height-request: 300; - width-request: 200; - content-fit: fill; + apply => $_on_breakpoint_applied(); + unapply => $_on_breakpoint_unapplied(); + } - styles ["poster"] - } + child: ScrolledWindow scrolled-window { + child: Box { + orientation: vertical; - Box { - orientation: vertical; - vexpand: true; - hexpand: true; + Box main-details { + orientation: horizontal; + margin-start: 50; + margin-top: 25; + margin-bottom: 50; margin-end: 50; - spacing: 6; - valign: center; - halign: start; - height-request: -1; - width-request: 400; - - Label _title_lbl { - lines: 2; - halign: start; - wrap: true; + spacing: 50; - styles ["title-1"] - } - - Label _tagline_lbl { - visible: false; - halign: start; + Picture _poster_picture { + height-request: 300; + width-request: 200; + content-fit: fill; - styles ["heading"] + styles [ + "poster" + ] } - Label _genres_lbl { - visible: false; - halign: start; - } - - Box { - orientation: horizontal; + Box details { + orientation: vertical; + margin-end: 50; spacing: 6; - Label _chip1_lbl { - visible: false; - - styles ["caption", "chip"] - } - - Label _chip2_lbl { - visible: false; + Label _title_lbl { + lines: 2; + halign: start; + wrap: true; - styles ["caption", "chip"] + styles [ + "title-1" + ] } - Label _chip3_lbl { + Label _tagline_lbl { visible: false; + halign: start; + wrap: true; + lines: 2; - styles ["caption", "chip"] + styles [ + "heading" + ] } - } - - Box { - orientation: horizontal; - Button _watched_btn { + Label _genres_lbl { visible: false; - child: Adw.ButtonContent _btn_content {}; - valign: center; - margin-end: 16; - - styles ["pill", "opaque"] - clicked => $_on_watched_btn_clicked(); + halign: start; + wrap: true; + lines: 2; } Box { orientation: horizontal; spacing: 6; - Button _update_btn { - icon-name: "update"; - tooltip-text: _("Update Metadata"); - valign: center; + Label _chip1_lbl { visible: false; - styles ["circular"] - clicked => $_on_update_btn_clicked(); + styles [ + "caption", + "chip" + ] } - Button _edit_btn { - icon-name: "document-edit"; - tooltip-text: _("Edit"); - valign: center; + Label _chip2_lbl { visible: false; - styles ["circular"] - clicked => $_on_edit_btn_clicked(); + styles [ + "caption", + "chip" + ] } - Button _delete_btn { - icon-name: "user-trash-symbolic"; - tooltip-text: _("Delete"); - valign: center; + Label _chip3_lbl { + visible: false; - styles ["circular"] - clicked => $_on_delete_btn_clicked(); + styles [ + "caption", + "chip" + ] } } - } - Box _description_box { - orientation: vertical; - visible: false; + Box { + orientation: horizontal; - Separator { - margin-top: 12; + Button _watched_btn { + visible: false; - styles ["spacer"] - } + child: Adw.ButtonContent _btn_content {}; - Label { - label: _("Description"); - halign: start; + valign: center; + margin-end: 16; - styles ["title-2"] - } + styles [ + "pill", + "opaque" + ] - Label _overview_lbl { - halign: start; - wrap: true; + clicked => $_on_watched_btn_clicked(); + } + + Box { + orientation: horizontal; + spacing: 6; + + Button _update_btn { + icon-name: "update"; + tooltip-text: _("Update Metadata"); + valign: center; + visible: false; + + styles [ + "circular" + ] + + clicked => $_on_update_btn_clicked(); + } + + Button _edit_btn { + icon-name: "document-edit"; + tooltip-text: _("Edit"); + valign: center; + visible: false; + + styles [ + "circular" + ] + + clicked => $_on_edit_btn_clicked(); + } + + Button _delete_btn { + icon-name: "user-trash-symbolic"; + tooltip-text: _("Delete"); + valign: center; + + styles [ + "circular" + ] + + clicked => $_on_delete_btn_clicked(); + } + } } - } - Separator { - styles ["spacer"] - } + Box _description_box { + orientation: vertical; + visible: false; - Box _creator_box { - visible: false; - halign: start; - orientation: vertical; + Separator { + margin-top: 12; - Label { - label: _("Created by"); - halign: start; + styles [ + "spacer" + ] + } + + Label { + label: _("Description"); + halign: start; - styles ["heading"] + styles [ + "title-2" + ] + } + + Label _overview_lbl { + halign: start; + wrap: true; + } } - Label _creator_lbl { + Separator { + styles [ + "spacer" + ] + } + + Box _creator_box { + visible: false; halign: start; + orientation: vertical; + + Label { + label: _("Created by"); + halign: start; + + styles [ + "heading" + ] + } + + Label _creator_lbl { + halign: start; + } } } } - } - - Separator { - margin-bottom: 16; + Separator { + margin-bottom: 16; - styles ["spacer"] - } + styles [ + "spacer" + ] + } - Box _seasons_box { - margin-start: 50; - margin-end: 50; - visible: false; - orientation: vertical; + Box _seasons_box { + margin-start: 50; + margin-end: 50; + visible: false; + orientation: vertical; - Label { - label: _("Seasons"); - halign: start; - margin-bottom: 6; + Label { + label: _("Seasons"); + halign: start; + margin-bottom: 6; - styles ["title-2"] - } + styles [ + "title-2" + ] + } - Adw.PreferencesGroup _seasons_group {} + Adw.PreferencesGroup _seasons_group {} - Separator { - margin-bottom: 16; + Separator { + margin-bottom: 16; - styles ["spacer"] + styles [ + "spacer" + ] + } } - } - Box _additional_info_box { - valign: start; - vexpand: true; - orientation: vertical; - margin-bottom: 20; - margin-start: 50; - margin-end: 50; + Box _additional_info_box { + valign: start; + vexpand: true; + orientation: vertical; + margin-bottom: 20; + margin-start: 50; + margin-end: 50; - Label { - label: _("Additional Information"); - halign: start; + Label { + label: _("Additional Information"); + halign: start; - styles ["title-2"] - } + styles [ + "title-2" + ] + } - FlowBox _flow_box { - orientation: horizontal; - hexpand: true; - selection-mode: none; - max-children-per-line: 12; + FlowBox _flow_box { + orientation: horizontal; + hexpand: true; + selection-mode: none; + max-children-per-line: 12; + } } - } + }; }; }; } @@ -258,11 +329,13 @@ template $DetailsView: Adw.NavigationPage { Adw.ViewStackPage { name: "loading"; - child: Adw.ToolbarView { + child: Adw.ToolbarView { [top] Adw.HeaderBar { - styles ["flat"] + styles [ + "flat" + ] } content: Box { @@ -271,10 +344,7 @@ template $DetailsView: Adw.NavigationPage { valign: center; vexpand: true; - Spinner { - spinning: true; - height-request: 64; - } + Adw.Spinner {} Box { orientation: vertical; @@ -286,7 +356,10 @@ template $DetailsView: Adw.NavigationPage { wrap: true; halign: center; justify: center; - styles ["title-1"] + + styles [ + "title-1" + ] } Label _status_lbl { diff --git a/src/ui/pages/edit_episode_page.blp b/src/ui/pages/edit_episode_page.blp index c22d543..1cbc4a3 100644 --- a/src/ui/pages/edit_episode_page.blp +++ b/src/ui/pages/edit_episode_page.blp @@ -1,17 +1,14 @@ // Copyright (C) 2022 - 2023 Alessandro Iepure -// +// // SPDX-License-Identifier: GPL-3.0-or-later - using Gtk 4.0; using Adw 1; template $EditEpisodeNavigationPage: Adw.NavigationPage { - map => $_on_map(); - title: _("Edit Episode"); - child: Adw.ToolbarView { + child: Adw.ToolbarView { [top] Adw.HeaderBar { show-end-title-buttons: false; @@ -20,66 +17,53 @@ template $EditEpisodeNavigationPage: Adw.NavigationPage { [end] Button _save_btn { label: _("Save"); - sensitive: bind $_enable_save(_title_entry.text, (_episode_spin_row.value) as ) as ; + sensitive: bind $_enable_save(_title_entry.text,(_episode_spin_row.value) as ) as ; clicked => $_on_save_btn_clicked(); - styles ["suggested-action"] + styles [ + "suggested-action" + ] } } content: ScrolledWindow { - vexpand: true; - propagate-natural-width: true; - Box { orientation: vertical; - vexpand: true; margin-start: 20; margin-end: 20; margin-bottom: 20; - Box { - orientation: horizontal; - margin-top: 20; - margin-bottom: 20; - spacing: 20; + $ImageSelector _still { + content-fit: cover; + } - $ImageSelector _still { - content-fit: cover; - } + Adw.PreferencesGroup { + title: _("General"); + margin-bottom: 20; - Box { - orientation: vertical; - spacing: 6; - valign: start; - width-request: 300; + Adw.SpinRow _episode_spin_row { + title: _("Episode Number (required)"); - Adw.PreferencesGroup { - title: _("General"); + adjustment: Adjustment { + lower: 0; + upper: 900; + step-increment: 1; + }; + } - Adw.SpinRow _episode_spin_row { - title: _("Episode Number (required)"); - adjustment: Adjustment { - lower: 0; - upper: 900; - step-increment: 1; - }; - } + Adw.EntryRow _title_entry { + title: _("Title (required)"); + use-markup: true; + } - Adw.EntryRow _title_entry { - title: _("Title (required)"); - use-markup: true; - } + Adw.SpinRow _runtime_spin_row { + title: _("Runtime (minutes)"); - Adw.SpinRow _runtime_spin_row { - title: _("Runtime (minutes)"); - adjustment: Adjustment { - lower: 0; - upper: 900; - step-increment: 1; - }; - } - } + adjustment: Adjustment { + lower: 0; + upper: 900; + step-increment: 1; + }; } } @@ -94,7 +78,9 @@ template $EditEpisodeNavigationPage: Adw.NavigationPage { left-margin: 12; wrap-mode: word; - styles ["card"] + styles [ + "card" + ] } } } diff --git a/src/ui/views/content_view.blp b/src/ui/views/content_view.blp index 27184bf..311ff02 100644 --- a/src/ui/views/content_view.blp +++ b/src/ui/views/content_view.blp @@ -1,7 +1,6 @@ // Copyright (C) 2023 Alessandro Iepure -// +// // SPDX-License-Identifier: GPL-3.0-or-later - using Gtk 4.0; using Adw 1; @@ -9,6 +8,7 @@ template $ContentView: Adw.Bin { Adw.ViewStack _stack { Adw.ViewStackPage { name: "empty"; + child: Adw.StatusPage { icon-name: bind template.icon-name; title: _("Your Watchlist Is Empty"); @@ -18,11 +18,12 @@ template $ContentView: Adw.Bin { Adw.ViewStackPage { name: "updating"; + child: Adw.StatusPage { child: Box { orientation: vertical; - Spinner { - spinning: true; + + Adw.Spinner { height-request: 64; } @@ -36,7 +37,10 @@ template $ContentView: Adw.Bin { wrap: true; halign: center; justify: center; - styles ["title-1"] + + styles [ + "title-1" + ] } Label _updating_status_lbl { @@ -51,11 +55,12 @@ template $ContentView: Adw.Bin { Adw.ViewStackPage { name: "loading"; + child: Adw.StatusPage { child: Box { orientation: vertical; - Spinner { - spinning: true; + + Adw.Spinner { height-request: 64; } @@ -69,7 +74,10 @@ template $ContentView: Adw.Bin { wrap: true; halign: center; justify: center; - styles ["title-1"] + + styles [ + "title-1" + ] } Label _status_lbl { @@ -85,6 +93,7 @@ template $ContentView: Adw.Bin { Adw.ViewStackPage { name: "filled"; + child: ScrolledWindow { Box { orientation: vertical; @@ -100,13 +109,16 @@ template $ContentView: Adw.Bin { margin-start: 12; margin-top: 12; label: _("Your Watchlist"); - styles ["title-1"] + + styles [ + "title-1" + ] } FlowBox _flow_box { orientation: horizontal; min-children-per-line: 2; - max-children-per-line: 11; + max-children-per-line: 15; selection-mode: none; halign: start; } @@ -126,13 +138,16 @@ template $ContentView: Adw.Bin { margin-start: 12; margin-top: 12; label: _("Unwatched"); - styles ["title-1"] + + styles [ + "title-1" + ] } FlowBox _unwatched_flow_box { orientation: horizontal; min-children-per-line: 2; - max-children-per-line: 11; + max-children-per-line: 15; selection-mode: none; halign: start; } @@ -146,18 +161,20 @@ template $ContentView: Adw.Bin { margin-start: 12; margin-top: 12; label: _("Watched"); - styles ["title-1"] + + styles [ + "title-1" + ] } FlowBox _watched_flow_box { orientation: horizontal; min-children-per-line: 2; - max-children-per-line: 11; + max-children-per-line: 15; selection-mode: none; halign: start; } } - } } }; diff --git a/src/ui/views/first_run_view.blp b/src/ui/views/first_run_view.blp index 6fba9b1..bde4c95 100644 --- a/src/ui/views/first_run_view.blp +++ b/src/ui/views/first_run_view.blp @@ -24,8 +24,7 @@ template $FirstRunView: Adw.Bin { valign: center; vexpand: true; - Spinner { - spinning: true; + Adw.Spinner { height-request: 64; } diff --git a/src/ui/widgets/background_indicator.blp b/src/ui/widgets/background_indicator.blp index 6ada7d8..4290c49 100644 --- a/src/ui/widgets/background_indicator.blp +++ b/src/ui/widgets/background_indicator.blp @@ -14,8 +14,7 @@ template $BackgroundIndicator: Adw.Bin { child: Overlay { [overlay] - Spinner _spinner { - spinning: true; + Adw.Spinner _spinner { visible: false; } diff --git a/src/ui/widgets/episode_row.blp b/src/ui/widgets/episode_row.blp index 59b873b..666d7a5 100644 --- a/src/ui/widgets/episode_row.blp +++ b/src/ui/widgets/episode_row.blp @@ -58,9 +58,8 @@ template $EpisodeRow: Adw.PreferencesRow { Box { visible: bind template.show-controls; - Button { + Button _watched_btn { visible: bind template.editable inverted; - child: Adw.ButtonContent _watched_btn {}; valign: center; clicked => $_on_watched_btn_clicked(); diff --git a/src/ui/widgets/image_selector.blp b/src/ui/widgets/image_selector.blp index 91c1805..640edde 100644 --- a/src/ui/widgets/image_selector.blp +++ b/src/ui/widgets/image_selector.blp @@ -6,14 +6,18 @@ using Gtk 4.0; using Adw 1; template $ImageSelector: Adw.Bin { + halign: center; map => $_on_map(); Overlay { + height-request: 250; + valign: center; + halign: center; + [overlay] - Spinner _spinner { - margin-start: 72; - margin-end: 72; + Adw.Spinner _spinner { + visible: false; } Overlay { @@ -53,7 +57,6 @@ template $ImageSelector: Adw.Bin { } Picture _poster_picture { - height-request: 200; content-fit: bind template.content-fit; styles ["poster"] diff --git a/src/ui/widgets/poster_button.blp b/src/ui/widgets/poster_button.blp index 35358a8..4db9619 100644 --- a/src/ui/widgets/poster_button.blp +++ b/src/ui/widgets/poster_button.blp @@ -34,9 +34,8 @@ template $PosterButton: Box { halign: center; [overlay] - Spinner _spinner { + Adw.Spinner _spinner { height-request: 32; - spinning: true; valign: center; } diff --git a/src/ui/widgets/search_result_row.blp b/src/ui/widgets/search_result_row.blp index fb9b9c9..69fe609 100644 --- a/src/ui/widgets/search_result_row.blp +++ b/src/ui/widgets/search_result_row.blp @@ -24,10 +24,9 @@ template $SearchResultRow: ListBoxRow { Overlay { [overlay] - Spinner _poster_spinner { + Adw.Spinner _poster_spinner { height-request: 32; width-request: 32; - spinning: true; valign: center; } @@ -99,8 +98,7 @@ template $SearchResultRow: ListBoxRow { styles ["suggested-action"] } - Spinner _add_spinner { - spinning: true; + Adw.Spinner _add_spinner { height-request: 16; visible: false; } diff --git a/src/views/main_view.py b/src/views/main_view.py index 296f966..464d47a 100644 --- a/src/views/main_view.py +++ b/src/views/main_view.py @@ -186,7 +186,7 @@ def _update_content(self, activity: BackgroundActivity) -> None: for serie in series: # type: ignore if not serie.manual: new_serie = SeriesModel(tmdb.get_serie(serie.id)) - local.update_series(old=serie,serie=new_serie) + local.update_series(old=serie,new=new_serie) def _on_update_done(self, source: GObject.Object, diff --git a/src/widgets/episode_row.py b/src/widgets/episode_row.py index 1a19bad..b09582b 100644 --- a/src/widgets/episode_row.py +++ b/src/widgets/episode_row.py @@ -47,6 +47,7 @@ class EpisodeRow(Adw.PreferencesRow): editable = GObject.Property(type=bool, default=False) show_controls = GObject.Property(type=bool, default=True) watched = GObject.Property(type=bool, default=False) + small_controls = GObject.Property(type=bool, default=False) __gsignals__ = { 'watched-clicked': (GObject.SIGNAL_RUN_FIRST, None, ()), @@ -67,7 +68,8 @@ def __init__(self, still_uri: str = '', watched: bool = False, editable: bool = False, - show_controls: bool = True): + show_controls: bool = True, + small_controls: bool = False): super().__init__() if episode: @@ -89,6 +91,7 @@ def __init__(self, self.editable = editable self.show_controls = show_controls + self.small_controls = small_controls @Gtk.Template.Callback('_on_map') def _on_map(self, user_data: object | None) -> None: @@ -104,18 +107,31 @@ def _on_map(self, user_data: object | None) -> None: """ if not self.editable and self.show_controls: - self.watched = local.get_episode_by_id(self.id).watched # type: ignore + self.watched = local.get_episode_by_id( + self.id).watched # type: ignore - self._still_picture.set_file(Gio.File.new_for_uri(self.still_uri)) + if self.small_controls: + self._still_picture.set_visible(False) + else: + self._still_picture.set_file(Gio.File.new_for_uri(self.still_uri)) + self._title_lbl.set_text(f'{self.episode_number}. {self.title}') self._runtime_lbl.set_text(self._format_runtime(self.runtime)) - if self.watched: - self._watched_btn.set_label(_('Watched')) - self._watched_btn.set_icon_name('check-plain') + if self.small_controls: + if self.watched: + self._watched_btn.set_icon_name('check-plain') + else: + self._watched_btn.set_icon_name('watchlist') else: - self._watched_btn.set_label(_('Mark as Watched')) - self._watched_btn.set_icon_name('watchlist') + btn_content = Adw.ButtonContent.new() + if self.watched: + btn_content.set_label(_('Watched')) + btn_content.set_icon_name('check-plain') + else: + btn_content.set_label(_('Mark as Watched')) + btn_content.set_icon_name('watchlist') + self._watched_btn.set_child(btn_content) @Gtk.Template.Callback('_on_watched_btn_clicked') def _on_watched_btn_clicked(self, user_data: object | None) -> None: @@ -218,7 +234,8 @@ def _on_episode_saved(self, parent._episodes.remove(old_episode) parent.update_episodes_ui() - parent._episodes.append((title, episode_number, runtime, overview, still_uri)) + parent._episodes.append( + (title, episode_number, runtime, overview, still_uri)) parent.update_episodes_ui() @@ -236,23 +253,26 @@ def _on_delete_btn_clicked(self, user_data: object | None) -> None: """ # TRANSLATORS: {title} is the showed content's title - dialog = Adw.MessageDialog.new(self.get_ancestor(Adw.Window), - C_('message dialog heading', 'Delete {title}?').format( - title=f'{self.episode_number}.{self.title}'), - C_('message dialog body', 'All changes to this episode will be lost.') - ) - dialog.add_response('cancel', C_('message dialog action', '_Cancel')) - dialog.add_response('delete', C_('message dialog action', '_Delete')) - dialog.set_response_appearance('delete', Adw.ResponseAppearance.DESTRUCTIVE) - dialog.choose(None, self._on_message_dialog_choose, None) - - def _on_message_dialog_choose(self, - source: GObject.Object | None, - result: Gio.AsyncResult, - user_data: object | None) -> None: + dialog = Adw.AlertDialog.new( + heading=C_('alert dialog heading', 'Delete {title}?').format( + title=f'{self.episode_number}. {self.title}'), + body=C_('alert dialog body', + 'All changes to this episode will be lost.') + ) + dialog.add_response('cancel', C_('alert dialog action', '_Cancel')) + dialog.add_response('delete', C_('alert dialog action', '_Delete')) + dialog.set_response_appearance( + 'delete', Adw.ResponseAppearance.DESTRUCTIVE) + dialog.choose(self, None, self._on_alert_dialog_choose, None) + + def _on_alert_dialog_choose(self, + source: GObject.Object | None, + result: Gio.AsyncResult, + user_data: object | None) -> None: """ - Callback for the message dialog. - Finishes the async operation and retrieves the user response. If the later is positive, deletes the episode from the list and updates the ui. + Callback for the alert dialog. + Finishes the async operation and retrieves the user response. If the later + is positive, deletes the episode from the list and updates the ui. Args: source (Gtk.Widget): object that started the async operation @@ -263,7 +283,7 @@ def _on_message_dialog_choose(self, None """ - result = Adw.MessageDialog.choose_finish(source, result) + result = Adw.AlertDialog.choose_finish(source, result) if result == 'cancel': return diff --git a/src/widgets/season_expander.py b/src/widgets/season_expander.py index b50ab11..b94efd3 100644 --- a/src/widgets/season_expander.py +++ b/src/widgets/season_expander.py @@ -136,18 +136,21 @@ def _on_delete_btn_clicked(self, user_data: object | None) -> None: """ # TRANSLATORS: {title} is the showed content's title - dialog = Adw.MessageDialog.new(self.get_ancestor(Adw.Window), - C_('message dialog heading', 'Delete {title}?').format(title=self.season_title), - C_('message dialog body', 'This season contains unsaved metadata.') - ) - dialog.add_response('cancel', C_('message dialog action', '_Cancel')) - dialog.add_response('delete', C_('message dialog action', '_Delete')) - dialog.set_response_appearance('delete', Adw.ResponseAppearance.DESTRUCTIVE) - dialog.choose(None, self._on_message_dialog_choose, None) - - def _on_message_dialog_choose(self, source: GObject.Object | None, result: Gio.AsyncResult, user_data: object | None) -> None: + dialog = Adw.AlertDialog.new( + heading=C_('alert dialog heading', 'Delete {title}?').format( + title=self.season_title), + body=C_('alert dialog body', + 'This season contains unsaved metadata.') + ) + dialog.add_response('cancel', C_('alert dialog action', '_Cancel')) + dialog.add_response('delete', C_('alert dialog action', '_Delete')) + dialog.set_response_appearance( + 'delete', Adw.ResponseAppearance.DESTRUCTIVE) + dialog.choose(self, None, self._on_alert_dialog_choose, None) + + def _on_alert_dialog_choose(self, source: GObject.Object | None, result: Gio.AsyncResult, user_data: object | None) -> None: """ - Callback for the message dialog. + Callback for the alert dialog. Finishes the async operation and retrieves the user response. If the later is positive, delete the content from the db. Args: @@ -159,11 +162,12 @@ def _on_message_dialog_choose(self, source: GObject.Object | None, result: Gio.A None """ - result = Adw.MessageDialog.choose_finish(source, result) + result = Adw.AlertDialog.choose_finish(source, result) if result == 'cancel': return parent_dialog = self.get_ancestor(dialog.AddManualDialog) - old_season = parent_dialog.get_season(self.season_title, self.poster_uri, self.episodes) + old_season = parent_dialog.get_season( + self.season_title, self.poster_uri, self.episodes) parent_dialog.seasons.remove(old_season) parent_dialog.update_seasons_ui() diff --git a/src/window.py b/src/window.py index 4a39beb..6a29b6e 100644 --- a/src/window.py +++ b/src/window.py @@ -59,15 +59,14 @@ def _add_tmdb(self, new_state: None, source: Gtk.Widget) -> None: Args: new_state (None): stateless action, always None - source (Gtk.Widget): widget that caused the activation Returns: None """ - dialog = AddTMDBDialog(source) + dialog = AddTMDBDialog() logging.info('Add from TMDB dialog open') - dialog.present() + dialog.present(source) def _add_manual(self, new_state: None, source: Gtk.Widget) -> None: """ @@ -75,15 +74,14 @@ def _add_manual(self, new_state: None, source: Gtk.Widget) -> None: Args: new_state (None): stateless action, always None - source (Gtk.Widget): widget that caused the activation Returns: None """ - dialog = AddManualDialog(source) + dialog = AddManualDialog() logging.info('Add manual dialog open') - dialog.present() + dialog.present(source) def _refresh(self, new_state: None, source: Gtk.Widget) -> None: """