diff --git a/control/__init__.py b/control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/control/app.py b/control/app.py index 5a05d64..c140444 100644 --- a/control/app.py +++ b/control/app.py @@ -1,262 +1,249 @@ -from flask import Flask, render_template, request, make_response, redirect; -from user import User; -import sqlite3; -from time import strftime; -import hashlib; +"""App -app = Flask( __name__ ); -app.debug = True; +Handles the I/O and database queries +""" -user = User(); +from hashlib import md5 +from sqlite3 import connect, Connection, Cursor +from time import strftime +from typing import Optional -def getConnection(): - connection = sqlite3.connect( 'data.db' ); - cursor = connection.cursor(); - - return ( connection, cursor ) +from flask import Flask, render_template, request, make_response, redirect, Response -# ... # +from control.user import User -def isInstalled(): - ( connection, cursor ) = getConnection(); - - cursor.execute( "SELECT COUNT( 1 ) FROM USERS" ); - hasUser = cursor.fetchone()[ 0 ]; +app = Flask(__name__) - return ( hasUser > 0 ); -def addRequest( reqType ): - ( connection, cursor ) = getConnection(); - - creationDate = strftime( '%d-%m-%Y %H:%M:%S' ); +TASK_ID_IS_NONE_ERROR = ( + "Error: No task associated with that ID. Maybe someone else already deleted it?" +) - cursor.execute( "INSERT INTO UPDATE_REQUESTS( UPDATE_TYPE, DONE, CREATION_DATE ) VALUES ( ?, 'N', ? )", [ reqType, creationDate ] ); - connection.commit(); +def get_connection() -> tuple[Connection, Cursor]: + """Returns the SQLite""" + connection = connect("data.db") + cursor = connection.cursor() + return connection, cursor -@app.before_request -def beforeRequest(): - if request.path == '/install': - return None; - - if not isInstalled(): - return render_template( 'install_prettypi.html' ); - - if request.path == '/login': - return None; - - if request.cookies.get( 'prettypi_username' ) != None: - user.setUsername( request.cookies.get( 'prettypi_username' ) ); - user.setHashedPassword( request.cookies.get( 'prettypi_password' ) ); - - if not user.hasPermission(): - return render_template( 'login.html' ); - else: - return None; - - return render_template( 'login.html' ); - -# ... # - -@app.route( '/' ) -def main(): - return render_template( 'main_page.html', name = user.getName() ); - -# ... # - -@app.route( '/install', methods = [ 'POST' ] ) -def install(): - ( connection, cursor ) = getConnection(); - - if isInstalled(): - return render_template( 'installer_message.html', message = 'PrettyPi Already Installed', type = 'danger' ); - - if not request.form[ 'username' ] or not request.form[ 'password' ] or not request.form[ 'name' ]: - return render_template( 'installer_message.html', message = 'Please fill all fields', type = 'danger' ); - - hashFunction = hashlib.md5( request.form[ 'password' ].encode( 'utf-8' ) ); - - hashedPassword = hashFunction.hexdigest(); - - cursor.execute( "INSERT INTO USERS( USER_ID, USERNAME, PASSWORD, NAME ) VALUES ( NULL, ?, ?, ? )", [ request.form[ 'username' ], hashedPassword, request.form[ 'name' ] ] ); - - connection.commit(); - - return render_template( 'installer_message.html', message = 'Congratulations! PrettyPi has been initialized. Go back to homepage to start using it', type = 'success' ); - -# ... # - -@app.route( '/login', methods = [ 'POST' ] ) -def login(): - user.setUsername( request.form[ 'username' ] ); - user.setPassword( request.form[ 'password' ].encode( 'utf-8' ) ); - - if user.hasPermission(): - response = make_response( redirect( '/' ) ); - response.set_cookie( 'prettypi_username', user.getUsername() ); - response.set_cookie( 'prettypi_password', user.getHashedPassword() ); - - return response; - else: - return 'Invalid'; - -# ... # - -@app.route( '/logout' ) -def logout(): - response = make_response( redirect( '/' ) ); - response.set_cookie( 'prettypi_username', '' ); - response.set_cookie( 'prettypi_password', '' ); - - return response; - -# ... # - -@app.route( '/tasks' ) -def tasksMain(): - ( connection, cursor ) = getConnection(); - - cursor.execute( "SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC" ); - - tasks = cursor.fetchall(); - - # ... # - - cursor.execute( "SELECT * FROM TODO WHERE DONE = 'Y' ORDER BY CREATION_DATE DESC" ); - - doneTasks = cursor.fetchall(); - - # ... # - - cursor.execute( "SELECT * FROM TODO WHERE WORKING_ON = 'Y'" ); - - workingTask = cursor.fetchone(); - - print( workingTask ); - - # ... # - - return render_template( 'tasks.html', tasks = tasks, doneTasks = doneTasks, name = user.getName(), workingTask = workingTask ); - -# ... # - -@app.route( '/add_task', methods = [ 'POST' ] ) -def newTask(): - ( connection, cursor ) = getConnection(); - - if not request.form[ 'task_details' ]: - return render_template( 'message.html', message = 'The details should not be empty', type = 'warning' ); - - creationDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "INSERT INTO TODO( TASK_ID, TASK, CREATION_DATE ) VALUES ( NULL, ?, ? )", [ request.form[ 'task_details' ], creationDate ] ); - connection.commit(); +def is_installed() -> bool: + """Returns True if a user already exists""" + _, cursor = get_connection() + cursor.execute("SELECT COUNT( 1 ) FROM USERS") + has_user = cursor.fetchone()[0] + return has_user > 0 - addRequest( "UPDATE_TODO_LIST" ); - return redirect( 'tasks' ); +def add_request(request_type: str) -> None: + """Adds a task to the DB""" + connection, cursor = get_connection() + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "INSERT INTO UPDATE_REQUESTS( UPDATE_TYPE, DONE, CREATION_DATE ) VALUES" + " ( ?, 'N', ? )", + (request_type, creation_date), + ) + connection.commit() -# ... # -@app.route( '/delete_task', methods = [ 'GET' ] ) -def deleteTask(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - cursor.execute( "DELETE FROM TODO WHERE TASK_ID = ?", [ taskId ] ); - cursor.execute( "DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [ taskId ] ); - - connection.commit(); - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -@app.route( '/task_done', methods = [ 'GET' ] ) -def markTaskAsDone(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - currentDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", [ currentDate, taskId ] ); - - connection.commit(); - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -@app.route( '/start_task', methods = [ 'GET' ] ) -def startWorkingOnTask(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - # ... # - - cursor.execute( "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", [ taskId ] ); - - currentlyWorkingOn = cursor.fetchone()[ 0 ]; - - if currentlyWorkingOn > 0: - return render_template( 'message.html', message = "You're already working on another task!", type = 'warning' ); - - # ... # - - creationDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?", [ taskId ] ); - cursor.execute( "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", [ taskId, creationDate ] ); - - connection.commit(); - - # ... # - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -# ... # - -@app.route( '/stop_task', methods = [ 'GET' ] ) -def stopWorkingOnTask(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - # ... # - - cursor.execute( "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", [ taskId ] ); - - currTaskLogId = cursor.fetchone()[ 0 ]; - - # ... # - - currentDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "UPDATE TODO SET WORKING_ON = 'N' WHERE TASK_ID = ?", [ taskId ] ); - cursor.execute( "UPDATE TASKS_LOG SET ENDED_AT = ? WHERE LOG_ID = ?", [ currentDate, currTaskLogId ] ); - - connection.commit(); - - # ... # - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); +@app.before_request +def before_request() -> Optional[str]: + """Determines which page to show the user based on install and login status""" + if request.path == "/install": + return None + if not is_installed(): + return render_template("install_prettypi.html") + if request.path == "/login": + return None + if request.cookies.get("prettypi_username") is not None: + User.set_username(request.cookies.get("prettypi_username")) + User.set_hashed_password(request.cookies.get("prettypi_password")) + return None if User.has_permission() else render_template("login.html") + return render_template("login.html") + + +@app.route("/") +def main() -> str: + """This is the landing page""" + return render_template("main_page.html", name=User.get_name()) + + +@app.route("/install", methods=["POST"]) +def install() -> str: + """Initializes the PrettyPi site for the network""" + connection, cursor = get_connection() + if is_installed(): + return render_template( + "installer_message.html", + message="PrettyPi Already Installed", + type="danger", + ) + if ( + not request.form["username"] + or not request.form["password"] + or not request.form["name"] + ): + return render_template( + "installer_message.html", message="Please fill all fields", type="danger" + ) + hash_function = md5(request.form["password"].encode("utf-8")) + hashed_password = hash_function.hexdigest() + cursor.execute( + "INSERT INTO USERS( USER_ID, USERNAME, PASSWORD, NAME ) VALUES ( NULL, ?, ?, ? )", + [request.form["username"], hashed_password, request.form["name"]], + ) + connection.commit() + return render_template( + "installer_message.html", + message="Congratulations! PrettyPi has been installed. " + "Go back to homepage to start using it", + type="success", + ) + + +@app.route("/login", methods=["POST"]) +def login() -> Response | str: + """Determines if the login information is valid""" + User.set_username(request.form["username"]) + User.set_password(request.form["password"].encode("utf-8")) + if User.has_permission(): + response = make_response(redirect("/")) + response.set_cookie("prettypi_username", User.get_username()) + response.set_cookie("prettypi_password", User.get_hashed_password()) + return response + return f'Invalid username ("{User.get_username()}") or password' + + +@app.route("/logout") +def logout() -> Response: + """Logs the current user out""" + response = make_response(redirect("/")) + response.set_cookie("prettypi_username", "") + response.set_cookie("prettypi_password", "") + return response + + +@app.route("/tasks") +def tasks_main(): + """Retrieves the completed, running, and done tasks from the DB to display""" + _, cursor = get_connection() + cursor.execute("SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC") + tasks = cursor.fetchall() + cursor.execute("SELECT * FROM TODO WHERE DONE = 'Y' ORDER BY CREATION_DATE DESC") + done_tasks = cursor.fetchall() + cursor.execute("SELECT * FROM TODO WHERE WORKING_ON = 'Y'") + working_task = cursor.fetchone() + return render_template( + "tasks.html", + tasks=tasks, + done_tasks=done_tasks, + name=User.get_name(), + working_task=working_task, + ) + + +@app.route("/add_task", methods=["POST"]) +def new_task(): + """Adds a new task to the DB""" + connection, cursor = get_connection() + if not request.form["task_details"]: + return render_template( + "message.html", + message="The details should not be empty.", + type="warning" + ) + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "INSERT INTO TODO( TASK_ID, TASK, CREATION_DATE ) VALUES ( NULL, ?, ? )", + [request.form["task_details"], creation_date], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/delete_task", methods=["GET"]) +def delete_task() -> Response | str: + """Deletes""" + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return TASK_ID_IS_NONE_ERROR + cursor.execute("DELETE FROM TODO WHERE TASK_ID = ?", [task_id]) + cursor.execute("DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [task_id]) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/task_done", methods=["GET"]) +def mark_task_as_done() -> Response | str: + """Move a task to the "Done" table""" + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return TASK_ID_IS_NONE_ERROR + current_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", + [current_date, task_id], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/start_task", methods=["GET"]) +def start_working_on_task() -> Response | str: + """Create the banner to indicate a task is currently pending""" + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return TASK_ID_IS_NONE_ERROR + cursor.execute( + "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", [task_id] + ) + currently_working_on = cursor.fetchone()[0] + if currently_working_on > 0: + return render_template( + "message.html", + message="You're already working on another task!", + type="warning", + ) + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute("UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?", [task_id]) + cursor.execute( + "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", + [task_id, creation_date], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/stop_task", methods=["GET"]) +def stop_working_on_task() -> Response | str: + """Remove a pending task from the banner""" + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return TASK_ID_IS_NONE_ERROR + cursor.execute( + "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", + [task_id], + ) + current_task_log_id = cursor.fetchone()[0] + current_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute("UPDATE TODO SET WORKING_ON = 'N' WHERE TASK_ID = ?", [task_id]) + cursor.execute( + "UPDATE TASKS_LOG SET ENDED_AT = ? WHERE LOG_ID = ?", + [current_date, current_task_log_id], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +if __name__ == "__main__": + app.debug = True diff --git a/control/templates/install_prettypi.html b/control/templates/install_prettypi.html index bde2e83..deff7b4 100644 --- a/control/templates/install_prettypi.html +++ b/control/templates/install_prettypi.html @@ -6,16 +6,11 @@ PrettyPi | Installation - - - - - - - - - - + + + + + @@ -24,10 +19,22 @@ +
@@ -41,7 +48,7 @@
- +
diff --git a/control/templates/installer_message.html b/control/templates/installer_message.html index 862d9c9..2150f7b 100644 --- a/control/templates/installer_message.html +++ b/control/templates/installer_message.html @@ -7,15 +7,11 @@ - - - - - - - - - + + + + + @@ -29,6 +25,8 @@

Alert!

{{ message }} +

+ Return
diff --git a/control/templates/login.html b/control/templates/login.html index 91ebd8b..5aca886 100644 --- a/control/templates/login.html +++ b/control/templates/login.html @@ -1,70 +1,49 @@ - + - - + + PrettyPi | Log in - - - - - - - - - - - - - - - - + + + + + + -
- - - {% endif %} diff --git a/control/templates/tasks.html b/control/templates/tasks.html index a5f63c6..6953a41 100644 --- a/control/templates/tasks.html +++ b/control/templates/tasks.html @@ -17,13 +17,13 @@

- {% if workingTask != None %} + {% if working_task != None %}
Working On - {{ workingTask.1 }} + {{ working_task.1 }}
{% endif %} @@ -47,66 +47,65 @@

New Task

-
+
-

Done!

+

TODO

+ + + - - - {% for doneTask in doneTasks %} + {% for task in tasks %} - - - - + + {% if task.5 == 'N' %} + + {% else %} + + {% endif %} + + + {% endfor %}
# TaskStartDoneDelete Created OnDone AtDelete
{{ loop.index }}.{{ doneTask.1 }}{{ doneTask.2 }}{{ doneTask.4 }}Delete{{ task.1 }}StartStopDoneDelete{{ task.2 }}
-
-
-
+
-

TODO

+

Done!

-
+
- + - - - + + - {% for task in tasks %} + {% for done_task in done_tasks %} - - {% if task.5 == 'N' %} - - {% else %} - - {% endif %} - - - + + + + {% endfor %}
# TaskStartDoneDelete Created OnDone AtDelete
{{ loop.index }}.{{ task.1 }}StartStopDoneDelete{{ task.2 }}{{ done_task.1 }}{{ done_task.2 }}{{ done_task.4 }}Delete
+
diff --git a/control/user.py b/control/user.py index bcdfb62..54e0596 100644 --- a/control/user.py +++ b/control/user.py @@ -1,50 +1,67 @@ -import sqlite3; -import hashlib; +"""User -class User: - __username = None; - __password = None; - __name = None; - __cursor = None; - - def getConnection( self ): - connection = sqlite3.connect( 'data.db' ); - cursor = connection.cursor(); - - return ( connection, cursor ) - - def hasPermission( self ): - ( connection, cursor ) = self.getConnection(); - - print ( self.__username, self.__password ); +Handles the user login credentials""" - cursor.execute( 'SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?', ( self.__username, self.__password ) ); +from hashlib import md5 +from sqlite3 import Connection, Cursor, connect +from typing import Optional - result = cursor.fetchall(); - if len( result ) > 0: - self.__name = result[ 0 ][ 3 ]; +class User: + """Stores the username, password, and name in memory""" - return True; + __name = Optional[str] + __username = Optional[str] + __password = Optional[str] - return False; + @classmethod + def get_connection(cls) -> tuple[Connection, Cursor]: + """Returns the connection and cursor from the DB""" + connection = connect("data.db") + cursor = connection.cursor() + return connection, cursor - def setUsername( self, username ): - self.__username = username; + @classmethod + def has_permission(cls) -> bool: + """Returns True if the user has permission to access Tasks""" + _, cursor = cls.get_connection() + cursor.execute( + "SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?", + (cls.__username, cls.__password), + ) + result = cursor.fetchall() + if len(result) > 0: + cls.__name = result[0][3] + return True + return False - def setPassword( self, password ): - hashFunction = hashlib.md5( password ); + @classmethod + def set_username(cls, username: str) -> None: + """Logs the current username""" + cls.__username = username - self.__password = hashFunction.hexdigest(); + @classmethod + def set_password(cls, password: str) -> None: + """Logs the current password""" + hash_function = md5(password) + cls.__password = hash_function.hexdigest() - def setHashedPassword( self, hashedPassword ): - self.__password = hashedPassword; + @classmethod + def set_hashed_password(cls, hashed_password: str) -> None: + """Sets the password hash""" + cls.__password = hashed_password - def getUsername( self ): - return self.__username; + @classmethod + def get_username(cls) -> str: + """Returns the current logged in user""" + return cls.__username - def getHashedPassword( self ): - return self.__password; + @classmethod + def get_hashed_password(cls) -> str: + """Returns the password hash""" + return cls.__password - def getName( self ): - return self.__name; + @classmethod + def get_name(cls) -> str: + """Returns the name of the logged in user""" + return cls.__name diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3a1c340 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,34 @@ +arabic-reshaper==3.0.0 +astroid==3.1.0 +black==24.4.2 +blinker==1.8.1 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +dill==0.3.8 +docutils==0.21.2 +Flask==3.0.3 +idna==3.7 +isort==5.13.2 +itsdangerous==2.2.0 +Jinja2==3.1.3 +Kivy==2.3.0 +Kivy-Garden==0.1.5 +kivymd==1.2.0 +MarkupSafe==2.1.5 +mccabe==0.7.0 +mypy==1.10.0 +mypy-extensions==1.0.0 +packaging==24.0 +pathspec==0.12.1 +pillow==10.3.0 +platformdirs==4.2.1 +Pygments==2.18.0 +pylint==3.1.0 +python-bidi==0.4.2 +requests==2.31.0 +six==1.16.0 +tomlkit==0.12.4 +typing_extensions==4.11.0 +urllib3==2.2.1 +Werkzeug==3.0.2 diff --git a/run_web.sh b/run_web.sh index 2c3418c..dc7ebb5 100755 --- a/run_web.sh +++ b/run_web.sh @@ -1,3 +1,3 @@ export FLASK_APP=control/app.py export FLASK_DEBUG=1 -flask run --host=0.0.0.0 +flask run --host=0.0.0.0 --port=5000 diff --git a/server/ArabicLabel.py b/server/ArabicLabel.py deleted file mode 100644 index 53b80b3..0000000 --- a/server/ArabicLabel.py +++ /dev/null @@ -1,16 +0,0 @@ -from kivymd.label import MDLabel -import arabic_reshaper -from bidi.algorithm import get_display - -class ArabicLabel( MDLabel ): - #def __init__( self, **kwargs ): - # super( ArabicLabel, self ).__init__( **kwargs ); - - #self.on_font_style( None, self.font_style ) - - # Overridden - def on_font_style( self, instance, style ): - super( ArabicLabel, self ).on_font_style( instance, style ); - - self.text = get_display( arabic_reshaper.reshape( self.text ) ); - self.font_name = 'fonts/KacstPenE'; diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/arabic_label.py b/server/arabic_label.py new file mode 100644 index 0000000..dcca158 --- /dev/null +++ b/server/arabic_label.py @@ -0,0 +1,26 @@ +"""Arabic Label + +Support for Arabic script +""" + +from typing import Any + +from kivymd.uix.label import MDLabel +from arabic_reshaper import reshape +from bidi.algorithm import get_display + + +# Don't really have control over the ancestors--ignoring +# pylint: disable = too-many-ancestors, too-few-public-methods +class ArabicLabel(MDLabel): + """Superclasses MDLabel to attach a new style""" + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.text = "" + self.font_name = "fonts/KacstPenE" + + def update_font_style(self, instance_label: Any, font_style: str) -> None: + """Updates the font style with a differnet default font""" + super().update_font_style(instance_label, font_style) + self.text = get_display(reshape(self.text)) diff --git a/server/main.py b/server/main.py index 76375c1..4255e20 100644 --- a/server/main.py +++ b/server/main.py @@ -1,26 +1,24 @@ +"""Main + +Builds the touchscreen portion of the PrettyPi app +""" + #!/usr/bin/python # -*- coding: utf8 -*- # [MQH] 16 June 2017. It has been a while since last code I have written in Python! :-) +from sqlite3 import connect +from sys import exit as sys_exit + from kivy.app import App +from kivy.clock import Clock from kivy.lang import Builder from kivymd.theming import ThemeManager -from kivy.properties import ObjectProperty -from kivymd.label import MDLabel -from kivy.animation import Animation -from ArabicLabel import ArabicLabel -import arabic_reshaper -import sqlite3 -import threading; -from kivy.clock import Clock -class PrettyPiApp( App ): - theme_cls = ThemeManager(); - mainBox = None; - connection = None; - cursor = None; +from server.arabic_label import ArabicLabel + - kvMain = ''' +KV_CONFIG = """ #:import Toolbar kivymd.toolbar.Toolbar #:import NavigationLayout kivymd.navigationdrawer.NavigationLayout #:import MDNavigationDrawer kivymd.navigationdrawer.MDNavigationDrawer @@ -33,7 +31,7 @@ class PrettyPiApp( App ): NavigationDrawerToolbar: title: "Navigation Drawer" NavigationDrawerIconButton: - id: quitBtn + id: quit_button icon: 'checkbox-blank-circle' text: "Quit" @@ -63,57 +61,64 @@ class PrettyPiApp( App ): name: 'mainScreen' BoxLayout: - id: mainBox + id: main_box orientation: 'vertical' - '''; - - def build( self ): - mainWidget = Builder.load_string( self.kvMain ); - - self.connection = sqlite3.connect( 'data.db' ); - self.cursor = self.connection.cursor(); - self.mainBox = mainWidget.ids.mainBox; - self.quitBtn = mainWidget.ids.quitBtn; - - self.quitBtn.bind( on_press = lambda e: exit() ); - - # ... # - - self.refreshList(); - - # .. # - - Clock.schedule_interval( self.checkUpdates, 0.5 ); - - # ... # - - return mainWidget; - - def refreshList( self ): - self.mainBox.clear_widgets(); - - self.cursor.execute( "SELECT * FROM TODO WHERE DONE = 'N'" ); - tasks = self.cursor.fetchall(); - - for task in tasks: - taskText = task[ 1 ]; - - if task[ 5 ] == 'Y': - taskText += " (Working On)"; - - self.mainBox.add_widget( ArabicLabel( text = taskText, halign = 'center', font_style = 'Display1' ) ); - - def checkUpdates( self, dt ): - self.cursor.execute( "SELECT COUNT( 1 ) FROM UPDATE_REQUESTS WHERE UPDATE_TYPE = 'UPDATE_TODO_LIST' AND DONE = 'N'" ); - - result = self.cursor.fetchone(); - - if result[ 0 ] > 0: - self.refreshList(); - - self.cursor.execute( "UPDATE UPDATE_REQUESTS SET DONE = 'Y' WHERE UPDATE_TYPE = 'UPDATE_TODO_LIST'" ); - self.connection.commit(); - - -if __name__ == '__main__': - PrettyPiApp().run(); + """ + + +class PrettyPiApp(App): + """Builds the PrettyPi interface for touchscreen""" + + theme_cls = ThemeManager() + main_box = None + connection = None + cursor = None + kv_main = KV_CONFIG + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.quit_button = None + + def build(self): + """Builds the Kivy UI""" + main_widget = Builder.load_string(self.kv_main) + self.connection = connect("data.db") + self.cursor = self.connection.cursor() + self.main_box = main_widget.ids.mainBox + self.quit_button = main_widget.ids.quit_button + self.quit_button.bind(on_press=lambda e: sys_exit()) + self.refresh_list() + Clock.schedule_interval(self.check_updates, 0.5) + return main_widget + + def refresh_list(self) -> None: + """Updates the TODO list with new items""" + self.main_box.clear_widgets() + self.cursor.execute("SELECT * FROM TODO WHERE DONE = 'N'") + tasks = self.cursor.fetchall() + for task in tasks: + task_text = task[1] + if task[5] == "Y": + task_text += " (Working On)" + self.main_box.add_widget( + ArabicLabel(text=task_text, halign="center", font_style="Display1") + ) + + def check_updates(self) -> None: + """Adds results to the TODO list if found""" + self.cursor.execute( + "SELECT COUNT( 1 ) FROM UPDATE_REQUESTS WHERE UPDATE_TYPE = " + "'UPDATE_TODO_LIST' AND DONE = 'N'" + ) + result = self.cursor.fetchone() + if result[0] > 0: + self.refresh_list() + self.cursor.execute( + "UPDATE UPDATE_REQUESTS SET DONE = 'Y' WHERE UPDATE_TYPE = " + "'UPDATE_TODO_LIST'" + ) + self.connection.commit() + + +if __name__ == "__main__": + PrettyPiApp().run()