initial commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,199 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import threading
|
||||
import asyncio
|
||||
import websockets
|
||||
from websockets import connect # Ensure this is imported
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def student_main_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.theme_mode = ft.ThemeMode.LIGHT # Set the theme to light
|
||||
page.title = "Room Information"
|
||||
page.vertical_alignment = ft.MainAxisAlignment.START # Align all content to the top
|
||||
page.scroll = "adaptive"
|
||||
page.padding = 20
|
||||
|
||||
# Container for lessons
|
||||
lessons_container = ft.Column(alignment=ft.MainAxisAlignment.CENTER, spacing=20)
|
||||
|
||||
# Buttons
|
||||
test_button = ft.TextButton(
|
||||
text="Auch Räume an anderen Tagen anzeigen",
|
||||
on_click=lambda e: page.go("/all_rooms"),
|
||||
style=ft.ButtonStyle(padding=20),
|
||||
visible=False, # Initially hidden
|
||||
)
|
||||
|
||||
join_more_rooms_button = ft.TextButton(
|
||||
text="Tritt noch mehr Räume bei",
|
||||
on_click=lambda e: page.go("/join"),
|
||||
style=ft.ButtonStyle(padding=25),
|
||||
)
|
||||
def leave_current_room(e=None, unique_id=None):
|
||||
if not unique_id:
|
||||
page.snack_bar = ft.SnackBar(ft.Text("No room selected to leave."))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
return
|
||||
|
||||
url = f"http://awesom-o.org:8000/student/leave_room/?unique_id={unique_id}"
|
||||
headers = {"accept": "application/json", "Content-Type": "application/json"}
|
||||
data = {"session_id": page.session.get("access_token")}
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
if e:
|
||||
page.snack_bar = ft.SnackBar(ft.Text(f"Left Room {unique_id}"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
except requests.RequestException as error:
|
||||
# Handle all bad responses
|
||||
error_detail = "Something went wrong."
|
||||
if hasattr(error, "response") and error.response:
|
||||
try:
|
||||
error_detail = error.response.json().get("detail", "Something went wrong.")
|
||||
except json.JSONDecodeError:
|
||||
error_detail = error.response.text or "Something went wrong."
|
||||
page.snack_bar = ft.SnackBar(ft.Text(f"Error: {error_detail}"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
|
||||
async def get_rooms():
|
||||
uri = "ws://localhost:8000/ws/student/my_room"
|
||||
headers = {"session-id": page.session.get("access_token")}
|
||||
|
||||
try:
|
||||
async with websockets.connect(uri, extra_headers=headers) as websocket:
|
||||
while True:
|
||||
message = await websocket.recv()
|
||||
data = json.loads(message)
|
||||
|
||||
# Clear previous lessons
|
||||
lessons_container.controls.clear()
|
||||
|
||||
if "error" in data:
|
||||
# Show "No Rooms for Today" message
|
||||
lessons_container.controls.append(
|
||||
ft.Text(
|
||||
"Für heute hast du noch keine Räume ausgewählt!",
|
||||
size=50,
|
||||
weight="bold",
|
||||
text_align="center",
|
||||
)
|
||||
)
|
||||
else:
|
||||
rooms = data.get("rooms", [])
|
||||
# Filter and sort lessons for today
|
||||
today = datetime.now().date()
|
||||
today_lessons = [room for room in rooms if room["lesson_date"] == str(today)]
|
||||
today_lessons.sort(key=lambda x: x["lesson_time"])
|
||||
|
||||
# Check if there are rooms on other dates
|
||||
other_date_rooms = [room for room in rooms if room["lesson_date"] != str(today)]
|
||||
test_button.visible = bool(other_date_rooms) # Show button if there are rooms on other dates
|
||||
|
||||
if today_lessons:
|
||||
for lesson in today_lessons:
|
||||
# Create lesson card
|
||||
room_card = ft.Card(
|
||||
content=ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
# Room details
|
||||
ft.Column(
|
||||
[
|
||||
# Top line: room_number, info, location
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(
|
||||
f"{lesson['room_number']} {lesson['info']}",
|
||||
size=20,
|
||||
weight="bold",
|
||||
),
|
||||
ft.Text(
|
||||
f"| {lesson['location']}",
|
||||
size=20,
|
||||
weight="bold",
|
||||
),
|
||||
],
|
||||
spacing=5,
|
||||
),
|
||||
# Second line: first_name and last_name
|
||||
ft.Text(
|
||||
f"Teacher: {lesson['first_name']} {lesson['last_name']}",
|
||||
size=16,
|
||||
),
|
||||
# Third line: lesson_time
|
||||
ft.Text(
|
||||
f"Time: {lesson['lesson_time']}",
|
||||
size=16,
|
||||
),
|
||||
],
|
||||
expand=True,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
),
|
||||
# IconButton on the right
|
||||
ft.IconButton(
|
||||
icon=ft.icons.EXIT_TO_APP,
|
||||
on_click=lambda e, lesson_id=lesson['unique_id']: leave_current_room(e, lesson_id),
|
||||
tooltip="View Room Details",
|
||||
),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
padding=20, # Add padding inside the card
|
||||
),
|
||||
margin=10,
|
||||
elevation=5, # Add shadow for better visual appeal
|
||||
)
|
||||
lessons_container.controls.append(room_card)
|
||||
else:
|
||||
# Show "No Rooms for Today" message
|
||||
lessons_container.controls.append(
|
||||
ft.Text(
|
||||
"No Rooms for Today",
|
||||
size=50,
|
||||
weight="bold",
|
||||
text_align="center",
|
||||
)
|
||||
)
|
||||
|
||||
# Update page
|
||||
page.update()
|
||||
except Exception as e:
|
||||
lessons_container.controls.clear()
|
||||
lessons_container.controls.append(ft.Text(f"Error: {e}", size=50, weight="bold", text_align="center"))
|
||||
page.update()
|
||||
|
||||
def start_get_rooms():
|
||||
# Ensure a new event loop in the thread
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(get_rooms())
|
||||
|
||||
# Start the WebSocket client in a thread
|
||||
import threading
|
||||
threading.Thread(target=start_get_rooms, daemon=True).start()
|
||||
|
||||
# Add components to page
|
||||
page.add(
|
||||
ft.Column(
|
||||
[
|
||||
# Lessons container (centered)
|
||||
ft.Column(
|
||||
[lessons_container],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
expand=True,
|
||||
),
|
||||
# Buttons in one row at the bottom
|
||||
ft.Row(
|
||||
[test_button, join_more_rooms_button],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
spacing=20, # Add spacing between buttons
|
||||
),
|
||||
],
|
||||
expand=True,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,288 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import datetime
|
||||
|
||||
def create_page(page: ft.Page):
|
||||
|
||||
page.clean()
|
||||
page.theme_mode = ft.ThemeMode.LIGHT # Set the theme to light
|
||||
def is_student():
|
||||
url = 'http://127.0.0.1:8000/check_role'
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
session_id = page.session.get("access_token")
|
||||
if not session_id:
|
||||
print("Session ID is missing.")
|
||||
return None
|
||||
|
||||
data = {'session_id': session_id}
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
role = response.json()
|
||||
|
||||
if 'role' in role and 'role' in role['role']:
|
||||
if role['role']['role'] == "student":
|
||||
role = "s"
|
||||
page.go("/login")
|
||||
return role
|
||||
elif role['role']['role'] == "teacher":
|
||||
role = "t"
|
||||
return role
|
||||
print("Unexpected response structure:", role)
|
||||
return None
|
||||
except requests.exceptions.RequestException as error:
|
||||
print("Error during role check:", error)
|
||||
return None
|
||||
|
||||
page.title = "Create Room Page"
|
||||
|
||||
if is_student == "s":
|
||||
page.gp("/")
|
||||
session = page.session.get("access_token")
|
||||
if not session:
|
||||
page.go("/login")
|
||||
|
||||
# Initialize selected room state (use a simple variable instead of ft.State)
|
||||
selected_room = ""
|
||||
selected_date = ""
|
||||
selected_time = ""
|
||||
|
||||
SEARCH_RESULT = ""
|
||||
|
||||
date_text = ft.Text(
|
||||
"", # Initially empty
|
||||
size=18, # Font size
|
||||
weight=ft.FontWeight.BOLD, # Make the text bold
|
||||
color="black", # Text colo
|
||||
visible=False # Initially hidden
|
||||
)
|
||||
|
||||
|
||||
# Function to fetch room data
|
||||
def fetch_rooms():
|
||||
try:
|
||||
response = requests.get("http://awesom-o.org:8000/teacher/all_room_information")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print("Error fetching room data:", e)
|
||||
return []
|
||||
|
||||
# Fetch rooms initially (don't display them immediately)
|
||||
room_data = fetch_rooms()
|
||||
|
||||
# Function to filter room data
|
||||
def search_rooms(query):
|
||||
query = query.lower()
|
||||
return [
|
||||
room for room in room_data
|
||||
if query in room["room_number"].lower()
|
||||
or query in room["info"].lower()
|
||||
or query in room["location"].lower()
|
||||
]
|
||||
|
||||
# Search results container
|
||||
search_results = ft.Column(
|
||||
visible=False, # Initially hidden
|
||||
scroll=ft.ScrollMode.AUTO,
|
||||
expand=True
|
||||
)
|
||||
|
||||
# Update search results
|
||||
def update_results(query):
|
||||
if query.strip() == "": # When the search field is empty, show all rooms
|
||||
search_results.controls.clear()
|
||||
for room in room_data:
|
||||
search_results.controls.append(
|
||||
ft.ListTile(
|
||||
title=ft.Text(f"Room {room['room_number']}: {room['info']}"),
|
||||
subtitle=ft.Text(room['location']),
|
||||
on_click=lambda e, r=room: select_room(r),
|
||||
)
|
||||
)
|
||||
search_results.visible = True
|
||||
else:
|
||||
search_results.controls.clear()
|
||||
filtered_rooms = search_rooms(query)
|
||||
for room in filtered_rooms:
|
||||
search_results.controls.append(
|
||||
ft.ListTile(
|
||||
title=ft.Text(f"Room {room['room_number']}: {room['info']}"),
|
||||
subtitle=ft.Text(room['location']),
|
||||
on_click=lambda e, r=room: select_room(r),
|
||||
)
|
||||
)
|
||||
search_results.visible = True
|
||||
|
||||
close_button.visible = search_results.visible # Show close button when results are visible
|
||||
page.update()
|
||||
|
||||
# Select a room
|
||||
def select_room(room):
|
||||
nonlocal selected_room
|
||||
nonlocal SEARCH_RESULT
|
||||
selected_room = f"Room {room['room_number']}: {room['info']} - {room['location']}"
|
||||
search_bar.value = selected_room
|
||||
SEARCH_RESULT = room['number']
|
||||
|
||||
search_results.visible = False
|
||||
close_button.visible = False
|
||||
page.update()
|
||||
|
||||
# Close button to hide search results
|
||||
def close_search_results(e):
|
||||
search_results.visible = False
|
||||
close_button.visible = False
|
||||
page.update()
|
||||
|
||||
def handle_change_date_picker(e):
|
||||
# Once a date is selected, update the Text and make it visible
|
||||
nonlocal selected_date
|
||||
selected_date = e.control.value.strftime('%Y-%m-%d')
|
||||
if selected_time != "":
|
||||
date_text.value = f"Your Room will be started on: {selected_date} at {selected_time}"
|
||||
date_text.visible = True # Make the Text visible after date selection
|
||||
page.update()
|
||||
else:
|
||||
date_text.value = f"Your Room will be started on: {selected_date}"
|
||||
date_text.visible = True # Make the Text visible after date selection
|
||||
page.update() # Update the page to reflect the changes
|
||||
|
||||
def handle_change_dropdown(e):
|
||||
nonlocal selected_time
|
||||
selected_time1 = e.control.value
|
||||
selected_time = selected_time1.replace("Start time: ", "")
|
||||
if selected_date != "":
|
||||
date_text.value = f"Your Room will be started on: {selected_date} at {selected_time}"
|
||||
date_text.visible = True # Make the Text visible after date selection
|
||||
page.update()
|
||||
else:
|
||||
date_text.value = f"Your Room will be started at {selected_time}"
|
||||
date_text.visible = True # Make the Text visible after date selection
|
||||
page.update() # Update the page to reflect the changes
|
||||
|
||||
def go_to_main(e):
|
||||
page.go("/") # Replace with the appropriate route for your register page
|
||||
|
||||
def create(e):
|
||||
page.update()
|
||||
search_results = SEARCH_RESULT.strip()
|
||||
max_students = max_students_field.value.strip()
|
||||
selected_date
|
||||
selected_time
|
||||
|
||||
# Validate input
|
||||
if not search_results or not max_students or not selected_date or not selected_time:
|
||||
info_label.value = "Please enter Room Number, Max Students, Lesson Date and Lesson Time."
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
return
|
||||
|
||||
try:
|
||||
max_students = int(max_students)
|
||||
except ValueError:
|
||||
info_label.value = "Max Students must be a number."
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
return
|
||||
|
||||
if max_students < 1:
|
||||
info_label.value = "Please don't enter a negative or zero value for Max Students."
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
return
|
||||
|
||||
try:
|
||||
url = 'http://127.0.0.1:8000/teacher/create_room'
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
data = {
|
||||
'room_number': search_results,
|
||||
'max_students': max_students,
|
||||
'session_id': page.session.get("access_token"),
|
||||
'lesson_time': selected_time,
|
||||
'lesson_date': selected_date
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
success_message = "The Room was created Successfully!"
|
||||
info_label.value = success_message
|
||||
info_label.color = "green"
|
||||
else:
|
||||
error_message = "Something went wrong..."
|
||||
info_label.value = error_message
|
||||
info_label.color = "red"
|
||||
except requests.exceptions.RequestException as e:
|
||||
info_label.value = f"An error occurred: {str(e)}"
|
||||
info_label.color = "red"
|
||||
|
||||
page.update()
|
||||
|
||||
# Search bar
|
||||
search_bar = ft.TextField(
|
||||
label="Search for a room",
|
||||
on_focus=lambda e: update_results(""), # Show all rooms when search bar is focused
|
||||
on_change=lambda e: update_results(e.control.value),
|
||||
expand=True # Make the search bar take up available space
|
||||
)
|
||||
|
||||
# Close button
|
||||
close_button = ft.IconButton(
|
||||
icon=ft.icons.CLOSE,
|
||||
on_click=close_search_results,
|
||||
visible=False # Initially hidden
|
||||
)
|
||||
|
||||
max_students_field = ft.TextField(label="Max Students")
|
||||
info_label = ft.Text("", theme_style=ft.TextThemeStyle.TITLE_SMALL)
|
||||
|
||||
|
||||
# Add components to the page
|
||||
page.add(
|
||||
ft.Row([search_bar, close_button]), # Add search bar and close button in a row
|
||||
search_results,
|
||||
max_students_field,
|
||||
ft.Row([
|
||||
ft.OutlinedButton(
|
||||
"Pick the date when your lesson begins",
|
||||
icon=ft.icons.CALENDAR_MONTH,
|
||||
height=40,
|
||||
on_click=lambda e: page.open(
|
||||
ft.DatePicker(
|
||||
first_date=datetime.datetime(year=2024, month=11, day=20),
|
||||
on_change=handle_change_date_picker,
|
||||
)
|
||||
),
|
||||
),
|
||||
date_text, # Add the styled Text below the button
|
||||
], spacing=20),
|
||||
ft.Dropdown(
|
||||
hint_text="Choose the Start of your Lesson",
|
||||
options=[
|
||||
ft.dropdown.Option("Start time: 8:00"),
|
||||
ft.dropdown.Option("Start time: 9:20"),
|
||||
ft.dropdown.Option("Start time: 10:40"),
|
||||
ft.dropdown.Option("Start time: 11:50"),
|
||||
ft.dropdown.Option("Start time: 12:50"),
|
||||
ft.dropdown.Option("Start time: 13:55"),
|
||||
ft.dropdown.Option("Start time: 15:00"),
|
||||
],on_change=handle_change_dropdown,
|
||||
label_style=ft.TextStyle(size=16), # Correct way to set font size for Dropdown
|
||||
),
|
||||
ft.ElevatedButton("Submit", on_click=create),
|
||||
info_label,
|
||||
ft.Row([
|
||||
ft.TextButton("Enough? Click here to go to the Main Page", on_click=go_to_main),
|
||||
]),
|
||||
)
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
ft.app(target=create_page, view=ft.AppView.WEB_BROWSER)
|
||||
@@ -0,0 +1,425 @@
|
||||
import flet as ft
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
import requests
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
def get_initials(user_name: str):
|
||||
if user_name:
|
||||
return user_name[0].capitalize()
|
||||
else:
|
||||
return "E"
|
||||
|
||||
|
||||
def get_avatar_color(user_name: str):
|
||||
colors_lookup = [
|
||||
ft.colors.AMBER,
|
||||
ft.colors.BLUE,
|
||||
ft.colors.BROWN,
|
||||
ft.colors.CYAN,
|
||||
ft.colors.GREEN,
|
||||
ft.colors.INDIGO,
|
||||
ft.colors.LIME,
|
||||
ft.colors.ORANGE,
|
||||
ft.colors.PINK,
|
||||
ft.colors.PURPLE,
|
||||
ft.colors.RED,
|
||||
ft.colors.TEAL,
|
||||
]
|
||||
return colors_lookup[hash(user_name) % len(colors_lookup)]
|
||||
|
||||
def join_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.theme_mode = ft.ThemeMode.LIGHT # Set the theme to light
|
||||
page.title = "Join Rooms"
|
||||
page.vertical_alignment = ft.MainAxisAlignment.START # Align all content to the top
|
||||
page.scroll = "adaptive"
|
||||
page.padding = 20
|
||||
|
||||
# Check if the user is logged in
|
||||
session = page.session.get("access_token")
|
||||
if not session:
|
||||
page.go("/login") # Redirect to login if no session
|
||||
|
||||
def test(e):
|
||||
print(e)
|
||||
page.go("/test")
|
||||
print("test")
|
||||
|
||||
# Search field
|
||||
search_field = ft.TextField(label="Lehrkraft / Raumnummer ", expand=True)
|
||||
|
||||
# Dropdown for time filter
|
||||
time_filter = ft.Dropdown(
|
||||
label="Stunde wählen",
|
||||
options=[
|
||||
ft.dropdown.Option("8:00", "8:00"),
|
||||
ft.dropdown.Option("9:20", "9:20"),
|
||||
ft.dropdown.Option("10:40", "10:40"),
|
||||
ft.dropdown.Option("11:50", "11:50"),
|
||||
ft.dropdown.Option("12:50", "12:50"),
|
||||
ft.dropdown.Option("13:55", "13:55"),
|
||||
ft.dropdown.Option("15:00", "15:00"),
|
||||
],
|
||||
width=170, # Shorter width for time filter
|
||||
)
|
||||
|
||||
# Cross icon button to clear time filter
|
||||
clear_time_filter = ft.IconButton(
|
||||
icon=ft.icons.CLOSE,
|
||||
on_click=lambda e: clear_filter("time"),
|
||||
tooltip="Clear Time Filter",
|
||||
)
|
||||
|
||||
# Container for the room list
|
||||
room_container = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
|
||||
|
||||
def ausloggen(e = None):
|
||||
page.go("/login")
|
||||
print(page.session.set("access_token", None))
|
||||
|
||||
# Function to clear filters
|
||||
def clear_filter(filter_type):
|
||||
if filter_type == "time":
|
||||
time_filter.value = None
|
||||
if filter_type == "lesson":
|
||||
lesson_filter.value = None
|
||||
|
||||
update_room_list(json.loads(page.session.get("rooms_data", "[]")))
|
||||
def update_room_list(rooms):
|
||||
# Ensure rooms is a list
|
||||
if not isinstance(rooms, list):
|
||||
rooms = []
|
||||
|
||||
# Apply search filter
|
||||
search_query = search_field.value.lower()
|
||||
filtered_rooms = [
|
||||
room
|
||||
for room in rooms
|
||||
if (search_query in str(room.get("room_name", "")).lower() # Search by room_name
|
||||
or search_query in room.get("info", "").lower() # Search by room_info
|
||||
or search_query in room.get("location", "").lower() # Search by location
|
||||
or search_query in room.get("subjects", "").lower() # Search by subjects
|
||||
or search_query in room.get("first_name", "").lower() # Search by teacher_first_name
|
||||
or search_query in room.get("last_name", "").lower()) # Search by teacher_last_name
|
||||
]
|
||||
|
||||
if time_filter.value: # Only apply the filter if a time is selected
|
||||
filtered_rooms = [
|
||||
room
|
||||
for room in filtered_rooms
|
||||
if room.get("lesson_time", "") == time_filter.value
|
||||
]
|
||||
|
||||
if lesson_filter.value: # Only apply the filter if a time is selected
|
||||
filtered_rooms = [
|
||||
room
|
||||
for room in filtered_rooms
|
||||
if lesson_filter.value.lower() in room.get("subjects", "").lower().split(", ")
|
||||
]
|
||||
|
||||
if show_full_rooms.value == True:
|
||||
filtered_rooms = [
|
||||
room
|
||||
for room in filtered_rooms
|
||||
if room.get("is_open", "") == True
|
||||
]
|
||||
|
||||
# Apply date filter (only if a date is selected)
|
||||
room_container.controls.clear()
|
||||
|
||||
def page_dialog_click(e, unique_id):
|
||||
dialog_join = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Bitte bestätige"),
|
||||
content=ft.Text("Möchtest du diesem Raum wirklich beitreten?"),
|
||||
actions=[
|
||||
ft.TextButton("Ja", on_click=lambda e: (page.close(dialog_join), room_clicked(e, unique_id))),
|
||||
ft.TextButton("Nein", on_click=lambda e: page.close(dialog_join)),
|
||||
],
|
||||
actions_alignment=ft.MainAxisAlignment.END,
|
||||
)
|
||||
page.dialog = dialog_join
|
||||
dialog_join.open = True
|
||||
page.update()
|
||||
|
||||
def room_clicked(e, unique_id):
|
||||
try:
|
||||
url = f"http://awesom-o.org:8000/student/join_room/?unique_id={unique_id}"
|
||||
headers = {"accept": "application/json", "Content-Type": "application/json"}
|
||||
data = {"session_id": page.session.get("access_token")}
|
||||
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
page.snack_bar = ft.SnackBar(ft.Text(f"Raum erfolgreich beigetreten: {unique_id}"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
page.go("/")
|
||||
except:
|
||||
page.snack_bar = ft.SnackBar(ft.Text(f"Ein fehler ist aufgetreten, bitte melde dich bei der Administration"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
else:
|
||||
dialog_join = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Fehler"),
|
||||
content=ft.Text(response.json().get("detail")),
|
||||
actions=[
|
||||
ft.TextButton("Schließen", on_click=lambda e: page.close(dialog_join)),
|
||||
],
|
||||
actions_alignment=ft.MainAxisAlignment.END,
|
||||
)
|
||||
page.dialog = dialog_join
|
||||
dialog_join.open = True
|
||||
page.update()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
dialog_join = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Fehler"),
|
||||
content=ft.Text("Ein Fehler ist aufgetreten"),
|
||||
actions=[
|
||||
ft.TextButton("Schließen", on_click=lambda e: page.close(dialog_join)),
|
||||
],
|
||||
actions_alignment=ft.MainAxisAlignment.END,
|
||||
)
|
||||
page.dialog = dialog_join
|
||||
dialog_join.open = True
|
||||
page.update()
|
||||
|
||||
|
||||
for room in filtered_rooms:
|
||||
room_name = room.get("room_name", "Unknown Room")
|
||||
room_info = room.get("info", "")
|
||||
location = room.get("location", "")
|
||||
first_name = room.get("first_name", "")
|
||||
last_name = room.get("last_name", "")
|
||||
subjects = room.get("subjects", "")
|
||||
lesson_time = room.get("lesson_time", "")
|
||||
lesson_date = room.get("lesson_date", "")
|
||||
max_students = room.get("max_students", 0)
|
||||
current_students = room.get("current_students", 0)
|
||||
unique_id = room.get("unique_id", "")
|
||||
is_open = room.get("is_open", True)
|
||||
joined = room.get("joined", False)
|
||||
|
||||
if current_students == max_students:
|
||||
BACKROUND_COLOR = "#f10d0c"
|
||||
TEXT_COLOR = ft.colors.WHITE
|
||||
user_interface_button_text = ft.Text("Raum bereits voll")
|
||||
else:
|
||||
BACKROUND_COLOR = "#729fcf"
|
||||
TEXT_COLOR = ft.colors.BLACK
|
||||
if joined == True:
|
||||
user_interface_button_text = ft.Text("Raum bereits gebucht")
|
||||
else:
|
||||
user_interface_button_text = ft.TextButton(
|
||||
"Jetzt Platz reservieren",
|
||||
on_click=lambda e, unique_id=unique_id: page_dialog_click(e, unique_id),
|
||||
style=ft.ButtonStyle(
|
||||
bgcolor=ft.colors.WHITE,
|
||||
color=ft.colors.BLACK,
|
||||
shape=ft.RoundedRectangleBorder(radius=5),
|
||||
padding=20,
|
||||
))
|
||||
room_card = ft.Card(
|
||||
content=ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
# Room details
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text(f"Heute, {lesson_time} Uhr", size=16, color=TEXT_COLOR),
|
||||
ft.Text(f"Lehrkraft: {first_name} {last_name} - {subjects}", size=16, color=TEXT_COLOR),
|
||||
ft.Text(f"Raum {room_name} {room_info} | {location}", size=16, color=TEXT_COLOR),
|
||||
ft.Text(f"Belegte Plätze: {current_students}/{max_students}", size=16, color=TEXT_COLOR),
|
||||
],
|
||||
expand=True,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
),
|
||||
# IconButton on the right
|
||||
user_interface_button_text,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
padding=20, # Add padding inside the card
|
||||
),
|
||||
color=BACKROUND_COLOR,
|
||||
margin=10,
|
||||
)
|
||||
room_container.controls.append(room_card)
|
||||
|
||||
|
||||
if not filtered_rooms:
|
||||
room_container.controls.append(ft.Text("Keine offenen Räume verfügbar für Heute.", size=18))
|
||||
|
||||
page.update()
|
||||
|
||||
# WebSocket listener for real-time updates
|
||||
async def listen_for_updates():
|
||||
uri = "ws://localhost:8000/ws/open_rooms"
|
||||
headers = {"session-id": page.session.get("access_token")}
|
||||
|
||||
async with websockets.connect(uri, extra_headers=headers) as websocket:
|
||||
print("Connected to WebSocket server")
|
||||
|
||||
try:
|
||||
while True:
|
||||
# Receive data from the server
|
||||
data = await websocket.recv()
|
||||
rooms = json.loads(data)
|
||||
# Ensure rooms is a list
|
||||
if isinstance(rooms, dict):
|
||||
rooms = rooms.get("rooms", [])
|
||||
elif not isinstance(rooms, list):
|
||||
rooms = []
|
||||
|
||||
# Store rooms data in session for filtering
|
||||
page.session.set("rooms_data", json.dumps(rooms))
|
||||
|
||||
# Extract unique dates and sort them from nearest to longest
|
||||
unique_dates = list(set(room.get("lesson_date", "") for room in rooms))
|
||||
unique_dates.sort(key=lambda x: datetime.strptime(x, "%Y-%m-%d")) # Sort dates
|
||||
|
||||
|
||||
# Update the room list
|
||||
update_room_list(rooms)
|
||||
|
||||
except websockets.ConnectionClosed:
|
||||
print("WebSocket connection closed")
|
||||
|
||||
# Start the WebSocket listener in a separate thread
|
||||
def start_websocket_listener():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(listen_for_updates())
|
||||
|
||||
threading.Thread(target=start_websocket_listener, daemon=True).start()
|
||||
|
||||
top_bar = ft.Row(
|
||||
[
|
||||
search_field,
|
||||
ft.Row([time_filter, clear_time_filter], spacing=5),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
height=50,
|
||||
)
|
||||
|
||||
lesson_filter = ft.Dropdown(
|
||||
label="Fach wählen",
|
||||
options=[
|
||||
ft.dropdown.Option("De", "De"),
|
||||
ft.dropdown.Option("Ma", "Ma"),
|
||||
ft.dropdown.Option("NW", "NW"),
|
||||
ft.dropdown.Option("En", "En"),
|
||||
ft.dropdown.Option("Gs", "Gs"),
|
||||
ft.dropdown.Option("Re", "Re"),
|
||||
ft.dropdown.Option("WN", "WN"),
|
||||
ft.dropdown.Option("Fr", "Fr"),
|
||||
ft.dropdown.Option("Sp", "Sp"),
|
||||
ft.dropdown.Option("Sn", "Sn"),
|
||||
],
|
||||
width=170,
|
||||
)
|
||||
|
||||
clear_lesson_filter = ft.IconButton(
|
||||
icon=ft.icons.CLOSE,
|
||||
on_click=lambda e: clear_filter("lesson"),
|
||||
tooltip="Clear Lesson Filter",
|
||||
)
|
||||
|
||||
|
||||
show_full_rooms = ft.Checkbox(label="Volle Räume ausblenden ", value=False, label_style=ft.TextStyle(size=20))
|
||||
|
||||
middle_bar = ft.Row(
|
||||
[
|
||||
show_full_rooms,
|
||||
ft.Row(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text(
|
||||
"Nur Räume anzeigen, in denen\nfolgendes Fach angeboten wird:",
|
||||
size=20,
|
||||
),
|
||||
],
|
||||
),
|
||||
lesson_filter,
|
||||
clear_lesson_filter,
|
||||
],
|
||||
spacing=5,
|
||||
alignment=ft.MainAxisAlignment.END,
|
||||
),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
height=50,
|
||||
)
|
||||
|
||||
go_home_button = ft.Container(
|
||||
content=ft.Text("Home", size=24, color=ft.colors.BLUE_900),
|
||||
on_click=lambda e: page.go("/"),
|
||||
alignment=ft.alignment.center,
|
||||
width=200,
|
||||
height=60,
|
||||
bgcolor='transparent',
|
||||
ink=True,
|
||||
)
|
||||
|
||||
navigation_bar = ft.Row(
|
||||
[go_home_button, ft.Text("Räume suchen", size=23, weight=ft.FontWeight.BOLD, width=170, no_wrap=True)],
|
||||
)
|
||||
|
||||
page.add(
|
||||
ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(
|
||||
"Daltonraum-Buchungssystem der IGS Garbsen", size=30),
|
||||
ft.Column([
|
||||
ft.PopupMenuButton(
|
||||
items=[
|
||||
ft.PopupMenuItem(content=ft.Text(f"Eingeloggt als: {page.session.get("username")}", weight=ft.FontWeight.BOLD)),
|
||||
ft.PopupMenuItem(text="Profil anzeigen", on_click=test),
|
||||
ft.PopupMenuItem(text="Ausloggen", on_click=ausloggen),
|
||||
],
|
||||
content=ft.CircleAvatar(
|
||||
content=ft.Text(get_initials(
|
||||
page.session.get("username"))),
|
||||
color=ft.colors.WHITE,
|
||||
bgcolor=get_avatar_color(
|
||||
page.session.get("username")),
|
||||
),
|
||||
menu_position=ft.PopupMenuPosition.UNDER,
|
||||
tooltip="",
|
||||
|
||||
)
|
||||
|
||||
],
|
||||
# Align column content to the end (optional)
|
||||
alignment=ft.MainAxisAlignment.END,
|
||||
),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN, # Space out the two elements
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER, # Align items vertically
|
||||
),
|
||||
|
||||
navigation_bar,
|
||||
top_bar,
|
||||
middle_bar,
|
||||
ft.Column(
|
||||
[
|
||||
room_container,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
expand=True,
|
||||
),
|
||||
],
|
||||
expand=True,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
|
||||
def login_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.title = "Login Page"
|
||||
page.theme_mode = ft.ThemeMode.LIGHT # Set the theme to light
|
||||
|
||||
# UI components
|
||||
username_field = ft.TextField(label="Accountname")
|
||||
password_field = ft.TextField(label="Passwort", password=True, can_reveal_password=True)
|
||||
|
||||
# Create an info text label
|
||||
info_label = ft.Text("", theme_style=ft.TextThemeStyle.TITLE_SMALL)
|
||||
|
||||
# Function to handle login
|
||||
|
||||
def login(e):
|
||||
username = username_field.value.strip() # Strip whitespace
|
||||
password = password_field.value.strip()
|
||||
|
||||
# Validate input
|
||||
if not username or not password:
|
||||
info_label.value = "Bitte Benutzername und Passwort eingeben"
|
||||
info_label.color = "red"
|
||||
page.update() # Update the page to reflect the changes
|
||||
return
|
||||
|
||||
# Send request over HTTP
|
||||
try:
|
||||
url = 'http://awesom-o.org:8000/login'
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
data = {
|
||||
'username': username,
|
||||
'password': password
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
token_data = response.json()
|
||||
page.session.set("username", token_data['username'])
|
||||
page.session.set("access_token", token_data['session_id'])
|
||||
page.go("/")
|
||||
else:
|
||||
error_message = response.json().get("detail")
|
||||
info_label.value = error_message
|
||||
info_label.color = "red"
|
||||
except requests.exceptions.RequestException as e:
|
||||
info_label.value = f"An error occurred: {str(e)}"
|
||||
info_label.color = "red"
|
||||
|
||||
# Update the page to reflect the changes
|
||||
page.update()
|
||||
|
||||
# Function to navigate to the register page
|
||||
def go_to_register(e):
|
||||
page.go("/register_student") # Replace with the appropriate route for your register page
|
||||
|
||||
# Add components to the page
|
||||
page.add(
|
||||
ft.Text("Daltonraum-Buchungssystem der IGS Garbsen", size=30),
|
||||
ft.Text("Login", size=23, weight=ft.FontWeight.BOLD),
|
||||
username_field,
|
||||
password_field,
|
||||
ft.ElevatedButton("Anmelden", on_click=login),
|
||||
info_label, # Add the info label to the page
|
||||
ft.Row([
|
||||
ft.TextButton("Noch nicht registriert? Registriere dich hier!", on_click=go_to_register),
|
||||
])
|
||||
|
||||
)
|
||||
|
||||
# You can run the login page separately or as part of your main app.
|
||||
if __name__ == "__main__":
|
||||
ft.app(target=login_page, view=ft.AppView.WEB_BROWSER)
|
||||
@@ -0,0 +1,272 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import threading
|
||||
import asyncio
|
||||
import websockets
|
||||
from websockets import connect # Ensure this is imported
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pages.student_main_page import student_main_page
|
||||
|
||||
def main_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.theme_mode = ft.ThemeMode.LIGHT
|
||||
|
||||
session = page.session.get("access_token")
|
||||
if not session:
|
||||
page.go("/login") # Redirect to login if no session
|
||||
|
||||
def is_student():
|
||||
url = 'http://awesom-o.org:8000/check_role'
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
session_id = page.session.get("access_token")
|
||||
if not session_id:
|
||||
return None
|
||||
|
||||
data = {'session_id': session_id}
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
role = response.json()
|
||||
|
||||
if role and 'role' in role and 'role' in role['role']:
|
||||
if role['role']['role'] == "student":
|
||||
role = "s"
|
||||
return role
|
||||
elif role['role']['role'] == "teacher":
|
||||
role = "t"
|
||||
return role
|
||||
print("Unexpected response structure:", role)
|
||||
return None
|
||||
except requests.exceptions.RequestExceptin as error:
|
||||
print("Error during role check:", error)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
page.title = "Room Information"
|
||||
page.vertical_alignment = ft.MainAxisAlignment.START # Align all content to the top
|
||||
|
||||
|
||||
if is_student() == "s":
|
||||
student_main_page(page)
|
||||
elif is_student() == "t":
|
||||
async def fetch_room_data(session_id: str, update_room_list):
|
||||
uri = "ws://localhost:8000/ws/teacher/open_rooms"
|
||||
headers = {"session-id": session_id} # Add session ID to headers
|
||||
|
||||
async with websockets.connect(uri, extra_headers=headers) as websocket:
|
||||
print("WebSocket connected")
|
||||
try:
|
||||
while True:
|
||||
# Receive data from the WebSocket
|
||||
data = await websocket.recv()
|
||||
room_data = json.loads(data)
|
||||
open_rooms = room_data.get("open_rooms", [])
|
||||
|
||||
# Update the room list
|
||||
update_room_list(open_rooms)
|
||||
|
||||
except websockets.ConnectionClosed:
|
||||
print("WebSocket connection closed")
|
||||
except Exception as e:
|
||||
print(f"WebSocket error: {e}")
|
||||
|
||||
# Function to fetch students in a room
|
||||
def fetch_students_in_room(unique_id: str, session_id: str):
|
||||
url = f"http://awesom-o.org:8000/teacher/room_students/?unique_id={unique_id}"
|
||||
headers = {"accept": "application/json", "Content-Type": "application/json"}
|
||||
data = {"session_id": session_id}
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.RequestException as e:
|
||||
print(f"Error fetching students: {e}")
|
||||
return None
|
||||
|
||||
# Function to delete a room
|
||||
def delete_room(unique_id: str, session_id: str):
|
||||
url = f"http://awesom-o.org:8000/teacher/delete_room/?unique_id={unique_id}"
|
||||
headers = {"accept": "application/json", "Content-Type": "application/json"}
|
||||
data = {"session_id": session_id}
|
||||
try:
|
||||
response = requests.delete(url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
return True
|
||||
except requests.RequestException as e:
|
||||
print(f"Error deleting room: {e}")
|
||||
return False
|
||||
|
||||
# Main Flet app
|
||||
page.title = "Room Management"
|
||||
page.vertical_alignment = ft.MainAxisAlignment.START # Align content to the top
|
||||
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
|
||||
page.padding = 20
|
||||
page.scroll = "adaptive"
|
||||
|
||||
# Check if the user is logged in
|
||||
session_id = page.session.get("access_token")
|
||||
if not session_id:
|
||||
page.go("/login") # Redirect to login if no session
|
||||
|
||||
# Search field
|
||||
search_field = ft.TextField(label="Search", expand=True)
|
||||
|
||||
# Icon button to navigate to "/create" page
|
||||
create_button = ft.IconButton(
|
||||
icon=ft.icons.ADD,
|
||||
on_click=lambda _: page.go("/create"),
|
||||
)
|
||||
|
||||
# Container for the room list
|
||||
room_container = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
|
||||
|
||||
# Function to update the room list
|
||||
def update_room_list(rooms):
|
||||
room_container.controls.clear()
|
||||
|
||||
for room in rooms:
|
||||
room_name = room.get("room_name", "Unknown Room")
|
||||
room_info = room.get("info", "")
|
||||
location = room.get("location", "")
|
||||
teacher_name = room.get("teacher_name", "")
|
||||
max_students = room.get("max_students", 0)
|
||||
current_students = room.get("current_students", 0)
|
||||
unique_id = room.get("unique_id", "")
|
||||
|
||||
# Create a clickable room card
|
||||
room_card = ft.GestureDetector(
|
||||
content=ft.Card(
|
||||
content=ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
# Top line: room_name, room_info, location
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(f"{room_name}", size=20),
|
||||
ft.Text(f"{room_info}", size=16),
|
||||
ft.Text(f"| {location}", size=16),
|
||||
],
|
||||
spacing=5,
|
||||
),
|
||||
# Teacher line
|
||||
ft.Text(f"Teacher: {teacher_name}", size=16),
|
||||
# Capacity line
|
||||
ft.Text(f"Capacity: {current_students}/{max_students}", size=16),
|
||||
],
|
||||
spacing=10,
|
||||
),
|
||||
padding=20,
|
||||
),
|
||||
margin=10,
|
||||
elevation=5,
|
||||
),
|
||||
on_tap=lambda e, unique_id=unique_id: open_room_dialog(unique_id),
|
||||
)
|
||||
|
||||
room_container.controls.append(room_card)
|
||||
|
||||
if not rooms:
|
||||
room_container.controls.append(ft.Text("No open rooms available.", size=18))
|
||||
|
||||
page.update()
|
||||
|
||||
# Function to open the room dialog
|
||||
def open_room_dialog(unique_id: str):
|
||||
# Fetch students in the room
|
||||
students_data = fetch_students_in_room(unique_id, session_id)
|
||||
if not students_data:
|
||||
page.snack_bar = ft.SnackBar(ft.Text("Failed to fetch students."))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
return
|
||||
|
||||
students = students_data.get("students", [])
|
||||
|
||||
if students != None:
|
||||
|
||||
students_list = ft.Column(
|
||||
[ft.Text(f"Student: {student}") for student in students],
|
||||
scroll=ft.ScrollMode.AUTO,
|
||||
expand=True,
|
||||
)
|
||||
else:
|
||||
|
||||
students_list = ft.Column(ft.Text(f"There are no students who joined your Room"),
|
||||
scroll=ft.ScrollMode.AUTO,
|
||||
expand=True,
|
||||
)
|
||||
# Confirmation dialog for deleting the room
|
||||
def open_delete_confirmation_dialog():
|
||||
page.close(dialog)
|
||||
def confirm_delete(e):
|
||||
if delete_room(unique_id, session_id):
|
||||
page.snack_bar = ft.SnackBar(ft.Text("Room deleted successfully."))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
page.close(confirm_dialog)
|
||||
page.update()
|
||||
else:
|
||||
page.snack_bar = ft.SnackBar(ft.Text("Failed to delete room."))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
|
||||
confirm_dialog = ft.AlertDialog(
|
||||
title=ft.Text("Are you sure you want to delete this room?"),
|
||||
actions=[
|
||||
ft.TextButton("No", on_click=lambda e: page.close(confirm_dialog)),
|
||||
ft.TextButton("Yes", on_click=confirm_delete),
|
||||
],
|
||||
)
|
||||
|
||||
page.dialog = confirm_dialog
|
||||
confirm_dialog.open = True
|
||||
page.update()
|
||||
|
||||
# Room dialog
|
||||
dialog = ft.AlertDialog(
|
||||
title=ft.Text("Room Details"),
|
||||
content=students_list,
|
||||
actions=[
|
||||
ft.TextButton("Close", on_click=lambda e: page.close(dialog)),
|
||||
ft.TextButton("Delete Room", on_click=lambda e: open_delete_confirmation_dialog()),
|
||||
],
|
||||
)
|
||||
|
||||
page.dialog = dialog
|
||||
dialog.open = True
|
||||
page.update()
|
||||
|
||||
# WebSocket listener for real-time updates
|
||||
async def listen_for_updates():
|
||||
while True:
|
||||
try:
|
||||
await fetch_room_data(session_id, update_room_list)
|
||||
except Exception as e:
|
||||
print(f"WebSocket connection error: {e}. Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5) # Wait before reconnecting
|
||||
|
||||
# Top bar layout
|
||||
top_bar = ft.Row(
|
||||
[
|
||||
search_field,
|
||||
create_button,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
)
|
||||
|
||||
# Add components to the page
|
||||
page.add(
|
||||
top_bar,
|
||||
room_container,
|
||||
)
|
||||
|
||||
# Start the WebSocket listener
|
||||
page.run_task(listen_for_updates)
|
||||
else:
|
||||
page.go("/login")
|
||||
@@ -0,0 +1,94 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import json
|
||||
|
||||
def register_student_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.title = "Register Page"
|
||||
page.theme_mode = ft.ThemeMode.LIGHT
|
||||
|
||||
# UI components
|
||||
username_field = ft.TextField(label="Accountname (darfst du dir ausdenken)")
|
||||
first_name_field = ft.TextField(label="Vorname")
|
||||
last_name_field = ft.TextField(label="Nachname")
|
||||
user_class_field = ft.TextField(label="Klasse")
|
||||
password_field0 = ft.TextField(label="Passwort", password=True, can_reveal_password=True)
|
||||
password_field1 = ft.TextField(label="Passwort bestätigen", password=True, can_reveal_password=True)
|
||||
|
||||
# Create an info text label
|
||||
info_label = ft.Text("", theme_style=ft.TextThemeStyle.TITLE_SMALL)
|
||||
|
||||
# Function to handle registration
|
||||
def register(e):
|
||||
username = username_field.value.strip()
|
||||
password0 = password_field0.value.strip()
|
||||
first_name = first_name_field.value.strip()
|
||||
last_name = last_name_field.value.strip()
|
||||
user_class = user_class_field.value.strip()
|
||||
password1 = password_field1.value.strip()
|
||||
|
||||
# Validate input
|
||||
if not username or not password0 or not password1 or not first_name or not last_name or not user_class :
|
||||
info_label.value = "Bitte fülle alle lücken aus"
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
return
|
||||
|
||||
if not password0 == password1:
|
||||
info_label.value = "Passwörter stimmen nicht überein"
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
return
|
||||
# Send request over HTTP
|
||||
try:
|
||||
url = 'http://awesom-o.org:8000/student/register'
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
data = {
|
||||
'username': username,
|
||||
'first_name': first_name,
|
||||
'last_name': last_name,
|
||||
'user_class': user_class,
|
||||
'password': password0,
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
session_id = response.json().get("session_id")
|
||||
username = response.json().get("username")
|
||||
page.session.set("access_token", session_id)
|
||||
page.session.set("username", username)
|
||||
page.go("/")
|
||||
|
||||
|
||||
else:
|
||||
error_message = response.json().get("detail", "Registrierung fehlgeschlagen")
|
||||
info_label.value = error_message
|
||||
info_label.color = "red"
|
||||
except requests.exceptions.RequestException as e:
|
||||
info_label.value = f"An error occurred: {str(e)}"
|
||||
info_label.color = "red"
|
||||
|
||||
page.update()
|
||||
|
||||
page.add(
|
||||
ft.Text("Daltonraum-Buchungssystem der IGS Garbsen", size=30),
|
||||
ft.Text("Registrieren", size=23, weight=ft.FontWeight.BOLD),
|
||||
username_field,
|
||||
first_name_field,
|
||||
last_name_field,
|
||||
user_class_field,
|
||||
password_field0,
|
||||
password_field1,
|
||||
ft.ElevatedButton("Registrieren", on_click=register),
|
||||
info_label, # Add the info label to the page
|
||||
ft.TextButton("Bereits registriert? Zurück zur Login-Seite.", on_click=lambda _: page.go("/login")) # Link to login
|
||||
)
|
||||
|
||||
# You can run the registration page separately or as part of your main app.
|
||||
if __name__ == "__main__":
|
||||
ft.app(target=register_student_page, view=ft.AppView.WEB_BROWSER)
|
||||
@@ -0,0 +1,180 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
|
||||
def register_teacher_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.title = "Register Page"
|
||||
page.theme_mode = ft.ThemeMode.LIGHT
|
||||
|
||||
subjects_list = []
|
||||
row = ft.Row()
|
||||
|
||||
# UI components
|
||||
username_field = ft.TextField(label="Benutzername")
|
||||
first_name_field = ft.TextField(label="Vorname")
|
||||
last_name_field = ft.TextField(label="Nachname")
|
||||
password_field0 = ft.TextField(label="Passwort", password=True, can_reveal_password=True)
|
||||
password_field1 = ft.TextField(label="Passwort bestätigen", password=True, can_reveal_password=True)
|
||||
secret_teacher_password_field = ft.TextField(label="Secret Teacher Password", password=True, can_reveal_password=True)
|
||||
info_label = ft.Text("", theme_style=ft.TextThemeStyle.TITLE_SMALL)
|
||||
|
||||
def delete_subject(dropdown, delete_button):
|
||||
if dropdown.value in subjects_list:
|
||||
subjects_list.remove(dropdown.value)
|
||||
row.controls.remove(dropdown)
|
||||
row.controls.remove(delete_button)
|
||||
page.update()
|
||||
refresh_dropdowns()
|
||||
|
||||
def append_subject(e=None, dropdown=None, plus_button=None):
|
||||
if not dropdown.value or dropdown.value in subjects_list or dropdown.value == "Wähle deine Fächer aus":
|
||||
dropdown.value = "" # Reset if empty or duplicate
|
||||
page.update()
|
||||
return
|
||||
|
||||
subjects_list.append(dropdown.value)
|
||||
|
||||
# Disable current dropdown and button after selection
|
||||
dropdown.disabled = True
|
||||
plus_button.visible = False
|
||||
|
||||
# Add new dropdown to the same row
|
||||
add_new_dropdown()
|
||||
|
||||
page.update()
|
||||
|
||||
def on_dropdown_change(e, dropdown, plus_button):
|
||||
if dropdown.value and dropdown.value not in subjects_list and dropdown.value != "Wähle deine Fächer aus":
|
||||
subjects_list.append(dropdown.value)
|
||||
dropdown.disabled = True
|
||||
plus_button.visible = False
|
||||
add_new_dropdown()
|
||||
page.update()
|
||||
|
||||
def refresh_dropdowns():
|
||||
for control in row.controls:
|
||||
if isinstance(control, ft.Dropdown) and not control.disabled:
|
||||
available_subjects = get_available_subjects()
|
||||
control.options = [
|
||||
ft.dropdown.Option("Wähle deine Fächer aus", disabled=True)
|
||||
] + [
|
||||
ft.dropdown.Option(subj) for subj in available_subjects
|
||||
]
|
||||
page.update()
|
||||
|
||||
def get_available_subjects():
|
||||
available_subjects = [
|
||||
"De", "En", "Fr", "Gs", "Ma", "NW", "Re", "Sn", "Sp", "Wn"
|
||||
]
|
||||
return [subj for subj in available_subjects if subj not in subjects_list]
|
||||
|
||||
def add_new_dropdown():
|
||||
options = [
|
||||
ft.dropdown.Option("Wähle deine Fächer aus", disabled=True)
|
||||
] + [
|
||||
ft.dropdown.Option(subj) for subj in get_available_subjects()
|
||||
]
|
||||
|
||||
dropdown = ft.Dropdown(
|
||||
width=250,
|
||||
options=options,
|
||||
value="Wähle deine Fächer aus",
|
||||
on_change=lambda e: on_dropdown_change(e, dropdown, plus_button)
|
||||
)
|
||||
|
||||
plus_button = ft.IconButton(
|
||||
icon=ft.icons.ADD,
|
||||
visible=False # Initially hidden
|
||||
)
|
||||
|
||||
delete_button = ft.IconButton(
|
||||
icon=ft.icons.CLOSE_OUTLINED,
|
||||
on_click=lambda e: delete_subject(dropdown, delete_button)
|
||||
)
|
||||
|
||||
plus_button.on_click = lambda e: append_subject(e, dropdown, plus_button)
|
||||
|
||||
# Add dropdown and buttons to the row
|
||||
row.controls.append(dropdown)
|
||||
row.controls.append(plus_button)
|
||||
row.controls.append(delete_button)
|
||||
page.update()
|
||||
|
||||
def register(e):
|
||||
print(subjects_list)
|
||||
username = username_field.value.strip()
|
||||
password0 = password_field0.value.strip()
|
||||
password1 = password_field1.value.strip()
|
||||
secret_teacher_password = secret_teacher_password_field.value.strip()
|
||||
last_name = last_name_field.value.strip()
|
||||
first_name = first_name_field.value.strip()
|
||||
|
||||
subjects_string = ", ".join(subjects_list)
|
||||
|
||||
if not username or not password0 or not secret_teacher_password or not password1 or not last_name or not first_name or not subjects_string:
|
||||
info_label.value = "Bitte fülle alle Felder aus"
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
#page.clean()
|
||||
#register_teacher_page(page=page)
|
||||
return
|
||||
|
||||
if not password0 == password1:
|
||||
info_label.value = "Passwörter stimmen nicht überein"
|
||||
info_label.color = "red"
|
||||
page.update()
|
||||
return
|
||||
|
||||
try:
|
||||
url = 'http://awesom-o.org:8000/teacher/register'
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
data = {
|
||||
'username': username,
|
||||
'password': password0,
|
||||
'teacher_secret_password': secret_teacher_password,
|
||||
'first_name': first_name,
|
||||
'last_name': last_name,
|
||||
'subjects': subjects_string
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
session_id = response.json().get("session_id")
|
||||
username = response.json().get("username")
|
||||
page.session.set("access_token", session_id)
|
||||
page.session.set("username", username)
|
||||
page.go("/")
|
||||
|
||||
else:
|
||||
error_message = response.json().get("detail", "Registrierung fehlgeschlagen")
|
||||
info_label.value = error_message
|
||||
info_label.color = "red"
|
||||
except requests.exceptions.RequestException as e:
|
||||
info_label.value = f"An error occurred: {str(e)}"
|
||||
info_label.color = "red"
|
||||
|
||||
page.update()
|
||||
|
||||
page.add(
|
||||
ft.Text("Daltonraum-Buchungssystem der IGS Garbsen", size=30),
|
||||
ft.Text("Registrieren", size=23, weight=ft.FontWeight.BOLD),
|
||||
username_field,
|
||||
ft.Row([
|
||||
first_name_field,
|
||||
last_name_field
|
||||
]),
|
||||
row,
|
||||
password_field0,
|
||||
password_field1,
|
||||
secret_teacher_password_field,
|
||||
ft.ElevatedButton("Registrieren", on_click=register),
|
||||
info_label,
|
||||
ft.TextButton("Kein Lehrer? Klicke hier!", on_click=lambda _: page.go("/register_student"))
|
||||
)
|
||||
|
||||
add_new_dropdown()
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import threading
|
||||
import asyncio
|
||||
import websockets
|
||||
from websockets import connect # Ensure this is imported
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
|
||||
def get_initials(user_name: str):
|
||||
if user_name:
|
||||
return user_name[0].capitalize()
|
||||
else:
|
||||
return "E" # or any default value you prefer
|
||||
|
||||
|
||||
def get_avatar_color(user_name: str):
|
||||
colors_lookup = [
|
||||
ft.colors.AMBER,
|
||||
ft.colors.BLUE,
|
||||
ft.colors.BROWN,
|
||||
ft.colors.CYAN,
|
||||
ft.colors.GREEN,
|
||||
ft.colors.INDIGO,
|
||||
ft.colors.LIME,
|
||||
ft.colors.ORANGE,
|
||||
ft.colors.PINK,
|
||||
ft.colors.PURPLE,
|
||||
ft.colors.RED,
|
||||
ft.colors.TEAL,
|
||||
]
|
||||
return colors_lookup[hash(user_name) % len(colors_lookup)]
|
||||
|
||||
|
||||
def student_main_page(page: ft.Page):
|
||||
page.clean()
|
||||
page.title = "Room Information"
|
||||
page.theme_mode = ft.ThemeMode.LIGHT
|
||||
|
||||
page.vertical_alignment = ft.MainAxisAlignment.START # Align all content to the top
|
||||
page.scroll = "adaptive"
|
||||
page.padding = 20
|
||||
|
||||
# Container for lessons
|
||||
lessons_container = ft.Column(
|
||||
alignment=ft.MainAxisAlignment.CENTER, spacing=20)
|
||||
|
||||
def test(e):
|
||||
print(e)
|
||||
page.go("/test")
|
||||
print("test")
|
||||
|
||||
def ausloggen(e=None):
|
||||
page.go("/login")
|
||||
print(page.session.get("access_token"))
|
||||
|
||||
def page_dialog_click(e, lesson_id):
|
||||
dialog_leave = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Bitte bestätige"),
|
||||
content=ft.Text("Möchtest du diesem Raum wirklich verlassen?"),
|
||||
actions=[
|
||||
ft.TextButton("Ja", on_click=lambda e: (page.close(
|
||||
dialog_leave), leave_current_room(e, lesson_id))),
|
||||
ft.TextButton(
|
||||
"Nein", on_click=lambda e: page.close(dialog_leave)),
|
||||
],
|
||||
actions_alignment=ft.MainAxisAlignment.END,
|
||||
)
|
||||
page.dialog = dialog_leave
|
||||
dialog_leave.open = True
|
||||
page.update()
|
||||
|
||||
def leave_current_room(e=None, unique_id=None):
|
||||
if not unique_id:
|
||||
page.snack_bar = ft.SnackBar(
|
||||
ft.Text("Kein Raum zum verlassen ausgewählt"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
return
|
||||
|
||||
url = f"http://awesom-o.org:8000/student/leave_room/?unique_id={
|
||||
unique_id}"
|
||||
headers = {"accept": "application/json",
|
||||
"Content-Type": "application/json"}
|
||||
data = {"session_id": page.session.get("access_token")}
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
if response:
|
||||
get_rooms()
|
||||
page.snack_bar = ft.SnackBar(
|
||||
ft.Text("Raum erfolgreich verlassen"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
get_rooms()
|
||||
except requests.RequestException as error:
|
||||
# Handle all bad responses
|
||||
error_detail = "Something went wrong."
|
||||
if hasattr(error, "response") and error.response:
|
||||
try:
|
||||
error_detail = error.response.json().get("detail", "Something went wrong.")
|
||||
except json.JSONDecodeError:
|
||||
error_detail = error.response.text or "Etwas ist schiefgelaufen"
|
||||
page.snack_bar = ft.SnackBar(ft.Text(f"Error: {error_detail}"))
|
||||
page.snack_bar.open = True
|
||||
page.update()
|
||||
|
||||
async def get_rooms():
|
||||
uri = "ws://localhost:8000/ws/student/my_room"
|
||||
headers = {"session-id": page.session.get("access_token")}
|
||||
|
||||
try:
|
||||
async with websockets.connect(uri, extra_headers=headers) as websocket:
|
||||
while True:
|
||||
message = await websocket.recv()
|
||||
data = json.loads(message)
|
||||
# Clear previous lessons
|
||||
lessons_container.controls.clear()
|
||||
|
||||
if "error" in data:
|
||||
# Show "No Rooms for Today" message
|
||||
lessons_container.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Text(
|
||||
"Für heute wurden noch keine Räume ausgewählt.",
|
||||
size=25,
|
||||
text_align="center",
|
||||
),
|
||||
border=ft.Border(
|
||||
top=ft.BorderSide(1, ft.colors.BLACK),
|
||||
bottom=ft.BorderSide(1, ft.colors.BLACK),
|
||||
left=ft.BorderSide(1, ft.colors.BLACK),
|
||||
right=ft.BorderSide(1, ft.colors.BLACK)
|
||||
),
|
||||
border_radius=20,
|
||||
padding=10,
|
||||
height=150,
|
||||
bgcolor=ft.colors.WHITE,
|
||||
alignment=ft.alignment.center,
|
||||
)
|
||||
)
|
||||
else:
|
||||
rooms = data.get("rooms", [])
|
||||
# Filter and sort lessons for today
|
||||
today = datetime.now().date()
|
||||
today_lessons = [
|
||||
room for room in rooms if room["lesson_date"] == str(today)]
|
||||
today_lessons.sort(key=lambda x: x["lesson_time"])
|
||||
|
||||
# Check if there are rooms on other dates
|
||||
other_date_rooms = [
|
||||
room for room in rooms if room["lesson_date"] != str(today)]
|
||||
if today_lessons:
|
||||
for lesson in today_lessons:
|
||||
if lesson['current_students'] == lesson['max_students']:
|
||||
BACKROUND_COLOR = "#f10d0c"
|
||||
TEXT_COLOR = ft.colors.WHITE
|
||||
else:
|
||||
BACKROUND_COLOR = "#729fcf"
|
||||
TEXT_COLOR = ft.colors.BLACK
|
||||
# Create lesson card
|
||||
room_card = ft.Card(
|
||||
content=ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
# Room details
|
||||
ft.Column(
|
||||
[
|
||||
# Top line:
|
||||
# room_number, info, location
|
||||
ft.Text(
|
||||
f"Heute, {
|
||||
lesson['lesson_time']} Uhr",
|
||||
size=16,
|
||||
color=TEXT_COLOR
|
||||
),
|
||||
|
||||
# Second line: first_name and last_name
|
||||
ft.Text(
|
||||
f"Lehrkraft: {lesson['first_name']} {
|
||||
lesson['last_name']} - {lesson['subjects']}",
|
||||
size=16,
|
||||
color=TEXT_COLOR
|
||||
),
|
||||
# Third line: lesson_time
|
||||
ft.Text(
|
||||
f"Raum {lesson['room_number']} {
|
||||
lesson['info']} | {lesson['location']}",
|
||||
size=16,
|
||||
color=TEXT_COLOR
|
||||
),
|
||||
|
||||
ft.Text(
|
||||
f"Belegte Plätze: {
|
||||
lesson['current_students']}/{lesson['max_students']}",
|
||||
size=16,
|
||||
color=TEXT_COLOR
|
||||
),
|
||||
],
|
||||
expand=True,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
),
|
||||
# IconButton on the right
|
||||
ft.Column(
|
||||
[
|
||||
ft.TextButton("Aus Raum abmelden", on_click=lambda e, lesson_id=lesson['unique_id']: page_dialog_click(e, lesson_id),
|
||||
style=ft.ButtonStyle(
|
||||
bgcolor=ft.colors.WHITE,
|
||||
color=ft.colors.BLACK,
|
||||
shape=ft.RoundedRectangleBorder(
|
||||
radius=5),
|
||||
padding=20,
|
||||
)
|
||||
),
|
||||
ft.Text("ACHTUNG: Hierdurch wird", size=12), ft.Text(
|
||||
"dein reservierter Platz für", size=12), ft.Text("jemand anderes freigegeben.", size=12),
|
||||
]
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
padding=20, # Add padding inside the card
|
||||
),
|
||||
color=BACKROUND_COLOR,
|
||||
margin=10,
|
||||
)
|
||||
lessons_container.controls.append(room_card)
|
||||
else:
|
||||
# Show "No Rooms for Today" message
|
||||
lessons_container.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Text(
|
||||
"Für heute wurden noch keine Räume ausgewählt.",
|
||||
size=25,
|
||||
text_align="center",
|
||||
),
|
||||
border=ft.Border(
|
||||
top=ft.BorderSide(1, ft.colors.BLACK),
|
||||
bottom=ft.BorderSide(
|
||||
1, ft.colors.BLACK),
|
||||
left=ft.BorderSide(1, ft.colors.BLACK),
|
||||
right=ft.BorderSide(1, ft.colors.BLACK)
|
||||
),
|
||||
border_radius=20,
|
||||
padding=10,
|
||||
height=150,
|
||||
bgcolor=ft.colors.WHITE,
|
||||
alignment=ft.alignment.center,
|
||||
)
|
||||
)
|
||||
|
||||
# Update page
|
||||
page.update()
|
||||
except Exception as e:
|
||||
lessons_container.controls.clear()
|
||||
lessons_container.controls.append(
|
||||
ft.Text(f"Error: {e}", size=50, weight="bold", text_align="center"))
|
||||
page.update()
|
||||
|
||||
def start_get_rooms():
|
||||
# Ensure a new event loop in the thread
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(get_rooms())
|
||||
|
||||
# Start the WebSocket client in a thread
|
||||
import threading
|
||||
threading.Thread(target=start_get_rooms, daemon=True).start()
|
||||
|
||||
join_more_rooms_button = ft.Container(
|
||||
content=ft.Text("Räume suchen", size=24, color=ft.colors.BLUE_900),
|
||||
on_click=lambda e: page.go("/join"),
|
||||
alignment=ft.alignment.center,
|
||||
width=200,
|
||||
height=60,
|
||||
bgcolor='transparent',
|
||||
ink=True,
|
||||
)
|
||||
|
||||
navigation_bar = ft.Row(
|
||||
[ft.Text("Home", size=23, weight=ft.FontWeight.BOLD,
|
||||
width=100), join_more_rooms_button],
|
||||
)
|
||||
|
||||
# Add components to page
|
||||
page.add(
|
||||
ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(
|
||||
"Daltonraum-Buchungssystem der IGS Garbsen", size=30),
|
||||
ft.Column([
|
||||
ft.PopupMenuButton(
|
||||
items=[
|
||||
ft.PopupMenuItem(content=ft.Text(f"Eingeloggt als: {page.session.get("username")}", weight=ft.FontWeight.BOLD)),
|
||||
ft.PopupMenuItem(text="Profil anzeigen", on_click=test),
|
||||
ft.PopupMenuItem(text="Ausloggen", on_click=ausloggen),
|
||||
],
|
||||
content=ft.CircleAvatar(
|
||||
content=ft.Text(get_initials(
|
||||
page.session.get("username"))),
|
||||
color=ft.colors.WHITE,
|
||||
bgcolor=get_avatar_color(
|
||||
page.session.get("username")),
|
||||
),
|
||||
menu_position=ft.PopupMenuPosition.UNDER,
|
||||
tooltip="",
|
||||
|
||||
)
|
||||
|
||||
],
|
||||
# Align column content to the end (optional)
|
||||
alignment=ft.MainAxisAlignment.END,
|
||||
),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN, # Space out the two elements
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER, # Align items vertically
|
||||
),
|
||||
|
||||
navigation_bar,
|
||||
ft.Row( # Wrap the Text in a Row
|
||||
[
|
||||
ft.Text(
|
||||
"Du hast dich heute in folgende Daltonräume eingetragen:", size=25)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER # Center the Text horizontally
|
||||
),
|
||||
ft.Column(
|
||||
[
|
||||
lessons_container,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
expand=True, # Make it take available space vertically
|
||||
),
|
||||
],
|
||||
expand=True, # This makes the outer column take available space as well
|
||||
alignment=ft.MainAxisAlignment.CENTER, # Center the whole column vertically
|
||||
)
|
||||
)
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
import flet as ft
|
||||
|
||||
def main(page: ft.Page):
|
||||
# Button click handler to launch the download URL
|
||||
def on_download_click(e):
|
||||
# Launch the URL to trigger file download
|
||||
page.launch_url("http://awesom-o.org:8000/download/tedt.pdf")
|
||||
|
||||
# Create an ElevatedButton that will trigger the download
|
||||
download_button = ft.ElevatedButton("Download myfile", on_click=on_download_click)
|
||||
|
||||
# Add the button to the page
|
||||
page.add(download_button)
|
||||
|
||||
ft.app(target=main)
|
||||
Reference in New Issue
Block a user