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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

+2 -1
View File
@@ -11,6 +11,7 @@ author_email = "ignace@suy.nl"
[tool.briefcase.app.justgetit] [tool.briefcase.app.justgetit]
formal_name = "Just Get It" formal_name = "Just Get It"
description = "Just Get It App" description = "Just Get It App"
icon = "icons/logo"
long_description = """More details about the app should go here. long_description = """More details about the app should go here.
""" """
sources = [ sources = [
@@ -22,7 +23,7 @@ test_sources = [
] ]
requires = [ requires = [
"requests", "httpx",
] ]
test_requires = [ test_requires = [
"pytest", "pytest",
+52 -13
View File
@@ -1,40 +1,79 @@
import toga import toga
from toga.style.pack import COLUMN, ROW, CENTER, START, END from toga.style.pack import COLUMN, ROW, CENTER, START, END
from toga.style import Pack from toga.style import Pack
import requests
from justgetit.constants import * from justgetit.constants import *
from justgetit.login import Login from justgetit.login import Login
from justgetit.register import Register from justgetit.register import Register
from justgetit.configuration import Configuration
from justgetit.providekey import ProvideKey
from justgetit.lists import Lists
class JustEatIt(toga.App): class JustEatIt(toga.App):
login_screen = toga.Box() login_screen = toga.Box()
register_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 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 :param widget: the widget that called this function
""" """
print(f"action 6 {widget.id}") if destination == PAGE_REGISTER:
if widget.id == 'register': self.set_page(self.register_screen)
self.main_window.content = self.register_screen elif destination == PAGE_LOGIN:
elif widget.id =='login': self.set_page(self.login_screen)
self.main_window.content = self.login_screen elif destination == PAGE_LISTS:
self.set_page(self.lists_screen)
def startup(self): def startup(self):
self.login_screen = Login(navigate_to=self.goto_next_page) self.configuration = Configuration(self)
self.register_screen = Register(navigate_to=self.goto_next_page)
self.main_window = toga.MainWindow(title=self.formal_name, size=(SCREENWIDTH, SCREENHEIGHT)) self.login_screen = Login()
self.main_window.content = self.register_screen 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() 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(): 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 SCREENWIDTH = 400
SCREENHEIGHT = 680 # 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 import toga
from toga.style import Pack from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
from justgetit.constants import SCREENHEIGHT, SCREENWIDTH # 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) super().__init__(*args, **kwargs)
self.navigate_to= navigate_to
register_button = toga.Button( # Name input
icon=toga.Icon("images/register"), self.name_input = toga.TextInput(
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(
placeholder="Could be you here", placeholder="Could be you here",
style=Pack( style=Pack(
width=250, width=250,
margin_bottom=10 margin_bottom=10
) )
) )
# Password input # Password input
password_input = toga.PasswordInput( self.password_input = toga.PasswordInput(
placeholder="Feeling lucky?", placeholder="Feeling lucky?",
style=Pack( style=Pack(
width=250, width=250,
margin_bottom=20 margin_bottom=20
) )
) )
# Save button
# Login button save_button = toga.Button(
login_button = toga.Button(
icon=toga.Icon("images/save"), icon=toga.Icon("images/save"),
style=Pack( style=Pack(
width=50, width=50,
margin=10, margin=10,
background_color="white"
), ),
on_press=self.login_button_press) on_press=self.login_button_pressed)
login_area= toga.Box( save_button_box= toga.Column(
children=[login_button], children=[save_button],
style=Pack( style=Pack(
width=250, flex=1,
direction=ROW,
align_items=START, align_items=START,
margin=20 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 # wrapping up the windows elements
screen = toga.Column( self.content_box.add(self.name_input)
children=[menu_bar, self.content_box.add(self.password_input)
liner, self.content_box.add(save_button_box)
content_box],
style=Pack(
width=SCREENWIDTH, # iPhone width
)
)
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): if len(user)>0 and len(pwd)>0:
print("pressesd") 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 import toga
from toga.style import Pack from toga.style import Pack
from toga.style.pack import COLUMN, ROW, END, START, CENTER from toga.style.pack import COLUMN, ROW, END, START, CENTER, HIDDEN, JUSTIFY
from justgetit.constants import SCREENHEIGHT, SCREENWIDTH # 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) 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")) # Name input
self.name_input = toga.TextInput(
menu_bar= toga.Row( placeholder="Could be you here",
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",
style=Pack( style=Pack(
width=250, width=250,
margin_bottom=10 margin_bottom=10
) )
) )
# Password input # Password input
password_input = toga.PasswordInput( self.password_input = toga.PasswordInput(
placeholder="You have a secret?", placeholder="Feeling lucky?",
style=Pack( style=Pack(
width=250, width=250,
margin_bottom=20 margin_bottom=20
) )
) )
# Save button
# Login button save_button = toga.Button(
login_button = toga.Button(
icon=toga.Icon("images/save"), icon=toga.Icon("images/save"),
style=Pack( style=Pack(
width=50, width=50,
margin=10, margin=10,
background_color="white"
), ),
on_press=self.register_button_pressed)
on_press=self.register_button_press)
login_area= toga.Box( save_button_box= toga.Column(
children=[login_button], children=[save_button],
style=Pack( style=Pack(
width=250, flex=1,
direction=ROW,
align_items=START, align_items=START,
margin=20 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 # wrapping up the windows elements
screen = toga.Column( self.content_box.add(self.name_input)
children=[menu_bar, self.content_box.add(self.password_input)
liner, self.content_box.add(save_button_box)
content_box],
style=Pack(
width=SCREENWIDTH, # iPhone width
)
)
self.add(screen)
def register_button_press(self, widget): async def register_button_pressed(self, widget):
print("presses register") 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)