Skip to content

Commit 2e471b3

Browse files
authored
Add a progressbar for flashing (#53)
This PR adds a progress bar do show the progress of the flashing.
2 parents 53712c1 + d28853a commit 2e471b3

5 files changed

Lines changed: 238 additions & 7 deletions

File tree

openandroidinstaller/views/base.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ def __init__(self, state: AppState, image: str = "placeholder.png"):
3030
self.state = state
3131

3232
# configs
33-
column_width = 600
33+
self.column_width = 600
3434
# right part of the display, add content here.
35-
self.right_view_header = Column(width=column_width, height=100, spacing=30)
36-
self.right_view = Column(alignment="center", width=column_width, height=650)
35+
self.right_view_header = Column(width=self.column_width, height=100, spacing=30)
36+
self.right_view = Column(
37+
alignment="center", width=self.column_width, height=650
38+
)
3739
# left part of the display: used for displaying the images
3840
self.left_view = Column(
39-
width=column_width,
41+
width=self.column_width,
4042
controls=[Image(src=f"/assets/imgs/{image}", height=600)],
4143
expand=True,
4244
horizontal_alignment="center",

openandroidinstaller/views/step_view.py

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from time import sleep
1818
from typing import Callable
1919
from functools import partial
20+
import regex as re
2021

2122
from flet import (
2223
UserControl,
@@ -29,6 +30,8 @@
2930
Container,
3031
Switch,
3132
alignment,
33+
ProgressBar,
34+
ProgressRing,
3235
colors,
3336
)
3437

@@ -99,6 +102,9 @@ def check_advanced_switch(e):
99102
# text box for terminal output
100103
self.terminal_box = TerminalBox(expand=True)
101104

105+
# container for progress indicators
106+
self.progress_indicator = ProgressIndicator(expand=True)
107+
102108
# main controls
103109
steps_indictor_img_lookup = {
104110
"Unlock the bootloader": "steps-header-unlock.png",
@@ -127,6 +133,7 @@ def check_advanced_switch(e):
127133
self.right_view.controls.extend(
128134
[
129135
Row([self.error_text]),
136+
Row([self.progress_indicator]),
130137
Column(
131138
[
132139
self.advanced_switch,
@@ -145,6 +152,7 @@ def check_advanced_switch(e):
145152
[
146153
self.inputtext,
147154
Row([self.error_text]),
155+
Row([self.progress_indicator]),
148156
Column(
149157
[
150158
self.advanced_switch,
@@ -188,6 +196,8 @@ def call_to_phone(self, e, command: str):
188196
"""
189197
# disable the call button while the command is running
190198
self.call_button.disabled = True
199+
# reset the progress indicators
200+
self.progress_indicator.clear()
191201
# reset terminal output
192202
if self.state.advanced:
193203
self.terminal_box.clear()
@@ -225,7 +235,14 @@ def call_to_phone(self, e, command: str):
225235
# run the right command
226236
if command in cmd_mapping.keys():
227237
for line in cmd_mapping.get(command)(bin_path=self.state.bin_path):
238+
# write the line to advanced output terminal
228239
self.terminal_box.write_line(line)
240+
# in case the install command is run, we want to update the progress bar
241+
if command == "adb_twrp_wipe_and_install":
242+
self.progress_indicator.display_progress_bar(line)
243+
self.progress_indicator.update()
244+
else:
245+
self.progress_indicator.display_progress_ring()
229246
else:
230247
msg = f"Unknown command type: {command}. Stopping."
231248
logger.error(msg)
@@ -242,9 +259,12 @@ def call_to_phone(self, e, command: str):
242259
else:
243260
sleep(5) # wait to make sure everything is fine
244261
logger.success(f"Command {command} run successfully. Allow to continue.")
245-
# emable the confirm buton and disable the call button
262+
# enable the confirm button and disable the call button
246263
self.confirm_button.disabled = False
247264
self.call_button.disabled = True
265+
# reset the progress indicator (let the progressbar stay for the install command)
266+
if command != "adb_twrp_wipe_and_install":
267+
self.progress_indicator.clear()
248268
self.view.update()
249269

250270

@@ -277,7 +297,7 @@ def write_line(self, line: str):
277297
self.update()
278298

279299
def toggle_visibility(self):
280-
"""Toogle the visibility of the terminal box."""
300+
"""Toggle the visibility of the terminal box."""
281301
self._box.visible = not self._box.visible
282302
self.update()
283303

@@ -289,3 +309,70 @@ def clear(self):
289309
def update(self):
290310
"""Update the view."""
291311
self._box.update()
312+
313+
314+
class ProgressIndicator(UserControl):
315+
def __init__(self, expand: bool = True):
316+
super().__init__(expand=expand)
317+
# placeholder for the flashing progressbar
318+
self.progress_bar = None
319+
# progress ring to display
320+
self.progress_ring = None
321+
322+
def build(self):
323+
self._container = Container(
324+
content=Column(scroll="auto", expand=True),
325+
margin=10,
326+
alignment=alignment.center,
327+
height=50,
328+
expand=True,
329+
visible=True,
330+
)
331+
return self._container
332+
333+
def display_progress_bar(self, line: str):
334+
"""Display and update the progress bar for the given line."""
335+
percentage_done = None
336+
result = None
337+
# get the progress numbers from the output lines
338+
if (type(line) == str) and line.strip():
339+
result = re.search(r"\(\~(\d{1,3})\%\)|(Total xfer: 1\.00x)", line.strip())
340+
if result:
341+
if result.group(1):
342+
percentage_done = int(result.group(1))
343+
elif result.group(2):
344+
percentage_done = 100
345+
346+
# create the progress bar on first occurrence
347+
if percentage_done == 0:
348+
self.progress_bar = ProgressBar(
349+
width=500, bar_height=32, color="#00d886", bgcolor="#eeeeee"
350+
)
351+
self.percentage_text = Text(f"{percentage_done}%")
352+
self._container.content.controls.append(
353+
Row([self.percentage_text, self.progress_bar])
354+
)
355+
# update the progress bar
356+
if self.progress_bar:
357+
self.progress_bar.value = percentage_done / 100
358+
self.percentage_text.value = f"{percentage_done}%"
359+
360+
def display_progress_ring(
361+
self,
362+
):
363+
"""Display a progress ring to signal progress."""
364+
if not self.progress_ring:
365+
self.progress_ring = ProgressRing(color="#00d886")
366+
self._container.content.controls.append(self.progress_ring)
367+
self._container.update()
368+
369+
def clear(self):
370+
"""Clear output."""
371+
self._container.content.controls = []
372+
self.progress_ring = None
373+
self.progress_bar = None
374+
self.update()
375+
376+
def update(self):
377+
"""Update the view."""
378+
self._container.update()

poetry.lock

Lines changed: 93 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ schema = "^0.7.5"
2323
py7zr = "^0.20.0"
2424
pytest-cov = "^4.0.0"
2525
pytest-mock = "^3.10.0"
26+
bandit = "^1.7.4"
2627

2728
[tool.poetry.dev-dependencies]
2829

tests/test_progress_bar.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Test the ProgressIndicator class."""
2+
3+
# This file is part of OpenAndroidInstaller.
4+
# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of
5+
# the GNU General Public License as published by the Free Software Foundation,
6+
# either version 3 of the License, or (at your option) any later version.
7+
8+
# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY
9+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
10+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
11+
12+
# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller.
13+
# If not, see <https://www.gnu.org/licenses/>."""
14+
# Author: Tobias Sterbak
15+
16+
import pytest
17+
from flet import Container
18+
19+
from openandroidinstaller.views.step_view import ProgressIndicator
20+
21+
22+
def test_init():
23+
"""Test if the field can be initialized properly."""
24+
progress_indicator = ProgressIndicator(expand=True)
25+
build_indicator = progress_indicator.build()
26+
27+
assert isinstance(build_indicator, Container)
28+
29+
30+
def test_update_progress_bar():
31+
"""Test if the progress bar is updated properly based on lines."""
32+
progress_indicator = ProgressIndicator(expand=True)
33+
build_indicator = progress_indicator.build()
34+
35+
# test if other line is fine
36+
progress_indicator.display_progress_bar(
37+
line="Failed to mount '/data' (Device or resource busy)"
38+
)
39+
assert not progress_indicator.progress_bar
40+
41+
# test if percentages are parsed correctly and update is performed
42+
for percentage in range(0, 47):
43+
line = f"serving: '/home/tobias/Repositories/openandroidinstaller/images/google-pixel3a/lineage-19.1-20221004-nightly-sargo-signed.zip' (~{percentage}%)\n"
44+
progress_indicator.display_progress_bar(line)
45+
assert progress_indicator.progress_bar.value == percentage / 100
46+
47+
# test if the finishing print is detected and updated correctly.
48+
progress_indicator.display_progress_bar(line="Total xfer: 1.00x\n")
49+
assert progress_indicator.progress_bar.value == 1.0

0 commit comments

Comments
 (0)