99from mpos .apps import Activity , Intent
1010import mpos .ui
1111
12- # Screens:
13- app_detail_screen = None
14- main_screen = None
15-
16- action_label_install = "Install"
17- action_label_uninstall = "Uninstall"
18- action_label_restore = "Restore Built-in"
19- 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
12+ class AppStore (Activity ):
13+ apps = []
14+ app_index_url = "https://apps.micropythonos.com/app_index.json"
15+ can_check_network = True
2016
21- class MainActivity ( Activity ) :
17+ # Widgets :
2218 main_screen = None
23- apps = []
2419 update_button = None
2520 install_button = None
2621 install_label = None
2722 please_wait_label = None
2823 progress_bar = None
29- can_check_network = True
3024
3125 def onCreate (self ):
3226 self .main_screen = lv .obj ()
@@ -44,7 +38,7 @@ def onStart(self, screen):
4438 self .please_wait_label .set_text ("Error: WiFi is not connected." )
4539 else :
4640 _thread .stack_size (mpos .apps .good_stack_size ())
47- _thread .start_new_thread (self .download_app_index , ("https://apps.micropythonos.com/app_index.json" ,))
41+ _thread .start_new_thread (self .download_app_index , (self . app_index_url ,))
4842
4943 def onDestroy (self , screen ):
5044 print ("appstore.py destroyed, restarting launcher to refresh..." )
@@ -81,7 +75,7 @@ def download_app_index(self, json_url):
8175
8276 def create_apps_list (self ):
8377 print ("create_apps_list" )
84- default_icon_dsc = load_icon ("builtin/res/mipmap-mdpi/default_icon_64x64.png" )
78+ default_icon_dsc = self . load_icon ("builtin/res/mipmap-mdpi/default_icon_64x64.png" )
8579 apps_list = lv .list (self .main_screen )
8680 apps_list .set_style_pad_all (0 , 0 )
8781 apps_list .set_size (lv .pct (100 ), lv .pct (100 ))
@@ -123,8 +117,11 @@ def create_apps_list(self):
123117
124118 def download_icons (self ):
125119 for app in self .apps :
120+ if app .image_dsc :
121+ print (f"Skipping icon download for { app .name } because already downloaded." )
122+ continue
126123 print (f"Downloading icon for { app .name } " )
127- image_dsc = download_icon (app .icon_url )
124+ image_dsc = self . download_icon (app .icon_url )
128125 app .image_dsc = image_dsc # save it for the app detail page
129126 lv .async_call (lambda l : app .image .set_src (image_dsc ), None )
130127 time .sleep_ms (100 ) # not waiting here will result in some async_calls() not being executed
@@ -135,7 +132,34 @@ def show_app_detail(self, app):
135132 intent .putExtra ("app" , app )
136133 self .startActivity (intent )
137134
138-
135+ @staticmethod
136+ def download_icon (url ):
137+ print (f"Downloading icon from { url } " )
138+ try :
139+ response = requests .get (url , timeout = 5 )
140+ if response .status_code == 200 :
141+ image_data = response .content
142+ print ("Downloaded image, size:" , len (image_data ), "bytes" )
143+ image_dsc = lv .image_dsc_t ({
144+ 'data_size' : len (image_data ),
145+ 'data' : image_data
146+ })
147+ return image_dsc
148+ else :
149+ print ("Failed to download image: Status code" , response .status_code )
150+ except Exception as e :
151+ print (f"Exception during download of icon: { e } " )
152+ return None
153+
154+ @staticmethod
155+ def load_icon (icon_path ):
156+ with open (icon_path , 'rb' ) as f :
157+ image_data = f .read ()
158+ image_dsc = lv .image_dsc_t ({
159+ 'data_size' : len (image_data ),
160+ 'data' : image_data
161+ })
162+ return image_dsc
139163
140164class AppDetail (Activity ):
141165
@@ -144,13 +168,20 @@ class AppDetail(Activity):
144168 except ImportError :
145169 zipfile = None
146170
171+ action_label_install = "Install"
172+ action_label_uninstall = "Uninstall"
173+ action_label_restore = "Restore Built-in"
174+ action_label_nothing = "Disable" # This could mark builtin apps as "Disabled" somehow and also allow for "Enable" then
175+
176+ # Widgets:
147177 install_button = None
148178 update_button = None
149179 progress_bar = None
150180 install_label = None
151181
152182 def onCreate (self ):
153183 print ("Creating app detail screen..." )
184+ app = self .getIntent ().extras .get ("app" )
154185 app_detail_screen = lv .obj ()
155186 app_detail_screen .set_size (lv .pct (100 ), lv .pct (100 ))
156187 app_detail_screen .set_pos (0 , 40 )
@@ -194,11 +225,10 @@ def onCreate(self):
194225 self .install_button .set_size (lv .pct (100 ), 40 )
195226 self .install_label = lv .label (self .install_button )
196227 self .install_label .center ()
197- set_install_label (app .fullname )
198- if is_update_available (app .fullname , app .version ):
228+ self . set_install_label (app .fullname )
229+ if self . is_update_available (app .fullname , app .version ):
199230 self .install_button .set_size (lv .pct (47 ), 40 ) # make space for update button
200231 print ("Update available, adding update button." )
201- global update_button
202232 self .update_button = lv .button (buttoncont )
203233 self .update_button .set_size (lv .pct (47 ), 40 )
204234 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 @@ def onCreate(self):
210240 version_label .set_width (lv .pct (100 ))
211241 version_label .set_text (f"Latest version: { app .version } " ) # make this bold if this is newer than the currently installed one
212242 version_label .set_style_text_font (lv .font_montserrat_12 , 0 )
213- version_label .align_to (install_button , lv .ALIGN .OUT_BOTTOM_MID , 0 , lv .pct (5 ))
243+ version_label .align_to (self . install_button , lv .ALIGN .OUT_BOTTOM_MID , 0 , lv .pct (5 ))
214244 long_desc_label = lv .label (app_detail_screen )
215245 long_desc_label .align_to (version_label , lv .ALIGN .OUT_BOTTOM_MID , 0 , lv .pct (5 ))
216246 long_desc_label .set_text (app .long_description )
@@ -231,8 +261,8 @@ def set_install_label(self, app_fullname):
231261 # - update is separate button, only shown if already installed and new version
232262 is_installed = True
233263 update_available = False
234- builtin_app = is_builtin_app (app_fullname )
235- overridden_builtin_app = is_overridden_builtin_app (app_fullname )
264+ builtin_app = self . is_builtin_app (app_fullname )
265+ overridden_builtin_app = self . is_overridden_builtin_app (app_fullname )
236266 if not overridden_builtin_app :
237267 is_installed = is_installed_by_name (app_fullname )
238268 if is_installed :
@@ -249,7 +279,6 @@ def set_install_label(self, app_fullname):
249279
250280
251281 def toggle_install (self , download_url , fullname ):
252- global install_label
253282 print (f"Install button clicked for { download_url } and fullname { fullname } " )
254283 label_text = self .install_label .get_text ()
255284 if label_text == action_label_install :
@@ -276,7 +305,7 @@ def update_button_click(self, download_url, fullname):
276305 except Exception as e :
277306 print ("Could not start download_and_unzip thread: " , e )
278307
279- def uninstall_app (app_folder , app_fullname ):
308+ def uninstall_app (self , app_folder , app_fullname ):
280309 self .install_button .remove_flag (lv .obj .FLAG .CLICKABLE ) # TODO: change color so it's clear the button is not clickable
281310 self .install_label .set_text ("Please wait..." ) # TODO: Put "Cancel" if cancellation is possible
282311 self .progress_bar .remove_flag (lv .obj .FLAG .HIDDEN )
@@ -295,12 +324,12 @@ def uninstall_app(app_folder, app_fullname):
295324 self .progress_bar .set_value (0 , lv .ANIM .OFF )
296325 set_install_label (app_fullname )
297326 self .install_button .add_flag (lv .obj .FLAG .CLICKABLE )
298- if is_builtin_app (app_fullname ):
327+ if self . is_builtin_app (app_fullname ):
299328 self .update_button .remove_flag (lv .obj .FLAG .HIDDEN )
300329 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
301330
302331
303- def download_and_unzip (zip_url , dest_folder , app_fullname ):
332+ def download_and_unzip (self , zip_url , dest_folder , app_fullname ):
304333 self .install_button .remove_flag (lv .obj .FLAG .CLICKABLE ) # TODO: change color so it's clear the button is not clickable
305334 self .install_label .set_text ("Please wait..." ) # TODO: Put "Cancel" if cancellation is possible
306335 self .progress_bar .remove_flag (lv .obj .FLAG .HIDDEN )
@@ -363,94 +392,65 @@ def download_and_unzip(zip_url, dest_folder, app_fullname):
363392 self .progress_bar .set_value (0 , lv .ANIM .OFF )
364393 set_install_label (app_fullname )
365394 self .install_button .add_flag (lv .obj .FLAG .CLICKABLE )
366-
367-
368395
369- # Non-class functions:
396+ @staticmethod
397+ def compare_versions (ver1 : str , ver2 : str ) -> bool :
398+ """Compare two version numbers (e.g., '1.2.3' vs '4.5.6').
399+ Returns True if ver1 is greater than ver2, False otherwise."""
400+ print (f"Comparing versions: { ver1 } vs { ver2 } " )
401+ v1_parts = [int (x ) for x in ver1 .split ('.' )]
402+ v2_parts = [int (x ) for x in ver2 .split ('.' )]
403+ print (f"Version 1 parts: { v1_parts } " )
404+ print (f"Version 2 parts: { v2_parts } " )
405+ for i in range (max (len (v1_parts ), len (v2_parts ))):
406+ v1 = v1_parts [i ] if i < len (v1_parts ) else 0
407+ v2 = v2_parts [i ] if i < len (v2_parts ) else 0
408+ print (f"Comparing part { i } : { v1 } vs { v2 } " )
409+ if v1 > v2 :
410+ print (f"{ ver1 } is greater than { ver2 } " )
411+ return True
412+ if v1 < v2 :
413+ print (f"{ ver1 } is less than { ver2 } " )
414+ return False
415+ print (f"Versions are equal or { ver1 } is not greater than { ver2 } " )
416+ return False
370417
371- def compare_versions (ver1 : str , ver2 : str ) -> bool :
372- """Compare two version numbers (e.g., '1.2.3' vs '4.5.6').
373- Returns True if ver1 is greater than ver2, False otherwise."""
374- print (f"Comparing versions: { ver1 } vs { ver2 } " )
375- v1_parts = [int (x ) for x in ver1 .split ('.' )]
376- v2_parts = [int (x ) for x in ver2 .split ('.' )]
377- print (f"Version 1 parts: { v1_parts } " )
378- print (f"Version 2 parts: { v2_parts } " )
379- for i in range (max (len (v1_parts ), len (v2_parts ))):
380- v1 = v1_parts [i ] if i < len (v1_parts ) else 0
381- v2 = v2_parts [i ] if i < len (v2_parts ) else 0
382- print (f"Comparing part { i } : { v1 } vs { v2 } " )
383- if v1 > v2 :
384- print (f"{ ver1 } is greater than { ver2 } " )
385- return True
386- if v1 < v2 :
387- print (f"{ ver1 } is less than { ver2 } " )
418+ @staticmethod
419+ def is_builtin_app (app_fullname ):
420+ return AppStore .is_installed_by_path (f"builtin/apps/{ app_fullname } " )
421+
422+ @staticmethod
423+ def is_overridden_builtin_app (app_fullname ):
424+ return AppStore .is_installed_by_path (f"apps/{ app_fullname } " ) and AppStore .is_installed_by_path (f"builtin/apps/{ app_fullname } " )
425+
426+ @staticmethod
427+ def is_update_available (app_fullname , new_version ):
428+ appdir = f"apps/{ app_fullname } "
429+ builtinappdir = f"builtin/apps/{ app_fullname } "
430+ installed_app = None
431+ if AppStore .is_installed_by_path (appdir ):
432+ print (f"{ appdir } found, getting version..." )
433+ installed_app = mpos .apps .parse_manifest (f"{ appdir } /META-INF/MANIFEST.JSON" )
434+ elif AppStore .is_installed_by_path (builtinappdir ):
435+ print (f"{ builtinappdir } found, getting version..." )
436+ installed_app = mpos .apps .parse_manifest (f"{ builtinappdir } /META-INF/MANIFEST.JSON" )
437+ 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
388438 return False
389- print (f"Versions are equal or { ver1 } is not greater than { ver2 } " )
390- return False
439+ return compare_versions (new_version , installed_app .version )
391440
392- def is_builtin_app (app_fullname ):
393- return is_installed_by_path (f"builtin/apps/{ app_fullname } " )
394-
395- def is_overridden_builtin_app (app_fullname ):
396- return is_installed_by_path (f"apps/{ app_fullname } " ) and is_installed_by_path (f"builtin/apps/{ app_fullname } " )
397-
398- def is_update_available (app_fullname , new_version ):
399- appdir = f"apps/{ app_fullname } "
400- builtinappdir = f"builtin/apps/{ app_fullname } "
401- installed_app = None
402- if is_installed_by_path (appdir ):
403- print (f"{ appdir } found, getting version..." )
404- installed_app = mpos .apps .parse_manifest (f"{ appdir } /META-INF/MANIFEST.JSON" )
405- elif is_installed_by_path (builtinappdir ):
406- print (f"{ builtinappdir } found, getting version..." )
407- installed_app = mpos .apps .parse_manifest (f"{ builtinappdir } /META-INF/MANIFEST.JSON" )
408- 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
441+ @staticmethod
442+ def is_installed_by_path (dir_path ):
443+ try :
444+ if os .stat (dir_path )[0 ] & 0x4000 :
445+ manifest = f"{ dir_path } /META-INF/MANIFEST.JSON"
446+ if os .stat (manifest )[0 ] & 0x8000 :
447+ return True
448+ except OSError :
449+ pass # Skip if directory or manifest doesn't exist
409450 return False
410- return compare_versions (new_version , installed_app .version )
411-
412-
413- def is_installed_by_path (dir_path ):
414- try :
415- if os .stat (dir_path )[0 ] & 0x4000 :
416- manifest = f"{ dir_path } /META-INF/MANIFEST.JSON"
417- if os .stat (manifest )[0 ] & 0x8000 :
418- return True
419- except OSError :
420- pass # Skip if directory or manifest doesn't exist
421- return False
422-
423- def is_installed_by_name (app_fullname ):
424- print (f"Checking if app { app_fullname } is installed..." )
425- return is_installed_by_path (f"apps/{ app_fullname } " ) or is_installed_by_path (f"builtin/apps/{ app_fullname } " )
426-
427-
428- def download_icon (url ):
429- print (f"Downloading icon from { url } " )
430- try :
431- response = requests .get (url , timeout = 5 )
432- if response .status_code == 200 :
433- image_data = response .content
434- print ("Downloaded image, size:" , len (image_data ), "bytes" )
435- image_dsc = lv .image_dsc_t ({
436- 'data_size' : len (image_data ),
437- 'data' : image_data
438- })
439- return image_dsc
440- else :
441- print ("Failed to download image: Status code" , response .status_code )
442- except Exception as e :
443- print (f"Exception during download of icon: { e } " )
444- return None
445-
446-
447- def load_icon (icon_path ):
448- with open (icon_path , 'rb' ) as f :
449- image_data = f .read ()
450- image_dsc = lv .image_dsc_t ({
451- 'data_size' : len (image_data ),
452- 'data' : image_data
453- })
454- return image_dsc
455451
452+ @staticmethod
453+ def is_installed_by_name (app_fullname ):
454+ print (f"Checking if app { app_fullname } is installed..." )
455+ return AppStore .is_installed_by_path (f"apps/{ app_fullname } " ) or AppStore .is_installed_by_path (f"builtin/apps/{ app_fullname } " )
456456
0 commit comments