Fix appstore, icons etc

This commit is contained in:
Thomas Farstrike
2025-06-04 15:32:16 +02:00
parent cff1421f06
commit 5fde5a87da
10 changed files with 127 additions and 125 deletions
@@ -3,15 +3,15 @@
"publisher": "LightningPiggy Foundation",
"short_description": "Display wallet that shows balance, transactions, receive QR code etc.",
"long_description": "See https://www.LightningPiggy.com",
"icon_url": "https://apps.micropythonos.com/icons/com.lightningpiggy.displaywallet_0.0.1.mpk_icon_64x64.png",
"download_url": "https://apps.micropythonos.com/mpks/com.lightningpiggy.displaywallet_0.0.1.mpk",
"icon_url": "https://apps.micropythonos.com/com.lightningpiggy.displaywallet/icons/com.lightningpiggy.displaywallet_0.0.1_64x64.png",
"download_url": "https://apps.micropythonos.com/com.lightningpiggy.displaywallet/mpks/com.lightningpiggy.displaywallet_0.0.1.mpk",
"fullname": "com.lightningpiggy.displaywallet",
"version": "0.0.1",
"category": "finance",
"activities": [
{
"entrypoint": "assets/displaywallet.py",
"classname": "MainActivity",
"classname": "DisplayWallet",
"intent_filters": [
{
"action": "main",
@@ -4,7 +4,7 @@ import mpos.config
from wallet import LNBitsWallet, NWCWallet
from captureqr import Camera
class MainActivity(Activity):
class DisplayWallet(Activity):
def __init__(self):
super().__init__()
@@ -3,8 +3,8 @@
"publisher": "MicroPythonOS",
"short_description": "Camera with QR decoding",
"long_description": "Camera for both internal camera's and webcams, that includes QR decoding.",
"icon_url": "https://apps.micropythonos.com/com.micropython.camera_0.0.3.mpk_icon_64x64.png",
"download_url": "https://apps.micropythonos.com/com.micropython.camera_0.0.3.mpk",
"icon_url": "https://apps.micropythonos.com/com.micropython.camera/icons/com.micropython.camera_0.0.3_64x64.png",
"download_url": "https://apps.micropythonos.com/com.micropython.camera/mpks/com.micropython.camera_0.0.3.mpk",
"fullname": "com.micropython.camera",
"version": "0.0.3",
"category": "camera",
@@ -3,15 +3,15 @@
"publisher": "MicroPythonOS",
"short_description": "Store for App(lication)s",
"long_description": "",
"icon_url": "https://apps.micropythonos.com/icons/com.micropythonos.appstore_0.0.3.mpk_icon_64x64.png",
"download_url": "https://apps.micropythonos.com/mpks/com.micropythonos.appstore_0.0.3.mpk",
"icon_url": "https://apps.micropythonos.com/com.micropythonos.appstore/icons/com.micropythonos.appstore_0.0.3_64x64.png",
"download_url": "https://apps.micropythonos.com/com.micropythonos.appstore/mpks/com.micropythonos.appstore_0.0.3.mpk",
"fullname": "com.micropythonos.appstore",
"version": "0.0.3",
"category": "appstore",
"activities": [
{
"entrypoint": "assets/appstore.py",
"classname": "MainActivity",
"classname": "AppStore",
"intent_filters": [
{
"action": "main",
@@ -9,24 +9,18 @@ import _thread
from mpos.apps import Activity, Intent
import mpos.ui
# Screens:
app_detail_screen = None
main_screen = None
action_label_install = "Install"
action_label_uninstall = "Uninstall"
action_label_restore = "Restore Built-in"
action_label_nothing = "Disable" # This doesn't do anything at the moment, but it could mark builtin apps as "Disabled" somehow and also allow for "Enable" then
class MainActivity(Activity):
main_screen = None
class AppStore(Activity):
apps = []
app_index_url = "https://apps.micropythonos.com/app_index.json"
can_check_network = True
# Widgets:
main_screen = None
update_button = None
install_button = None
install_label = None
please_wait_label = None
progress_bar = None
can_check_network = True
def onCreate(self):
self.main_screen = lv.obj()
@@ -44,7 +38,7 @@ class MainActivity(Activity):
self.please_wait_label.set_text("Error: WiFi is not connected.")
else:
_thread.stack_size(mpos.apps.good_stack_size())
_thread.start_new_thread(self.download_app_index, ("https://apps.micropythonos.com/app_index.json",))
_thread.start_new_thread(self.download_app_index, (self.app_index_url,))
def onDestroy(self, screen):
print("appstore.py destroyed, restarting launcher to refresh...")
@@ -81,7 +75,7 @@ class MainActivity(Activity):
def create_apps_list(self):
print("create_apps_list")
default_icon_dsc = load_icon("builtin/res/mipmap-mdpi/default_icon_64x64.png")
default_icon_dsc = self.load_icon("builtin/res/mipmap-mdpi/default_icon_64x64.png")
apps_list = lv.list(self.main_screen)
apps_list.set_style_pad_all(0, 0)
apps_list.set_size(lv.pct(100), lv.pct(100))
@@ -123,8 +117,11 @@ class MainActivity(Activity):
def download_icons(self):
for app in self.apps:
if app.image_dsc:
print(f"Skipping icon download for {app.name} because already downloaded.")
continue
print(f"Downloading icon for {app.name}")
image_dsc = download_icon(app.icon_url)
image_dsc = self.download_icon(app.icon_url)
app.image_dsc = image_dsc # save it for the app detail page
lv.async_call(lambda l: app.image.set_src(image_dsc), None)
time.sleep_ms(100) # not waiting here will result in some async_calls() not being executed
@@ -135,7 +132,34 @@ class MainActivity(Activity):
intent.putExtra("app", app)
self.startActivity(intent)
@staticmethod
def download_icon(url):
print(f"Downloading icon from {url}")
try:
response = requests.get(url, timeout=5)
if response.status_code == 200:
image_data = response.content
print("Downloaded image, size:", len(image_data), "bytes")
image_dsc = lv.image_dsc_t({
'data_size': len(image_data),
'data': image_data
})
return image_dsc
else:
print("Failed to download image: Status code", response.status_code)
except Exception as e:
print(f"Exception during download of icon: {e}")
return None
@staticmethod
def load_icon(icon_path):
with open(icon_path, 'rb') as f:
image_data = f.read()
image_dsc = lv.image_dsc_t({
'data_size': len(image_data),
'data': image_data
})
return image_dsc
class AppDetail(Activity):
@@ -144,6 +168,12 @@ class AppDetail(Activity):
except ImportError:
zipfile = None
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
@@ -151,6 +181,7 @@ class AppDetail(Activity):
def onCreate(self):
print("Creating app detail screen...")
app = self.getIntent().extras.get("app")
app_detail_screen = lv.obj()
app_detail_screen.set_size(lv.pct(100), lv.pct(100))
app_detail_screen.set_pos(0, 40)
@@ -194,11 +225,10 @@ class AppDetail(Activity):
self.install_button.set_size(lv.pct(100), 40)
self.install_label = lv.label(self.install_button)
self.install_label.center()
set_install_label(app.fullname)
if is_update_available(app.fullname, app.version):
self.set_install_label(app.fullname)
if self.is_update_available(app.fullname, app.version):
self.install_button.set_size(lv.pct(47), 40) # make space for update button
print("Update available, adding update button.")
global 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, d=app.download_url, f=app.fullname: self.update_button_click(d,f), lv.EVENT.CLICKED, None)
@@ -210,7 +240,7 @@ class AppDetail(Activity):
version_label.set_width(lv.pct(100))
version_label.set_text(f"Latest version: {app.version}") # make this bold if this is newer than the currently installed one
version_label.set_style_text_font(lv.font_montserrat_12, 0)
version_label.align_to(install_button, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
version_label.align_to(self.install_button, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
long_desc_label = lv.label(app_detail_screen)
long_desc_label.align_to(version_label, lv.ALIGN.OUT_BOTTOM_MID, 0, lv.pct(5))
long_desc_label.set_text(app.long_description)
@@ -231,8 +261,8 @@ class AppDetail(Activity):
# - update is separate button, only shown if already installed and new version
is_installed = True
update_available = False
builtin_app = is_builtin_app(app_fullname)
overridden_builtin_app = is_overridden_builtin_app(app_fullname)
builtin_app = self.is_builtin_app(app_fullname)
overridden_builtin_app = self.is_overridden_builtin_app(app_fullname)
if not overridden_builtin_app:
is_installed = is_installed_by_name(app_fullname)
if is_installed:
@@ -249,7 +279,6 @@ class AppDetail(Activity):
def toggle_install(self, download_url, fullname):
global install_label
print(f"Install button clicked for {download_url} and fullname {fullname}")
label_text = self.install_label.get_text()
if label_text == action_label_install:
@@ -276,7 +305,7 @@ class AppDetail(Activity):
except Exception as e:
print("Could not start download_and_unzip thread: ", e)
def uninstall_app(app_folder, app_fullname):
def uninstall_app(self, app_folder, app_fullname):
self.install_button.remove_flag(lv.obj.FLAG.CLICKABLE) # TODO: change color so it's clear the button is not clickable
self.install_label.set_text("Please wait...") # TODO: Put "Cancel" if cancellation is possible
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
@@ -295,12 +324,12 @@ class AppDetail(Activity):
self.progress_bar.set_value(0, lv.ANIM.OFF)
set_install_label(app_fullname)
self.install_button.add_flag(lv.obj.FLAG.CLICKABLE)
if is_builtin_app(app_fullname):
if self.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
def download_and_unzip(zip_url, dest_folder, app_fullname):
def download_and_unzip(self, zip_url, dest_folder, app_fullname):
self.install_button.remove_flag(lv.obj.FLAG.CLICKABLE) # TODO: change color so it's clear the button is not clickable
self.install_label.set_text("Please wait...") # TODO: Put "Cancel" if cancellation is possible
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
@@ -363,94 +392,65 @@ class AppDetail(Activity):
self.progress_bar.set_value(0, lv.ANIM.OFF)
set_install_label(app_fullname)
self.install_button.add_flag(lv.obj.FLAG.CLICKABLE)
# Non-class functions:
def compare_versions(ver1: str, ver2: str) -> bool:
"""Compare two version numbers (e.g., '1.2.3' vs '4.5.6').
Returns True if ver1 is greater than ver2, False otherwise."""
print(f"Comparing versions: {ver1} vs {ver2}")
v1_parts = [int(x) for x in ver1.split('.')]
v2_parts = [int(x) for x in ver2.split('.')]
print(f"Version 1 parts: {v1_parts}")
print(f"Version 2 parts: {v2_parts}")
for i in range(max(len(v1_parts), len(v2_parts))):
v1 = v1_parts[i] if i < len(v1_parts) else 0
v2 = v2_parts[i] if i < len(v2_parts) else 0
print(f"Comparing part {i}: {v1} vs {v2}")
if v1 > v2:
print(f"{ver1} is greater than {ver2}")
return True
if v1 < v2:
print(f"{ver1} is less than {ver2}")
return False
print(f"Versions are equal or {ver1} is not greater than {ver2}")
return False
def is_builtin_app(app_fullname):
return is_installed_by_path(f"builtin/apps/{app_fullname}")
def is_overridden_builtin_app(app_fullname):
return is_installed_by_path(f"apps/{app_fullname}") and is_installed_by_path(f"builtin/apps/{app_fullname}")
def is_update_available(app_fullname, new_version):
appdir = f"apps/{app_fullname}"
builtinappdir = f"builtin/apps/{app_fullname}"
installed_app=None
if is_installed_by_path(appdir):
print(f"{appdir} found, getting version...")
installed_app = mpos.apps.parse_manifest(f"{appdir}/META-INF/MANIFEST.JSON")
elif is_installed_by_path(builtinappdir):
print(f"{builtinappdir} found, getting version...")
installed_app = mpos.apps.parse_manifest(f"{builtinappdir}/META-INF/MANIFEST.JSON")
if not installed_app or installed_app.version == "0.0.0": # special case, if the installed app doesn't have a version number then there's no update
return False
return compare_versions(new_version, installed_app.version)
def is_installed_by_path(dir_path):
try:
if os.stat(dir_path)[0] & 0x4000:
manifest = f"{dir_path}/META-INF/MANIFEST.JSON"
if os.stat(manifest)[0] & 0x8000:
@staticmethod
def compare_versions(ver1: str, ver2: str) -> bool:
"""Compare two version numbers (e.g., '1.2.3' vs '4.5.6').
Returns True if ver1 is greater than ver2, False otherwise."""
print(f"Comparing versions: {ver1} vs {ver2}")
v1_parts = [int(x) for x in ver1.split('.')]
v2_parts = [int(x) for x in ver2.split('.')]
print(f"Version 1 parts: {v1_parts}")
print(f"Version 2 parts: {v2_parts}")
for i in range(max(len(v1_parts), len(v2_parts))):
v1 = v1_parts[i] if i < len(v1_parts) else 0
v2 = v2_parts[i] if i < len(v2_parts) else 0
print(f"Comparing part {i}: {v1} vs {v2}")
if v1 > v2:
print(f"{ver1} is greater than {ver2}")
return True
except OSError:
pass # Skip if directory or manifest doesn't exist
return False
if v1 < v2:
print(f"{ver1} is less than {ver2}")
return False
print(f"Versions are equal or {ver1} is not greater than {ver2}")
return False
def is_installed_by_name(app_fullname):
print(f"Checking if app {app_fullname} is installed...")
return is_installed_by_path(f"apps/{app_fullname}") or is_installed_by_path(f"builtin/apps/{app_fullname}")
@staticmethod
def is_builtin_app(app_fullname):
return AppStore.is_installed_by_path(f"builtin/apps/{app_fullname}")
@staticmethod
def is_overridden_builtin_app(app_fullname):
return AppStore.is_installed_by_path(f"apps/{app_fullname}") and AppStore.is_installed_by_path(f"builtin/apps/{app_fullname}")
def download_icon(url):
print(f"Downloading icon from {url}")
try:
response = requests.get(url, timeout=5)
if response.status_code == 200:
image_data = response.content
print("Downloaded image, size:", len(image_data), "bytes")
image_dsc = lv.image_dsc_t({
'data_size': len(image_data),
'data': image_data
})
return image_dsc
else:
print("Failed to download image: Status code", response.status_code)
except Exception as e:
print(f"Exception during download of icon: {e}")
return None
@staticmethod
def is_update_available(app_fullname, new_version):
appdir = f"apps/{app_fullname}"
builtinappdir = f"builtin/apps/{app_fullname}"
installed_app=None
if AppStore.is_installed_by_path(appdir):
print(f"{appdir} found, getting version...")
installed_app = mpos.apps.parse_manifest(f"{appdir}/META-INF/MANIFEST.JSON")
elif AppStore.is_installed_by_path(builtinappdir):
print(f"{builtinappdir} found, getting version...")
installed_app = mpos.apps.parse_manifest(f"{builtinappdir}/META-INF/MANIFEST.JSON")
if not installed_app or installed_app.version == "0.0.0": # special case, if the installed app doesn't have a version number then there's no update
return False
return compare_versions(new_version, installed_app.version)
@staticmethod
def is_installed_by_path(dir_path):
try:
if os.stat(dir_path)[0] & 0x4000:
manifest = f"{dir_path}/META-INF/MANIFEST.JSON"
if os.stat(manifest)[0] & 0x8000:
return True
except OSError:
pass # Skip if directory or manifest doesn't exist
return False
def load_icon(icon_path):
with open(icon_path, 'rb') as f:
image_data = f.read()
image_dsc = lv.image_dsc_t({
'data_size': len(image_data),
'data': image_data
})
return image_dsc
@staticmethod
def is_installed_by_name(app_fullname):
print(f"Checking if app {app_fullname} is installed...")
return AppStore.is_installed_by_path(f"apps/{app_fullname}") or AppStore.is_installed_by_path(f"builtin/apps/{app_fullname}")
@@ -3,15 +3,15 @@
"publisher": "MicroPythonOS",
"short_description": "Simple launcher to start apps.",
"long_description": "",
"icon_url": "https://apps.micropythonos.com/icons/com.micropythonos.launcher_0.0.3.mpk_icon_64x64.png",
"download_url": "https://apps.micropythonos.com/mpks/com.micropythonos.launcher_0.0.3.mpk",
"icon_url": "https://apps.micropythonos.com/com.micropythonos.launcher/icons/com.micropythonos.launcher_0.0.3_64x64.png",
"download_url": "https://apps.micropythonos.com/com.micropythonos.launcher/mpks/com.micropythonos.launcher_0.0.3.mpk",
"fullname": "com.micropythonos.launcher",
"version": "0.0.3",
"category": "launcher",
"activities": [
{
"entrypoint": "assets/launcher.py",
"classname": "MainActivity",
"classname": "Launcher",
"intent_filters": [
{
"action": "main",
@@ -15,7 +15,7 @@ import lvgl as lv
import mpos.apps
import mpos.ui
class MainActivity(mpos.apps.Activity):
class Launcher(mpos.apps.Activity):
def onCreate(self):
print("launcher.py onCreate()")
@@ -3,10 +3,10 @@
"publisher": "MicroPythonOS",
"short_description": "Operating System Updater",
"long_description": "",
"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.osupdate/icons/com.micropythonos.osupdate_0.0.2_64x64.png",
"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.osupdate/mpks/com.micropythonos.osupdate_0.0.2.mpk",
"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.osupdate/icons/com.micropythonos.osupdate_0.0.3_64x64.png",
"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.osupdate/mpks/com.micropythonos.osupdate_0.0.3.mpk",
"fullname": "com.micropythonos.osupdate",
"version": "0.0.2",
"version": "0.0.3",
"category": "osupdate",
"activities": [
{
+2
View File
@@ -16,6 +16,8 @@ rm "$outputjson"
echo "[" | tee -a "$outputjson"
# currently, this script doesn't purge unnecessary information from the manifests, such as activities
for apprepo in internal_filesystem/apps internal_filesystem/builtin/apps; do
echo "Listing apps in $apprepo"
ls -1 "$apprepo" | while read appdir; do