diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 2cf4d76b2..cd89bebdd 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -44,6 +44,32 @@ } +class ChipProperties: + """ChipProperties class for kivy UI""" + CENTER_X_ANDROID = 0.91 + CENTER_X_OTHER = 0.94 + CENTER_Y = 0.3 + HEIGHT_DP = 18 + RADIUS = [8] + TEXT_COLOR = (1, 1, 1, 1) + SIZE_HINT_ANDROID = (0.16, None) + SIZE_HINT_OTHER = (0.08, None) + + +class BadgeProperties: + """BadgeProperties class for kivy UI""" + SIZE_ANDROID = [120, 140] + SIZE_OTHER = [64, 80] + FONT_SIZE = "11sp" + + +DIALOG_WIDTH_ANDROID = 0.8 +DIALOG_WIDTH_OTHER = 0.55 +DIALOG_HEIGHT = 0.25 +LONG_PRESS_DURATION = 1 +DELETE_DELAY = 4 + + def load_image_path(): """Return the path of kivy images""" image_path = os.path.abspath(os.path.join('pybitmessage', 'images', 'kivy')) diff --git a/src/bitmessagekivy/baseclass/maildetail.py b/src/bitmessagekivy/baseclass/maildetail.py index 11fb6c794..22de4a423 100644 --- a/src/bitmessagekivy/baseclass/maildetail.py +++ b/src/bitmessagekivy/baseclass/maildetail.py @@ -1,78 +1,69 @@ # pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init # pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods +# pylint: disable=broad-except """ -Maildetail screen for inbox, sent, draft and trash. +MailDetail screen for inbox, sent, draft and trash. """ import os from datetime import datetime -from kivy.core.clipboard import Clipboard +from kivy.app import App from kivy.clock import Clock -from kivy.properties import ( - StringProperty, - NumericProperty -) -from kivy.uix.screenmanager import Screen +from kivy.core.clipboard import Clipboard from kivy.factory import Factory -from kivy.app import App - +from kivy.properties import NumericProperty, StringProperty +from kivy.uix.screenmanager import Screen from kivymd.uix.button import MDFlatButton, MDIconButton from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - OneLineListItem, - IRightBodyTouch -) +from kivymd.uix.list import IRightBodyTouch, OneLineListItem from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, avatar_image_first_letter, show_time_history, kivy_state_variables -) + avatar_image_first_letter, kivy_state_variables, show_time_history, toast, + DIALOG_WIDTH_ANDROID, DIALOG_WIDTH_OTHER, DIALOG_HEIGHT, LONG_PRESS_DURATION, + DELETE_DELAY, ChipProperties, BadgeProperties) from pybitmessage.bitmessagekivy.baseclass.popup import SenderDetailPopup from pybitmessage.bitmessagekivy.get_platform import platform from pybitmessage.helper_sql import sqlQuery class OneLineListTitle(OneLineListItem): - """OneLineListTitle class for kivy Ui""" + """OneLineListTitle class for Kivy UI.""" __events__ = ('on_long_press', ) - long_press_time = NumericProperty(1) + long_press_time = NumericProperty(LONG_PRESS_DURATION) def on_state(self, instance, value): - """On state""" + """Handle state change for long press.""" if value == 'down': - lpt = self.long_press_time - self._clockev = Clock.schedule_once(self._do_long_press, lpt) + self._clock_event = Clock.schedule_once(self._do_long_press, self.long_press_time) else: - self._clockev.cancel() + self._clock_event.cancel() def _do_long_press(self, dt): - """Do long press""" + """Trigger the long press event.""" self.dispatch('on_long_press') - def on_long_press(self, *largs): - """On long press""" - self.copymessageTitle(self.text) + def on_long_press(self, *args): + """Handle long press and display message title.""" + self.copy_message_title(self.text) - def copymessageTitle(self, title_text): - """this method is for displaying dialog box""" + def copy_message_title(self, title_text): + """Display dialog box with options to copy the message title.""" self.title_text = title_text - width = .8 if platform == 'android' else .55 + width = DIALOG_WIDTH_ANDROID if platform == 'android' else DIALOG_WIDTH_OTHER self.dialog_box = MDDialog( text=title_text, - size_hint=(width, .25), + size_hint=(width, DIALOG_HEIGHT), buttons=[ - MDFlatButton( - text="Copy", on_release=self.callback_for_copy_title - ), - MDFlatButton( - text="Cancel", on_release=self.callback_for_copy_title, - ), - ],) + MDFlatButton(text="Copy", on_release=self.copy_title_callback), + MDFlatButton(text="Cancel", on_release=self.copy_title_callback), + ], + ) self.dialog_box.open() - def callback_for_copy_title(self, instance): - """Callback of alert box""" + def copy_title_callback(self, instance): + """Handle dialog box button callback.""" if instance.text == 'Copy': Clipboard.copy(self.title_text) self.dialog_box.dismiss() @@ -80,11 +71,11 @@ def callback_for_copy_title(self, instance): class IconRightSampleWidget(IRightBodyTouch, MDIconButton): - """IconRightSampleWidget class for kivy Ui""" + """IconRightSampleWidget class for Kivy UI.""" class MailDetail(Screen): # pylint: disable=too-many-instance-attributes - """MailDetail Screen class for kivy Ui""" + """MailDetail Screen class for Kivy UI.""" to_addr = StringProperty() from_addr = StringProperty() @@ -93,102 +84,125 @@ class MailDetail(Screen): # pylint: disable=too-many-instance-attributes status = StringProperty() page_type = StringProperty() time_tag = StringProperty() - avatarImg = StringProperty() + avatar_image = StringProperty() no_subject = '(no subject)' + chipProperties = ChipProperties() + badgeProperties = BadgeProperties() def __init__(self, *args, **kwargs): - """Mail Details method""" - super(MailDetail, self).__init__(*args, **kwargs) + """Initialize MailDetail screen.""" + super().__init__(*args, **kwargs) # pylint: disable=missing-super-argument self.kivy_state = kivy_state_variables() Clock.schedule_once(self.init_ui, 0) def init_ui(self, dt=0): - """Clock Schdule for method MailDetail mails""" - self.page_type = self.kivy_state.detail_page_type if self.kivy_state.detail_page_type else '' + """Initialize UI elements based on page type.""" + self.page_type = self.kivy_state.detail_page_type or '' try: - if self.kivy_state.detail_page_type in ('sent', 'draft'): + if self.page_type in ('sent', 'draft'): App.get_running_app().set_mail_detail_header() - elif self.kivy_state.detail_page_type == 'inbox': + elif self.page_type == 'inbox': data = sqlQuery( - "select toaddress, fromaddress, subject, message, received from inbox" - " where msgid = ?", self.kivy_state.mail_id) + "SELECT toaddress, fromaddress, subject, message, received FROM inbox " + "WHERE msgid = ?", self.kivy_state.mail_id + ) self.assign_mail_details(data) App.get_running_app().set_mail_detail_header() - except Exception as e: # pylint: disable=unused-variable - print('Something wents wrong!!') + except Exception: + print("Error during MailDetail initalization") def assign_mail_details(self, data): - """Assigning mail details""" + """Assign mail details from query result.""" subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2] - body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3] + body = data[0][3].decode() if isinstance(data[0][3], bytes) else data[0][3] self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' ' self.from_addr = data[0][1] - self.subject = subject.capitalize( - ) if subject.capitalize() else self.no_subject + self.subject = subject.capitalize() or self.no_subject self.message = body if len(data[0]) == 7: self.status = data[0][4] - self.time_tag = show_time_history(data[0][4]) if self.kivy_state.detail_page_type == 'inbox' \ + self.time_tag = ( + show_time_history(data[0][4]) if self.page_type == 'inbox' else show_time_history(data[0][6]) - self.avatarImg = os.path.join(self.kivy_state.imageDir, 'draft-icon.png') \ - if self.kivy_state.detail_page_type == 'draft' \ - else (os.path.join(self.kivy_state.imageDir, 'text_images', '{0}.png'.format(avatar_image_first_letter( - self.subject.strip())))) - self.timeinseconds = data[0][4] if self.kivy_state.detail_page_type == 'inbox' else data[0][6] + ) + self.avatar_image = ( + os.path.join(self.kivy_state.image_dir, 'draft-icon.png') + if self.page_type == 'draft' + else os.path.join( + self.kivy_state.image_dir, 'text_images', + f'{avatar_image_first_letter(self.subject.strip())}.png' + ) + ) + self.timeinseconds = data[0][4] if self.page_type == 'inbox' else data[0][6] def delete_mail(self): - """Method for mail delete""" + """Delete the current mail and update UI.""" msg_count_objs = App.get_running_app().root.ids.content_drawer.ids self.kivy_state.searching_text = '' self.children[0].children[0].active = True - if self.kivy_state.detail_page_type == 'sent': - App.get_running_app().root.ids.id_sent.ids.sent_search.ids.search_field.text = '' - msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1) - self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1) - self.parent.screens[2].ids.ml.clear_widgets() - self.parent.screens[2].loadSent(self.kivy_state.selected_address) - elif self.kivy_state.detail_page_type == 'inbox': - App.get_running_app().root.ids.id_inbox.ids.inbox_search.ids.search_field.text = '' - msg_count_objs.inbox_cnt.ids.badge_txt.text = str( - int(self.kivy_state.inbox_count) - 1) - self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1) - self.parent.screens[0].ids.ml.clear_widgets() - self.parent.screens[0].loadMessagelist(self.kivy_state.selected_address) - - elif self.kivy_state.detail_page_type == 'draft': - msg_count_objs.draft_cnt.ids.badge_txt.text = str( - int(self.kivy_state.draft_count) - 1) - self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1) - self.parent.screens[13].clear_widgets() - self.parent.screens[13].add_widget(Factory.Draft()) - - if self.kivy_state.detail_page_type != 'draft': - msg_count_objs.trash_cnt.ids.badge_txt.text = str( - int(self.kivy_state.trash_count) + 1) - msg_count_objs.allmail_cnt.ids.badge_txt.text = str( - int(self.kivy_state.all_count) - 1) - self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1) - self.kivy_state.all_count = str(int(self.kivy_state.all_count) - 1) if \ - int(self.kivy_state.all_count) else '0' - self.parent.screens[3].clear_widgets() - self.parent.screens[3].add_widget(Factory.Trash()) - self.parent.screens[14].clear_widgets() - self.parent.screens[14].add_widget(Factory.AllMails()) - Clock.schedule_once(self.callback_for_delete, 4) + + if self.page_type == 'sent': + self._update_sent_mail(msg_count_objs) + elif self.page_type == 'inbox': + self._update_inbox_mail(msg_count_objs) + elif self.page_type == 'draft': + self._update_draft_mail(msg_count_objs) + + if self.page_type != 'draft': + self._update_mail_counts(msg_count_objs) + + Clock.schedule_once(self.callback_for_delete, DELETE_DELAY) + + def _update_sent_mail(self, msg_count_objs): + """Update UI for sent mail.""" + App.get_running_app().root.ids.id_sent.ids.sent_search.ids.search_field.text = '' + msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1) + self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1) + self.parent.screens[2].ids.ml.clear_widgets() + self.parent.screens[2].loadSent(self.kivy_state.selected_address) + + def _update_inbox_mail(self, msg_count_objs): + """Update UI for inbox mail.""" + App.get_running_app().root.ids.id_inbox.ids.inbox_search.ids.search_field.text = '' + msg_count_objs.inbox_cnt.ids.badge_txt.text = str(int(self.kivy_state.inbox_count) - 1) + self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1) + self.parent.screens[0].ids.ml.clear_widgets() + self.parent.screens[0].loadMessagelist(self.kivy_state.selected_address) + + def _update_draft_mail(self, msg_count_objs): + """Update UI for draft mail.""" + msg_count_objs.draft_cnt.ids.badge_txt.text = str(int(self.kivy_state.draft_count) - 1) + self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1) + self.parent.screens[13].clear_widgets() + self.parent.screens[13].add_widget(Factory.Draft()) + + def _update_mail_counts(self, msg_count_objs): + """Update mail counts and refresh relevant screens.""" + msg_count_objs.trash_cnt.ids.badge_txt.text = str(int(self.kivy_state.trash_count) + 1) + msg_count_objs.allmail_cnt.ids.badge_txt.text = str(int(self.kivy_state.all_count) - 1) + self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1) + self.kivy_state.all_count = ( + str(int(self.kivy_state.all_count) - 1) + if int(self.kivy_state.all_count) + else '0' + ) + self.parent.screens[3].clear_widgets() + self.parent.screens[3].add_widget(Factory.Trash()) + self.parent.screens[14].clear_widgets() + self.parent.screens[14].add_widget(Factory.AllMails()) def callback_for_delete(self, dt=0): - """Delete method from allmails""" - if self.kivy_state.detail_page_type: + """Handle post-deletion operations.""" + if self.page_type: self.children[0].children[0].active = False App.get_running_app().set_common_header() - self.parent.current = 'allmails' \ - if self.kivy_state.is_allmail else self.kivy_state.detail_page_type + self.parent.current = 'allmails' if self.kivy_state.is_allmail else self.page_type self.kivy_state.detail_page_type = '' toast('Deleted') def get_message_details_to_reply(self, data): - """Getting message details and fill into fields when reply""" + """Prepare message details for reply.""" sender_address = ' wrote:--------------\n' message_time = '\n\n --------------On ' composer_obj = self.parent.screens[1].children[1].ids @@ -196,17 +210,19 @@ def get_message_details_to_reply(self, data): composer_obj.composer_dropdown.text = data[0][0] composer_obj.txt_input.text = data[0][1] split_subject = data[0][2].split('Re:', 1) - composer_obj.subject.text = 'Re: ' + (split_subject[1] if len(split_subject) > 1 else split_subject[0]) + subject_text = split_subject[1] if len(split_subject) > 1 else split_subject[0] + composer_obj.subject.text = 'Re: ' + subject_text time_obj = datetime.fromtimestamp(int(data[0][4])) time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") sender_name = data[0][1] composer_obj.body.text = ( - message_time + time_tag + ', ' + sender_name + sender_address + data[0][3]) + message_time + time_tag + ', ' + sender_name + sender_address + data[0][3] + ) composer_obj.body.focus = True composer_obj.body.cursor = (0, 0) def inbox_reply(self): - """Reply inbox messages""" + """Prepare for replying to an inbox message.""" self.kivy_state.in_composer = True App.get_running_app().root.ids.id_create.children[1].ids.rv.data = '' App.get_running_app().root.ids.sc3.children[1].ids.rv.data = '' @@ -214,29 +230,27 @@ def inbox_reply(self): App.get_running_app().set_navbar_for_composer() def get_message_details_for_draft_reply(self, data): - """Getting and setting message details fill into fields when draft reply""" - composer_ids = ( - self.parent.parent.ids.id_create.children[1].ids) + """Prepare message details for a draft reply.""" + composer_ids = self.parent.parent.ids.id_create.children[1].ids composer_ids.ti.text = data[0][1] composer_ids.btn.text = data[0][1] composer_ids.txt_input.text = data[0][0] composer_ids.subject.text = data[0][2] if data[0][2] != self.no_subject else '' composer_ids.body.text = data[0][3] - def write_msg(self, navApp): - """Write on draft mail""" + def write_msg(self, nav_app): + """Switch to draft mail composition.""" self.kivy_state.send_draft_mail = self.kivy_state.mail_id self.parent.current = 'create' - navApp.set_navbar_for_composer() + nav_app.set_navbar_for_composer() - def detailedPopup(self): - """Detailed popup""" + def detailed_popup(self): + """Show detailed sender information popup.""" obj = SenderDetailPopup() obj.open() - arg = (self.to_addr, self.from_addr, self.timeinseconds) - obj.assignDetail(*arg) + obj.assignDetail(self.to_addr, self.from_addr, self.timeinseconds) @staticmethod - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" + def callback_for_menu_items(text_item, *args): + """Handle menu item callback.""" toast(text_item) diff --git a/src/bitmessagekivy/kv/maildetail.kv b/src/bitmessagekivy/kv/maildetail.kv index e98b86610..636f8ac9b 100644 --- a/src/bitmessagekivy/kv/maildetail.kv +++ b/src/bitmessagekivy/kv/maildetail.kv @@ -1,3 +1,6 @@ +#:set color_transparent (0,0,0,0) # transparent black +#:set color_black (0,0,0,1) # black + : name: 'mailDetail' ScrollView: @@ -8,15 +11,6 @@ # height: dp(bod.height) + self.minimum_height height: self.minimum_height padding: dp(10) - # MDLabel: - # size_hint_y: None - # id: subj - # text: root.subject - # theme_text_color: 'Primary' - # halign: 'left' - # font_style: 'H5' - # height: dp(40) - # on_touch_down: root.allclick(self) OneLineListTitle: id: subj text: app.tr._(root.subject) @@ -24,45 +18,41 @@ font_style: 'H5' theme_text_color: 'Primary' _no_ripple_effect: True - long_press_time: 1 + long_press_time: self.long_press_time TwoLineAvatarIconListItem: id: subaft text: app.tr._(root.from_addr) secondary_text: app.tr._('to ' + root.to_addr) divider: None - on_press: root.detailedPopup() + on_press: root.detailed_popup() BadgeText: size_hint:(None, None) - size:[120, 140] if app.app_platform == 'android' else [64, 80] + size: root.badgeProperties.SIZE_ANDROID if app.app_platform == 'android' else root.badgeProperties.SIZE_OTHER text: app.tr._(root.time_tag) halign:'center' font_style:'Caption' pos_hint: {'center_y': .8} _txt_right_pad: dp(70) - font_size: '11sp' + font_size: root.badgeProperties.FONT_SIZE MDChip: - size_hint: (.16 if app.app_platform == 'android' else .08 , None) + size_hint: root.chipProperties.SIZE_HINT_ANDROID if app.app_platform == 'android' else root.chipProperties.SIZE_HINT_OTHER text: app.tr._(root.page_type) icon: '' - text_color: (1,1,1,1) - pos_hint: {'center_x': .91 if app.app_platform == 'android' else .95, 'center_y': .3} - radius: [8] + text_color: root.chipProperties.TEXT_COLOR + pos_hint: { + 'center_x': root.chipProperties.CENTER_X_ANDROID if app.app_platform == 'android' else root.chipProperties.CENTER_X_OTHER, + 'center_y': root.chipProperties.CENTER_Y + } + radius: root.chipProperties.RADIUS height: self.parent.height/4 AvatarSampleWidget: - source: root.avatarImg + source: root.avatar_image MDLabel: text: root.status disabled: True font_style: 'Body2' halign:'left' padding_x: 20 - # MDLabel: - # id: bod - # font_style: 'Subtitle2' - # theme_text_color: 'Primary' - # text: root.message - # halign: 'left' - # height: self.texture_size[1] MyMDTextField: id: bod size_hint_y: None @@ -70,18 +60,18 @@ text: root.message multiline: True readonly: True - line_color_normal: [0,0,0,0] - _current_line_color: [0,0,0,0] - line_color_focus: [0,0,0,0] + line_color_normal: color_transparent + _current_line_color: color_transparent + line_color_focus: color_transparent markup: True font_size: '15sp' canvas.before: Color: - rgba: (0,0,0,1) + rgba: color_black Loader: : canvas.before: Color: - rgba: (0,0,0,1) + rgba: color_black