Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activity indicator for file download / decryption. #702

Merged
merged 7 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from securedrop_client.export import ExportStatus, ExportError
from securedrop_client.gui import SecureQLabel, SvgLabel, SvgPushButton, SvgToggleButton
from securedrop_client.logic import Controller
from securedrop_client.resources import load_icon, load_image
from securedrop_client.resources import load_icon, load_image, load_movie
from securedrop_client.utils import humanize_filesize

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -1742,7 +1742,7 @@ class FileWidget(QWidget):
}
QPushButton#export_print {
border: none;
padding: 8px;
padding: 0px 8px;
font-family: 'Source Sans Pro';
font-weight: 500;
font-size: 13px;
Expand All @@ -1763,7 +1763,7 @@ class FileWidget(QWidget):
padding-right: 8px;
font-family: 'Source Sans Pro';
font-weight: 700;
font-size: 14px;
font-size: 13px;
color: #2a319d;
}
QLabel#no_file_name {
Expand Down Expand Up @@ -1838,6 +1838,7 @@ def __init__(
self.download_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.download_button.setIcon(load_icon('download_file.svg'))
self.download_button.setFont(file_buttons_font)
self.download_animation = load_movie("download_animation.gif")
self.export_button = QPushButton(_('EXPORT'))
self.export_button.setObjectName('export_print')
self.export_button.setFont(file_buttons_font)
Expand Down Expand Up @@ -1958,9 +1959,21 @@ def _on_left_click(self):
# Open the already downloaded file.
self.controller.on_file_open(self.file.uuid)
else:
# Indicate in downloading state...
self.download_animation.frameChanged.connect(self.set_button_animation_frame)
self.download_animation.start()
self.download_button.setText(_(" DOWNLOADING "))
self.download_button.setStyleSheet("color: #05a6fe")
# Download the file.
self.controller.on_submission_download(File, self.file.uuid)

def set_button_animation_frame(self, frame_number):
"""
Sets the download button's icon to the current frame of the spinner
animation.
"""
self.download_button.setIcon(QIcon(self.download_animation.currentPixmap()))


class PrintDialog(QDialog):

Expand Down
9 changes: 8 additions & 1 deletion securedrop_client/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import os

from pkg_resources import resource_filename, resource_string
from PyQt5.QtGui import QPixmap, QIcon, QFontDatabase
from PyQt5.QtGui import QPixmap, QIcon, QFontDatabase, QMovie
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import QDir

Expand Down Expand Up @@ -133,3 +133,10 @@ def load_css(name: str) -> str:
Return the contents of the referenced CSS file in the resources.
"""
return resource_string(__name__, "css/" + name).decode('utf-8')


def load_movie(name: str) -> str:
"""
Return a GIF animation to use in the UI.
"""
return QMovie(path(name))
11 changes: 11 additions & 0 deletions securedrop_client/resources/images/download_active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,13 +1372,18 @@ def test_FileWidget_on_left_click_download(mocker, session, source):
mock_controller = mocker.MagicMock(get_file=mock_get_file)

fw = FileWidget(file_.uuid, mock_controller, mock_signal)
fw.download_button = mocker.MagicMock()
mock_get_file.assert_called_once_with(file_.uuid)
mock_get_file.reset_mock()

fw._on_left_click()
mock_get_file.assert_called_once_with(file_.uuid)
mock_controller.on_submission_download.assert_called_once_with(
db.File, file_.uuid)
# Check indicators of activity have been updated.
assert fw.download_button.setIcon.call_count == 1
fw.download_button.setText.assert_called_once_with(" DOWNLOADING ")
fw.download_button.setStyleSheet.assert_called_once_with("color: #05a6fe")


def test_FileWidget_on_left_click_open(mocker, session, source):
Expand All @@ -1399,6 +1404,28 @@ def test_FileWidget_on_left_click_open(mocker, session, source):
fw.controller.on_file_open.assert_called_once_with(file_.uuid)


def test_FileWidget_set_button_animation_frame(mocker, session, source):
"""
Left click on download when file is not downloaded should trigger
a download.
"""
mock_signal = mocker.MagicMock() # not important for this test

file_ = factory.File(source=source['source'],
is_downloaded=False,
is_decrypted=None)
session.add(file_)
session.commit()

mock_get_file = mocker.MagicMock(return_value=file_)
mock_controller = mocker.MagicMock(get_file=mock_get_file)

fw = FileWidget(file_.uuid, mock_controller, mock_signal)
fw.download_button = mocker.MagicMock()
fw.set_button_animation_frame(1)
assert fw.download_button.setIcon.call_count == 1


def test_FileWidget_update(mocker, session, source):
"""
The update method should show/hide widgets if file is downloaded
Expand Down
10 changes: 9 additions & 1 deletion tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Tests for the resources sub-module.
"""
import securedrop_client.resources
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtGui import QIcon, QPixmap, QMovie
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import QApplication

Expand Down Expand Up @@ -63,3 +63,11 @@ def test_load_css(mocker):
assert 'foo' == securedrop_client.resources.load_css('foo')
rs.assert_called_once_with(securedrop_client.resources.__name__,
'css/foo')


def test_load_movie():
"""
Check the load_movie function returns the expected QMovie object.
"""
result = securedrop_client.resources.load_movie('download_animation.gif')
assert isinstance(result, QMovie)