Add Kivy message composer screen UI
This commit is contained in:
parent
3f561057be
commit
f6c7e50acf
|
@ -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.
|
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.toast import kivytoast
|
||||||
from kivymd.uix.card import MDCardSwipe
|
from kivymd.uix.card import MDCardSwipe
|
||||||
from kivymd.uix.chip import MDChip
|
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.bitmessagekivy.get_platform import platform
|
||||||
from pybitmessage.bmconfigparser import config
|
from pybitmessage.bmconfigparser import config
|
||||||
|
@ -208,3 +210,27 @@ def msg_content_length(body, subject, max_length=50):
|
||||||
else:
|
else:
|
||||||
subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '')
|
subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '')
|
||||||
return subject
|
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)
|
||||||
|
|
188
src/bitmessagekivy/baseclass/msg_composer.py
Normal file
188
src/bitmessagekivy/baseclass/msg_composer.py
Normal file
|
@ -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"]
|
||||||
|
)
|
|
@ -23,7 +23,6 @@
|
||||||
font_size: '15sp'
|
font_size: '15sp'
|
||||||
multiline: False
|
multiline: False
|
||||||
required: True
|
required: True
|
||||||
# height: self.parent.height/2
|
|
||||||
height: 100
|
height: 100
|
||||||
current_hint_text_color: 0,0,0,0.5
|
current_hint_text_color: 0,0,0,0.5
|
||||||
helper_text_mode: "on_error"
|
helper_text_mode: "on_error"
|
||||||
|
@ -38,11 +37,8 @@
|
||||||
IdentitySpinner:
|
IdentitySpinner:
|
||||||
id: btn
|
id: btn
|
||||||
background_color: app.theme_cls.primary_dark
|
background_color: app.theme_cls.primary_dark
|
||||||
values: app.variable_1
|
values: app.identity_list
|
||||||
on_text: root.auto_fill_fromaddr() if self.text != 'Select' else ''
|
|
||||||
option_cls: Factory.get("ComposerSpinnerOption")
|
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_normal: ''
|
||||||
background_color: app.theme_cls.primary_color
|
background_color: app.theme_cls.primary_color
|
||||||
color: color_font
|
color: color_font
|
||||||
|
@ -62,7 +58,6 @@
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
font_size: '15sp'
|
font_size: '15sp'
|
||||||
color: color_font
|
color: color_font
|
||||||
# height: self.parent.height/2
|
|
||||||
current_hint_text_color: 0,0,0,0.5
|
current_hint_text_color: 0,0,0,0.5
|
||||||
height: 100
|
height: 100
|
||||||
hint_text: app.tr._('Type or Scan QR code for recipients address')
|
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')
|
if root.is_camara_attached(): app.set_screen('scanscreen')
|
||||||
else: root.camera_alert()
|
else: root.camera_alert()
|
||||||
on_press:
|
on_press:
|
||||||
app.root.ids.is_scanscreen.get_screen('composer')
|
app.root.ids.id_scanscreen.get_screen('composer')
|
||||||
|
|
||||||
MyMDTextField:
|
MyMDTextField:
|
||||||
id: subject
|
id: subject
|
||||||
|
@ -98,17 +93,6 @@
|
||||||
Color:
|
Color:
|
||||||
rgba: (0,0,0,1)
|
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:
|
ScrollView:
|
||||||
id: scrlv
|
id: scrlv
|
||||||
MDTextField:
|
MDTextField:
|
||||||
|
@ -171,8 +155,6 @@
|
||||||
|
|
||||||
<ComposerSpinnerOption@SpinnerOption>:
|
<ComposerSpinnerOption@SpinnerOption>:
|
||||||
font_size: '13.5sp'
|
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_normal: 'atlas://data/images/defaulttheme/textinput_active'
|
||||||
background_color: app.theme_cls.primary_color
|
background_color: app.theme_cls.primary_color
|
||||||
color: color_font
|
color: color_font
|
|
@ -218,6 +218,9 @@ MDNavigationLayout:
|
||||||
id:id_scanscreen
|
id:id_scanscreen
|
||||||
Payment:
|
Payment:
|
||||||
id:id_payment
|
id:id_payment
|
||||||
|
Create:
|
||||||
|
id:id_create
|
||||||
|
|
||||||
MDNavigationDrawer:
|
MDNavigationDrawer:
|
||||||
id: nav_drawer
|
id: nav_drawer
|
||||||
|
|
||||||
|
|
|
@ -16,5 +16,10 @@
|
||||||
"kv_string": "payment",
|
"kv_string": "payment",
|
||||||
"name_screen": "payment",
|
"name_screen": "payment",
|
||||||
"Import": "from pybitmessage.bitmessagekivy.baseclass.payment import 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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue
Block a user