diff --git a/src/main.py b/src/main.py index e6785f6..7afaade 100644 --- a/src/main.py +++ b/src/main.py @@ -29,6 +29,8 @@ gi.require_version('Handy', '1') from gi.repository import Handy Handy.init() +from .widgets import LoginPage, Timeline, PostItem + class Application(Gtk.Application): def __init__(self): @@ -127,11 +129,13 @@ class Application(Gtk.Application): global_tl = Timeline('global') self.stack.add_titled(global_tl, "global", "Global") + new_post_button = Gtk.Button.new_from_icon_name('list-add-symbolic', 1) + self.header.pack_start(new_post_button) + reload_button = Gtk.Button.new_from_icon_name('view-refresh-symbolic', 1) reload_button.connect('clicked', self.emit_refresh) self.header.pack_start(reload_button) - new_post_button = Gtk.Button.new_from_icon_name('list-add-symbolic', 1) - self.header.pack_start(new_post_button) + self.header.show_all() self.stack.show_all() @@ -140,155 +144,6 @@ class Application(Gtk.Application): timeline = self.stack.get_visible_child() timeline.emit('refresh') -class LoginPage(Gtk.Box): - - __gsignals__ = { - 'login': (GObject.SIGNAL_RUN_FIRST, None, (str,)) - } - - def __init__(self): - super().__init__(orientation='vertical') - self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) - - client_id = "1PiUzxfX_CQxKvtz93lUzPX9-FMtz-va" - redirect_uri = "urn:ietf:wg:oauth:2.0:oob" - scope = "basic,stream,write_post,follow,presence,messages,files,polls" - - uri = "https://pnut.io/oauth/authenticate" - uri += "?client_id=" + client_id - uri += "&redirect_uri=" + redirect_uri - uri += "&scope=" + scope - uri += "&response_type=token" - - self.login_button = Gtk.LinkButton.new_with_label(uri, "Log In to pnut.io") - self.login_button.connect("clicked", self.prompt_code) - - self.set_center_widget(self.login_button) - - def prompt_code(self, button): - self.remove(self.login_button) - - label = Gtk.Label() - label.set_markup('Enter authorization code') - self.code = Gtk.Entry() - - paste_button = Gtk.Button(label="Paste from clipboard") - paste_button.connect("clicked", self.paste_code) - - cancel_button = Gtk.Button(label="Cancel") - cancel_button.connect("clicked", self.cancel_login) - confirm_button = Gtk.Button(label="Confirm") - confirm_button.connect("clicked", self.confirm_login) - lbox = Gtk.Box(orientation='horizontal') - lbox.pack_start(cancel_button, True, True, 0) - lbox.pack_start(confirm_button, True, True, 0) - - vbox = Gtk.Box(orientation='vertical') - vbox.pack_start(label, False, False, 10) - vbox.pack_start(self.code, False, False, 10) - vbox.pack_start(paste_button, False, False, 10) - vbox.add(lbox) - - hbox = Gtk.Box(orientation='horizontal') - hbox.set_center_widget(vbox) - - self.set_center_widget(hbox) - self.show_all() - - def paste_code(self, button): - text = self.clipboard.wait_for_text() - if text is not None: - self.code.set_text(text) - - def cancel_login(self, button): - # TODO: something actually useful here - logging.debug("uh cancel i guess") - - def confirm_login(self, button): - code = self.code.get_text() - self.emit('login', code) - -class Timeline(Gtk.Box): - - __gsignals__ = { - 'refresh': (GObject.SIGNAL_RUN_FIRST, None, ()) - } - - def __init__(self, stream): - super().__init__(orientation='vertical') - - scroller = Gtk.ScrolledWindow( - halign='fill', - kinetic_scrolling=True - ) - self.view = Gtk.ListBox( - selection_mode=Gtk.SelectionMode.NONE - ) - scroller.add(self.view) - self.pack_start(scroller, True, True, 0) - - self.stream = stream - self.load_timeline() - - def load_timeline(self): - if self.stream == 'unified': - posts, meta = pnutpy.api.users_post_streams_unified() - elif self.stream == 'mentions': - posts, meta = pnutpy.api.users_mentioned_posts('me') - elif self.stream == 'bookmarks': - posts, meta = pnutpy.api.users_bookmarked_posts('me') - else: - posts, meta = pnutpy.api.posts_streams_global() - - for item in posts: - if 'is_deleted' in item: - continue - self.view.add(PostItem(item)) - - def do_refresh(self): - rows = self.view.get_children() - for item in rows: - self.view.remove(item) - self.load_timeline() - self.show_all() - -class PostItem(Gtk.ListBoxRow): - - def __init__(self, post): - super(Gtk.ListBoxRow, self).__init__() - self.post = post - - self.box = Gtk.Box(orientation='vertical') - self.add(self.box) - - # name container - self.name_box = Gtk.Box(orientation='vertical') - self.username = Gtk.Label(label="@" + post.user.username, xalign=0) - self.name = Gtk.Label(xalign=0) - if 'name' in post.user: - self.name.set_markup(f"{post.user.name}") - self.name_box.pack_start(self.name, True, True, 0) - self.name_box.pack_start(self.username, True, True, 0) - - # header container - self.h_box = Gtk.Box(orientation='horizontal') - self.avatar = Handy.Avatar(size=32) - # TODO: get the actual image - self.h_box.pack_start(self.avatar, False, False, 18) - self.h_box.pack_start(self.name_box, False, False, 0) - - # content container - self.c_box = Gtk.Box(orientation='horizontal') - self.content = Gtk.Label(wrap=True, xalign=0) - # TODO: parse content links - if 'content' in post: - self.content.set_text(post.content.text) - # TODO: add media - self.c_box.pack_start(self.content, True, True, 18) - - self.box.pack_start(self.h_box, True, True, 10) - self.box.pack_start(self.c_box, True, True, 10) - def main(version): logging.basicConfig(level=logging.DEBUG) app = Application() diff --git a/src/meson.build b/src/meson.build index 984592f..860f7f5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -28,6 +28,7 @@ configure_file( squeak_sources = [ '__init__.py', 'main.py', + 'widgets.py', ] install_data(squeak_sources, install_dir: moduledir) diff --git a/src/widgets.py b/src/widgets.py new file mode 100644 index 0000000..767c8f7 --- /dev/null +++ b/src/widgets.py @@ -0,0 +1,179 @@ +# widgets.py +# +# Copyright 2020 Morgan McMillian +# +# 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 . + +import sys +import gi +import os +import pnutpy +import logging + +gi.require_version('Gdk', '3.0') +gi.require_version('Gtk', '3.0') +from gi.repository import GObject, Gdk, Gtk, Gio, GLib + +gi.require_version('Handy', '1') +from gi.repository import Handy + +class LoginPage(Gtk.Box): + + __gsignals__ = { + 'login': (GObject.SIGNAL_RUN_FIRST, None, (str,)) + } + + def __init__(self): + super().__init__(orientation='vertical') + self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + + client_id = "1PiUzxfX_CQxKvtz93lUzPX9-FMtz-va" + redirect_uri = "urn:ietf:wg:oauth:2.0:oob" + scope = "basic,stream,write_post,follow,presence,messages,files,polls" + + uri = "https://pnut.io/oauth/authenticate" + uri += "?client_id=" + client_id + uri += "&redirect_uri=" + redirect_uri + uri += "&scope=" + scope + uri += "&response_type=token" + + self.login_button = Gtk.LinkButton.new_with_label(uri, "Log In to pnut.io") + self.login_button.connect("clicked", self.prompt_code) + + self.set_center_widget(self.login_button) + + def prompt_code(self, button): + self.remove(self.login_button) + + label = Gtk.Label() + label.set_markup('Enter authorization code') + self.code = Gtk.Entry() + + paste_button = Gtk.Button(label="Paste from clipboard") + paste_button.connect("clicked", self.paste_code) + + cancel_button = Gtk.Button(label="Cancel") + cancel_button.connect("clicked", self.cancel_login) + confirm_button = Gtk.Button(label="Confirm") + confirm_button.connect("clicked", self.confirm_login) + lbox = Gtk.Box(orientation='horizontal') + lbox.pack_start(cancel_button, True, True, 0) + lbox.pack_start(confirm_button, True, True, 0) + + vbox = Gtk.Box(orientation='vertical') + vbox.pack_start(label, False, False, 10) + vbox.pack_start(self.code, False, False, 10) + vbox.pack_start(paste_button, False, False, 10) + vbox.add(lbox) + + hbox = Gtk.Box(orientation='horizontal') + hbox.set_center_widget(vbox) + + self.set_center_widget(hbox) + self.show_all() + + def paste_code(self, button): + text = self.clipboard.wait_for_text() + if text is not None: + self.code.set_text(text) + + def cancel_login(self, button): + # TODO: something actually useful here + logging.debug("uh cancel i guess") + + def confirm_login(self, button): + code = self.code.get_text() + self.emit('login', code) + +class Timeline(Gtk.Box): + + __gsignals__ = { + 'refresh': (GObject.SIGNAL_RUN_FIRST, None, ()) + } + + def __init__(self, stream): + super().__init__(orientation='vertical') + + scroller = Gtk.ScrolledWindow( + halign='fill', + kinetic_scrolling=True + ) + self.view = Gtk.ListBox( + selection_mode=Gtk.SelectionMode.NONE + ) + scroller.add(self.view) + self.pack_start(scroller, True, True, 0) + + self.stream = stream + self.load_timeline() + + def load_timeline(self): + if self.stream == 'unified': + posts, meta = pnutpy.api.users_post_streams_unified() + elif self.stream == 'mentions': + posts, meta = pnutpy.api.users_mentioned_posts('me') + elif self.stream == 'bookmarks': + posts, meta = pnutpy.api.users_bookmarked_posts('me') + else: + posts, meta = pnutpy.api.posts_streams_global() + + for item in posts: + if 'is_deleted' in item: + continue + self.view.add(PostItem(item)) + + def do_refresh(self): + rows = self.view.get_children() + for item in rows: + self.view.remove(item) + self.load_timeline() + self.show_all() + +class PostItem(Gtk.ListBoxRow): + + def __init__(self, post): + super(Gtk.ListBoxRow, self).__init__() + self.post = post + + self.box = Gtk.Box(orientation='vertical') + self.add(self.box) + + # name container + self.name_box = Gtk.Box(orientation='vertical') + self.username = Gtk.Label(label="@" + post.user.username, xalign=0) + self.name = Gtk.Label(xalign=0) + if 'name' in post.user: + self.name.set_markup(f"{post.user.name}") + self.name_box.pack_start(self.name, True, True, 0) + self.name_box.pack_start(self.username, True, True, 0) + + # header container + self.h_box = Gtk.Box(orientation='horizontal') + self.avatar = Handy.Avatar(size=32) + # TODO: get the actual image + self.h_box.pack_start(self.avatar, False, False, 18) + self.h_box.pack_start(self.name_box, False, False, 0) + + # content container + self.c_box = Gtk.Box(orientation='horizontal') + self.content = Gtk.Label(wrap=True, xalign=0) + # TODO: parse content links + if 'content' in post: + self.content.set_text(post.content.text) + # TODO: add media + self.c_box.pack_start(self.content, True, True, 18) + + self.box.pack_start(self.h_box, True, True, 10) + self.box.pack_start(self.c_box, True, True, 10) +