mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
AppStore app: move AppDetail to its own file and simplify
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
import os
|
||||
import lvgl as lv
|
||||
|
||||
from mpos import Activity, DownloadManager, PackageManager, TaskManager
|
||||
|
||||
class AppDetail(Activity):
|
||||
|
||||
action_label_install = "Install"
|
||||
action_label_uninstall = "Uninstall"
|
||||
action_label_restore = "Restore Built-in"
|
||||
action_label_nothing = "Disable" # This could mark builtin apps as "Disabled" somehow and also allow for "Enable" then
|
||||
|
||||
# Widgets:
|
||||
install_button = None
|
||||
update_button = None
|
||||
progress_bar = None
|
||||
install_label = None
|
||||
long_desc_label = None
|
||||
version_label = None
|
||||
buttoncont = None
|
||||
publisher_label = None
|
||||
|
||||
# Received from the Intent extras:
|
||||
app = None
|
||||
appstore = None
|
||||
|
||||
def onCreate(self):
|
||||
print("Creating app detail screen...")
|
||||
self.app = self.getIntent().extras.get("app")
|
||||
self.appstore = self.getIntent().extras.get("appstore")
|
||||
app_detail_screen = lv.obj()
|
||||
app_detail_screen.set_style_pad_all(5, 0)
|
||||
app_detail_screen.set_size(lv.pct(100), lv.pct(100))
|
||||
app_detail_screen.set_pos(0, 40)
|
||||
app_detail_screen.set_flex_flow(lv.FLEX_FLOW.COLUMN)
|
||||
|
||||
headercont = lv.obj(app_detail_screen)
|
||||
headercont.set_style_border_width(0, 0)
|
||||
headercont.set_style_pad_all(0, 0)
|
||||
headercont.set_flex_flow(lv.FLEX_FLOW.ROW)
|
||||
headercont.set_size(lv.pct(100), lv.SIZE_CONTENT)
|
||||
headercont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
icon_spacer = lv.image(headercont)
|
||||
icon_spacer.set_size(64, 64)
|
||||
if self.app.icon_data:
|
||||
image_dsc = lv.image_dsc_t({
|
||||
'data_size': len(self.app.icon_data),
|
||||
'data': self.app.icon_data
|
||||
})
|
||||
icon_spacer.set_src(image_dsc)
|
||||
else:
|
||||
icon_spacer.set_src(lv.SYMBOL.IMAGE)
|
||||
detail_cont = lv.obj(headercont)
|
||||
detail_cont.set_style_border_width(0, 0)
|
||||
detail_cont.set_style_radius(0, 0)
|
||||
detail_cont.set_style_pad_all(0, 0)
|
||||
detail_cont.set_flex_flow(lv.FLEX_FLOW.COLUMN)
|
||||
detail_cont.set_size(lv.pct(75), lv.SIZE_CONTENT)
|
||||
detail_cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
name_label = lv.label(detail_cont)
|
||||
name_label.set_text(self.app.name)
|
||||
name_label.set_style_text_font(lv.font_montserrat_24, 0)
|
||||
self.publisher_label = lv.label(detail_cont)
|
||||
if self.app.publisher:
|
||||
self.publisher_label.set_text(self.app.publisher)
|
||||
else:
|
||||
self.publisher_label.set_text("Unknown publisher")
|
||||
self.publisher_label.set_style_text_font(lv.font_montserrat_16, 0)
|
||||
|
||||
self.progress_bar = lv.bar(app_detail_screen)
|
||||
self.progress_bar.set_width(lv.pct(100))
|
||||
self.progress_bar.set_range(0, 100)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Always have this button:
|
||||
self.buttoncont = lv.obj(app_detail_screen)
|
||||
self.buttoncont.set_style_border_width(0, 0)
|
||||
self.buttoncont.set_style_radius(0, 0)
|
||||
self.buttoncont.set_style_pad_all(0, 0)
|
||||
self.buttoncont.set_flex_flow(lv.FLEX_FLOW.ROW)
|
||||
self.buttoncont.set_size(lv.pct(100), lv.SIZE_CONTENT)
|
||||
self.buttoncont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
self.add_action_buttons(self.buttoncont, self.app)
|
||||
# version label:
|
||||
self.version_label = lv.label(app_detail_screen)
|
||||
self.version_label.set_width(lv.pct(100))
|
||||
if self.app.version:
|
||||
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
|
||||
else:
|
||||
self.version_label.set_text(f"Unknown version")
|
||||
self.version_label.set_style_text_font(lv.font_montserrat_12, 0)
|
||||
self.version_label.align_to(self.install_button, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
|
||||
self.long_desc_label = lv.label(app_detail_screen)
|
||||
self.long_desc_label.align_to(self.version_label, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
|
||||
if self.app.long_description:
|
||||
self.long_desc_label.set_text(self.app.long_description)
|
||||
else:
|
||||
self.long_desc_label.set_text(self.app.short_description)
|
||||
self.long_desc_label.set_style_text_font(lv.font_montserrat_12, 0)
|
||||
self.long_desc_label.set_width(lv.pct(100))
|
||||
print("Loading app detail screen...")
|
||||
self.setContentView(app_detail_screen)
|
||||
|
||||
def onResume(self, screen):
|
||||
backend_type = self.appstore.get_backend_type_from_settings()
|
||||
if backend_type == self.appstore._BACKEND_API_BADGEHUB:
|
||||
TaskManager.create_task(self.fetch_and_set_app_details())
|
||||
else:
|
||||
print("No need to fetch app details as the github app index already contains all the app data.")
|
||||
|
||||
def add_action_buttons(self, buttoncont, app):
|
||||
buttoncont.clean()
|
||||
print(f"Adding (un)install button for url: {self.app.download_url}")
|
||||
self.install_button = lv.button(buttoncont)
|
||||
self.install_button.add_event_cb(lambda e, a=self.app: self.toggle_install(a), lv.EVENT.CLICKED, None)
|
||||
self.install_button.set_size(lv.pct(100), 40)
|
||||
self.install_label = lv.label(self.install_button)
|
||||
self.install_label.center()
|
||||
self.set_install_label(self.app.fullname)
|
||||
if app.version and PackageManager.is_update_available(self.app.fullname, app.version):
|
||||
self.install_button.set_size(lv.pct(47), 40) # make space for update button
|
||||
print("Update available, adding update button.")
|
||||
self.update_button = lv.button(buttoncont)
|
||||
self.update_button.set_size(lv.pct(47), 40)
|
||||
self.update_button.add_event_cb(lambda e, a=self.app: self.update_button_click(a), lv.EVENT.CLICKED, None)
|
||||
update_label = lv.label(self.update_button)
|
||||
update_label.set_text("Update")
|
||||
update_label.center()
|
||||
|
||||
async def fetch_and_set_app_details(self):
|
||||
await self.appstore.fetch_badgehub_app_details(self.app)
|
||||
print(f"app has version: {self.app.version}")
|
||||
self.version_label.set_text(self.app.version)
|
||||
self.long_desc_label.set_text(self.app.long_description)
|
||||
self.publisher_label.set_text(self.app.publisher)
|
||||
self.add_action_buttons(self.buttoncont, self.app)
|
||||
|
||||
def set_install_label(self, app_fullname):
|
||||
# Figure out whether to show:
|
||||
# - "install" option if not installed
|
||||
# - "update" option if already installed and new version
|
||||
# - "uninstall" option if already installed and not builtin
|
||||
# - "restore builtin" option if it's an overridden builtin app
|
||||
# So:
|
||||
# - install, uninstall and restore builtin can be same button, always shown
|
||||
# - update is separate button, only shown if already installed and new version
|
||||
is_installed = True
|
||||
update_available = False
|
||||
builtin_app = PackageManager.is_builtin_app(app_fullname)
|
||||
overridden_builtin_app = PackageManager.is_overridden_builtin_app(app_fullname)
|
||||
if not overridden_builtin_app:
|
||||
is_installed = PackageManager.is_installed_by_name(app_fullname)
|
||||
if is_installed:
|
||||
if builtin_app:
|
||||
if overridden_builtin_app:
|
||||
action_label = self.action_label_restore
|
||||
else:
|
||||
action_label = self.action_label_nothing
|
||||
else:
|
||||
action_label = self.action_label_uninstall
|
||||
else:
|
||||
action_label = self.action_label_install
|
||||
self.install_label.set_text(action_label)
|
||||
|
||||
def toggle_install(self, app_obj):
|
||||
print(f"Install button clicked for {app_obj}")
|
||||
download_url = app_obj.download_url
|
||||
fullname = app_obj.fullname
|
||||
print(f"With {download_url} and fullname {fullname}")
|
||||
label_text = self.install_label.get_text()
|
||||
if label_text == self.action_label_install:
|
||||
print("Starting install task...")
|
||||
TaskManager.create_task(self.download_and_install(app_obj, f"apps/{fullname}"))
|
||||
elif label_text == self.action_label_uninstall or label_text == self.action_label_restore:
|
||||
print("Starting uninstall task...")
|
||||
TaskManager.create_task(self.uninstall_app(fullname))
|
||||
|
||||
def update_button_click(self, app_obj):
|
||||
download_url = app_obj.download_url
|
||||
fullname = app_obj.fullname
|
||||
print(f"Update button clicked for {download_url} and fullname {fullname}")
|
||||
self.update_button.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.install_button.set_size(lv.pct(100), 40)
|
||||
TaskManager.create_task(self.download_and_install(app_obj, f"apps/{fullname}"))
|
||||
|
||||
async def uninstall_app(self, app_fullname):
|
||||
self.install_button.add_state(lv.STATE.DISABLED)
|
||||
self.install_label.set_text("Please wait...")
|
||||
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(21, True)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
self.progress_bar.set_value(42, True)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
PackageManager.uninstall_app(app_fullname)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
self.progress_bar.set_value(100, False)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(0, False)
|
||||
self.set_install_label(app_fullname)
|
||||
self.install_button.remove_state(lv.STATE.DISABLED)
|
||||
if PackageManager.is_builtin_app(app_fullname):
|
||||
self.update_button.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
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
|
||||
|
||||
async def pcb(self, percent):
|
||||
print(f"pcb called: {percent}")
|
||||
scaled_percent_start = 5 # before 5% is preparation
|
||||
scaled_percent_finished = 60 # after 60% is unzip
|
||||
scaled_percent_diff = scaled_percent_finished - scaled_percent_start
|
||||
scale = 100 / scaled_percent_diff # 100 / 55 = 1.81
|
||||
scaled_percent = round(percent / scale)
|
||||
scaled_percent += scaled_percent_start
|
||||
self.progress_bar.set_value(scaled_percent, True)
|
||||
|
||||
async def download_and_install(self, app_obj, dest_folder):
|
||||
zip_url = app_obj.download_url
|
||||
app_fullname = app_obj.fullname
|
||||
download_url_size = None
|
||||
if hasattr(app_obj, "download_url_size"):
|
||||
download_url_size = app_obj.download_url_size
|
||||
self.install_button.add_state(lv.STATE.DISABLED)
|
||||
self.install_label.set_text("Please wait...")
|
||||
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(5, True)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
# Download the .mpk file to temporary location
|
||||
try:
|
||||
# Make sure there's no leftover file filling the storage
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
os.mkdir("tmp")
|
||||
except Exception:
|
||||
pass
|
||||
temp_zip_path = "tmp/temp.mpk"
|
||||
print(f"Downloading .mpk file from: {zip_url} to {temp_zip_path}")
|
||||
try:
|
||||
result = await DownloadManager.download_url(zip_url, outfile=temp_zip_path, total_size=download_url_size, progress_callback=self.pcb)
|
||||
if result is not True:
|
||||
print("Download failed...") # Would be good to show an error to the user if this failed...
|
||||
else:
|
||||
print("Downloaded .mpk file, size:", os.stat(temp_zip_path)[6], "bytes")
|
||||
# Install it:
|
||||
PackageManager.install_mpk(temp_zip_path, dest_folder) # 60 until 90 percent is the unzip but no progress there...
|
||||
self.progress_bar.set_value(90, True)
|
||||
except Exception as e:
|
||||
print(f"Download failed with exception: {e}")
|
||||
if DownloadManager.is_network_error(e):
|
||||
self.install_label.set_text(f"Network error - check WiFi")
|
||||
else:
|
||||
self.install_label.set_text(f"Download failed: {str(e)[:30]}")
|
||||
self.install_button.remove_state(lv.STATE.DISABLED)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(0, False)
|
||||
# Make sure there's no leftover file filling the storage:
|
||||
try:
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
# Make sure there's no leftover file filling the storage:
|
||||
try:
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
# Success:
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
self.progress_bar.set_value(100, False)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(0, False)
|
||||
self.set_install_label(app_fullname)
|
||||
self.install_button.remove_state(lv.STATE.DISABLED)
|
||||
@@ -1,15 +1,10 @@
|
||||
import lvgl as lv
|
||||
import json
|
||||
import requests
|
||||
import gc
|
||||
import os
|
||||
import json
|
||||
import lvgl as lv
|
||||
|
||||
from mpos.apps import Activity, Intent
|
||||
from mpos.app import App
|
||||
from mpos import TaskManager, DownloadManager, SettingActivity
|
||||
import mpos.ui
|
||||
from mpos.content.package_manager import PackageManager
|
||||
from mpos.config import SharedPreferences
|
||||
from mpos import Activity, App, Intent, DownloadManager, SettingActivity, SharedPreferences, TaskManager
|
||||
|
||||
from app_detail import AppDetail
|
||||
|
||||
class AppStore(Activity):
|
||||
|
||||
@@ -26,8 +21,6 @@ class AppStore(Activity):
|
||||
_BACKEND_API_GITHUB = "github"
|
||||
_BACKEND_API_BADGEHUB = "badgehub"
|
||||
|
||||
apps = []
|
||||
|
||||
_ICON_SIZE = 64
|
||||
|
||||
# Hardcoded list for now:
|
||||
@@ -39,34 +32,9 @@ class AppStore(Activity):
|
||||
|
||||
_DEFAULT_BACKEND = _BACKEND_API_GITHUB + "," + _GITHUB_PROD_BASE_URL + "/" + _GITHUB_LIST
|
||||
|
||||
@staticmethod
|
||||
def get_backend_pref_string(index):
|
||||
backend_info = AppStore.backends[index]
|
||||
if backend_info:
|
||||
api = backend_info[1]
|
||||
base_url = backend_info[2]
|
||||
list_suffix = backend_info[3]
|
||||
details_suffix = backend_info[4]
|
||||
toreturn = api + "," + base_url + "/" + list_suffix
|
||||
if api == AppStore._BACKEND_API_BADGEHUB:
|
||||
toreturn += "," + base_url + "/" + details_suffix
|
||||
return toreturn
|
||||
|
||||
@staticmethod
|
||||
def backend_pref_string_to_backend(string):
|
||||
return string.split(",")
|
||||
|
||||
def get_backend_type_from_settings(self):
|
||||
return AppStore.backend_pref_string_to_backend(self.prefs.get_string("backend", self._DEFAULT_BACKEND))[0]
|
||||
|
||||
def get_backend_list_url_from_settings(self):
|
||||
return AppStore.backend_pref_string_to_backend(self.prefs.get_string("backend", self._DEFAULT_BACKEND))[1]
|
||||
|
||||
def get_backend_details_url_from_settings():
|
||||
return AppStore.backend_pref_string_to_backend(self.prefs.get_string("backend", self._DEFAULT_BACKEND))[2]
|
||||
|
||||
can_check_network = True
|
||||
apps = []
|
||||
prefs = None
|
||||
can_check_network = True
|
||||
|
||||
# Widgets:
|
||||
main_screen = None
|
||||
@@ -104,13 +72,12 @@ class AppStore(Activity):
|
||||
def refresh_list(self):
|
||||
try:
|
||||
import network
|
||||
if not network.WLAN(network.STA_IF).isconnected():
|
||||
self.please_wait_label.remove_flag(lv.obj.FLAG.HIDDEN) # make sure it's visible
|
||||
self.please_wait_label.set_text("Error: WiFi is not connected.")
|
||||
except Exception as e:
|
||||
self.can_check_network = False
|
||||
if self.can_check_network and not network.WLAN(network.STA_IF).isconnected():
|
||||
self.please_wait_label.remove_flag(lv.obj.FLAG.HIDDEN) # make sure it's visible
|
||||
self.please_wait_label.set_text("Error: WiFi is not connected.")
|
||||
else:
|
||||
TaskManager.create_task(self.download_app_index(self.get_backend_list_url_from_settings()))
|
||||
print("Warning: can't check network state, assuming we're online...")
|
||||
TaskManager.create_task(self.download_app_index(self.get_backend_list_url_from_settings()))
|
||||
|
||||
def settings_button_tap(self, event):
|
||||
intent = Intent(activity_class=SettingActivity)
|
||||
@@ -328,271 +295,28 @@ class AppStore(Activity):
|
||||
self.please_wait_label.set_text(err)
|
||||
return
|
||||
|
||||
def get_backend_type_from_settings(self):
|
||||
return AppStore.backend_pref_string_to_backend(self.prefs.get_string("backend", self._DEFAULT_BACKEND))[0]
|
||||
|
||||
class AppDetail(Activity):
|
||||
def get_backend_list_url_from_settings(self):
|
||||
return AppStore.backend_pref_string_to_backend(self.prefs.get_string("backend", self._DEFAULT_BACKEND))[1]
|
||||
|
||||
action_label_install = "Install"
|
||||
action_label_uninstall = "Uninstall"
|
||||
action_label_restore = "Restore Built-in"
|
||||
action_label_nothing = "Disable" # This could mark builtin apps as "Disabled" somehow and also allow for "Enable" then
|
||||
def get_backend_details_url_from_settings():
|
||||
return AppStore.backend_pref_string_to_backend(self.prefs.get_string("backend", self._DEFAULT_BACKEND))[2]
|
||||
|
||||
# Widgets:
|
||||
install_button = None
|
||||
update_button = None
|
||||
progress_bar = None
|
||||
install_label = None
|
||||
long_desc_label = None
|
||||
version_label = None
|
||||
buttoncont = None
|
||||
publisher_label = None
|
||||
@staticmethod
|
||||
def get_backend_pref_string(index):
|
||||
backend_info = AppStore.backends[index]
|
||||
if backend_info:
|
||||
api = backend_info[1]
|
||||
base_url = backend_info[2]
|
||||
list_suffix = backend_info[3]
|
||||
details_suffix = backend_info[4]
|
||||
toreturn = api + "," + base_url + "/" + list_suffix
|
||||
if api == AppStore._BACKEND_API_BADGEHUB:
|
||||
toreturn += "," + base_url + "/" + details_suffix
|
||||
return toreturn
|
||||
|
||||
# Received from the Intent extras:
|
||||
app = None
|
||||
appstore = None
|
||||
|
||||
def onCreate(self):
|
||||
print("Creating app detail screen...")
|
||||
self.app = self.getIntent().extras.get("app")
|
||||
self.appstore = self.getIntent().extras.get("appstore")
|
||||
app_detail_screen = lv.obj()
|
||||
app_detail_screen.set_style_pad_all(5, 0)
|
||||
app_detail_screen.set_size(lv.pct(100), lv.pct(100))
|
||||
app_detail_screen.set_pos(0, 40)
|
||||
app_detail_screen.set_flex_flow(lv.FLEX_FLOW.COLUMN)
|
||||
|
||||
headercont = lv.obj(app_detail_screen)
|
||||
headercont.set_style_border_width(0, 0)
|
||||
headercont.set_style_pad_all(0, 0)
|
||||
headercont.set_flex_flow(lv.FLEX_FLOW.ROW)
|
||||
headercont.set_size(lv.pct(100), lv.SIZE_CONTENT)
|
||||
headercont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
icon_spacer = lv.image(headercont)
|
||||
icon_spacer.set_size(64, 64)
|
||||
if self.app.icon_data:
|
||||
image_dsc = lv.image_dsc_t({
|
||||
'data_size': len(self.app.icon_data),
|
||||
'data': self.app.icon_data
|
||||
})
|
||||
icon_spacer.set_src(image_dsc)
|
||||
else:
|
||||
icon_spacer.set_src(lv.SYMBOL.IMAGE)
|
||||
detail_cont = lv.obj(headercont)
|
||||
detail_cont.set_style_border_width(0, 0)
|
||||
detail_cont.set_style_radius(0, 0)
|
||||
detail_cont.set_style_pad_all(0, 0)
|
||||
detail_cont.set_flex_flow(lv.FLEX_FLOW.COLUMN)
|
||||
detail_cont.set_size(lv.pct(75), lv.SIZE_CONTENT)
|
||||
detail_cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
name_label = lv.label(detail_cont)
|
||||
name_label.set_text(self.app.name)
|
||||
name_label.set_style_text_font(lv.font_montserrat_24, 0)
|
||||
self.publisher_label = lv.label(detail_cont)
|
||||
if self.app.publisher:
|
||||
self.publisher_label.set_text(self.app.publisher)
|
||||
else:
|
||||
self.publisher_label.set_text("Unknown publisher")
|
||||
self.publisher_label.set_style_text_font(lv.font_montserrat_16, 0)
|
||||
|
||||
self.progress_bar = lv.bar(app_detail_screen)
|
||||
self.progress_bar.set_width(lv.pct(100))
|
||||
self.progress_bar.set_range(0, 100)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Always have this button:
|
||||
self.buttoncont = lv.obj(app_detail_screen)
|
||||
self.buttoncont.set_style_border_width(0, 0)
|
||||
self.buttoncont.set_style_radius(0, 0)
|
||||
self.buttoncont.set_style_pad_all(0, 0)
|
||||
self.buttoncont.set_flex_flow(lv.FLEX_FLOW.ROW)
|
||||
self.buttoncont.set_size(lv.pct(100), lv.SIZE_CONTENT)
|
||||
self.buttoncont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
self.add_action_buttons(self.buttoncont, self.app)
|
||||
# version label:
|
||||
self.version_label = lv.label(app_detail_screen)
|
||||
self.version_label.set_width(lv.pct(100))
|
||||
if self.app.version:
|
||||
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
|
||||
else:
|
||||
self.version_label.set_text(f"Unknown version")
|
||||
self.version_label.set_style_text_font(lv.font_montserrat_12, 0)
|
||||
self.version_label.align_to(self.install_button, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
|
||||
self.long_desc_label = lv.label(app_detail_screen)
|
||||
self.long_desc_label.align_to(self.version_label, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
|
||||
if self.app.long_description:
|
||||
self.long_desc_label.set_text(self.app.long_description)
|
||||
else:
|
||||
self.long_desc_label.set_text(self.app.short_description)
|
||||
self.long_desc_label.set_style_text_font(lv.font_montserrat_12, 0)
|
||||
self.long_desc_label.set_width(lv.pct(100))
|
||||
print("Loading app detail screen...")
|
||||
self.setContentView(app_detail_screen)
|
||||
|
||||
def onResume(self, screen):
|
||||
backend_type = self.get_backend_type_from_settings()
|
||||
if backend_type == self.appstore._BACKEND_API_BADGEHUB:
|
||||
TaskManager.create_task(self.fetch_and_set_app_details())
|
||||
else:
|
||||
print("No need to fetch app details as the github app index already contains all the app data.")
|
||||
|
||||
def add_action_buttons(self, buttoncont, app):
|
||||
buttoncont.clean()
|
||||
print(f"Adding (un)install button for url: {self.app.download_url}")
|
||||
self.install_button = lv.button(buttoncont)
|
||||
self.install_button.add_event_cb(lambda e, a=self.app: self.toggle_install(a), lv.EVENT.CLICKED, None)
|
||||
self.install_button.set_size(lv.pct(100), 40)
|
||||
self.install_label = lv.label(self.install_button)
|
||||
self.install_label.center()
|
||||
self.set_install_label(self.app.fullname)
|
||||
if app.version and PackageManager.is_update_available(self.app.fullname, app.version):
|
||||
self.install_button.set_size(lv.pct(47), 40) # make space for update button
|
||||
print("Update available, adding update button.")
|
||||
self.update_button = lv.button(buttoncont)
|
||||
self.update_button.set_size(lv.pct(47), 40)
|
||||
self.update_button.add_event_cb(lambda e, a=self.app: self.update_button_click(a), lv.EVENT.CLICKED, None)
|
||||
update_label = lv.label(self.update_button)
|
||||
update_label.set_text("Update")
|
||||
update_label.center()
|
||||
|
||||
async def fetch_and_set_app_details(self):
|
||||
await self.appstore.fetch_badgehub_app_details(self.app)
|
||||
print(f"app has version: {self.app.version}")
|
||||
self.version_label.set_text(self.app.version)
|
||||
self.long_desc_label.set_text(self.app.long_description)
|
||||
self.publisher_label.set_text(self.app.publisher)
|
||||
self.add_action_buttons(self.buttoncont, self.app)
|
||||
|
||||
def set_install_label(self, app_fullname):
|
||||
# Figure out whether to show:
|
||||
# - "install" option if not installed
|
||||
# - "update" option if already installed and new version
|
||||
# - "uninstall" option if already installed and not builtin
|
||||
# - "restore builtin" option if it's an overridden builtin app
|
||||
# So:
|
||||
# - install, uninstall and restore builtin can be same button, always shown
|
||||
# - update is separate button, only shown if already installed and new version
|
||||
is_installed = True
|
||||
update_available = False
|
||||
builtin_app = PackageManager.is_builtin_app(app_fullname)
|
||||
overridden_builtin_app = PackageManager.is_overridden_builtin_app(app_fullname)
|
||||
if not overridden_builtin_app:
|
||||
is_installed = PackageManager.is_installed_by_name(app_fullname)
|
||||
if is_installed:
|
||||
if builtin_app:
|
||||
if overridden_builtin_app:
|
||||
action_label = self.action_label_restore
|
||||
else:
|
||||
action_label = self.action_label_nothing
|
||||
else:
|
||||
action_label = self.action_label_uninstall
|
||||
else:
|
||||
action_label = self.action_label_install
|
||||
self.install_label.set_text(action_label)
|
||||
|
||||
def toggle_install(self, app_obj):
|
||||
print(f"Install button clicked for {app_obj}")
|
||||
download_url = app_obj.download_url
|
||||
fullname = app_obj.fullname
|
||||
print(f"With {download_url} and fullname {fullname}")
|
||||
label_text = self.install_label.get_text()
|
||||
if label_text == self.action_label_install:
|
||||
print("Starting install task...")
|
||||
TaskManager.create_task(self.download_and_install(app_obj, f"apps/{fullname}"))
|
||||
elif label_text == self.action_label_uninstall or label_text == self.action_label_restore:
|
||||
print("Starting uninstall task...")
|
||||
TaskManager.create_task(self.uninstall_app(fullname))
|
||||
|
||||
def update_button_click(self, app_obj):
|
||||
download_url = app_obj.download_url
|
||||
fullname = app_obj.fullname
|
||||
print(f"Update button clicked for {download_url} and fullname {fullname}")
|
||||
self.update_button.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.install_button.set_size(lv.pct(100), 40)
|
||||
TaskManager.create_task(self.download_and_install(app_obj, f"apps/{fullname}"))
|
||||
|
||||
async def uninstall_app(self, app_fullname):
|
||||
self.install_button.add_state(lv.STATE.DISABLED)
|
||||
self.install_label.set_text("Please wait...")
|
||||
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(21, True)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
self.progress_bar.set_value(42, True)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
PackageManager.uninstall_app(app_fullname)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
self.progress_bar.set_value(100, False)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(0, False)
|
||||
self.set_install_label(app_fullname)
|
||||
self.install_button.remove_state(lv.STATE.DISABLED)
|
||||
if PackageManager.is_builtin_app(app_fullname):
|
||||
self.update_button.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
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
|
||||
|
||||
async def pcb(self, percent):
|
||||
print(f"pcb called: {percent}")
|
||||
scaled_percent_start = 5 # before 5% is preparation
|
||||
scaled_percent_finished = 60 # after 60% is unzip
|
||||
scaled_percent_diff = scaled_percent_finished - scaled_percent_start
|
||||
scale = 100 / scaled_percent_diff # 100 / 55 = 1.81
|
||||
scaled_percent = round(percent / scale)
|
||||
scaled_percent += scaled_percent_start
|
||||
self.progress_bar.set_value(scaled_percent, True)
|
||||
|
||||
async def download_and_install(self, app_obj, dest_folder):
|
||||
zip_url = app_obj.download_url
|
||||
app_fullname = app_obj.fullname
|
||||
download_url_size = None
|
||||
if hasattr(app_obj, "download_url_size"):
|
||||
download_url_size = app_obj.download_url_size
|
||||
self.install_button.add_state(lv.STATE.DISABLED)
|
||||
self.install_label.set_text("Please wait...")
|
||||
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(5, True)
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
# Download the .mpk file to temporary location
|
||||
try:
|
||||
# Make sure there's no leftover file filling the storage
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
os.mkdir("tmp")
|
||||
except Exception:
|
||||
pass
|
||||
temp_zip_path = "tmp/temp.mpk"
|
||||
print(f"Downloading .mpk file from: {zip_url} to {temp_zip_path}")
|
||||
try:
|
||||
result = await DownloadManager.download_url(zip_url, outfile=temp_zip_path, total_size=download_url_size, progress_callback=self.pcb)
|
||||
if result is not True:
|
||||
print("Download failed...") # Would be good to show an error to the user if this failed...
|
||||
else:
|
||||
print("Downloaded .mpk file, size:", os.stat(temp_zip_path)[6], "bytes")
|
||||
# Install it:
|
||||
PackageManager.install_mpk(temp_zip_path, dest_folder) # 60 until 90 percent is the unzip but no progress there...
|
||||
self.progress_bar.set_value(90, True)
|
||||
except Exception as e:
|
||||
print(f"Download failed with exception: {e}")
|
||||
if DownloadManager.is_network_error(e):
|
||||
self.install_label.set_text(f"Network error - check WiFi")
|
||||
else:
|
||||
self.install_label.set_text(f"Download failed: {str(e)[:30]}")
|
||||
self.install_button.remove_state(lv.STATE.DISABLED)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(0, False)
|
||||
# Make sure there's no leftover file filling the storage:
|
||||
try:
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
# Make sure there's no leftover file filling the storage:
|
||||
try:
|
||||
os.remove(temp_zip_path)
|
||||
except Exception:
|
||||
pass
|
||||
# Success:
|
||||
await TaskManager.sleep(1) # seems silly but otherwise it goes so quickly that the user can't tell something happened and gets confused
|
||||
self.progress_bar.set_value(100, False)
|
||||
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.progress_bar.set_value(0, False)
|
||||
self.set_install_label(app_fullname)
|
||||
self.install_button.remove_state(lv.STATE.DISABLED)
|
||||
@staticmethod
|
||||
def backend_pref_string_to_backend(string):
|
||||
return string.split(",")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Core framework
|
||||
from .app.app import App
|
||||
from .app.activity import Activity
|
||||
from .config import SharedPreferences
|
||||
from .net.connectivity_manager import ConnectivityManager
|
||||
from .net import download_manager as DownloadManager
|
||||
from .content.intent import Intent
|
||||
@@ -16,7 +17,10 @@ from .app.activities.share import ShareActivity
|
||||
from .ui.setting_activity import SettingActivity
|
||||
|
||||
__all__ = [
|
||||
"App", "Activity", "ConnectivityManager", "DownloadManager", "Intent",
|
||||
"App",
|
||||
"Activity",
|
||||
"SharedPreferences",
|
||||
"ConnectivityManager", "DownloadManager", "Intent",
|
||||
"ActivityNavigator", "PackageManager", "TaskManager",
|
||||
"ChooserActivity", "ViewActivity", "ShareActivity",
|
||||
"SettingActivity"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import lvgl as lv
|
||||
|
||||
import mpos
|
||||
from mpos.apps import Activity, Intent
|
||||
from mpos.apps import Activity
|
||||
|
||||
"""
|
||||
SettingActivity is used to edit one setting.
|
||||
|
||||
Reference in New Issue
Block a user