From f0acb386b738581994d3065390e590f688250771 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Sun, 26 Mar 2023 13:30:36 +0200 Subject: [PATCH 1/5] fix lang files path --- src/language/lang.py | 1 + src/main.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/language/lang.py b/src/language/lang.py index 481a45f..87c030b 100644 --- a/src/language/lang.py +++ b/src/language/lang.py @@ -22,6 +22,7 @@ class TranslationProvider: path = Path(cls.src_dir, LANGUAGE_FOLDER, f"{language}.json") print(f"loading language file: {path}") + print(f'src_dir: {cls.src_dir}') try: with open(path, "r", encoding="utf-8") as f: result = json.load(f) diff --git a/src/main.py b/src/main.py index 939b0cc..23aea71 100644 --- a/src/main.py +++ b/src/main.py @@ -325,7 +325,16 @@ class ShoppingListApp(MDApp): print(self.settings) print(self.user_data_dir) print(self.directory) - TranslationProvider.src_dir = Path(self.directory) + + if kivy.utils.platform in ["android", "ios"]: + print('android or ios') + src_path = Path(self.user_data_dir, 'app') + else: + print('not android or ios') + src_path = Path(self.directory) + + print(src_path) + TranslationProvider.src_dir = src_path # TODO @Tom Theme anpassen self.theme_cls.primary_palette = "Gray" From 27ecbb15c4851c10aa4b291ae01aa4d1c1b3f832 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Sun, 26 Mar 2023 14:35:57 +0200 Subject: [PATCH 2/5] dont crash on mqtt error --- src/main.py | 8 ++++++-- src/mqtt.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main.py b/src/main.py index 23aea71..3983641 100644 --- a/src/main.py +++ b/src/main.py @@ -352,8 +352,12 @@ class ShoppingListApp(MDApp): subscribe_callback=lambda msg_dict, _:shoppingEntryScreen.update_from_mqtt(msg_dict), username=self.settings.mqtt_username, password=self.settings.mqtt_password) - self.mqtt.connect() - self.mqtt.subscribe() + try: + self.mqtt.connect() + self.mqtt.subscribe() + except Exception as e: + print(e) + toast("MQTT-Connection failed") return sm def update_theme(self): diff --git a/src/mqtt.py b/src/mqtt.py index 8d56fa5..c2bd3ac 100644 --- a/src/mqtt.py +++ b/src/mqtt.py @@ -74,7 +74,8 @@ class MqttClient: result = self.__client.publish(topic, msg_str, retain=retain) status = result[0] if status != 0: - raise MqttSendError() + print("Failed to send message to MQTT broker") + # raise MqttSendError() def subscribe(self, callback=None) -> None: if self.__client is None: @@ -88,10 +89,13 @@ class MqttClient: def connect(self) -> None: self.__client = mqtt_client.Client(self.__client_id) if self.__username: + print("setting username and password") self.__client.username_pw_set(self.__username, self.__password) self.__client.on_connect = self.on_connect - self.__client.connect(self.__broker, self.__port) + print("Connecting to MQTT broker...") + print('broker:',self.__broker, 'port:', self.__port) + self.__client.connect(host=self.__broker, port= self.__port) self.__client.loop_start() def disconnect(self) -> None: From 30668474c2512594bbc41c727033a56a596dec2d Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Mon, 27 Mar 2023 13:54:12 +0200 Subject: [PATCH 3/5] Save changes to the entries --- src/main.py | 174 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 51 deletions(-) diff --git a/src/main.py b/src/main.py index 3983641..97e854c 100644 --- a/src/main.py +++ b/src/main.py @@ -32,12 +32,34 @@ class ShoppingEntry(OneLineAvatarIconListItem): def __init__(self, text, **kwargs): super().__init__(**kwargs) - + self.initialized = False self.text = text self.is_checked = False + self.initialized = True + # region events + + def on_is_checked(self, *_): + if not self.initialized: + return + print('on_is_checked') + list = self.get_shopping_list() + if list: + list.update_entry(self) + + # endregion + def get_shopping_list(self): + if not self.initialized: + return None + if self.parent: + return self.parent.parent.parent.parent + + return None + def delete(self, shopping_entry): - self.parent.remove_widget(shopping_entry) + list = self.get_shopping_list() + if list: + list.delete_entry(shopping_entry) def edit_popup(self): if self.edit_dialog: @@ -76,6 +98,9 @@ class ShoppingEntry(OneLineAvatarIconListItem): self.text = changed_text self.close_edit_popup() + list = self.get_shopping_list() + if list: + list.update_entry(self) def get_translated(self, key: str) -> str: settings = AppSettings.get_or_create() @@ -89,6 +114,9 @@ class ShoppingEntry(OneLineAvatarIconListItem): self.edit_dialog.dismiss() self.edit_dialog = None + def __str__(self) -> str: + return super().__str__() + f'is_checked: {self.is_checked} text: {self.text}' + class AddDialog(MDBoxLayout): text = StringProperty("") @@ -108,22 +136,11 @@ class ShoppingEntryScreen(Screen): def __init__(self, **kwargs): super().__init__(**kwargs) - - self.from_mqtt = False + self.initialized = False self.update_from_file() - - def update_from_file(self): - try: - entries = read_entries_from_files() - except OSError: - return - - self.set_entries(entries['entries']) - - def update_from_mqtt(self, msg_dict): - self.from_mqtt = True - self.entries = msg_dict['entries'] + self.initialized = True + # region events def on_bestaetigen(self, *_): if self.add_dialog is None: @@ -136,6 +153,41 @@ class ShoppingEntryScreen(Screen): self.add_shopping_entry(text) + @mainthread + def on_entries(self, *_): + print("on_entries called") + self.ids["shopping_list"].clear_widgets() + for entry in self.entries: + shopping_entry = ShoppingEntry(text=entry['text']) + shopping_entry.is_checked = entry['is_checked'] # type: ignore + self.ids["shopping_list"].add_widget(shopping_entry) + + # endregion + + # region entries + + # region update entries + + def update_from_file(self): + try: + entries = read_entries_from_files() + except OSError: + return + + self.set_entries(entries['entries']) + + def update_from_mqtt(self, msg_dict): + self.from_mqtt = True + self.set_entries(msg_dict['entries'], from_mqtt=True) + + def update_entry(self, entry: ShoppingEntry): + print('update entry called', entry) + self.export_entries() + + # endregion + + # region add entry + def open_add_popup(self): if self.add_dialog: return @@ -171,46 +223,59 @@ class ShoppingEntryScreen(Screen): def add_shopping_entry(self, text): self.ids["shopping_list"].add_widget(ShoppingEntry(text=text)) self.close_add_popup() + + # endregion - def export_entries(self): + def delete_entry(self, entry: ShoppingEntry): + print('remove_entry called', entry) + self.ids.shopping_list.remove_widget(entry) + + def export_entries(self, from_mqtt=False): + """ + Speichert die einträge in einer JSON datei und wenn 'from_mqtt' == False ist + sendet es die daten an MQTT zur synchronisation + + :param from_mqtt: ob der aufruf von MQTT kommt und nicht auf MQTT zurück geschrieben werden soll + """ + if not self.initialized: + return + print('exporting entries', from_mqtt) entries = self.get_entires() entries_dict = {"entries": entries} try: write_entries_to_files(entries_dict) except OSError: return - - # dont push to mqtt if coming from mqtt - if not self.from_mqtt: - app.mqtt.publish(entries_dict) - else: - self.from_mqtt = False - - @mainthread - def on_entries(self, *_): - print("on_entries called") - self.ids["shopping_list"].clear_widgets() - for entry in self.entries: - shopping_entry = ShoppingEntry(text=entry['text']) - shopping_entry.is_checked = entry['is_checked'] # type: ignore - self.ids["shopping_list"].add_widget(shopping_entry) - self.export_entries() + # dont push to mqtt if coming from mqtt + if not from_mqtt: + app.mqtt.publish(entries_dict) def get_entires(self): entries = [] for entry in self.ids.shopping_list.children: entry_dict = {"is_checked": entry.is_checked, "text": entry.text} entries.append(entry_dict) - + entries.sort(key=lambda entry: (entry['is_checked'], entry['text'])) return entries - - def set_entries(self, entries: List[Dict[str, Union[str,bool]]]): - self.entries = entries - + def set_entries(self, entries: List[Dict[str, Union[str, bool]]], from_mqtt=False): + """ + Setzt die werte der liste auf die mitgegebenen einträge + + :param entries: die neuen einträge + :param from_mqtt: ob der aufruf von MQTT kommt und nicht auf MQTT zurück geschrieben werden soll + """ + self.entries = entries + self.export_entries(from_mqtt=from_mqtt) + + # endregion + + # region general + def navigate_to_settings(self): + """Navigiert zur Einstellungs-Seite""" self.export_entries() self.manager.transition.direction = "left" self.manager.current = "settings" @@ -218,7 +283,11 @@ class ShoppingEntryScreen(Screen): def get_translated(self, key) -> str: language = app.settings.language return TranslationProvider.get_translated(key, language) + + def __str__(self) -> str: + return super().__str__() + f'count: {len(self.entries)}' + # endregion class SettingsScreen(Screen): def __init__(self, **kwargs): @@ -226,8 +295,8 @@ class SettingsScreen(Screen): self.initialized = False # remember previous mqtt-settings to check if they changed - self.previous_mqtt_server = app.settings.mqtt_server - self.previous_mqtt_topic = app.settings.mqtt_topic + self.previous_mqtt_server = app.settings.mqtt_server + self.previous_mqtt_topic = app.settings.mqtt_topic self.previous_mqtt_password = app.settings.mqtt_password self.previous_mqtt_username = app.settings.mqtt_username @@ -250,20 +319,20 @@ class SettingsScreen(Screen): self.menu.bind() # update the current selected from settings - self.set_item(app.settings.language) + self.set_item(app.settings.language) self.initialized = True def apply_mqtt_settings(self): - if self.previous_mqtt_server != app.settings.mqtt_server or \ - self.previous_mqtt_topic != app.settings.mqtt_topic or \ + if self.previous_mqtt_server != app.settings.mqtt_server or \ + self.previous_mqtt_topic != app.settings.mqtt_topic or \ self.previous_mqtt_password != app.settings.mqtt_password or \ self.previous_mqtt_username != app.settings.mqtt_username: app.mqtt.disconnect() app.mqtt.set_target(app.settings.mqtt_server, - app.settings.mqtt_topic, + app.settings.mqtt_topic, 1883, - app.settings.mqtt_username, + app.settings.mqtt_username, app.settings.mqtt_password) app.mqtt.connect() @@ -284,7 +353,7 @@ class SettingsScreen(Screen): def get_translated(self, key) -> str: language = app.settings.language return TranslationProvider.get_translated(key, language) - + def update_language(self): if not self.initialized: return @@ -315,9 +384,10 @@ class SettingsScreen(Screen): class ShoppingListApp(MDApp): settings = ObjectProperty(AppSettings(), rebind=True) + def __init__(self, **kwargs): super().__init__(**kwargs) - self.mqtt: MqttClient = None # type: ignore + self.mqtt: MqttClient = None # type: ignore def build(self): self.title = "Shopping List App" @@ -325,17 +395,17 @@ class ShoppingListApp(MDApp): print(self.settings) print(self.user_data_dir) print(self.directory) - + if kivy.utils.platform in ["android", "ios"]: print('android or ios') src_path = Path(self.user_data_dir, 'app') else: print('not android or ios') src_path = Path(self.directory) - + print(src_path) TranslationProvider.src_dir = src_path - + # TODO @Tom Theme anpassen self.theme_cls.primary_palette = "Gray" self.update_theme() @@ -349,10 +419,12 @@ class ShoppingListApp(MDApp): port=1883, topic=self.settings.mqtt_topic, client_id=None, - subscribe_callback=lambda msg_dict, _:shoppingEntryScreen.update_from_mqtt(msg_dict), + subscribe_callback=lambda msg_dict, _: shoppingEntryScreen.update_from_mqtt( + msg_dict), username=self.settings.mqtt_username, password=self.settings.mqtt_password) try: + # print('skipping mqtt connection') self.mqtt.connect() self.mqtt.subscribe() except Exception as e: From c1be1cfc771fed671af9c2675cad19f163a3de09 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Mon, 27 Mar 2023 14:01:22 +0200 Subject: [PATCH 4/5] update entires on add --- src/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 97e854c..5f950a6 100644 --- a/src/main.py +++ b/src/main.py @@ -221,7 +221,9 @@ class ShoppingEntryScreen(Screen): self.add_dialog = None def add_shopping_entry(self, text): - self.ids["shopping_list"].add_widget(ShoppingEntry(text=text)) + entry = ShoppingEntry(text=text) + self.ids["shopping_list"].add_widget(entry) + self.update_entry(entry) self.close_add_popup() # endregion From 3f62264eacb6b97804faf9cc1e12bb59860162bb Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Mon, 27 Mar 2023 15:29:20 +0200 Subject: [PATCH 5/5] Sorting & fix dismiss error Co-Authored-By: fadam2104 <119661061+fadam2104@users.noreply.github.com> Co-Authored-By: tomw41 <118167823+tomw41@users.noreply.github.com> Co-Authored-By: Jan <49588429+MapManagement@users.noreply.github.com> --- src/main.py | 54 +++++++++++++++++++++++++++++---------------- src/shoppinglist.kv | 4 ++-- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/main.py b/src/main.py index 5f950a6..ee49647 100644 --- a/src/main.py +++ b/src/main.py @@ -42,7 +42,7 @@ class ShoppingEntry(OneLineAvatarIconListItem): def on_is_checked(self, *_): if not self.initialized: return - print('on_is_checked') + list = self.get_shopping_list() if list: list.update_entry(self) @@ -69,11 +69,11 @@ class ShoppingEntry(OneLineAvatarIconListItem): buttons = [ MDFlatButton( text=self.get_translated("cancel"), - on_release=self.close_edit_popup + on_release=lambda _: self.edit_dialog.dismiss() # type: ignore ), MDFlatButton( text=self.get_translated("confirm"), - on_release=lambda args: self.save_changes(args) + on_release=lambda _: self.save_changes() ), ] @@ -83,10 +83,10 @@ class ShoppingEntry(OneLineAvatarIconListItem): content_cls=AddDialog(self.text), buttons=buttons, ) - + self.edit_dialog.on_dismiss = lambda: self.close_edit_popup() self.edit_dialog.open() - def save_changes(self, *_): + def save_changes(self): if self.edit_dialog is None: return @@ -97,7 +97,7 @@ class ShoppingEntry(OneLineAvatarIconListItem): self.text = changed_text - self.close_edit_popup() + self.edit_dialog.dismiss() list = self.get_shopping_list() if list: list.update_entry(self) @@ -106,12 +106,10 @@ class ShoppingEntry(OneLineAvatarIconListItem): settings = AppSettings.get_or_create() return TranslationProvider.get_translated(key, settings.language) - # *args bzw. *_ ist noetig, da weitere Parameter mitgegeben werden, die aber nicht genutzt werden - def close_edit_popup(self, *_): + def close_edit_popup(self): if not self.edit_dialog: return - self.edit_dialog.dismiss() self.edit_dialog = None def __str__(self) -> str: @@ -133,6 +131,7 @@ class AddDialog(MDBoxLayout): class ShoppingEntryScreen(Screen): add_dialog = None entries = ListProperty([]) + sort_reverse = BooleanProperty(False) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -153,14 +152,14 @@ class ShoppingEntryScreen(Screen): self.add_shopping_entry(text) - @mainthread def on_entries(self, *_): - print("on_entries called") - self.ids["shopping_list"].clear_widgets() - for entry in self.entries: - shopping_entry = ShoppingEntry(text=entry['text']) - shopping_entry.is_checked = entry['is_checked'] # type: ignore - self.ids["shopping_list"].add_widget(shopping_entry) + print("on_entries called", self) + self.set_entries_widgets() + + def on_sort_reverse(self, *_): + print('on_sort_reverse', self.sort_reverse) + self.entries = self.get_entires() + # self.set_entries_widgets() # endregion @@ -228,6 +227,15 @@ class ShoppingEntryScreen(Screen): # endregion + @mainthread + def set_entries_widgets(self): + print('set_entries_widgets', self) + self.ids["shopping_list"].clear_widgets() + for entry in self.entries: + shopping_entry = ShoppingEntry(text=entry['text']) + shopping_entry.is_checked = entry['is_checked'] # type: ignore + self.ids["shopping_list"].add_widget(shopping_entry) + def delete_entry(self, entry: ShoppingEntry): print('remove_entry called', entry) self.ids.shopping_list.remove_widget(entry) @@ -241,9 +249,10 @@ class ShoppingEntryScreen(Screen): """ if not self.initialized: return + print('exporting entries', from_mqtt) - entries = self.get_entires() - entries_dict = {"entries": entries} + self.entries = self.get_entires() + entries_dict = {"entries": self.sort(self.entries, False)} try: write_entries_to_files(entries_dict) except OSError: @@ -259,9 +268,13 @@ class ShoppingEntryScreen(Screen): entry_dict = {"is_checked": entry.is_checked, "text": entry.text} entries.append(entry_dict) - entries.sort(key=lambda entry: (entry['is_checked'], entry['text'])) + entries = self.sort(entries, self.sort_reverse) return entries + @staticmethod + def sort(entries, reverse)-> list: + return sorted(entries,key=lambda entry: (entry['is_checked'], entry['text']), reverse=reverse) + def set_entries(self, entries: List[Dict[str, Union[str, bool]]], from_mqtt=False): """ Setzt die werte der liste auf die mitgegebenen einträge @@ -275,6 +288,9 @@ class ShoppingEntryScreen(Screen): # endregion # region general + + def toggle_sort(self): + self.sort_reverse = not self.sort_reverse def navigate_to_settings(self): """Navigiert zur Einstellungs-Seite""" diff --git a/src/shoppinglist.kv b/src/shoppinglist.kv index 9462d2c..d819616 100644 --- a/src/shoppinglist.kv +++ b/src/shoppinglist.kv @@ -39,7 +39,7 @@ title: root.get_translated('app_title') md_bg_color: app.theme_cls.primary_color specific_text_color: '#1C2120' - right_action_items: [['res/settings.png', lambda x: root.navigate_to_settings()]] + right_action_items: [['sort-variant', lambda x: root.toggle_sort()],['cog', lambda x: root.navigate_to_settings()]] ScrollView: size_hint: 0.85, 0.85 @@ -61,7 +61,7 @@ MDTopAppBar: title: root.get_translated('settings') md_bg_color: app.theme_cls.primary_color - left_action_items: [['res/arrow_back.png', lambda x: root.navigate_to_shopping_list()]] + left_action_items: [['arrow-left', lambda x: root.navigate_to_shopping_list()]] MDGridLayout: pos_hint: { 'center_x': 0.5 }