Skip to content

Commit

Permalink
Merge pull request #37 from YvoElling/feature/rewrite-startup-procedure
Browse files Browse the repository at this point in the history
Rewrite startup screen to use event-driven updates instead of waiting…
  • Loading branch information
Staartvin authored Mar 16, 2023
2 parents 43d7aed + 43b13fc commit c360500
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 29 deletions.
19 changes: 16 additions & 3 deletions StellaPay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)

Expand Down
89 changes: 63 additions & 26 deletions scrs/StartupScreen.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions utils/async_requests/AsyncData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import Any


class AsyncData:
def __init__(self, data: Any):
self.data = data
3 changes: 3 additions & 0 deletions utils/async_requests/AsyncError.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class AsyncError:
def __init__(self, message: str):
self.message = message
16 changes: 16 additions & 0 deletions utils/async_requests/AsyncResult.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit c360500

Please sign in to comment.