Skip to content

Commit

Permalink
feat: added syncing page layout (adaptive)
Browse files Browse the repository at this point in the history
- chore: added .pyi files for better IDE working
- added realization for easy cover update in SongCard for future
  • Loading branch information
Dzheremi2 committed Dec 24, 2024
1 parent f53ea66 commit 1d0927d
Show file tree
Hide file tree
Showing 30 changed files with 606 additions and 23 deletions.
1 change: 1 addition & 0 deletions chronograph/__builtins__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def _(_msg: str, /) -> str: ...
4 changes: 3 additions & 1 deletion chronograph/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class ChronographApplication(Adw.Application):
win: ChronographWindow

def __init__(self) -> None:
super().__init__(application_id=shared.APP_ID)
super().__init__(
application_id=shared.APP_ID, flags=Gio.ApplicationFlags.DEFAULT_FLAGS
)
theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
theme.add_resource_path(shared.PREFIX + "/data/icons")

Expand Down
9 changes: 6 additions & 3 deletions chronograph/shared.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from pathlib import Path

from gi.repository import Gio, Adw # type: ignore
from gi.repository import Gio

from chronograph.main import ChronographApplication
from chronograph.window import ChronographWindow # type: ignore

APP_ID: str
VERSION: str
Expand All @@ -12,5 +15,5 @@ cache_dir: Path
schema: Gio.Settings
state_schema: Gio.Settings

app: Adw.Application
win: Adw.ApplicationWindow
app: ChronographApplication
win: ChronographWindow
4 changes: 2 additions & 2 deletions chronograph/ui/BoxDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class BoxDialog(Adw.Dialog):
diaglog_title_label : Gtk.Label -> Label of the dialog
props_list : Gtk.ListBox -> ListBox with `Adw.ActionRow(s)` with provided data
"""

__gtype_name__ = "BoxDialog"

dialog_title_label: Gtk.Label = Gtk.Template.Child()
Expand All @@ -33,7 +33,7 @@ def __init__(self, label: str, lines_content: tuple) -> None:
for entry in lines_content:
self.props_list.append(
Adw.ActionRow(
title=entry[0], subtitle=entry[1], css_classes=["property"]
title=entry[0], subtitle=entry[1], css_classes=["property"], use_markup=False
)
)

Expand Down
22 changes: 22 additions & 0 deletions chronograph/ui/BoxDialog.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from gi.repository import Adw, Gtk

class BoxDialog(Adw.Dialog):
"""Dialog with lines of `Adw.ActionRow(s)` with provided content
Parameters
----------
label : str
Label of the dialog
lines_content : tuple
titles and subtitles of `Adw.ActionRow(s)`. Like `(("1st Title", "1st subtitle"), ("2nd title", "2nd subtitle"), ...)`
GTK Objects
----------
::
diaglog_title_label : Gtk.Label -> Label of the dialog
props_list : Gtk.ListBox -> ListBox with `Adw.ActionRow(s)` with provided data
"""

dialog_title_label: Gtk.Label
props_list: Gtk.ListBox
77 changes: 65 additions & 12 deletions chronograph/ui/SongCard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
from gi.repository import GObject, Gtk # type: ignore

from chronograph import shared
from chronograph.ui.BoxDialog import BoxDialog
from chronograph.utils.file_mutagen_id3 import FileID3
from chronograph.utils.file_mutagen_vorbis import FileVorbis

label_str = _("About File")
title_str = _("Title")
artist_str = _("Artist")
album_str = _("Album")
path_str = _("Path")


@Gtk.Template(resource_path=shared.PREFIX + "/gtk/ui/SongCard.ui")
class SongCard(Gtk.Box):
"""Card with Title, Artist and Cover of provided file
Parameters
----------
# ----------
file : Union[FileID3, FileVorbis]
File of `.ogg`, `.flac`, `.mp3` and `.wav` formats
Expand All @@ -34,8 +41,9 @@ class SongCard(Gtk.Box):
buttons_revealer: Gtk.Revealer = Gtk.Template.Child()
play_button: Gtk.Button = Gtk.Template.Child()
metadata_editor_button: Gtk.Button = Gtk.Template.Child()
info_button: Gtk.Button = Gtk.Template.Child()
cover_button: Gtk.Button = Gtk.Template.Child()
cover: Gtk.Image = Gtk.Template.Child()
cover_img: Gtk.Image = Gtk.Template.Child()
title_label: Gtk.Label = Gtk.Template.Child()
artist_label: Gtk.Label = Gtk.Template.Child()

Expand All @@ -48,15 +56,35 @@ def __init__(self, file: Union[FileID3, FileVorbis]) -> None:
self.add_controller(self.event_controller_motion)
self.event_controller_motion.connect("enter", self.toggle_buttons)
self.event_controller_motion.connect("leave", self.toggle_buttons)
# self.metadata_editor_button.connect(
# "clicked", lambda *_: self.set_property("title", "New value")
# )
self.info_button.connect(
"clicked",
lambda *_: BoxDialog(
label_str,
(
(title_str, self.title),
(artist_str, self.artist),
(album_str, self.album),
(path_str, self._file.path),
),
).present(shared.win),
)
self.cover_button.connect(
"clicked",
lambda *_: (
shared.win.navigation_view.push(shared.win.sync_navigation_page),
mediastream := Gtk.MediaFile.new_for_filename(self._file.path),
shared.win.controls.set_media_stream(mediastream),
shared.win.controls_shrinked.set_media_stream(mediastream),
),
)
self.metadata_editor_button.connect(
"clicked",
lambda *_: self.set_property(
"cover", open("/home/dzheremi/Pictures/pp.jpg", "rb").read()
),
)
self.bind_props()

if (_texture := self._file.get_cover_texture()) == "icon":
self.cover.set_from_icon_name("note-placeholder")
else:
self.cover.props.paintable = _texture
self.invalidate_cover()

def toggle_buttons(self, *_args) -> None:
"""Sets if buttons should be visible or not"""
Expand All @@ -74,10 +102,24 @@ def invalidate_update(self, property: str) -> None:
"""
getattr(self, f"{property}_label").set_text(getattr(self._file, f"{property}"))

def invalidate_cover(self) -> None:
"""Automatically updates cover on property change"""
if (_texture := self._file.get_cover_texture()) == "icon":
self.cover_img.set_from_icon_name("note-placeholder")
else:
self.cover_img.props.paintable = _texture

def bind_props(self) -> None:
"""Binds properties to update interface labels on change"""
self.connect("notify::title", lambda *_: self.invalidate_update("title"))
self.connect("notify::artist", lambda *_: self.invalidate_update("artist"))
self.connect(
"notify::title",
lambda _object, property: self.invalidate_update(property.name),
)
self.connect(
"notify::artist",
lambda _object, property: self.invalidate_update(property.name),
)
self.connect("notify::cover", lambda *_: self.invalidate_cover())

@GObject.Property(type=str)
def title(self) -> str:
Expand All @@ -102,3 +144,14 @@ def album(self) -> str:
@album.setter
def album(self, value: str) -> None:
self._file.album = value

@GObject.Property
def cover(self) -> Union[str, bytes]:
return self._file.cover

@cover.setter
def cover(self, data: bytes) -> None:
if type(data) == bytes:
self._file.cover = data
else:
raise ValueError("Cover must be bytes")
43 changes: 43 additions & 0 deletions chronograph/ui/SongCard.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Union

from gi.repository import Gtk

from chronograph.utils.file_mutagen_id3 import FileID3
from chronograph.utils.file_mutagen_vorbis import FileVorbis # type: ignore

class SongCard(Gtk.Box):
"""Card with Title, Artist and Cover of provided file
Parameters
----------
file : Union[FileID3, FileVorbis]
File of `.ogg`, `.flac`, `.mp3` and `.wav` formats
GTK Objects
----------
::
buttons_revealer: Gtk.Revealer -> Revealer for Play and Edit buttons
play_button: Gtk.Button -> Play button
metadata_editor_button: Gtk.Button -> Metadata editor button
cover_button: Gtk.Button -> Clickable cover of song
cover: Gtk.Image -> Cover image of song
title_label: Gtk.Label -> Title of song
artist_label: Gtk.Label -> Artist of song
"""

buttons_revealer: Gtk.Revealer
play_button: Gtk.Button
metadata_editor_button: Gtk.Button
info_button: Gtk.Button
cover_button: Gtk.Button
cover_img: Gtk.Image
title_label: Gtk.Label
artist_label: Gtk.Label

_file: Union[FileID3, FileVorbis]

def toggle_buttons(self, *_args) -> None: ...
def invalidate_update(self, property: str) -> None: ...
def invalidate_cover(self) -> None: ...
def bind_props(self) -> None: ...
6 changes: 5 additions & 1 deletion chronograph/utils/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class BaseFile:
_title: str = "Unknown"
_artist: str = "Unknown"
_album: str = "Unknown"
_cover: Union[Gdk.Texture, str] = None
_cover: Union[bytes, str] = None
_mutagen_file: dict = None

def __init__(self, path: str) -> None:
Expand Down Expand Up @@ -91,6 +91,10 @@ def cover(self) -> bytes:
def cover(self, data: bytes) -> None:
self._cover = data

@property
def path(self) -> str:
return self._path

def load_str_data(self) -> None:
"""Should be implemenmted in file specific child classes"""
raise NotImplementedError
Expand Down
34 changes: 34 additions & 0 deletions chronograph/utils/file.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Union

from gi.repository import Gdk

class BaseFile:
"""A base class for mutagen filetypes classes
Parameters
----------
path : str
A path to file for loading
Props
--------
::
title : str -> Title of song
artist : str -> Artist of song
album : str -> Album of song
cover : Gdk.Texture | str -> Cover of song
"""

_title: str
_artist: str
_album: str
_cover: Union[bytes, str]
_mutagen_file: dict

_path: str

def load_from_file(self, path: str) -> None: ...
def get_cover_texture(self) -> Union[Gdk.Texture, str]: ...
def load_str_data(self) -> None: ...
def load_cover(self) -> None: ...
13 changes: 13 additions & 0 deletions chronograph/utils/file_mutagen_id3.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .file import BaseFile

class FileID3(BaseFile):
"""A ID3 compatible file class. Inherited from `BaseFile`
Parameters
--------
path : str
A path to file for loading
"""

def load_cover(self) -> None: ...
def load_str_data(self) -> None: ...
13 changes: 13 additions & 0 deletions chronograph/utils/file_mutagen_vorbis.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .file import BaseFile

class FileVorbis(BaseFile):
"""A Vorbis (ogg, flac) compatible file class. Inherited from `BaseFile`
Parameters
--------
path : str
A path to file for loading
"""

def load_cover(self) -> None: ...
def load_str_data(self, tags: list = ["title", "artist", "album"]) -> None: ...
2 changes: 1 addition & 1 deletion chronograph/utils/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def dir_parser(path: str, *_args) -> None:
Path to directory to parse
"""
shared.win.library.remove_all()
shared.win.library_scrolled_window.set_child(shared.win.library)
path = path + "/"
mutagen_files = []
for file in os.listdir(path):
Expand All @@ -31,7 +32,6 @@ def dir_parser(path: str, *_args) -> None:
for file in mutagen_files:
GLib.idle_add(songcard_idle, file)

shared.win.library_scrolled_window.set_child(shared.win.library)
shared.win.right_buttons_revealer.set_reveal_child(True)
shared.win.left_buttons_revealer.set_reveal_child(True)
# NOTE: This should be implemented in ALL parsers functions
Expand Down
7 changes: 7 additions & 0 deletions chronograph/utils/parsers.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import Union

from chronograph.utils.file_mutagen_id3 import FileID3
from chronograph.utils.file_mutagen_vorbis import FileVorbis

def dir_parser(path: str, *_args) -> None: ...
def songcard_idle(file: Union[FileID3, FileVorbis]) -> None: ...
2 changes: 0 additions & 2 deletions chronograph/utils/select_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import threading
from typing import Any

from gi.repository import Gio, Gtk # type: ignore

Expand All @@ -25,7 +24,6 @@ def on_selected_dir(file_dialog: Gtk.FileDialog, result: Gio.Task) -> None:
result : Gio.Task
Task for reading, callbacked from `select_dir`
"""
print(result)
dir = file_dialog.select_folder_finish(result)
thread = threading.Thread(target=lambda: (dir_parser(dir.get_path())))
thread.daemon = True
Expand Down
4 changes: 4 additions & 0 deletions chronograph/utils/select_data.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from gi.repository import Gio, Gtk # type: ignore

def select_dir(*_args) -> None: ...
def on_selected_dir(file_dialog: Gtk.FileDialog, result: Gio.Task) -> None: ...
5 changes: 5 additions & 0 deletions chronograph/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class ChronographWindow(Adw.ApplicationWindow):
library_scrolled_window: Gtk.ScrolledWindow = Gtk.Template.Child()
library: Gtk.FlowBox = Gtk.Template.Child()

# Syncing page widgets
sync_navigation_page: Adw.NavigationPage = Gtk.Template.Child()
controls: Gtk.MediaControls = Gtk.Template.Child()
controls_shrinked: Gtk.MediaControls = Gtk.Template.Child()

sort_state: str = shared.state_schema.get_string("sorting")

def __init__(self, **kwargs) -> None:
Expand Down
Loading

0 comments on commit 1d0927d

Please sign in to comment.