Skip to content

Commit 9b99243

Browse files
AppStore app: move AppDetail to its own file and simplify
1 parent b4d851b commit 9b99243

File tree

4 files changed

+311
-311
lines changed

4 files changed

+311
-311
lines changed
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
import os
2+
import lvgl as lv
3+
4+
from mpos import Activity, DownloadManager, PackageManager, TaskManager
5+
6+
class AppDetail(Activity):
7+
8+
action_label_install = "Install"
9+
action_label_uninstall = "Uninstall"
10+
action_label_restore = "Restore Built-in"
11+
action_label_nothing = "Disable" # This could mark builtin apps as "Disabled" somehow and also allow for "Enable" then
12+
13+
# Widgets:
14+
install_button = None
15+
update_button = None
16+
progress_bar = None
17+
install_label = None
18+
long_desc_label = None
19+
version_label = None
20+
buttoncont = None
21+
publisher_label = None
22+
23+
# Received from the Intent extras:
24+
app = None
25+
appstore = None
26+
27+
def onCreate(self):
28+
print("Creating app detail screen...")
29+
self.app = self.getIntent().extras.get("app")
30+
self.appstore = self.getIntent().extras.get("appstore")
31+
app_detail_screen = lv.obj()
32+
app_detail_screen.set_style_pad_all(5, 0)
33+
app_detail_screen.set_size(lv.pct(100), lv.pct(100))
34+
app_detail_screen.set_pos(0, 40)
35+
app_detail_screen.set_flex_flow(lv.FLEX_FLOW.COLUMN)
36+
37+
headercont = lv.obj(app_detail_screen)
38+
headercont.set_style_border_width(0, 0)
39+
headercont.set_style_pad_all(0, 0)
40+
headercont.set_flex_flow(lv.FLEX_FLOW.ROW)
41+
headercont.set_size(lv.pct(100), lv.SIZE_CONTENT)
42+
headercont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
43+
icon_spacer = lv.image(headercont)
44+
icon_spacer.set_size(64, 64)
45+
if self.app.icon_data:
46+
image_dsc = lv.image_dsc_t({
47+
'data_size': len(self.app.icon_data),
48+
'data': self.app.icon_data
49+
})
50+
icon_spacer.set_src(image_dsc)
51+
else:
52+
icon_spacer.set_src(lv.SYMBOL.IMAGE)
53+
detail_cont = lv.obj(headercont)
54+
detail_cont.set_style_border_width(0, 0)
55+
detail_cont.set_style_radius(0, 0)
56+
detail_cont.set_style_pad_all(0, 0)
57+
detail_cont.set_flex_flow(lv.FLEX_FLOW.COLUMN)
58+
detail_cont.set_size(lv.pct(75), lv.SIZE_CONTENT)
59+
detail_cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
60+
name_label = lv.label(detail_cont)
61+
name_label.set_text(self.app.name)
62+
name_label.set_style_text_font(lv.font_montserrat_24, 0)
63+
self.publisher_label = lv.label(detail_cont)
64+
if self.app.publisher:
65+
self.publisher_label.set_text(self.app.publisher)
66+
else:
67+
self.publisher_label.set_text("Unknown publisher")
68+
self.publisher_label.set_style_text_font(lv.font_montserrat_16, 0)
69+
70+
self.progress_bar = lv.bar(app_detail_screen)
71+
self.progress_bar.set_width(lv.pct(100))
72+
self.progress_bar.set_range(0, 100)
73+
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
74+
# Always have this button:
75+
self.buttoncont = lv.obj(app_detail_screen)
76+
self.buttoncont.set_style_border_width(0, 0)
77+
self.buttoncont.set_style_radius(0, 0)
78+
self.buttoncont.set_style_pad_all(0, 0)
79+
self.buttoncont.set_flex_flow(lv.FLEX_FLOW.ROW)
80+
self.buttoncont.set_size(lv.pct(100), lv.SIZE_CONTENT)
81+
self.buttoncont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
82+
self.add_action_buttons(self.buttoncont, self.app)
83+
# version label:
84+
self.version_label = lv.label(app_detail_screen)
85+
self.version_label.set_width(lv.pct(100))
86+
if self.app.version:
87+
self.version_label.set_text(f"Latest version: {self.app.version}") # would be nice to make this bold if this is newer than the currently installed one
88+
else:
89+
self.version_label.set_text(f"Unknown version")
90+
self.version_label.set_style_text_font(lv.font_montserrat_12, 0)
91+
self.version_label.align_to(self.install_button, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
92+
self.long_desc_label = lv.label(app_detail_screen)
93+
self.long_desc_label.align_to(self.version_label, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
94+
if self.app.long_description:
95+
self.long_desc_label.set_text(self.app.long_description)
96+
else:
97+
self.long_desc_label.set_text(self.app.short_description)
98+
self.long_desc_label.set_style_text_font(lv.font_montserrat_12, 0)
99+
self.long_desc_label.set_width(lv.pct(100))
100+
print("Loading app detail screen...")
101+
self.setContentView(app_detail_screen)
102+
103+
def onResume(self, screen):
104+
backend_type = self.appstore.get_backend_type_from_settings()
105+
if backend_type == self.appstore._BACKEND_API_BADGEHUB:
106+
TaskManager.create_task(self.fetch_and_set_app_details())
107+
else:
108+
print("No need to fetch app details as the github app index already contains all the app data.")
109+
110+
def add_action_buttons(self, buttoncont, app):
111+
buttoncont.clean()
112+
print(f"Adding (un)install button for url: {self.app.download_url}")
113+
self.install_button = lv.button(buttoncont)
114+
self.install_button.add_event_cb(lambda e, a=self.app: self.toggle_install(a), lv.EVENT.CLICKED, None)
115+
self.install_button.set_size(lv.pct(100), 40)
116+
self.install_label = lv.label(self.install_button)
117+
self.install_label.center()
118+
self.set_install_label(self.app.fullname)
119+
if app.version and PackageManager.is_update_available(self.app.fullname, app.version):
120+
self.install_button.set_size(lv.pct(47), 40) # make space for update button
121+
print("Update available, adding update button.")
122+
self.update_button = lv.button(buttoncont)
123+
self.update_button.set_size(lv.pct(47), 40)
124+
self.update_button.add_event_cb(lambda e, a=self.app: self.update_button_click(a), lv.EVENT.CLICKED, None)
125+
update_label = lv.label(self.update_button)
126+
update_label.set_text("Update")
127+
update_label.center()
128+
129+
async def fetch_and_set_app_details(self):
130+
await self.appstore.fetch_badgehub_app_details(self.app)
131+
print(f"app has version: {self.app.version}")
132+
self.version_label.set_text(self.app.version)
133+
self.long_desc_label.set_text(self.app.long_description)
134+
self.publisher_label.set_text(self.app.publisher)
135+
self.add_action_buttons(self.buttoncont, self.app)
136+
137+
def set_install_label(self, app_fullname):
138+
# Figure out whether to show:
139+
# - "install" option if not installed
140+
# - "update" option if already installed and new version
141+
# - "uninstall" option if already installed and not builtin
142+
# - "restore builtin" option if it's an overridden builtin app
143+
# So:
144+
# - install, uninstall and restore builtin can be same button, always shown
145+
# - update is separate button, only shown if already installed and new version
146+
is_installed = True
147+
update_available = False
148+
builtin_app = PackageManager.is_builtin_app(app_fullname)
149+
overridden_builtin_app = PackageManager.is_overridden_builtin_app(app_fullname)
150+
if not overridden_builtin_app:
151+
is_installed = PackageManager.is_installed_by_name(app_fullname)
152+
if is_installed:
153+
if builtin_app:
154+
if overridden_builtin_app:
155+
action_label = self.action_label_restore
156+
else:
157+
action_label = self.action_label_nothing
158+
else:
159+
action_label = self.action_label_uninstall
160+
else:
161+
action_label = self.action_label_install
162+
self.install_label.set_text(action_label)
163+
164+
def toggle_install(self, app_obj):
165+
print(f"Install button clicked for {app_obj}")
166+
download_url = app_obj.download_url
167+
fullname = app_obj.fullname
168+
print(f"With {download_url} and fullname {fullname}")
169+
label_text = self.install_label.get_text()
170+
if label_text == self.action_label_install:
171+
print("Starting install task...")
172+
TaskManager.create_task(self.download_and_install(app_obj, f"apps/{fullname}"))
173+
elif label_text == self.action_label_uninstall or label_text == self.action_label_restore:
174+
print("Starting uninstall task...")
175+
TaskManager.create_task(self.uninstall_app(fullname))
176+
177+
def update_button_click(self, app_obj):
178+
download_url = app_obj.download_url
179+
fullname = app_obj.fullname
180+
print(f"Update button clicked for {download_url} and fullname {fullname}")
181+
self.update_button.add_flag(lv.obj.FLAG.HIDDEN)
182+
self.install_button.set_size(lv.pct(100), 40)
183+
TaskManager.create_task(self.download_and_install(app_obj, f"apps/{fullname}"))
184+
185+
async def uninstall_app(self, app_fullname):
186+
self.install_button.add_state(lv.STATE.DISABLED)
187+
self.install_label.set_text("Please wait...")
188+
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
189+
self.progress_bar.set_value(21, True)
190+
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
191+
self.progress_bar.set_value(42, True)
192+
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
193+
PackageManager.uninstall_app(app_fullname)
194+
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
195+
self.progress_bar.set_value(100, False)
196+
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
197+
self.progress_bar.set_value(0, False)
198+
self.set_install_label(app_fullname)
199+
self.install_button.remove_state(lv.STATE.DISABLED)
200+
if PackageManager.is_builtin_app(app_fullname):
201+
self.update_button.remove_flag(lv.obj.FLAG.HIDDEN)
202+
self.install_button.set_size(lv.pct(47), 40) # if a builtin app was removed, then it was overridden, and a new version is available, so make space for update button
203+
204+
async def pcb(self, percent):
205+
print(f"pcb called: {percent}")
206+
scaled_percent_start = 5 # before 5% is preparation
207+
scaled_percent_finished = 60 # after 60% is unzip
208+
scaled_percent_diff = scaled_percent_finished - scaled_percent_start
209+
scale = 100 / scaled_percent_diff # 100 / 55 = 1.81
210+
scaled_percent = round(percent / scale)
211+
scaled_percent += scaled_percent_start
212+
self.progress_bar.set_value(scaled_percent, True)
213+
214+
async def download_and_install(self, app_obj, dest_folder):
215+
zip_url = app_obj.download_url
216+
app_fullname = app_obj.fullname
217+
download_url_size = None
218+
if hasattr(app_obj, "download_url_size"):
219+
download_url_size = app_obj.download_url_size
220+
self.install_button.add_state(lv.STATE.DISABLED)
221+
self.install_label.set_text("Please wait...")
222+
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
223+
self.progress_bar.set_value(5, True)
224+
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
225+
# Download the .mpk file to temporary location
226+
try:
227+
# Make sure there's no leftover file filling the storage
228+
os.remove(temp_zip_path)
229+
except Exception:
230+
pass
231+
try:
232+
os.mkdir("tmp")
233+
except Exception:
234+
pass
235+
temp_zip_path = "tmp/temp.mpk"
236+
print(f"Downloading .mpk file from: {zip_url} to {temp_zip_path}")
237+
try:
238+
result = await DownloadManager.download_url(zip_url, outfile=temp_zip_path, total_size=download_url_size, progress_callback=self.pcb)
239+
if result is not True:
240+
print("Download failed...") # Would be good to show an error to the user if this failed...
241+
else:
242+
print("Downloaded .mpk file, size:", os.stat(temp_zip_path)[6], "bytes")
243+
# Install it:
244+
PackageManager.install_mpk(temp_zip_path, dest_folder) # 60 until 90 percent is the unzip but no progress there...
245+
self.progress_bar.set_value(90, True)
246+
except Exception as e:
247+
print(f"Download failed with exception: {e}")
248+
if DownloadManager.is_network_error(e):
249+
self.install_label.set_text(f"Network error - check WiFi")
250+
else:
251+
self.install_label.set_text(f"Download failed: {str(e)[:30]}")
252+
self.install_button.remove_state(lv.STATE.DISABLED)
253+
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
254+
self.progress_bar.set_value(0, False)
255+
# Make sure there's no leftover file filling the storage:
256+
try:
257+
os.remove(temp_zip_path)
258+
except Exception:
259+
pass
260+
return
261+
# Make sure there's no leftover file filling the storage:
262+
try:
263+
os.remove(temp_zip_path)
264+
except Exception:
265+
pass
266+
# Success:
267+
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
268+
self.progress_bar.set_value(100, False)
269+
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
270+
self.progress_bar.set_value(0, False)
271+
self.set_install_label(app_fullname)
272+
self.install_button.remove_state(lv.STATE.DISABLED)

0 commit comments

Comments
 (0)