## type: ignore import kivy from kivy.app import App from kivy.graphics import Color, RoundedRectangle from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition from kivy.uix.boxlayout import BoxLayout from kivy.uix.gridlayout import GridLayout from kivy.uix.scrollview import ScrollView from kivy.uix.label import Label from kivy.uix.button import Button from kivy.uix.textinput import TextInput from kivy.uix.popup import Popup from kivy.core.window import Window from utils.helpers import format_currency, format_date, validate_pin, parse_price class RoundedButton(Button): def __init__(self, **kwargs): self.btn_color = kwargs.pop("btn_color", [0.0, 0.65, 0.57, 1]) super(RoundedButton, self).__init__(**kwargs) self.background_normal = "" self.background_color = [0, 0, 0, 0] self.color = [1, 1, 1, 1] self.bold = True with self.canvas.before: self.color_instruction = Color(*self.btn_color) self.rect = RoundedRectangle(pos=self.pos, size=self.size, radius=[8]) self.bind(pos=self.update_rect, size=self.update_rect, state=self.update_state) def update_rect(self, instance, value): self.rect.pos = self.pos self.rect.size = self.size def update_state(self, instance, value): if value == "down": self.color_instruction.rgba = [ self.btn_color[0] * 0.8, self.btn_color[1] * 0.8, self.btn_color[2] * 0.8, 1, ] else: self.color_instruction.rgba = self.btn_color class MessagePopup(Popup): def __init__(self, title, message, **kwargs): content = BoxLayout(orientation="vertical", padding=20, spacing=20) lbl = Label(text=message, font_size="16sp", halign="center", valign="middle") lbl.bind(size=lbl.setter("text_size")) content.add_widget(lbl) btn = RoundedButton( text="OK", size_hint_y=None, height="50dp", btn_color=[0.0, 0.65, 0.57, 1] ) content.add_widget(btn) super(MessagePopup, self).__init__( title=title, content=content, size_hint=(0.85, None), height="250dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) btn.bind(on_release=self.dismiss) class ConfirmPopup(Popup): def __init__(self, title, message, on_confirm, **kwargs): self.on_confirm = on_confirm content = BoxLayout(orientation="vertical", padding=20, spacing=20) lbl = Label(text=message, font_size="16sp", halign="center", valign="middle") lbl.bind(size=lbl.setter("text_size")) content.add_widget(lbl) btn_box = BoxLayout( orientation="horizontal", spacing=10, size_hint_y=None, height="50dp" ) yes_btn = RoundedButton(text="Ja", btn_color=[0.0, 0.65, 0.57, 1]) no_btn = RoundedButton(text="Nein", btn_color=[0.25, 0.25, 0.28, 1]) btn_box.add_widget(yes_btn) btn_box.add_widget(no_btn) content.add_widget(btn_box) super(ConfirmPopup, self).__init__( title=title, content=content, size_hint=(0.85, None), height="250dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) yes_btn.bind(on_release=self._confirm) no_btn.bind(on_release=self.dismiss) def _confirm(self, instance): self.dismiss() self.on_confirm() class NumpadPopup(Popup): def __init__( self, title, is_password=False, is_float=False, on_submit=None, **kwargs ): self.is_password = is_password self.is_float = is_float self.on_submit = on_submit self.entered_text = "" content = BoxLayout(orientation="vertical", padding=15, spacing=15) self.display = Label( text="PIN eingeben:" if is_password else "Betrag eingeben:", font_size="20sp", bold=True, size_hint_y=0.15, halign="center", valign="middle", ) self.display.bind(size=self.display.setter("text_size")) content.add_widget(self.display) grid = GridLayout(cols=3, spacing=10, size_hint_y=0.6) buttons = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "," if is_float else "C", "0", "<-", ] for btn_text in buttons: btn = RoundedButton( text=btn_text, btn_color=[0.15, 0.15, 0.17, 1] if btn_text in ["C", "<-", ","] else [0.0, 0.65, 0.57, 1], ) btn.bind(on_release=self.on_key_press) grid.add_widget(btn) content.add_widget(grid) actions = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25) ok_btn = RoundedButton(text="OK", btn_color=[0.0, 0.65, 0.57, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) actions.add_widget(ok_btn) actions.add_widget(cancel_btn) content.add_widget(actions) super(NumpadPopup, self).__init__( title=title, content=content, size_hint=(0.85, 0.75), title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) ok_btn.bind(on_release=self.submit) cancel_btn.bind(on_release=self.dismiss) def on_key_press(self, instance): key = instance.text if key == "<-": self.entered_text = self.entered_text[:-1] elif key == "C": self.entered_text = "" elif key == ",": if "," not in self.entered_text: self.entered_text += "," else: self.entered_text += key self.update_display() def update_display(self): if not self.entered_text: self.display.text = ( "PIN eingeben:" if self.is_password else "Betrag eingeben:" ) else: if self.is_password: self.display.text = "*" * len(self.entered_text) else: self.display.text = self.entered_text def submit(self, instance): self.dismiss() if self.on_submit: self.on_submit(self.entered_text) class KeyboardPopup(Popup): def __init__(self, title, is_password=False, on_submit=None, **kwargs): self.is_password = is_password self.on_submit = on_submit self.entered_text = "" self.is_shift = False self.is_symbols = False content = BoxLayout(orientation="vertical", padding=15, spacing=15) self.display = Label( text="Eingeben:" if not is_password else "****", font_size="20sp", bold=True, size_hint_y=0.15, halign="center", valign="middle", ) self.display.bind(size=self.display.setter("text_size")) content.add_widget(self.display) self.kbd_layout = BoxLayout(orientation="vertical", spacing=5, size_hint_y=0.6) self.build_keyboard() content.add_widget(self.kbd_layout) actions = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25) ok_btn = RoundedButton(text="OK", btn_color=[0.0, 0.65, 0.57, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) actions.add_widget(ok_btn) actions.add_widget(cancel_btn) content.add_widget(actions) super(KeyboardPopup, self).__init__( title=title, content=content, size_hint=(0.95, 0.85), title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) ok_btn.bind(on_release=self.submit) cancel_btn.bind(on_release=self.dismiss) def build_keyboard(self): self.kbd_layout.clear_widgets() if self.is_symbols: rows = [ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], ["+", "-", "*", "/", "=", "%", "<", ">", "[", "]"], ["@", "#", "$", "&", "(", ")", "_", "\"", "'", ";"], ["ABC", "?", "!", ",", ".", ":", "\\", "<-", "C"], ["Leertaste"] ] else: if self.is_shift: rows = [ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], ["Q", "W", "E", "R", "T", "Z", "U", "I", "O", "P"], ["A", "S", "D", "F", "G", "H", "J", "K", "L"], ["Shift", "Y", "X", "C", "V", "B", "N", "M", "<-", "C"], ["Sym", "Leertaste", "-", "_", "."] ] else: rows = [ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], ["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"], ["a", "s", "d", "f", "g", "h", "j", "k", "l"], ["shift", "y", "x", "c", "v", "b", "n", "m", "<-", "C"], ["Sym", "Leertaste", "-", "_", "."] ] for row in rows: row_box = BoxLayout(orientation="horizontal", spacing=5) for char in row: if char in ["C", "<-", "shift", "Shift", "Sym", "ABC", "Leertaste"]: btn_color = [0.25, 0.25, 0.28, 1] else: btn_color = [0.0, 0.65, 0.57, 1] if (char == "shift" or char == "Shift") and self.is_shift: btn_color = [0.0, 0.45, 0.4, 1] elif char == "Sym" and self.is_symbols: btn_color = [0.0, 0.45, 0.4, 1] btn = RoundedButton( text=char, btn_color=btn_color, size_hint_x=2.5 if char == "Leertaste" else 1.0 ) btn.bind(on_release=self.on_key_press) row_box.add_widget(btn) self.kbd_layout.add_widget(row_box) def on_key_press(self, instance): key = instance.text if key == "<-": self.entered_text = self.entered_text[:-1] elif key == "C": self.entered_text = "" elif key in ["shift", "Shift"]: self.is_shift = not self.is_shift self.build_keyboard() return elif key == "Sym": self.is_symbols = True self.build_keyboard() return elif key == "ABC": self.is_symbols = False self.build_keyboard() return elif key == "Leertaste": self.entered_text += " " else: self.entered_text += key self.update_display() def update_display(self): if not self.entered_text: self.display.text = "Eingeben:" if not self.is_password else "****" else: if self.is_password: self.display.text = "*" * len(self.entered_text) else: self.display.text = self.entered_text def submit(self, instance): self.dismiss() if self.on_submit: self.on_submit(self.entered_text) class AdjustDebtPopup(Popup): def __init__(self, user_id, user_name, current_debt, on_adjust, **kwargs): self.on_adjust = on_adjust self.user_id = user_id content = BoxLayout(orientation="vertical", padding=15, spacing=15) info = Label( text=f"Benutzer: {user_name}\nAktuelle Schulden: {current_debt:.2f} €\n\nGezahlter Betrag (€):", halign="center", valign="middle", size_hint_y=0.4, ) info.bind(size=info.setter("text_size")) content.add_widget(info) self.amount_input = TextInput( text="", multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.25, padding=[10, 10], ) self.amount_input.bind(focus=self.trigger_numpad) content.add_widget(self.amount_input) btn_box = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.35) save_btn = RoundedButton(text="Speichern", btn_color=[0.0, 0.65, 0.57, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) btn_box.add_widget(save_btn) btn_box.add_widget(cancel_btn) content.add_widget(btn_box) super(AdjustDebtPopup, self).__init__( title="Schulden anpassen", content=content, size_hint=(0.85, None), height="300dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) save_btn.bind(on_release=self._save) cancel_btn.bind(on_release=self.dismiss) def trigger_numpad(self, instance, value): if value: instance.focus = False popup = NumpadPopup( title="Betrag eingeben", is_password=False, is_float=True, on_submit=self.on_amount_submitted, ) popup.open() def on_amount_submitted(self, text): self.amount_input.text = text def _save(self, instance): val = self.amount_input.text.strip() try: amount = float(val.replace(",", ".")) if amount < 0: raise ValueError self.dismiss() self.on_adjust(self.user_id, amount) except ValueError: err = MessagePopup(title="Fehler", message="Ungültiger Betrag!") err.open() class CreateUserPopup(Popup): def __init__(self, on_create, **kwargs): self.on_create = on_create content = BoxLayout(orientation="vertical", padding=15, spacing=15) info = Label( text="Name des neuen Benutzers eingeben:", font_size="16sp", size_hint_y=0.25, halign="center", valign="middle", ) info.bind(size=info.setter("text_size")) content.add_widget(info) self.name_input = TextInput( text="", multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.3, padding=[10, 10], ) self.name_input.bind(focus=self.trigger_keyboard) content.add_widget(self.name_input) btn_box = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.45) save_btn = RoundedButton(text="Erstellen", btn_color=[0.0, 0.65, 0.57, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) btn_box.add_widget(save_btn) btn_box.add_widget(cancel_btn) content.add_widget(btn_box) super(CreateUserPopup, self).__init__( title="Benutzer hinzufügen", content=content, size_hint=(0.85, None), height="260dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) save_btn.bind(on_release=self._save) cancel_btn.bind(on_release=self.dismiss) def trigger_keyboard(self, instance, value): if value: instance.focus = False popup = KeyboardPopup( title="Name eingeben", is_password=False, on_submit=self.on_name_submitted, ) popup.open() def on_name_submitted(self, text): self.name_input.text = text def _save(self, instance): name = self.name_input.text.strip() if not name: err = MessagePopup(title="Fehler", message="Name darf nicht leer sein!") err.open() return self.dismiss() self.on_create(name) class EditUserPopup(Popup): def __init__( self, user_id, current_name, current_pin, on_save, on_delete, **kwargs ): self.user_id = user_id self.on_save = on_save self.on_delete = on_delete content = BoxLayout(orientation="vertical", padding=15, spacing=15) info = Label( text="Benutzer bearbeiten:", font_size="16sp", size_hint_y=0.15, halign="center", valign="middle", ) info.bind(size=info.setter("text_size")) content.add_widget(info) content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left")) self.name_input = TextInput( text=current_name, multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.15, padding=[10, 10], ) self.name_input.bind(focus=self.trigger_keyboard) content.add_widget(self.name_input) content.add_widget(Label(text="PIN:", size_hint_y=0.08, halign="left")) self.pin_input = TextInput( text=current_pin, multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.15, padding=[10, 10], ) self.pin_input.bind(focus=self.trigger_numpad) content.add_widget(self.pin_input) btn_box = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25) save_btn = RoundedButton(text="Speichern", btn_color=[0.0, 0.65, 0.57, 1]) delete_btn = RoundedButton(text="Löschen", btn_color=[0.85, 0.35, 0.35, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) btn_box.add_widget(save_btn) btn_box.add_widget(delete_btn) btn_box.add_widget(cancel_btn) content.add_widget(btn_box) super(EditUserPopup, self).__init__( title="Benutzer bearbeiten", content=content, size_hint=(0.85, None), height="400dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) save_btn.bind(on_release=self._save) delete_btn.bind(on_release=self._delete) cancel_btn.bind(on_release=self.dismiss) def trigger_keyboard(self, instance, value): if value: instance.focus = False popup = KeyboardPopup( title="Name eingeben", is_password=False, on_submit=self.on_name_submitted, ) popup.open() def on_name_submitted(self, text): self.name_input.text = text def trigger_numpad(self, instance, value): if value: instance.focus = False popup = NumpadPopup( title="PIN eingeben", is_password=False, on_submit=self.on_pin_submitted ) popup.open() def on_pin_submitted(self, text): self.pin_input.text = text def _save(self, instance): name = self.name_input.text.strip() pin = self.pin_input.text.strip() if not name: err = MessagePopup(title="Fehler", message="Name darf nicht leer sein!") err.open() return if len(pin) < 4: err = MessagePopup( title="Fehler", message="Die PIN must mindestens 4 Ziffern lang sein!" ) err.open() return self.dismiss() self.on_save(self.user_id, name, pin) def _delete(self, instance): self.dismiss() self.on_delete(self.user_id) class CreateItemPopup(Popup): def __init__(self, on_create, **kwargs): self.on_create = on_create content = BoxLayout(orientation="vertical", padding=15, spacing=15) info = Label( text="Neuen Artikel hinzufügen:", font_size="16sp", size_hint_y=0.15, halign="center", valign="middle", ) info.bind(size=info.setter("text_size")) content.add_widget(info) content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left")) self.name_input = TextInput( text="", multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.15, padding=[10, 10], ) self.name_input.bind(focus=self.trigger_keyboard) content.add_widget(self.name_input) content.add_widget(Label(text="Preis (€):", size_hint_y=0.08, halign="left")) self.price_input = TextInput( text="", multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.15, padding=[10, 10], ) self.price_input.bind(focus=self.trigger_numpad) content.add_widget(self.price_input) btn_box = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25) save_btn = RoundedButton(text="Erstellen", btn_color=[0.0, 0.65, 0.57, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) btn_box.add_widget(save_btn) btn_box.add_widget(cancel_btn) content.add_widget(btn_box) super(CreateItemPopup, self).__init__( title="Artikel hinzufügen", content=content, size_hint=(0.85, None), height="380dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) save_btn.bind(on_release=self._save) cancel_btn.bind(on_release=self.dismiss) def trigger_keyboard(self, instance, value): if value: instance.focus = False popup = KeyboardPopup( title="Artikelname eingeben", is_password=False, on_submit=self.on_name_submitted, ) popup.open() def on_name_submitted(self, text): self.name_input.text = text def trigger_numpad(self, instance, value): if value: instance.focus = False popup = NumpadPopup( title="Preis eingeben", is_password=False, is_float=True, on_submit=self.on_price_submitted, ) popup.open() def on_price_submitted(self, text): self.price_input.text = text def _save(self, instance): name = self.name_input.text.strip() price_str = self.price_input.text.strip().replace(",", ".") if not name: err = MessagePopup(title="Fehler", message="Name darf nicht leer sein!") err.open() return try: price = float(price_str) if price < 0: raise ValueError except ValueError: err = MessagePopup(title="Fehler", message="Ungültiger Preis!") err.open() return self.dismiss() self.on_create(name, price) class EditItemPopup(Popup): def __init__( self, item_id, current_name, current_price, on_save, on_delete, **kwargs ): self.item_id = item_id self.on_save = on_save self.on_delete = on_delete content = BoxLayout(orientation="vertical", padding=15, spacing=15) info = Label( text="Artikel bearbeiten:", font_size="16sp", size_hint_y=0.15, halign="center", valign="middle", ) info.bind(size=info.setter("text_size")) content.add_widget(info) content.add_widget(Label(text="Name:", size_hint_y=0.08, halign="left")) self.name_input = TextInput( text=current_name, multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.15, padding=[10, 10], ) self.name_input.bind(focus=self.trigger_keyboard) content.add_widget(self.name_input) content.add_widget(Label(text="Preis (€):", size_hint_y=0.08, halign="left")) self.price_input = TextInput( text=f"{current_price:.2f}".replace(".", ","), multiline=False, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], size_hint_y=0.15, padding=[10, 10], ) self.price_input.bind(focus=self.trigger_numpad) content.add_widget(self.price_input) btn_box = BoxLayout(orientation="horizontal", spacing=10, size_hint_y=0.25) save_btn = RoundedButton(text="Speichern", btn_color=[0.0, 0.65, 0.57, 1]) delete_btn = RoundedButton(text="Löschen", btn_color=[0.85, 0.35, 0.35, 1]) cancel_btn = RoundedButton(text="Abbrechen", btn_color=[0.25, 0.25, 0.28, 1]) btn_box.add_widget(save_btn) btn_box.add_widget(delete_btn) btn_box.add_widget(cancel_btn) content.add_widget(btn_box) super(EditItemPopup, self).__init__( title="Artikel bearbeiten", content=content, size_hint=(0.85, None), height="400dp", title_align="center", title_size="18sp", background_color=[0.08, 0.08, 0.09, 0.95], **kwargs, ) save_btn.bind(on_release=self._save) delete_btn.bind(on_release=self._delete) cancel_btn.bind(on_release=self.dismiss) def trigger_keyboard(self, instance, value): if value: instance.focus = False popup = KeyboardPopup( title="Artikelname eingeben", is_password=False, on_submit=self.on_name_submitted, ) popup.open() def on_name_submitted(self, text): self.name_input.text = text def trigger_numpad(self, instance, value): if value: instance.focus = False popup = NumpadPopup( title="Preis eingeben", is_password=False, is_float=True, on_submit=self.on_price_submitted, ) popup.open() def on_price_submitted(self, text): self.price_input.text = text def _save(self, instance): name = self.name_input.text.strip() price_str = self.price_input.text.strip().replace(",", ".") if not name: err = MessagePopup(title="Fehler", message="Name darf nicht leer sein!") err.open() return try: price = float(price_str) if price < 0: raise ValueError except ValueError: err = MessagePopup(title="Fehler", message="Ungültiger Preis!") err.open() return self.dismiss() self.on_save(self.item_id, name, price) def _delete(self, instance): self.dismiss() self.on_delete(self.item_id) class DebtRow(BoxLayout): def __init__(self, name, amount, **kwargs): super(DebtRow, self).__init__( orientation="horizontal", size_hint_y=None, height="45dp", padding=[15, 0], spacing=10, **kwargs, ) with self.canvas.before: Color(0.15, 0.15, 0.17, 1) self.rect = RoundedRectangle(pos=self.pos, size=self.size, radius=[6]) self.bind(pos=self.update_rect, size=self.update_rect) name_lbl = Label(text=name, font_size="16sp", halign="left", valign="middle") name_lbl.bind(size=name_lbl.setter("text_size")) amount_lbl = Label( text=f"{amount:.2f} €", font_size="16sp", halign="right", valign="middle", bold=True, ) amount_lbl.bind(size=amount_lbl.setter("text_size")) self.add_widget(name_lbl) self.add_widget(amount_lbl) def update_rect(self, instance, value): self.rect.pos = self.pos self.rect.size = self.size class TransactionRow(BoxLayout): def __init__(self, date, user_name, item_name, amount, **kwargs): super(TransactionRow, self).__init__( orientation="horizontal", size_hint_y=None, height="50dp", padding=[15, 5], spacing=10, **kwargs, ) with self.canvas.before: Color(0.15, 0.15, 0.17, 1) self.rect = RoundedRectangle(pos=self.pos, size=self.size, radius=[6]) self.bind(pos=self.update_rect, size=self.update_rect) date_lbl = Label( text=format_date(date), font_size="14sp", size_hint_x=0.25, halign="left", color=[0.7, 0.7, 0.7, 1], valign="middle", ) name_lbl = Label( text=user_name, font_size="15sp", size_hint_x=0.35, halign="left", bold=True, valign="middle", ) item_lbl = Label( text=item_name, font_size="14sp", size_hint_x=0.25, halign="left", valign="middle", ) amount_lbl = Label( text=f"{amount:.2f} €", font_size="15sp", size_hint_x=0.15, halign="right", color=[0.3, 0.9, 0.3, 1], bold=True, valign="middle", ) date_lbl.bind(size=date_lbl.setter("text_size")) name_lbl.bind(size=name_lbl.setter("text_size")) item_lbl.bind(size=item_lbl.setter("text_size")) amount_lbl.bind(size=amount_lbl.setter("text_size")) self.add_widget(date_lbl) self.add_widget(name_lbl) self.add_widget(item_lbl) self.add_widget(amount_lbl) def update_rect(self, instance, value): self.rect.pos = self.pos self.rect.size = self.size class StyledScreen(Screen): def __init__(self, title_text, show_back=False, back_screen=None, **kwargs): super(StyledScreen, self).__init__(**kwargs) self.title_text = title_text self.show_back = show_back self.back_screen = back_screen self.layout = BoxLayout(orientation="vertical", padding=20, spacing=15) header = BoxLayout( orientation="horizontal", size_hint_y=None, height="60dp", spacing=10 ) if self.show_back and self.back_screen: back_btn = RoundedButton( text="Zurück", size_hint_x=0.25, btn_color=[0.25, 0.25, 0.28, 1] ) back_btn.bind(on_release=self.go_back) header.add_widget(back_btn) self.title_label = Label( text=self.title_text, font_size="22sp", bold=True, color=[1, 1, 1, 1], halign="center" if not (self.show_back and self.back_screen) else "left", valign="middle", ) self.title_label.bind(size=self.title_label.setter("text_size")) header.add_widget(self.title_label) self.layout.add_widget(header) self.content_layout = BoxLayout(orientation="vertical", spacing=15) self.layout.add_widget(self.content_layout) self.add_widget(self.layout) def go_back(self, instance): if self.back_screen: self.manager.current = self.back_screen class MainMenuScreen(StyledScreen): def __init__(self, **kwargs): super(MainMenuScreen, self).__init__(title_text="Getränkeliste", **kwargs) self.content_layout.padding = [0, 50, 0, 50] self.content_layout.spacing = 20 btn_user = RoundedButton( text="Benutzer auswählen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_user.bind(on_release=self.go_user_selection) self.content_layout.add_widget(btn_user) btn_admin = RoundedButton( text="Admin-Bereich", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_admin.bind(on_release=self.go_admin) self.content_layout.add_widget(btn_admin) def go_user_selection(self, instance): self.manager.current = "user_selection" def go_admin(self, instance): app = App.get_running_app() if app.admin_password: self.manager.current = "admin_menu" else: self.manager.current = "admin_password" class UserSelectionScreen(StyledScreen): def __init__(self, **kwargs): super(UserSelectionScreen, self).__init__( title_text="Benutzer auswählen", show_back=True, back_screen="main_menu", **kwargs, ) self.scroll = ScrollView() self.user_layout = BoxLayout( orientation="vertical", spacing=10, size_hint_y=None ) self.user_layout.bind(minimum_height=self.user_layout.setter("height")) self.scroll.add_widget(self.user_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.user_layout.clear_widgets() app = App.get_running_app() users = app.tracker.get_all_users() for user_id, user in users.items(): btn = RoundedButton( text=user.name, size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.0, 0.65, 0.57, 1], ) btn.bind(on_release=lambda instance, uid=user_id: self.select_user(uid)) self.user_layout.add_widget(btn) def select_user(self, user_id): app = App.get_running_app() user = app.tracker.get_user_by_id(user_id) if not user.pin: popup = NumpadPopup( title=f"PIN einrichten: {user.name}", is_password=True, on_submit=lambda pin: self.setup_pin_confirm(user_id, pin), ) popup.open() else: popup = NumpadPopup( title=f"PIN eingeben: {user.name}", is_password=True, on_submit=lambda pin: self.verify_pin(user_id, pin), ) popup.open() def setup_pin_confirm(self, user_id, pin): if len(pin) < 4: err = MessagePopup( title="Fehler", message="Die PIN muss mindestens 4 Ziffern lang sein!" ) err.open() return popup = NumpadPopup( title="PIN bestätigen", is_password=True, on_submit=lambda confirm_pin: self.save_new_pin(user_id, pin, confirm_pin), ) popup.open() def save_new_pin(self, user_id, pin, confirm_pin): if pin != confirm_pin: err = MessagePopup(title="Fehler", message="PINs stimmen nicht überein!") err.open() return app = App.get_running_app() app.tracker.set_user_pin(user_id, pin) app.current_user = user_id self.manager.current = "user_menu" def verify_pin(self, user_id, pin): app = App.get_running_app() user = app.tracker.get_user_by_id(user_id) if user.pin == pin: app.current_user = user_id self.manager.current = "user_menu" else: err = MessagePopup(title="Fehler", message="Falsche PIN!") err.open() class UserMenuScreen(StyledScreen): def __init__(self, **kwargs): super(UserMenuScreen, self).__init__(title_text="Benutzermenü", **kwargs) self.info_label = Label( text="", font_size="18sp", halign="center", valign="middle", size_hint_y=None, height="120dp", markup=True, ) self.info_label.bind(size=self.info_label.setter("text_size")) self.content_layout.add_widget(self.info_label) self.content_layout.spacing = 15 btn_purchase = RoundedButton( text="Kauf hinzufügen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_purchase.bind(on_release=self.go_add_purchase) self.content_layout.add_widget(btn_purchase) btn_change_pin = RoundedButton( text="PIN ändern", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_change_pin.bind(on_release=self.change_pin) self.content_layout.add_widget(btn_change_pin) btn_logout = RoundedButton( text="Abmelden", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.25, 0.25, 0.28, 1], ) btn_logout.bind(on_release=self.logout) self.content_layout.add_widget(btn_logout) def on_enter(self, *args): app = App.get_running_app() if not app.current_user: self.manager.current = "main_menu" return user = app.tracker.get_user_by_id(app.current_user) debt = app.tracker.get_user_debt(app.current_user) self.title_label.text = f"Menü - {user.name}" self.info_label.text = f"Eingeloggt als: [b]{user.name}[/b]\n\nAktuelle Schulden:\n[b][color=ff3333]{debt:.2f} €[/color][/b]" def go_add_purchase(self, instance): self.manager.current = "add_purchase" def change_pin(self, instance): popup = NumpadPopup( title="Aktuelle PIN eingeben", is_password=True, on_submit=lambda pin: self.verify_current_pin(pin), ) popup.open() def verify_current_pin(self, pin): app = App.get_running_app() user = app.tracker.get_user_by_id(app.current_user) if user.pin == pin: popup = NumpadPopup( title="Neue PIN eingeben", is_password=True, on_submit=lambda new_pin: self.setup_new_pin(new_pin), ) popup.open() else: err = MessagePopup(title="Fehler", message="Falsche PIN!") err.open() def setup_new_pin(self, new_pin): if len(new_pin) < 4: err = MessagePopup( title="Fehler", message="Die PIN muss mindestens 4 Ziffern lang sein!" ) err.open() return popup = NumpadPopup( title="Neue PIN bestätigen", is_password=True, on_submit=lambda confirm_pin: self.save_changed_pin(new_pin, confirm_pin), ) popup.open() def save_changed_pin(self, new_pin, confirm_pin): if new_pin != confirm_pin: err = MessagePopup(title="Fehler", message="PINs stimmen nicht überein!") err.open() return app = App.get_running_app() app.tracker.set_user_pin(app.current_user, new_pin) success = MessagePopup(title="Erfolg", message="PIN erfolgreich geändert!") success.open() def logout(self, instance): app = App.get_running_app() app.current_user = None self.manager.current = "main_menu" class AddPurchaseScreen(StyledScreen): def __init__(self, **kwargs): super(AddPurchaseScreen, self).__init__( title_text="Kauf hinzufügen", show_back=True, back_screen="user_menu", **kwargs, ) self.scroll = ScrollView() self.items_layout = BoxLayout( orientation="vertical", spacing=10, size_hint_y=None ) self.items_layout.bind(minimum_height=self.items_layout.setter("height")) self.scroll.add_widget(self.items_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.items_layout.clear_widgets() app = App.get_running_app() items = app.tracker.get_items() for item_id, item in items.items(): btn = RoundedButton( text=f"{item.name} - {item.price:.2f} €", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.0, 0.65, 0.57, 1], ) btn.bind( on_release=lambda instance, iid=item_id: self.confirm_purchase_popup( iid ) ) self.items_layout.add_widget(btn) def confirm_purchase_popup(self, item_id): app = App.get_running_app() user = app.tracker.get_user_by_id(app.current_user) item = app.tracker.get_items()[item_id] popup = ConfirmPopup( title="Kauf bestätigen", message=f"Möchtest du {item.name} für {item.price:.2f} € zu den Schulden von {user.name} hinzufügen?", on_confirm=lambda: self.execute_purchase(item_id), ) popup.open() def execute_purchase(self, item_id): app = App.get_running_app() result = app.tracker.add_purchase(app.current_user, item_id) if result["status"] == "success": popup = MessagePopup( title="Erfolg", message=f"Kauf erfolgreich!\n\nNeuer Schuldenstand: {result['new_debt']:.2f} €", ) popup.bind(on_dismiss=lambda instance: app.motor.lock_async()) app.motor.unlock_async() popup.open() self.manager.current = "user_menu" else: popup = MessagePopup(title="Fehler", message=f"Fehler: {result['message']}") popup.open() class AllDebtsScreen(StyledScreen): def __init__(self, **kwargs): super(AllDebtsScreen, self).__init__( title_text="Alle Schulden", show_back=True, back_screen="main_menu", **kwargs, ) self.scroll = ScrollView() self.debts_layout = BoxLayout( orientation="vertical", spacing=8, size_hint_y=None ) self.debts_layout.bind(minimum_height=self.debts_layout.setter("height")) self.scroll.add_widget(self.debts_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.debts_layout.clear_widgets() app = App.get_running_app() all_debts = app.tracker.get_all_debts() if not all_debts: no_debts = Label( text="Keine Schulden registriert.", font_size="16sp", size_hint_y=None, height="40dp", ) self.debts_layout.add_widget(no_debts) return total = 0.0 for user_id, debt_info in all_debts.items(): name = debt_info["name"] amount = debt_info["debt"] total += amount row = DebtRow(name=name, amount=amount) self.debts_layout.add_widget(row) divider = Label( text="-" * 40, size_hint_y=None, height="20dp", color=[0.5, 0.5, 0.5, 1] ) self.debts_layout.add_widget(divider) total_row = BoxLayout( orientation="horizontal", size_hint_y=None, height="40dp", padding=[15, 0] ) total_name = Label( text="GESAMT", font_size="18sp", bold=True, halign="left", valign="middle" ) total_name.bind(size=total_name.setter("text_size")) total_val = Label( text=f"{total:.2f} €", font_size="18sp", bold=True, color=[1, 0.3, 0.3, 1], halign="right", valign="middle", ) total_val.bind(size=total_val.setter("text_size")) total_row.add_widget(total_name) total_row.add_widget(total_val) self.debts_layout.add_widget(total_row) class AdminPasswordScreen(StyledScreen): def __init__(self, **kwargs): super(AdminPasswordScreen, self).__init__( title_text="Admin-Bereich", show_back=True, back_screen="main_menu", **kwargs, ) self.content_layout.add_widget( Label( text="Bitte Admin-Passwort eingeben:", font_size="16sp", size_hint_y=None, height="40dp", ) ) self.pwd_input = TextInput( password=True, multiline=False, size_hint=(0.7, None), height="50dp", pos_hint={"center_x": 0.5}, font_size="18sp", background_normal="", background_active="", background_color=[0.15, 0.15, 0.17, 1], foreground_color=[1, 1, 1, 1], cursor_color=[1, 1, 1, 1], padding=[15, 12, 15, 12], ) self.pwd_input.bind(focus=self.trigger_keyboard) self.content_layout.add_widget(self.pwd_input) self.error_label = Label( text="", font_size="14sp", color=[1, 0.3, 0.3, 1], size_hint_y=None, height="30dp", ) self.content_layout.add_widget(self.error_label) btn_login = RoundedButton( text="Einloggen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_login.bind(on_release=self.check_password) self.content_layout.add_widget(btn_login) def on_enter(self, *args): self.pwd_input.text = "" self.error_label.text = "" self.pwd_input.focus = True def trigger_keyboard(self, instance, value): if value: instance.focus = False popup = KeyboardPopup( title="Admin-Passwort eingeben", is_password=True, on_submit=self.on_password_submitted, ) popup.open() def on_password_submitted(self, text): self.pwd_input.text = text self.check_password(None) def check_password(self, instance): app = App.get_running_app() password = self.pwd_input.text.strip() from config import ADMIN_PASSWORD if password == ADMIN_PASSWORD: app.admin_password = password self.manager.current = "admin_menu" else: self.error_label.text = "Falsches Passwort!" class AdminMenuScreen(StyledScreen): def __init__(self, **kwargs): super(AdminMenuScreen, self).__init__(title_text="Admin Panel", **kwargs) scroll = ScrollView() box = BoxLayout(orientation="vertical", spacing=15, size_hint_y=None) box.bind(minimum_height=box.setter("height")) scroll.add_widget(box) self.content_layout.add_widget(scroll) btn_debts = RoundedButton( text="Alle Schulden anzeigen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_debts.bind(on_release=self.go_all_debts) box.add_widget(btn_debts) btn_adjust = RoundedButton( text="Schulden anpassen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_adjust.bind(on_release=self.go_adjust_debt) box.add_widget(btn_adjust) btn_clear = RoundedButton( text="Schulden begleichen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_clear.bind(on_release=self.go_clear_debt) box.add_widget(btn_clear) btn_history = RoundedButton( text="Transaktionsverlauf anzeigen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_history.bind(on_release=self.go_history) box.add_widget(btn_history) btn_manage_users = RoundedButton( text="Benutzer verwalten", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_manage_users.bind(on_release=self.go_manage_users) box.add_widget(btn_manage_users) btn_manage_items = RoundedButton( text="Artikel verwalten", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, ) btn_manage_items.bind(on_release=self.go_manage_items) box.add_widget(btn_manage_items) btn_open = RoundedButton( text="Kühlschrank öffnen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.0, 0.65, 0.57, 1], ) btn_open.bind(on_release=self.open_fridge) box.add_widget(btn_open) btn_close = RoundedButton( text="Kühlschrank schließen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.8, 0.2, 0.2, 1], ) btn_close.bind(on_release=self.close_fridge) box.add_widget(btn_close) btn_logout = RoundedButton( text="Admin-Bereich verlassen", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.25, 0.25, 0.28, 1], ) btn_logout.bind(on_release=self.logout) box.add_widget(btn_logout) def open_fridge(self, instance): app = App.get_running_app() app.motor.unlock_async() def close_fridge(self, instance): app = App.get_running_app() app.motor.lock_async() def go_all_debts(self, instance): self.manager.get_screen("all_debts").back_screen = "admin_menu" self.manager.current = "all_debts" def go_adjust_debt(self, instance): self.manager.current = "admin_adjust_debt" def go_clear_debt(self, instance): self.manager.current = "admin_clear_debt" def go_history(self, instance): self.manager.current = "admin_history" def go_manage_users(self, instance): self.manager.current = "admin_manage_users" def go_manage_items(self, instance): self.manager.current = "admin_manage_items" def logout(self, instance): app = App.get_running_app() app.admin_password = None self.manager.current = "main_menu" class AdminAdjustDebtScreen(StyledScreen): def __init__(self, **kwargs): super(AdminAdjustDebtScreen, self).__init__( title_text="Schulden anpassen", show_back=True, back_screen="admin_menu", **kwargs, ) self.scroll = ScrollView() self.user_layout = BoxLayout( orientation="vertical", spacing=10, size_hint_y=None ) self.user_layout.bind(minimum_height=self.user_layout.setter("height")) self.scroll.add_widget(self.user_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.user_layout.clear_widgets() app = App.get_running_app() users = app.tracker.get_all_users() for user_id, user in users.items(): debt = app.tracker.get_user_debt(user_id) btn = RoundedButton( text=f"{user.name} (Schulden: {debt:.2f} €)", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.0, 0.65, 0.57, 1], ) btn.bind( on_release=lambda instance, uid=user_id, name=user.name, d=debt: ( self.open_adjust_popup(uid, name, d) ) ) self.user_layout.add_widget(btn) def open_adjust_popup(self, user_id, user_name, current_debt): popup = AdjustDebtPopup( user_id=user_id, user_name=user_name, current_debt=current_debt, on_adjust=self.adjust_debt, ) popup.open() def adjust_debt(self, user_id, amount): app = App.get_running_app() result = app.tracker.adjust_debt(user_id, amount, app.admin_password) if result["status"] == "success": popup = MessagePopup( title="Erfolg", message=f"Schulden erfolgreich angepasst!\n\nNeuer Schuldenstand: {result['new_debt']:.2f} €", ) popup.open() self.on_enter() else: popup = MessagePopup(title="Fehler", message=f"Fehler: {result['message']}") popup.open() class AdminClearDebtScreen(StyledScreen): def __init__(self, **kwargs): super(AdminClearDebtScreen, self).__init__( title_text="Schulden begleichen", show_back=True, back_screen="admin_menu", **kwargs, ) self.scroll = ScrollView() self.user_layout = BoxLayout( orientation="vertical", spacing=10, size_hint_y=None ) self.user_layout.bind(minimum_height=self.user_layout.setter("height")) self.scroll.add_widget(self.user_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.user_layout.clear_widgets() app = App.get_running_app() users = app.tracker.get_all_users() for user_id, user in users.items(): debt = app.tracker.get_user_debt(user_id) btn = RoundedButton( text=f"{user.name} (Schulden: {debt:.2f} €)", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.85, 0.35, 0.35, 1] if debt > 0 else [0.25, 0.25, 0.28, 1], ) btn.bind( on_release=lambda instance, uid=user_id, name=user.name, d=debt: ( self.confirm_clear_popup(uid, name, d) ) ) self.user_layout.add_widget(btn) def confirm_clear_popup(self, user_id, user_name, current_debt): if current_debt <= 0: popup = MessagePopup( title="Info", message=f"{user_name} hat keine Schulden." ) popup.open() return popup = ConfirmPopup( title="Schulden begleichen", message=f"Möchtest du die Schulden von {user_name} in Höhe von {current_debt:.2f} € vollständig begleichen?", on_confirm=lambda: self.clear_debt(user_id, current_debt), ) popup.open() def clear_debt(self, user_id, amount): app = App.get_running_app() result = app.tracker.adjust_debt(user_id, amount, app.admin_password) if result["status"] == "success": popup = MessagePopup( title="Erfolg", message=f"Schulden von {app.tracker.get_user_by_id(user_id).name} wurden vollständig beglichen!", ) popup.open() self.on_enter() else: popup = MessagePopup(title="Fehler", message=f"Fehler: {result['message']}") popup.open() class AdminHistoryScreen(StyledScreen): def __init__(self, **kwargs): super(AdminHistoryScreen, self).__init__( title_text="Transaktionsverlauf", show_back=True, back_screen="admin_menu", **kwargs, ) self.scroll = ScrollView() self.history_layout = BoxLayout( orientation="vertical", spacing=8, size_hint_y=None ) self.history_layout.bind(minimum_height=self.history_layout.setter("height")) self.scroll.add_widget(self.history_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.history_layout.clear_widgets() app = App.get_running_app() transactions = app.tracker.transactions if not transactions: no_trans = Label( text="Bisher keine Transaktionen.", font_size="16sp", size_hint_y=None, height="40dp", ) self.history_layout.add_widget(no_trans) return for t in sorted(transactions, key=lambda x: x.timestamp, reverse=True): user = app.tracker.get_user_by_id(t.user_id) user_name = user.name if user else "Gelöschter Benutzer" if t.item_id in ("adjustment", "Abbuchung"): item_name = "Abbuchung" else: item = app.tracker.get_items().get(t.item_id) item_name = item.name if item else "Gelöschter Artikel" row = TransactionRow( date=t.timestamp, user_name=user_name, item_name=item_name, amount=t.amount, ) self.history_layout.add_widget(row) class AdminManageUsersScreen(StyledScreen): def __init__(self, **kwargs): super(AdminManageUsersScreen, self).__init__( title_text="Benutzer verwalten", show_back=True, back_screen="admin_menu", **kwargs, ) btn_add = RoundedButton( text="Benutzer hinzufügen", size_hint=(0.7, None), height="50dp", pos_hint={"center_x": 0.5}, btn_color=[0.0, 0.65, 0.57, 1], ) btn_add.bind(on_release=self.open_create_user_popup) self.content_layout.add_widget(btn_add) self.scroll = ScrollView() self.user_layout = BoxLayout( orientation="vertical", spacing=10, size_hint_y=None ) self.user_layout.bind(minimum_height=self.user_layout.setter("height")) self.scroll.add_widget(self.user_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.refresh_users() def refresh_users(self): self.user_layout.clear_widgets() app = App.get_running_app() users = app.tracker.get_all_users() for user_id, user in users.items(): btn = RoundedButton( text=f"{user.name} (PIN: {user.pin})", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.15, 0.15, 0.17, 1], ) btn.bind( on_release=lambda instance, uid=user_id, name=user.name, pin=user.pin: ( self.open_edit_user_popup(uid, name, pin) ) ) self.user_layout.add_widget(btn) def open_create_user_popup(self, instance): popup = CreateUserPopup(on_create=self.create_user) popup.open() def create_user(self, name): import uuid app = App.get_running_app() user_id = f"user_{uuid.uuid4().hex[:8]}" res = app.tracker.add_user(user_id, name) if res["status"] == "success": msg = MessagePopup( title="Erfolg", message=f"Benutzer {name} erfolgreich erstellt!\n(Standard-PIN: 4242)", ) msg.open() self.refresh_users() else: msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}") msg.open() def open_edit_user_popup(self, user_id, name, pin): popup = EditUserPopup( user_id=user_id, current_name=name, current_pin=pin, on_save=self.save_user, on_delete=self.confirm_delete_user, ) popup.open() def save_user(self, user_id, name, pin): app = App.get_running_app() res = app.tracker.edit_user(user_id, name, pin) if res["status"] == "success": msg = MessagePopup(title="Erfolg", message=f"Benutzer {name} aktualisiert!") msg.open() self.refresh_users() else: msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}") msg.open() def confirm_delete_user(self, user_id): app = App.get_running_app() user = app.tracker.get_user_by_id(user_id) if not user: return popup = ConfirmPopup( title="Benutzer löschen", message=f"Möchtest du den Benutzer {user.name} wirklich unwiderruflich löschen?", on_confirm=lambda: self.delete_user(user_id), ) popup.open() def delete_user(self, user_id): app = App.get_running_app() res = app.tracker.remove_user(user_id) if res["status"] == "success": msg = MessagePopup(title="Erfolg", message="Benutzer gelöscht!") msg.open() self.refresh_users() else: msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}") msg.open() class AdminManageItemsScreen(StyledScreen): def __init__(self, **kwargs): super(AdminManageItemsScreen, self).__init__( title_text="Artikel verwalten", show_back=True, back_screen="admin_menu", **kwargs, ) btn_add = RoundedButton( text="Artikel hinzufügen", size_hint=(0.7, None), height="50dp", pos_hint={"center_x": 0.5}, btn_color=[0.0, 0.65, 0.57, 1], ) btn_add.bind(on_release=self.open_create_item_popup) self.content_layout.add_widget(btn_add) self.scroll = ScrollView() self.items_layout = BoxLayout( orientation="vertical", spacing=10, size_hint_y=None ) self.items_layout.bind(minimum_height=self.items_layout.setter("height")) self.scroll.add_widget(self.items_layout) self.content_layout.add_widget(self.scroll) def on_enter(self, *args): self.refresh_items() def refresh_items(self): self.items_layout.clear_widgets() app = App.get_running_app() items = app.tracker.get_items() for item_id, item in items.items(): btn = RoundedButton( text=f"{item.name} ({item.price:.2f} €)", size_hint=(0.7, None), height="60dp", pos_hint={"center_x": 0.5}, btn_color=[0.15, 0.15, 0.17, 1], ) btn.bind( on_release=lambda instance, iid=item_id, name=item.name, price=item.price: ( self.open_edit_item_popup(iid, name, price) ) ) self.items_layout.add_widget(btn) def open_create_item_popup(self, instance): popup = CreateItemPopup(on_create=self.create_item) popup.open() def create_item(self, name, price): import uuid app = App.get_running_app() item_id = f"item_{uuid.uuid4().hex[:8]}" res = app.tracker.add_item(item_id, name, price) if res["status"] == "success": msg = MessagePopup( title="Erfolg", message=f"Artikel {name} erfolgreich erstellt!" ) msg.open() self.refresh_items() else: msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}") msg.open() def open_edit_item_popup(self, item_id, name, price): popup = EditItemPopup( item_id=item_id, current_name=name, current_price=price, on_save=self.save_item, on_delete=self.confirm_delete_item, ) popup.open() def save_item(self, item_id, name, price): app = App.get_running_app() res = app.tracker.edit_item(item_id, name, price) if res["status"] == "success": msg = MessagePopup(title="Erfolg", message=f"Artikel {name} aktualisiert!") msg.open() self.refresh_items() else: msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}") msg.open() def confirm_delete_item(self, item_id): app = App.get_running_app() items = app.tracker.get_items() item = items.get(item_id) if not item: return popup = ConfirmPopup( title="Artikel löschen", message=f"Möchtest du den Artikel {item.name} wirklich unwiderruflich löschen?", on_confirm=lambda: self.delete_item(item_id), ) popup.open() def delete_item(self, item_id): app = App.get_running_app() res = app.tracker.remove_item(item_id) if res["status"] == "success": msg = MessagePopup(title="Erfolg", message="Artikel gelöscht!") msg.open() self.refresh_items() else: msg = MessagePopup(title="Fehler", message=f"Fehler: {res['message']}") msg.open() class KuehlschrankApp(App): def __init__(self, tracker, **kwargs): super(KuehlschrankApp, self).__init__(**kwargs) self.tracker = tracker self.current_user = None self.admin_password = None from raspi.motor import Raspi self.motor = Raspi() def build(self): self.title = "Getränkeliste" Window.size = (450, 700) Window.clearcolor = (0.08, 0.08, 0.09, 1) sm = ScreenManager(transition=SlideTransition()) sm.add_widget(MainMenuScreen(name="main_menu")) sm.add_widget(UserSelectionScreen(name="user_selection")) sm.add_widget(UserMenuScreen(name="user_menu")) sm.add_widget(AddPurchaseScreen(name="add_purchase")) sm.add_widget(AllDebtsScreen(name="all_debts")) sm.add_widget(AdminPasswordScreen(name="admin_password")) sm.add_widget(AdminMenuScreen(name="admin_menu")) sm.add_widget(AdminAdjustDebtScreen(name="admin_adjust_debt")) sm.add_widget(AdminClearDebtScreen(name="admin_clear_debt")) sm.add_widget(AdminHistoryScreen(name="admin_history")) sm.add_widget(AdminManageUsersScreen(name="admin_manage_users")) sm.add_widget(AdminManageItemsScreen(name="admin_manage_items")) return sm class KivyUI: def __init__(self, tracker): self.tracker = tracker def run(self): app = KuehlschrankApp(self.tracker) app.run()