From f6c7e50acf1586ff9ce367be418c3ea522014d07 Mon Sep 17 00:00:00 2001 From: shekhar-cis Date: Mon, 3 Oct 2022 13:26:21 +0530 Subject: [PATCH] Add Kivy message composer screen UI --- src/bitmessagekivy/baseclass/common.py | 28 ++- src/bitmessagekivy/baseclass/msg_composer.py | 188 +++++++++++++++++++ src/bitmessagekivy/kv/msg_composer.kv | 24 +-- src/bitmessagekivy/main.kv | 3 + src/bitmessagekivy/screens_data.json | 5 + 5 files changed, 226 insertions(+), 22 deletions(-) create mode 100644 src/bitmessagekivy/baseclass/msg_composer.py diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py index 720f4245..2cf4d76b 100644 --- a/src/bitmessagekivy/baseclass/common.py +++ b/src/bitmessagekivy/baseclass/common.py @@ -1,4 +1,4 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error +# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument """ All Common widgets of kivy are managed here. """ @@ -24,6 +24,8 @@ from kivymd.uix.label import MDLabel from kivymd.toast import kivytoast from kivymd.uix.card import MDCardSwipe from kivymd.uix.chip import MDChip +from kivymd.uix.dialog import MDDialog +from kivymd.uix.button import MDFlatButton from pybitmessage.bitmessagekivy.get_platform import platform from pybitmessage.bmconfigparser import config @@ -208,3 +210,27 @@ def msg_content_length(body, subject, max_length=50): else: subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '') return subject + + +def composer_common_dialog(alert_msg): + """Common alert popup for message composer""" + is_android_width = .8 + other_platform_width = .55 + dialog_height = .25 + width = is_android_width if platform == 'android' else other_platform_width + + dialog_box = MDDialog( + text=alert_msg, + size_hint=(width, dialog_height), + buttons=[ + MDFlatButton( + text="Ok", on_release=lambda x: callback_for_menu_items("Ok") + ), + ], + ) + dialog_box.open() + + def callback_for_menu_items(text_item, *arg): + """Callback of alert box""" + dialog_box.dismiss() + toast(text_item) diff --git a/src/bitmessagekivy/baseclass/msg_composer.py b/src/bitmessagekivy/baseclass/msg_composer.py new file mode 100644 index 00000000..4c385e25 --- /dev/null +++ b/src/bitmessagekivy/baseclass/msg_composer.py @@ -0,0 +1,188 @@ +# pylint: disable=unused-argument, consider-using-f-string, too-many-ancestors +# pylint: disable=no-member, no-name-in-module, too-few-public-methods, no-name-in-module +""" + Message composer screen UI +""" + +import logging + +from kivy.app import App +from kivy.properties import ( + BooleanProperty, + ListProperty, + NumericProperty, + ObjectProperty, +) +from kivy.uix.behaviors import FocusBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.label import Label +from kivy.uix.recycleview import RecycleView +from kivy.uix.recycleboxlayout import RecycleBoxLayout +from kivy.uix.recycleview.layout import LayoutSelectionBehavior +from kivy.uix.recycleview.views import RecycleDataViewBehavior +from kivy.uix.screenmanager import Screen + +from kivymd.uix.textfield import MDTextField + +from pybitmessage import state +from pybitmessage.bitmessagekivy.get_platform import platform +from pybitmessage.bitmessagekivy.baseclass.common import ( + toast, kivy_state_variables, composer_common_dialog +) + +logger = logging.getLogger('default') + + +class Create(Screen): + """Creates Screen class for kivy Ui""" + + def __init__(self, **kwargs): + """Getting Labels and address from addressbook""" + super(Create, self).__init__(**kwargs) + self.kivy_running_app = App.get_running_app() + self.kivy_state = kivy_state_variables() + self.dropdown_widget = DropDownWidget() + self.dropdown_widget.ids.txt_input.starting_no = 2 + self.add_widget(self.dropdown_widget) + self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y) + + def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument + """show data on scroll down""" + if self.children[1].ids.btn.is_open: + self.children[1].ids.btn.is_open = False + + +class RV(RecycleView): + """Recycling View class for kivy Ui""" + + def __init__(self, **kwargs): + """Recycling Method""" + super(RV, self).__init__(**kwargs) + + +class SelectableRecycleBoxLayout( + FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout +): + """Adds selection and focus behaviour to the view""" + # pylint: disable = duplicate-bases + + +class DropDownWidget(BoxLayout): + """DropDownWidget class for kivy Ui""" + + # pylint: disable=too-many-statements + + txt_input = ObjectProperty() + rv = ObjectProperty() + + def __init__(self, **kwargs): + super(DropDownWidget, self).__init__(**kwargs) + self.kivy_running_app = App.get_running_app() + self.kivy_state = kivy_state_variables() + + @staticmethod + def callback_for_msgsend(dt=0): # pylint: disable=unused-argument + """Callback method for messagesend""" + state.kivyapp.root.ids.id_create.children[0].active = False + state.in_sent_method = True + state.kivyapp.back_press() + toast("sent") + + def reset_composer(self): + """Method will reset composer""" + self.ids.ti.text = "" + self.ids.btn.text = "Select" + self.ids.txt_input.text = "" + self.ids.subject.text = "" + self.ids.body.text = "" + toast("Reset message") + + def auto_fill_fromaddr(self): + """Fill the text automatically From Address""" + self.ids.ti.text = self.ids.btn.text + self.ids.ti.focus = True + + def is_camara_attached(self): + """Checks the camera availability in device""" + self.parent.parent.parent.ids.id_scanscreen.check_camera() + is_available = self.parent.parent.parent.ids.id_scanscreen.camera_available + return is_available + + @staticmethod + def camera_alert(): + """Show camera availability alert message""" + feature_unavailable = 'Currently this feature is not available!' + cam_not_available = 'Camera is not available!' + alert_text = feature_unavailable if platform == 'android' else cam_not_available + composer_common_dialog(alert_text) + + +class MyTextInput(MDTextField): + """MyTextInput class for kivy Ui""" + + txt_input = ObjectProperty() + flt_list = ObjectProperty() + word_list = ListProperty() + starting_no = NumericProperty(3) + suggestion_text = '' + + def __init__(self, **kwargs): + """Getting Text Input.""" + super(MyTextInput, self).__init__(**kwargs) + self.__lineBreak__ = 0 + + def on_text(self, instance, value): # pylint: disable=unused-argument + """Find all the occurrence of the word""" + self.parent.parent.parent.parent.parent.ids.rv.data = [] + max_recipient_len = 10 + box_height = 250 + box_max_height = 400 + + matches = [self.word_list[i] for i in range( + len(self.word_list)) if self.word_list[ + i][:self.starting_no] == value[:self.starting_no]] + display_data = [] + for i in matches: + display_data.append({'text': i}) + self.parent.parent.parent.parent.parent.ids.rv.data = display_data + if len(matches) <= max_recipient_len: + self.parent.height = (box_height + (len(matches) * 20)) + else: + self.parent.height = box_max_height + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + """Keyboard on key Down""" + if self.suggestion_text and keycode[1] == 'tab' and modifiers is None: + self.insert_text(self.suggestion_text + ' ') + return True + return super(MyTextInput, self).keyboard_on_key_down( + window, keycode, text, modifiers) + + +class SelectableLabel(RecycleDataViewBehavior, Label): + """Add selection support to the Label""" + + index = None + selected = BooleanProperty(False) + selectable = BooleanProperty(True) + + def refresh_view_attrs(self, rv, index, data): + """Catch and handle the view changes""" + self.index = index + return super(SelectableLabel, self).refresh_view_attrs(rv, index, data) + + def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements + """Add selection on touch down""" + if super(SelectableLabel, self).on_touch_down(touch): + return True + if self.collide_point(*touch.pos) and self.selectable: + return self.parent.select_with_touch(self.index, touch) + + def apply_selection(self, rv, index, is_selected): + """Respond to the selection of items in the view""" + self.selected = is_selected + if is_selected: + logger.debug("selection changed to %s", rv.data[index]) + rv.parent.txt_input.text = rv.parent.txt_input.text.replace( + rv.parent.txt_input.text, rv.data[index]["text"] + ) diff --git a/src/bitmessagekivy/kv/msg_composer.kv b/src/bitmessagekivy/kv/msg_composer.kv index 02c6cdd8..aeb7063a 100644 --- a/src/bitmessagekivy/kv/msg_composer.kv +++ b/src/bitmessagekivy/kv/msg_composer.kv @@ -23,7 +23,6 @@ font_size: '15sp' multiline: False required: True - # height: self.parent.height/2 height: 100 current_hint_text_color: 0,0,0,0.5 helper_text_mode: "on_error" @@ -38,11 +37,8 @@ IdentitySpinner: id: btn background_color: app.theme_cls.primary_dark - values: app.variable_1 - on_text: root.auto_fill_fromaddr() if self.text != 'Select' else '' + values: app.identity_list option_cls: Factory.get("ComposerSpinnerOption") - #background_color: color_button if self.state == 'normal' else color_button_pressed - #background_down: 'atlas://data/images/defaulttheme/spinner' background_normal: '' background_color: app.theme_cls.primary_color color: color_font @@ -62,7 +58,6 @@ size_hint_y: None font_size: '15sp' color: color_font - # height: self.parent.height/2 current_hint_text_color: 0,0,0,0.5 height: 100 hint_text: app.tr._('Type or Scan QR code for recipients address') @@ -79,7 +74,7 @@ if root.is_camara_attached(): app.set_screen('scanscreen') else: root.camera_alert() on_press: - app.root.ids.is_scanscreen.get_screen('composer') + app.root.ids.id_scanscreen.get_screen('composer') MyMDTextField: id: subject @@ -98,17 +93,6 @@ Color: rgba: (0,0,0,1) - # MyMDTextField: - # id: body - # multiline: True - # hint_text: 'body' - # size_hint_y: None - # font_size: '15sp' - # required: True - # helper_text_mode: "on_error" - # canvas.before: - # Color: - # rgba: (0,0,0,1) ScrollView: id: scrlv MDTextField: @@ -171,8 +155,6 @@ : font_size: '13.5sp' - #background_color: color_button if self.state == 'down' else color_button_pressed - #background_down: 'atlas://data/images/defaulttheme/button' background_normal: 'atlas://data/images/defaulttheme/textinput_active' background_color: app.theme_cls.primary_color - color: color_font \ No newline at end of file + color: color_font diff --git a/src/bitmessagekivy/main.kv b/src/bitmessagekivy/main.kv index 7221c0d6..4f5de8cf 100644 --- a/src/bitmessagekivy/main.kv +++ b/src/bitmessagekivy/main.kv @@ -218,6 +218,9 @@ MDNavigationLayout: id:id_scanscreen Payment: id:id_payment + Create: + id:id_create + MDNavigationDrawer: id: nav_drawer diff --git a/src/bitmessagekivy/screens_data.json b/src/bitmessagekivy/screens_data.json index 392b071a..81a3ce70 100644 --- a/src/bitmessagekivy/screens_data.json +++ b/src/bitmessagekivy/screens_data.json @@ -16,5 +16,10 @@ "kv_string": "payment", "name_screen": "payment", "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment" + }, + "Create": { + "kv_string": "msg_composer", + "name_screen": "create", + "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create" } }