base app with app-key, registration, login, logout

This commit is contained in:
2026-01-09 13:36:21 +01:00
parent e316001fa7
commit 362a83b2e3
13 changed files with 602 additions and 160 deletions
+52 -13
View File
@@ -1,40 +1,79 @@
import toga
from toga.style.pack import COLUMN, ROW, CENTER, START, END
from toga.style import Pack
import requests
from justgetit.constants import *
from justgetit.login import Login
from justgetit.register import Register
from justgetit.configuration import Configuration
from justgetit.providekey import ProvideKey
from justgetit.lists import Lists
class JustEatIt(toga.App):
login_screen = toga.Box()
register_screen = toga.Box()
providekey_screen = toga.Box()
lists_screen = toga.Box()
configuration = None
last_screen_width = SCREENWIDTH
def goto_next_page(self, widget):
def goto_next_page_by_name(self, destination):
"""
ask app to activate a next page, id of the page is in the id of the widget
:param widget: the widget that called this function
"""
print(f"action 6 {widget.id}")
if widget.id == 'register':
self.main_window.content = self.register_screen
elif widget.id =='login':
self.main_window.content = self.login_screen
if destination == PAGE_REGISTER:
self.set_page(self.register_screen)
elif destination == PAGE_LOGIN:
self.set_page(self.login_screen)
elif destination == PAGE_LISTS:
self.set_page(self.lists_screen)
def startup(self):
self.login_screen = Login(navigate_to=self.goto_next_page)
self.register_screen = Register(navigate_to=self.goto_next_page)
self.configuration = Configuration(self)
self.main_window = toga.MainWindow(title=self.formal_name, size=(SCREENWIDTH, SCREENHEIGHT))
self.main_window.content = self.register_screen
self.login_screen = Login()
self.register_screen = Register()
self.providekey_screen = ProvideKey()
self.lists_screen = Lists()
self.main_window = toga.MainWindow(title=self.formal_name)
# here comes the startup logic.
# check if there is a valid APP-KEY
# check if there is va alid user and user has been logged in recently
if self.configuration.has_valid_app_key():
if self.configuration.user_is_logged_in():
self.set_page(self.lists_screen)
else:
self.set_page(self.login_screen)
else:
self.set_page(self.providekey_screen)
self.main_window.show()
# Initial layout clamp
self.on_resize(self.main_window)
def set_page(self, page):
page.set_screen_width(self.last_screen_width)
self.main_window.content = page
def on_resize(self, widget, **kwargs):
width = self.main_window.size.width
if width < SCREENWIDTH:
# Phone-like: fill available width
elf.last_screen_width = width
else:
self.last_screen_width = SCREENWIDTH
# set actie screen to the corre ct width
self.main_window.content.set_screen_width(self.last_screen_width)
def on_exit(self):
self.configuration.save()
return super().on_exit()
def main():
return JustEatIt()
return JustEatIt(icon='images/logo.png')
+101
View File
@@ -0,0 +1,101 @@
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
# from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
import asyncio
from justgetit.constants import *
import httpx
class BasePage(toga.Box):
dialog_text = ''
first_menu_button_icon = ''
first_menu_button_callback = None
second_menu_button_icon = ''
second_menu_button_callback = None
page_pngimage = ''
def __init__(self, *args, **kwargs):
"""
This is a skeleton page with the basic setup for all pages:
header with button
some services
:param self: me
:param args: args for super
:param kwargs: kwars for super
"""
super().__init__(*args, **kwargs)
# menu bar. button(s) to be added in subclasses
menu_bar_inner = toga.Box(
children = [],
style=Pack(
background_color="#efefef",
justify_content=END,
flex=1),
)
if self.first_menu_button_callback and self.first_menu_button_icon:
first_button = toga.Button(
icon=toga.Icon(f"images/{self.first_menu_button_icon}"),
style=Pack(margin=5),
on_press=self.first_menu_button_callback)
menu_bar_inner.add(first_button)
if self.second_menu_button_callback and self.second_menu_button_icon:
second_button = toga.Button(
icon=toga.Icon(f"images/{self.second_menu_button_icon}"),
style=Pack(margin=5),
on_press=self.second_menu_button_callback)
menu_bar_inner.add(second_button)
menu_bar_outer = toga.Row(
children = [menu_bar_inner]
)
# line ast the bottom of the menubar
liner=toga.Box(style=Pack(flex=1, height = 2, background_color="#ddd"))
# create the main body
self.content_box = toga.Box(
style=Pack(
direction=COLUMN,
align_items=CENTER,
justify_content=END,
margin=24
)
)
# add image
if self.page_pngimage:
center_image = toga.Image(f"images/{self.page_pngimage}.png")
center_image_view = toga.ImageView(center_image, style=Pack(
width=50,
margin_bottom=20
))
self.content_box.add(center_image_view)
# wrapping up the page elements header liner and body
self.add(menu_bar_outer)
self.add(liner)
self.add(self.content_box)
self.style=Pack(
direction=COLUMN,
flex=1,
align_items=CENTER,
)
def set_screen_width(self, w):
"""
mainscreen is calling this for setting proper screen width, upon start and on-resize
:param self: this screen
:param w: width
"""
self.content_box.style.width = w
async def dialog(self, widget, **kwargs):
ask_a_question = toga.InfoDialog(
"Help",
"some text")
+79
View File
@@ -0,0 +1,79 @@
import shelve
import datetime
DBFILE = "config.shelve"
class Configuration():
"""
This class holds persistent configuration:
- appkey: the app key, alowing traffic with the backend
- last_key_validation_date: last date the appkey was validated, should be no more than a week ago otherwise the app will not run
- username: the user name
- userpwd: the users pwd
- userlang: the users language
- lastlogin_date: date of last sucessful login
"""
loaded = False
data = {}
def __init__(self, app):
self.app = app
self.load()
def load(self):
path = self.app.paths.config / DBFILE
if path.exists():
shelve_file = shelve.open(path)
self.data = shelve_file['data']
shelve_file.close()
print("shelve opened")
print(self.data)
def save(self):
shelve_file = shelve.open(self.app.paths.config / DBFILE)
shelve_file['data'] = self.data
shelve_file.close()
print("shelve stored")
def has_valid_app_key(self):
if 'appkey' in self.data:
print("found app key {self.data['appkey']}")
today = datetime.date.today()
last_month = today - datetime.timedelta(days=30)
if self.data['last_key_validation_date'] > last_month:
self.data['last_key_validation_date'] = today
return True
return False
def store_app_key(self, new_key):
self.data['appkey'] = new_key
self.data['last_key_validation_date'] = datetime.date.today()
self.save()
def user_is_logged_in(self):
if 'username' in self.data:
print("found user {self.data['username']}")
today = datetime.date.today()
last_week = today - datetime.timedelta(days=7)
if self.data['lastlogin_date'] > last_week:
self.update_last_activity_date()
return True
return False
def store_login(self, user, pwd):
self.data['username'] = user
self.data['userpwd'] = pwd
self.data['lastlogin_date'] = datetime.date.today()
self.save()
def update_last_activity_date(self):
today = datetime.date.today()
self.data['last_key_validation_date'] = today
self.data['lastlogin_date'] = today
def logout_user(self):
self.data.pop('username', None)
self.data.pop('userpwd', None)
self.data.pop('lastlogin_date', None)
self.save()
+16 -2
View File
@@ -1,2 +1,16 @@
SCREENWIDTH = 380
SCREENHEIGHT = 680
SCREENWIDTH = 400
# SCREENHEIGHT = 680
PAGE_LOGIN = 'login'
PAGE_REGISTER = 'register'
PAGE_LISTS = 'lists'
PAGE_EDITLISTS = 'editlists'
PAGE_ITEMS = 'items'
PAGE_ADDITEMS = 'additems'
HELP_ON_KEY = """
You should have received a application-key (a bunch of characters) from the person referring you to this app.
If not, sorry, but this is not a free app.
"""
API = "http://127.0.0.1:5001/api"
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

+89
View File
@@ -0,0 +1,89 @@
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
# from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
import asyncio
from justgetit.constants import *
import httpx
class Lists(toga.Box):
dialog_txt = ""
def __init__(self, *args, **kwargs):
"""
Docstring for __init__
:param self: register-screen
:param args: args for super
:param kwargs: kwars for super
"""
super().__init__(*args, **kwargs)
# menu bar with register button
register_button = toga.Button(
icon=toga.Icon("images/logout"),
id="login",
style=Pack(margin=5),
on_press=self.logout_button_pressed)
menu_bar_inner = toga.Box(
children = [register_button],
style=Pack(
background_color="#efefef",
justify_content=END,
flex=1),
)
menu_bar = toga.Row(
children = [menu_bar_inner]
)
# line ast the bottom of the menubar
liner=toga.Box(style=Pack(flex=1, height = 2, background_color="#ddd"))
# body of the screen with image 2 edit fielfds and ok button
# Title icon
my_image = toga.Image("images/key.png")
title = toga.ImageView(my_image, style=Pack(
width=50,
margin_bottom=20
))
# wrapping up the windows elements
self.content_box = toga.Box(
children=[
title,
# self.helpbox
],
style=Pack(
direction=COLUMN,
align_items=CENTER,
justify_content=END,
margin=24
)
)
# bringing the 3 parts together
self.add(menu_bar)
self.add(liner)
self.add(self.content_box)
self.style=Pack(
direction=COLUMN,
flex=1,
align_items=CENTER,
)
def set_screen_width(self, w):
"""
mainscreen is calling this for setting proper screen width, upon start and on-resize
:param self: this screen
:param w: width
"""
self.content_box.style.width = w
def logout_button_pressed(self, widget):
self.app.configuration.logout_user()
self.app.goto_next_page_by_name(PAGE_LOGIN)
+63 -71
View File
@@ -1,106 +1,98 @@
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER
from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
# from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
import asyncio
from justgetit.constants import *
import httpx
from justgetit.basepage import BasePage
class Login(toga.Box):
class Login(BasePage):
def __init__(self, *args, **kwargs):
"""
This page allows the user to login
Login details will be kept for a week after the user last uses this app
:param self: login-screen
:param args: args for super
:param kwargs: kwars for super
"""
# set button and image
self.first_menu_button_icon = 'register'
self.first_menu_button_callback = self.goto_registration
self.page_pngimage = 'login'
def __init__(self, navigate_to, *args, **kwargs):
super().__init__(*args, **kwargs)
self.navigate_to= navigate_to
register_button = toga.Button(
icon=toga.Icon("images/register"),
style=Pack(
width=50,
margin=5,
),
id="register",
on_press=self.navigate_to)
liner =toga.Box(style=Pack(width=SCREENWIDTH, height = 2, background_color="#ddd"))
menu_bar= toga.Row(
children=[register_button, liner],
style=Pack(
justify_content=END,
width = SCREENWIDTH,
background_color="#eee"
)
)
# Title icon
my_image = toga.Image("images/login.png")
title = toga.ImageView(my_image, style=Pack(
width=50,
margin_bottom=20
))
# Email input
name_input = toga.TextInput(
# Name input
self.name_input = toga.TextInput(
placeholder="Could be you here",
style=Pack(
width=250,
margin_bottom=10
)
)
# Password input
password_input = toga.PasswordInput(
self.password_input = toga.PasswordInput(
placeholder="Feeling lucky?",
style=Pack(
width=250,
margin_bottom=20
)
)
# Login button
login_button = toga.Button(
# Save button
save_button = toga.Button(
icon=toga.Icon("images/save"),
style=Pack(
width=50,
margin=10,
background_color="white"
),
on_press=self.login_button_press)
on_press=self.login_button_pressed)
login_area= toga.Box(
children=[login_button],
save_button_box= toga.Column(
children=[save_button],
style=Pack(
width=250,
direction=ROW,
flex=1,
align_items=START,
margin=20
)
)
# Main content box (centered)
content_box = toga.Column(
children=[
title,
name_input,
password_input,
login_area
],
style=Pack(
align_items=CENTER,
height=SCREENHEIGHT - 50, # iPhone height
margin=20,
)
)
# Outer box to simulate iPhone screen
screen = toga.Column(
children=[menu_bar,
liner,
content_box],
style=Pack(
width=SCREENWIDTH, # iPhone width
)
)
# wrapping up the windows elements
self.content_box.add(self.name_input)
self.content_box.add(self.password_input)
self.content_box.add(save_button_box)
self.add(screen)
async def login_button_pressed(self, widget):
self.result = 0
self.message = ''
user = self.name_input.value
pwd = self.password_input.value
def login_button_press(self, widget):
print("pressesd")
if len(user)>0 and len(pwd)>0:
data = {'user': user, 'password': pwd}
try:
async with httpx.AsyncClient() as client:
response = await client.post(API+'/login', data=data)
payload = response.json()
print(payload)
self.result = int(payload['status'])
if 'message' in payload:
self.message = payload['message']
except httpx.RequestError as exc:
self.result = -2
if self.result == 1:
# store login etc in config
self.app.configuration.store_login(user, pwd)
# goto lists screen
self.app.goto_next_page_by_name(PAGE_LISTS)
else:
await self.app.main_window.dialog(
toga.InfoDialog("Login error", self.message)
)
def goto_registration(self, dummy):
self.app.goto_next_page_by_name(PAGE_REGISTER)
+128
View File
@@ -0,0 +1,128 @@
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
# from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
import asyncio
from justgetit.constants import *
import httpx
from justgetit.basepage import BasePage
class ProvideKey(BasePage):
def __init__(self, *args, **kwargs):
"""
This page allows you to enter the aplication key
The aplication-key is kept until a month after the app is last used
:param self: register-screen
:param navigate_to: function to call if we want to switch screens
:param args: args for super
:param kwargs: kwars for super
"""
# set button and image
self.first_menu_button_icon = 'help'
self.first_menu_button_callback = self.help_dialog
self.page_pngimage = 'key'
super().__init__(*args, **kwargs)
# Key input
self.app_key = toga.TextInput(
placeholder="your application key goes here",
style=Pack(
width=250,
margin_bottom=10
)
)
# Save button
save_button = toga.Button(
icon=toga.Icon("images/save"),
style=Pack(
width=50,
margin=10,
),
on_press=self.save_button_pressed)
save_button_box= toga.Column(
children=[save_button],
style=Pack(
flex=1,
align_items=START,
margin=20
)
)
# # the help text box
# self.helptext = toga.Label(HELP_ON_KEY,
# style=Pack(margin=10,
# background_color="#eee",
# text_align=JUSTIFY)
# )
# self.helpbox_inner = toga.Box(
# children = [self.helptext],
# style=Pack(
# direction=COLUMN,
# justify_content=CENTER,
# margin=2,
# background_color="#eee"
# )
# )
# self.helpbox = toga.Box(
# children = [self.helpbox_inner],
# style=Pack(
# direction=COLUMN,
# justify_content=CENTER,
# margin=24,
# background_color="#ccc",
# visibility=HIDDEN
# )
# )
# wrapping up the windows elements
self.content_box.add(self.app_key)
self.content_box.add(save_button_box)
async def save_button_pressed(self, widget):
self.result = 0
print(self.app_key.value)
if len(self.app_key.value)>1:
data = {'key': self.app_key.value}
try:
async with httpx.AsyncClient() as client:
response = await client.post(API+'/validate_key', data=data)
payload = response.json()
print(payload)
self.result = int(payload['status'])
except httpx.RequestError as exc:
self.result = -2
self.dialog_txt = "Key unknowm. \nTry again."
if self.result == 1:
# success
self.dialog_txt = "Key validated. \nProceed to login or registration."
await self.app.main_window.dialog(
toga.InfoDialog("Key Validation", self.dialog_txt)
)
self.app.configuration.store_app_key(self.app_key.value)
# goto login screen
self.app.goto_next_page_by_name(PAGE_LOGIN)
else:
await self.app.main_window.dialog(
toga.InfoDialog("Key Validation", "Key unknowm. \nTry again.")
)
async def help_dialog(self, widget, **kwargs):
ask_a_question = toga.InfoDialog(
"Help",
HELP_ON_KEY)
if await self.app.main_window.dialog(ask_a_question):
print("The user said yes!")
else:
print("The user said no.")
+72 -73
View File
@@ -1,107 +1,106 @@
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER
from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
# from justgetit.constants import SCREENHEIGHT, SCREENWIDTH
import asyncio
from justgetit.constants import *
import httpx
from justgetit.basepage import BasePage
class Register(toga.Box):
class Register(BasePage):
def __init__(self, *args, **kwargs):
"""
This page allows a new user to register
:param self: registratioin-page
:param args: args for super
:param kwargs: kwars for super
"""
# set button and image
self.first_menu_button_icon = 'login'
self.first_menu_button_callback = self.goto_login
self.page_pngimage = 'register'
def __init__(self, navigate_to, *args, **kwargs):
super().__init__(*args, **kwargs)
self.navigate_to= navigate_to
register_button = toga.Button(
icon=toga.Icon("images/login"),
style=Pack(
width=50,
margin=5,
),
id="login",
on_press=self.navigate_to)
liner = toga.Box(style=Pack(width=SCREENWIDTH, height = 2, background_color="#ddd"))
menu_bar= toga.Row(
children=[register_button, liner],
style=Pack(
justify_content=END,
width = SCREENWIDTH,
background_color="#eee"
)
)
# Title icon
my_image = toga.Image("images/register.png")
title = toga.ImageView(my_image, style=Pack(
width=50,
margin_bottom=20
))
# Email input
name_input = toga.TextInput(
placeholder="Gimme you wallet",
# Name input
self.name_input = toga.TextInput(
placeholder="Could be you here",
style=Pack(
width=250,
margin_bottom=10
)
)
# Password input
password_input = toga.PasswordInput(
placeholder="You have a secret?",
self.password_input = toga.PasswordInput(
placeholder="Feeling lucky?",
style=Pack(
width=250,
margin_bottom=20
)
)
# Login button
login_button = toga.Button(
# Save button
save_button = toga.Button(
icon=toga.Icon("images/save"),
style=Pack(
width=50,
margin=10,
background_color="white"
),
on_press=self.register_button_press)
on_press=self.register_button_pressed)
login_area= toga.Box(
children=[login_button],
save_button_box= toga.Column(
children=[save_button],
style=Pack(
width=250,
direction=ROW,
flex=1,
align_items=START,
margin=20
)
)
# Main content box (centered)
content_box = toga.Column(
children=[
title,
name_input,
password_input,
login_area
],
style=Pack(
align_items=CENTER,
height=SCREENHEIGHT - 50, # iPhone height
margin=20,
)
)
# Outer box to simulate iPhone screen
screen = toga.Column(
children=[menu_bar,
liner,
content_box],
style=Pack(
width=SCREENWIDTH, # iPhone width
)
)
# wrapping up the windows elements
self.content_box.add(self.name_input)
self.content_box.add(self.password_input)
self.content_box.add(save_button_box)
self.add(screen)
def register_button_press(self, widget):
print("presses register")
async def register_button_pressed(self, widget):
self.result = 0
self.message = ''
user = self.name_input.value
pwd = self.password_input.value
pwd2 = self.password_verify_input.value
if len(user)>0 and len(pwd)>0 and len(pwd2)>0:
if pwd==pwd2:
data = {'user': user, 'password': pwd}
try:
async with httpx.AsyncClient() as client:
response = await client.post(API+'/register', data=data)
payload = response.json()
print(payload)
self.result = int(payload['status'])
if 'message' in payload:
self.message = payload['message']
except httpx.RequestError as exc:
self.result = -2
if self.result == 1:
# store login etc in config
self.app.configuration.store_login(user, pwd)
# goto lists screen
self.app.goto_next_page_by_name(PAGE_LOGIN)
else:
await self.app.main_window.dialog(
toga.InfoDialog("Registration error", self.message)
)
else:
await self.app.main_window.dialog(
toga.InfoDialog("Error", "Passwords do not match")
)
def goto_login(self, dummy):
self.app.goto_next_page_by_name(PAGE_LOGIN)