Skip to content

Commit f6c7e50

Browse files
committed
Add Kivy message composer screen UI
1 parent 3f56105 commit f6c7e50

File tree

5 files changed

+226
-22
lines changed

5 files changed

+226
-22
lines changed

src/bitmessagekivy/baseclass/common.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error
1+
# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument
22
"""
33
All Common widgets of kivy are managed here.
44
"""
@@ -24,6 +24,8 @@
2424
from kivymd.toast import kivytoast
2525
from kivymd.uix.card import MDCardSwipe
2626
from kivymd.uix.chip import MDChip
27+
from kivymd.uix.dialog import MDDialog
28+
from kivymd.uix.button import MDFlatButton
2729

2830
from pybitmessage.bitmessagekivy.get_platform import platform
2931
from pybitmessage.bmconfigparser import config
@@ -208,3 +210,27 @@ def msg_content_length(body, subject, max_length=50):
208210
else:
209211
subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '')
210212
return subject
213+
214+
215+
def composer_common_dialog(alert_msg):
216+
"""Common alert popup for message composer"""
217+
is_android_width = .8
218+
other_platform_width = .55
219+
dialog_height = .25
220+
width = is_android_width if platform == 'android' else other_platform_width
221+
222+
dialog_box = MDDialog(
223+
text=alert_msg,
224+
size_hint=(width, dialog_height),
225+
buttons=[
226+
MDFlatButton(
227+
text="Ok", on_release=lambda x: callback_for_menu_items("Ok")
228+
),
229+
],
230+
)
231+
dialog_box.open()
232+
233+
def callback_for_menu_items(text_item, *arg):
234+
"""Callback of alert box"""
235+
dialog_box.dismiss()
236+
toast(text_item)
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# pylint: disable=unused-argument, consider-using-f-string, too-many-ancestors
2+
# pylint: disable=no-member, no-name-in-module, too-few-public-methods, no-name-in-module
3+
"""
4+
Message composer screen UI
5+
"""
6+
7+
import logging
8+
9+
from kivy.app import App
10+
from kivy.properties import (
11+
BooleanProperty,
12+
ListProperty,
13+
NumericProperty,
14+
ObjectProperty,
15+
)
16+
from kivy.uix.behaviors import FocusBehavior
17+
from kivy.uix.boxlayout import BoxLayout
18+
from kivy.uix.label import Label
19+
from kivy.uix.recycleview import RecycleView
20+
from kivy.uix.recycleboxlayout import RecycleBoxLayout
21+
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
22+
from kivy.uix.recycleview.views import RecycleDataViewBehavior
23+
from kivy.uix.screenmanager import Screen
24+
25+
from kivymd.uix.textfield import MDTextField
26+
27+
from pybitmessage import state
28+
from pybitmessage.bitmessagekivy.get_platform import platform
29+
from pybitmessage.bitmessagekivy.baseclass.common import (
30+
toast, kivy_state_variables, composer_common_dialog
31+
)
32+
33+
logger = logging.getLogger('default')
34+
35+
36+
class Create(Screen):
37+
"""Creates Screen class for kivy Ui"""
38+
39+
def __init__(self, **kwargs):
40+
"""Getting Labels and address from addressbook"""
41+
super(Create, self).__init__(**kwargs)
42+
self.kivy_running_app = App.get_running_app()
43+
self.kivy_state = kivy_state_variables()
44+
self.dropdown_widget = DropDownWidget()
45+
self.dropdown_widget.ids.txt_input.starting_no = 2
46+
self.add_widget(self.dropdown_widget)
47+
self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y)
48+
49+
def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument
50+
"""show data on scroll down"""
51+
if self.children[1].ids.btn.is_open:
52+
self.children[1].ids.btn.is_open = False
53+
54+
55+
class RV(RecycleView):
56+
"""Recycling View class for kivy Ui"""
57+
58+
def __init__(self, **kwargs):
59+
"""Recycling Method"""
60+
super(RV, self).__init__(**kwargs)
61+
62+
63+
class SelectableRecycleBoxLayout(
64+
FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout
65+
):
66+
"""Adds selection and focus behaviour to the view"""
67+
# pylint: disable = duplicate-bases
68+
69+
70+
class DropDownWidget(BoxLayout):
71+
"""DropDownWidget class for kivy Ui"""
72+
73+
# pylint: disable=too-many-statements
74+
75+
txt_input = ObjectProperty()
76+
rv = ObjectProperty()
77+
78+
def __init__(self, **kwargs):
79+
super(DropDownWidget, self).__init__(**kwargs)
80+
self.kivy_running_app = App.get_running_app()
81+
self.kivy_state = kivy_state_variables()
82+
83+
@staticmethod
84+
def callback_for_msgsend(dt=0): # pylint: disable=unused-argument
85+
"""Callback method for messagesend"""
86+
state.kivyapp.root.ids.id_create.children[0].active = False
87+
state.in_sent_method = True
88+
state.kivyapp.back_press()
89+
toast("sent")
90+
91+
def reset_composer(self):
92+
"""Method will reset composer"""
93+
self.ids.ti.text = ""
94+
self.ids.btn.text = "Select"
95+
self.ids.txt_input.text = ""
96+
self.ids.subject.text = ""
97+
self.ids.body.text = ""
98+
toast("Reset message")
99+
100+
def auto_fill_fromaddr(self):
101+
"""Fill the text automatically From Address"""
102+
self.ids.ti.text = self.ids.btn.text
103+
self.ids.ti.focus = True
104+
105+
def is_camara_attached(self):
106+
"""Checks the camera availability in device"""
107+
self.parent.parent.parent.ids.id_scanscreen.check_camera()
108+
is_available = self.parent.parent.parent.ids.id_scanscreen.camera_available
109+
return is_available
110+
111+
@staticmethod
112+
def camera_alert():
113+
"""Show camera availability alert message"""
114+
feature_unavailable = 'Currently this feature is not available!'
115+
cam_not_available = 'Camera is not available!'
116+
alert_text = feature_unavailable if platform == 'android' else cam_not_available
117+
composer_common_dialog(alert_text)
118+
119+
120+
class MyTextInput(MDTextField):
121+
"""MyTextInput class for kivy Ui"""
122+
123+
txt_input = ObjectProperty()
124+
flt_list = ObjectProperty()
125+
word_list = ListProperty()
126+
starting_no = NumericProperty(3)
127+
suggestion_text = ''
128+
129+
def __init__(self, **kwargs):
130+
"""Getting Text Input."""
131+
super(MyTextInput, self).__init__(**kwargs)
132+
self.__lineBreak__ = 0
133+
134+
def on_text(self, instance, value): # pylint: disable=unused-argument
135+
"""Find all the occurrence of the word"""
136+
self.parent.parent.parent.parent.parent.ids.rv.data = []
137+
max_recipient_len = 10
138+
box_height = 250
139+
box_max_height = 400
140+
141+
matches = [self.word_list[i] for i in range(
142+
len(self.word_list)) if self.word_list[
143+
i][:self.starting_no] == value[:self.starting_no]]
144+
display_data = []
145+
for i in matches:
146+
display_data.append({'text': i})
147+
self.parent.parent.parent.parent.parent.ids.rv.data = display_data
148+
if len(matches) <= max_recipient_len:
149+
self.parent.height = (box_height + (len(matches) * 20))
150+
else:
151+
self.parent.height = box_max_height
152+
153+
def keyboard_on_key_down(self, window, keycode, text, modifiers):
154+
"""Keyboard on key Down"""
155+
if self.suggestion_text and keycode[1] == 'tab' and modifiers is None:
156+
self.insert_text(self.suggestion_text + ' ')
157+
return True
158+
return super(MyTextInput, self).keyboard_on_key_down(
159+
window, keycode, text, modifiers)
160+
161+
162+
class SelectableLabel(RecycleDataViewBehavior, Label):
163+
"""Add selection support to the Label"""
164+
165+
index = None
166+
selected = BooleanProperty(False)
167+
selectable = BooleanProperty(True)
168+
169+
def refresh_view_attrs(self, rv, index, data):
170+
"""Catch and handle the view changes"""
171+
self.index = index
172+
return super(SelectableLabel, self).refresh_view_attrs(rv, index, data)
173+
174+
def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements
175+
"""Add selection on touch down"""
176+
if super(SelectableLabel, self).on_touch_down(touch):
177+
return True
178+
if self.collide_point(*touch.pos) and self.selectable:
179+
return self.parent.select_with_touch(self.index, touch)
180+
181+
def apply_selection(self, rv, index, is_selected):
182+
"""Respond to the selection of items in the view"""
183+
self.selected = is_selected
184+
if is_selected:
185+
logger.debug("selection changed to %s", rv.data[index])
186+
rv.parent.txt_input.text = rv.parent.txt_input.text.replace(
187+
rv.parent.txt_input.text, rv.data[index]["text"]
188+
)

src/bitmessagekivy/kv/msg_composer.kv

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
font_size: '15sp'
2424
multiline: False
2525
required: True
26-
# height: self.parent.height/2
2726
height: 100
2827
current_hint_text_color: 0,0,0,0.5
2928
helper_text_mode: "on_error"
@@ -38,11 +37,8 @@
3837
IdentitySpinner:
3938
id: btn
4039
background_color: app.theme_cls.primary_dark
41-
values: app.variable_1
42-
on_text: root.auto_fill_fromaddr() if self.text != 'Select' else ''
40+
values: app.identity_list
4341
option_cls: Factory.get("ComposerSpinnerOption")
44-
#background_color: color_button if self.state == 'normal' else color_button_pressed
45-
#background_down: 'atlas://data/images/defaulttheme/spinner'
4642
background_normal: ''
4743
background_color: app.theme_cls.primary_color
4844
color: color_font
@@ -62,7 +58,6 @@
6258
size_hint_y: None
6359
font_size: '15sp'
6460
color: color_font
65-
# height: self.parent.height/2
6661
current_hint_text_color: 0,0,0,0.5
6762
height: 100
6863
hint_text: app.tr._('Type or Scan QR code for recipients address')
@@ -79,7 +74,7 @@
7974
if root.is_camara_attached(): app.set_screen('scanscreen')
8075
else: root.camera_alert()
8176
on_press:
82-
app.root.ids.is_scanscreen.get_screen('composer')
77+
app.root.ids.id_scanscreen.get_screen('composer')
8378

8479
MyMDTextField:
8580
id: subject
@@ -98,17 +93,6 @@
9893
Color:
9994
rgba: (0,0,0,1)
10095

101-
# MyMDTextField:
102-
# id: body
103-
# multiline: True
104-
# hint_text: 'body'
105-
# size_hint_y: None
106-
# font_size: '15sp'
107-
# required: True
108-
# helper_text_mode: "on_error"
109-
# canvas.before:
110-
# Color:
111-
# rgba: (0,0,0,1)
11296
ScrollView:
11397
id: scrlv
11498
MDTextField:
@@ -171,8 +155,6 @@
171155

172156
<ComposerSpinnerOption@SpinnerOption>:
173157
font_size: '13.5sp'
174-
#background_color: color_button if self.state == 'down' else color_button_pressed
175-
#background_down: 'atlas://data/images/defaulttheme/button'
176158
background_normal: 'atlas://data/images/defaulttheme/textinput_active'
177159
background_color: app.theme_cls.primary_color
178-
color: color_font
160+
color: color_font

src/bitmessagekivy/main.kv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ MDNavigationLayout:
218218
id:id_scanscreen
219219
Payment:
220220
id:id_payment
221+
Create:
222+
id:id_create
223+
221224
MDNavigationDrawer:
222225
id: nav_drawer
223226

src/bitmessagekivy/screens_data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@
1616
"kv_string": "payment",
1717
"name_screen": "payment",
1818
"Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment"
19+
},
20+
"Create": {
21+
"kv_string": "msg_composer",
22+
"name_screen": "create",
23+
"Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create"
1924
}
2025
}

0 commit comments

Comments
 (0)