diff --git a/StellaPay.py b/StellaPay.py index 738faf1..ff0414b 100644 --- a/StellaPay.py +++ b/StellaPay.py @@ -31,6 +31,7 @@ from utils import Connections from utils.Screens import Screens from utils.SessionManager import SessionManager +from utils.async_requests.AsyncResult import AsyncResult class StellaPay(MDApp): @@ -195,26 +196,38 @@ def handle_user_data(user_data): # Store the user mapping so other screens can use it. self.user_mapping = user_data - screen_manager.get_screen(Screens.STARTUP_SCREEN.value).users_loaded = True + screen_manager.get_screen(Screens.STARTUP_SCREEN.value).users_loaded = AsyncResult(True, data=True) # Load user data self.data_controller.get_user_data(callback=handle_user_data) # Callback for loaded product data def handle_product_data(product_data): + + if product_data is None: + Logger.error(f"StellaPayUI: Something went wrong when fetching product data!") + screen_manager.get_screen(Screens.STARTUP_SCREEN.value).products_loaded = AsyncResult(False) + return + Logger.info(f"StellaPayUI: Loaded {len(product_data)} products.") # Signal to the startup screen that the products have been loaded. - screen_manager.get_screen(Screens.STARTUP_SCREEN.value).products_loaded = True + screen_manager.get_screen(Screens.STARTUP_SCREEN.value).products_loaded = AsyncResult(True, data=True) self.loaded_all_users_and_products() # Callback for loaded category data def handle_category_data(category_data): + + if category_data is None: + Logger.error(f"StellaPayUI: Something went wrong when fetching category data!") + screen_manager.get_screen(Screens.STARTUP_SCREEN.value).categories_loaded = AsyncResult(False) + return + Logger.info(f"StellaPayUI: Loaded {len(category_data)} categories.") # Signal to the startup screen that the categories have been loaded. - screen_manager.get_screen(Screens.STARTUP_SCREEN.value).categories_loaded = True + screen_manager.get_screen(Screens.STARTUP_SCREEN.value).categories_loaded = AsyncResult(True, data=True) self.data_controller.get_product_data(callback=handle_product_data) diff --git a/scrs/StartupScreen.py b/scrs/StartupScreen.py index 684ee83..7f9f91b 100644 --- a/scrs/StartupScreen.py +++ b/scrs/StartupScreen.py @@ -1,59 +1,96 @@ +import sys + from kivy import Logger from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder +from kivy.properties import ObjectProperty from kivy.uix.screenmanager import Screen # Load KV file for this screen from utils.Screens import Screens +from utils.async_requests.AsyncResult import AsyncResult Builder.load_file('kvs/StartupScreen.kv') class StartupScreen(Screen): + users_loaded: AsyncResult = ObjectProperty() + categories_loaded: AsyncResult = ObjectProperty() + products_loaded: AsyncResult = ObjectProperty() + def __init__(self, **kwargs): # call to user with arguments super(StartupScreen, self).__init__(**kwargs) - # Keep track of when data is loaded - self.users_loaded = False - self.categories_loaded = False - self.products_loaded = False - # Calls upon entry of this screen # def on_enter(self, *args): - self.ids.loading_text.text = "Waiting for data to load..." - - App.get_running_app().loop.call_soon_threadsafe(self.wait_for_data_to_load) + App.get_running_app().loop.call_soon_threadsafe(self.check_if_all_data_is_loaded) # Called when all data has loaded def finished_loading(self, dt): self.manager.current = Screens.DEFAULT_SCREEN.value - # Called when we're waiting for the data to load - def wait_for_data_to_load(self): + def on_users_loaded(self, _, _2): + self.check_if_all_data_is_loaded() - all_data_loaded = True + def on_categories_loaded(self, _, _2): + self.check_if_all_data_is_loaded() - if not self.users_loaded: - all_data_loaded = False + def on_products_loaded(self, _, _2): + self.check_if_all_data_is_loaded() + + def check_if_all_data_is_loaded(self) -> None: + + self.set_loading_text("Waiting for data to load... (0/3)") + + if self.users_loaded is None or self.users_loaded.result is None: Logger.debug("StellaPayUI: Waiting for users to load..") + return + elif self.users_loaded.received_result is False or self.users_loaded.result.data is False: + self.set_loading_text("Failed to retrieve user data!") + self.on_failed_to_load() + return + elif self.users_loaded.result.data is True: + Logger.debug("StellaPayUI: Loaded user data!") - if not self.products_loaded: - all_data_loaded = False - Logger.debug("StellaPayUI: Waiting for products to load..") + self.set_loading_text("Waiting for data to load... (1/3)") - if not self.categories_loaded: - all_data_loaded = False + if self.categories_loaded is None or self.categories_loaded.result is None: Logger.debug("StellaPayUI: Waiting for categories to load..") + return + elif self.categories_loaded.received_result is False or self.categories_loaded.result.data is False: + self.set_loading_text("Failed to retrieve category data!") + self.on_failed_to_load() + return + elif self.categories_loaded.result.data is True: + Logger.debug("StellaPayUI: Loaded categories data!") + + self.set_loading_text("Waiting for data to load... (2/3)") + + if self.products_loaded is None or self.products_loaded.result is None: + Logger.debug("StellaPayUI: Waiting for products to load..") + return + elif self.products_loaded.received_result is False or self.products_loaded.result.data is False: + self.set_loading_text("Failed to retrieve products data!") + self.on_failed_to_load() + return + elif self.products_loaded.result.data is True: + Logger.debug("StellaPayUI: Loaded products data!") + + self.set_loading_text("Data has loaded!") + + self.set_loading_spinner(False) + + Clock.schedule_once(self.finished_loading, 1) + + def on_failed_to_load(self): + self.set_loading_spinner(False) + Clock.schedule_once(lambda: sys.exit(1), 3) - if all_data_loaded: - # After everything has been loaded. - self.ids.loading_text.text = "Data has loaded!" + def set_loading_spinner(self, active: bool): + self.ids.spinner_startup.active = active - # Done loading, so call callback in one second. - Clock.schedule_once(self.finished_loading, 1) - else: - # Run the check again - App.get_running_app().loop.call_later(0.5, self.wait_for_data_to_load) + def set_loading_text(self, text: str): + self.ids.loading_text.text = text diff --git a/utils/async_requests/AsyncData.py b/utils/async_requests/AsyncData.py new file mode 100644 index 0000000..4b96a29 --- /dev/null +++ b/utils/async_requests/AsyncData.py @@ -0,0 +1,6 @@ +from typing import Any + + +class AsyncData: + def __init__(self, data: Any): + self.data = data diff --git a/utils/async_requests/AsyncError.py b/utils/async_requests/AsyncError.py new file mode 100644 index 0000000..5a82522 --- /dev/null +++ b/utils/async_requests/AsyncError.py @@ -0,0 +1,3 @@ +class AsyncError: + def __init__(self, message: str): + self.message = message diff --git a/utils/async_requests/AsyncResult.py b/utils/async_requests/AsyncResult.py new file mode 100644 index 0000000..d027528 --- /dev/null +++ b/utils/async_requests/AsyncResult.py @@ -0,0 +1,16 @@ +from typing import Any, Union + +from utils.async_requests.AsyncData import AsyncData +from utils.async_requests.AsyncError import AsyncError + + +class AsyncResult: + """ + An object of this class represents a result that is received after some time. The result will indicate whether a + result was successfully obtained or not using the ``received_result`` member variable. If this variable is False, + you can expect ``result`` to be of type `AsyncError`. Otherwise, it will be of type `AsyncData`. + """ + + def __init__(self, success: bool, data: Any = None, error_message: str = ""): + self.received_result = success + self.result: Union[AsyncData, AsyncError, None] = AsyncData(data) if success else AsyncError(error_message)