identicon or subscription package functionality implementation
This commit is contained in:
parent
d78b000f8a
commit
81284422df
|
@ -53,15 +53,15 @@
|
||||||
color: color_font
|
color: color_font
|
||||||
|
|
||||||
<ContentNavigationDrawer@Navigatorss>:
|
<ContentNavigationDrawer@Navigatorss>:
|
||||||
drawer_logo: './images/drawer_logo1.png'
|
drawer_logo: app.address_identicon()
|
||||||
NavigationDrawerDivider:
|
NavigationDrawerDivider:
|
||||||
|
|
||||||
NavigationDrawerTwoLineListItem:
|
NavigationDrawerTwoLineListItem:
|
||||||
text: "Accounts"
|
text: "Accounts"
|
||||||
NavigationDrawerIconButton:
|
NavigationDrawerIconButton:
|
||||||
CustomSpinner:
|
CustomSpinner:
|
||||||
pos_hint:{"x":0,"y":.25}
|
|
||||||
id: btn
|
id: btn
|
||||||
|
pos_hint:{"x":0,"y":.25}
|
||||||
option_cls: Factory.get("MySpinnerOption")
|
option_cls: Factory.get("MySpinnerOption")
|
||||||
font_size: '12.5sp'
|
font_size: '12.5sp'
|
||||||
text: app.getDefaultAccData()
|
text: app.getDefaultAccData()
|
||||||
|
@ -70,6 +70,11 @@
|
||||||
color: color_font
|
color: color_font
|
||||||
values: app.variable_1
|
values: app.variable_1
|
||||||
on_text:app.getCurrentAccountData(self.text)
|
on_text:app.getCurrentAccountData(self.text)
|
||||||
|
Image:
|
||||||
|
source: app.get_default_image()
|
||||||
|
x: self.width/4
|
||||||
|
y: self.parent.y + self.parent.height/2 - self.height + 10
|
||||||
|
size: 20, 20
|
||||||
ArrowImg:
|
ArrowImg:
|
||||||
NavigationDrawerIconButton:
|
NavigationDrawerIconButton:
|
||||||
id: inbox_cnt
|
id: inbox_cnt
|
||||||
|
@ -200,6 +205,7 @@ NavigationLayout:
|
||||||
id: search_field
|
id: search_field
|
||||||
hint_text: 'Search'
|
hint_text: 'Search'
|
||||||
on_text: app.searchQuery(self)
|
on_text: app.searchQuery(self)
|
||||||
|
|
||||||
ScreenManager:
|
ScreenManager:
|
||||||
id: scr_mngr
|
id: scr_mngr
|
||||||
Inbox:
|
Inbox:
|
||||||
|
@ -304,7 +310,7 @@ NavigationLayout:
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: dp(500)
|
height: self.minimum_height + 2 * self.parent.height/4
|
||||||
padding: dp(32)
|
padding: dp(32)
|
||||||
spacing: 15
|
spacing: 15
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
|
@ -634,6 +640,117 @@ NavigationLayout:
|
||||||
|
|
||||||
<Payment>:
|
<Payment>:
|
||||||
name: 'payment'
|
name: 'payment'
|
||||||
|
ScrollView:
|
||||||
|
do_scroll_x: False
|
||||||
|
BoxLayout:
|
||||||
|
orientation: 'horizontal'
|
||||||
|
padding: dp(20)
|
||||||
|
spacing: 10
|
||||||
|
BoxLayout:
|
||||||
|
orientation: 'vertical'
|
||||||
|
padding: dp(5)
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.957, 0.890, 0.843, 1
|
||||||
|
Rectangle:
|
||||||
|
# self here refers to the widget i.e FloatLayout
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
MDLabel:
|
||||||
|
size_hint_y: None
|
||||||
|
font_style: 'Headline'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: 'Platinum'
|
||||||
|
halign: 'center'
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Subhead'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: 'We provide subscriptions to real-time streaming market data directly from exchanges, quote aggregators and index providers. The Market Data Subscriptions page lets you sign up for additional market data subscriptions such as NASDAQ TotalView and NYSE Open Book or unsubscribe from market data. '
|
||||||
|
halign: 'center'
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Headline'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: '€ 50.0'
|
||||||
|
halign: 'center'
|
||||||
|
MDRaisedButton:
|
||||||
|
size_hint: 1, None
|
||||||
|
height: dp(40)
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Title'
|
||||||
|
text: 'Select'
|
||||||
|
font_size: '13sp'
|
||||||
|
color: (1,1,1,1)
|
||||||
|
halign: 'center'
|
||||||
|
BoxLayout:
|
||||||
|
orientation: 'vertical'
|
||||||
|
padding: dp(5)
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.957, 0.890, 0.843, 1
|
||||||
|
Rectangle:
|
||||||
|
# self here refers to the widget i.e FloatLayout
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
MDLabel:
|
||||||
|
size_hint_y: None
|
||||||
|
font_style: 'Headline'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: 'Silver'
|
||||||
|
halign: 'center'
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Subhead'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: 'We provide subscriptions to real-time streaming market data directly from exchanges, quote aggregators and index providers. The Market Data Subscriptions page lets you sign up for additional market data subscriptions such as NASDAQ TotalView and NYSE Open Book or unsubscribe from market data. '
|
||||||
|
halign: 'center'
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Headline'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: '€ 100.0'
|
||||||
|
halign: 'center'
|
||||||
|
MDRaisedButton:
|
||||||
|
size_hint: 1, None
|
||||||
|
height: dp(40)
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Title'
|
||||||
|
text: 'Select'
|
||||||
|
font_size: '13sp'
|
||||||
|
color: (1,1,1,1)
|
||||||
|
halign: 'center'
|
||||||
|
BoxLayout:
|
||||||
|
orientation: 'vertical'
|
||||||
|
padding: dp(5)
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.957, 0.890, 0.843, 1
|
||||||
|
Rectangle:
|
||||||
|
# self here refers to the widget i.e FloatLayout
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
MDLabel:
|
||||||
|
size_hint_y: None
|
||||||
|
font_style: 'Headline'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: 'Gold'
|
||||||
|
halign: 'center'
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Subhead'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: 'We provide subscriptions to real-time streaming market data directly from exchanges, quote aggregators and index providers. The Market Data Subscriptions page lets you sign up for additional market data subscriptions such as NASDAQ TotalView and NYSE Open Book or unsubscribe from market data. '
|
||||||
|
halign: 'center'
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Headline'
|
||||||
|
theme_text_color: 'Primary'
|
||||||
|
text: '€ 500.0'
|
||||||
|
halign: 'center'
|
||||||
|
MDRaisedButton:
|
||||||
|
size_hint: 1, None
|
||||||
|
height: dp(40)
|
||||||
|
MDLabel:
|
||||||
|
font_style: 'Title'
|
||||||
|
text: 'Select'
|
||||||
|
font_size: '13sp'
|
||||||
|
color: (1,1,1,1)
|
||||||
|
halign: 'center'
|
||||||
|
|
||||||
|
|
||||||
<GrashofPopup>:
|
<GrashofPopup>:
|
||||||
|
@ -663,10 +780,12 @@ NavigationLayout:
|
||||||
hint_text: "Address"
|
hint_text: "Address"
|
||||||
required: True
|
required: True
|
||||||
helper_text_mode: "on_error"
|
helper_text_mode: "on_error"
|
||||||
|
on_text: root.checkAddress_valid(self)
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
spacing:5
|
spacing:5
|
||||||
orientation: 'horizontal'
|
orientation: 'horizontal'
|
||||||
MDRaisedButton:
|
MDRaisedButton:
|
||||||
|
id: save_addr
|
||||||
size_hint: 1.5, None
|
size_hint: 1.5, None
|
||||||
height: dp(40)
|
height: dp(40)
|
||||||
on_release:
|
on_release:
|
||||||
|
@ -698,6 +817,7 @@ NavigationLayout:
|
||||||
color: (1,1,1,1)
|
color: (1,1,1,1)
|
||||||
halign: 'center'
|
halign: 'center'
|
||||||
|
|
||||||
|
|
||||||
<NetworkStat>:
|
<NetworkStat>:
|
||||||
name: 'networkstat'
|
name: 'networkstat'
|
||||||
MDTabbedPanel:
|
MDTabbedPanel:
|
||||||
|
@ -795,23 +915,27 @@ NavigationLayout:
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: dp(400)
|
height: dp(400) + self.minimum_height
|
||||||
padding: dp(32)
|
padding: dp(32)
|
||||||
MDLabel:
|
MDLabel:
|
||||||
font_style: 'Headline'
|
font_style: 'Headline'
|
||||||
theme_text_color: 'Primary'
|
theme_text_color: 'Primary'
|
||||||
text: root.subject
|
text: root.subject
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
|
font_size: '20sp'
|
||||||
|
CopyTextBtn:
|
||||||
MDLabel:
|
MDLabel:
|
||||||
font_style: 'Subhead'
|
font_style: 'Subhead'
|
||||||
theme_text_color: 'Primary'
|
theme_text_color: 'Primary'
|
||||||
text: "From: " + root.from_addr
|
text: "From: " + root.from_addr
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
|
CopyTextBtn:
|
||||||
MDLabel:
|
MDLabel:
|
||||||
font_style: 'Subhead'
|
font_style: 'Subhead'
|
||||||
theme_text_color: 'Primary'
|
theme_text_color: 'Primary'
|
||||||
text: "To: " + root.to_addr
|
text: "To: " + root.to_addr
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
|
CopyTextBtn:
|
||||||
MDLabel:
|
MDLabel:
|
||||||
font_style: 'Subhead'
|
font_style: 'Subhead'
|
||||||
theme_text_color: 'Primary'
|
theme_text_color: 'Primary'
|
||||||
|
@ -823,12 +947,17 @@ NavigationLayout:
|
||||||
text: root.message
|
text: root.message
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
bold: True
|
bold: True
|
||||||
|
CopyTextBtn:
|
||||||
|
BoxLayout:
|
||||||
|
orientation: 'vertical'
|
||||||
|
size_hint_y: None
|
||||||
|
height: dp(100) + self.minimum_height
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
spacing:20
|
spacing:20
|
||||||
MDRaisedButton:
|
MDRaisedButton:
|
||||||
size_hint: 1, None
|
size_hint: 1, None
|
||||||
height: dp(40)
|
height: dp(40)
|
||||||
on_press: root.inbox_reply() if root.page_type == 'inbox' else root.copy_sent_mail() if root.page_type == 'sent' else root.write_msg(app)
|
on_press: root.inbox_reply() if root.page_type == 'inbox' else root.write_msg(app) if root.page_type == 'draft' else root.copy_sent_mail()
|
||||||
MDLabel:
|
MDLabel:
|
||||||
font_style: 'Title'
|
font_style: 'Title'
|
||||||
text: 'Reply' if root.page_type == 'inbox' else 'Copy' if root.page_type == 'sent' else 'Write'
|
text: 'Reply' if root.page_type == 'inbox' else 'Copy' if root.page_type == 'sent' else 'Write'
|
||||||
|
@ -846,6 +975,19 @@ NavigationLayout:
|
||||||
color: (1,1,1,1)
|
color: (1,1,1,1)
|
||||||
halign: 'center'
|
halign: 'center'
|
||||||
|
|
||||||
|
<CopyTextBtn@Button>:
|
||||||
|
id: cpyButton
|
||||||
|
color: 0,0,0,1
|
||||||
|
background_color: (0,0,0,0)
|
||||||
|
center_x: self.parent.center_x * 2 - self.parent.parent.padding[0]/2
|
||||||
|
center_y: self.parent.center_y
|
||||||
|
on_press:app.root.ids.sc14.copy_composer_text(self)
|
||||||
|
Image:
|
||||||
|
source: './images/copy_text.png'
|
||||||
|
center_x: self.parent.center_x
|
||||||
|
center_y: self.parent.center_y
|
||||||
|
size: 20, 20
|
||||||
|
|
||||||
<ComposerButton@BoxLayout>:
|
<ComposerButton@BoxLayout>:
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: dp(56)
|
height: dp(56)
|
||||||
|
|
|
@ -61,6 +61,12 @@ import state
|
||||||
|
|
||||||
from uikivysignaler import UIkivySignaler
|
from uikivysignaler import UIkivySignaler
|
||||||
|
|
||||||
|
import identiconGeneration
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from kivy.core.clipboard import Clipboard
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
# pylint: disable=broad-except
|
# pylint: disable=broad-except
|
||||||
|
|
||||||
|
@ -120,10 +126,7 @@ class Inbox(Screen):
|
||||||
third_text = mail[3].replace('\n', ' ')
|
third_text = mail[3].replace('\n', ' ')
|
||||||
data.append({
|
data.append({
|
||||||
'text': mail[4].strip(),
|
'text': mail[4].strip(),
|
||||||
'secondary_text': mail[5][:10] + '...........' if len(
|
'secondary_text': mail[5][:50] + '........' if len(mail[5]) >= 50 else (mail[5] + ',' + mail[3].replace('\n', ''))[0:50] + '........',
|
||||||
mail[3]) > 10 else mail[3] + '\n' + " " + (
|
|
||||||
third_text[:25] + '...!') if len(
|
|
||||||
third_text) > 25 else third_text,
|
|
||||||
'receivedTime': mail[6]})
|
'receivedTime': mail[6]})
|
||||||
for item in data:
|
for item in data:
|
||||||
meny = ThreeLineAvatarIconListItem(
|
meny = ThreeLineAvatarIconListItem(
|
||||||
|
@ -131,11 +134,11 @@ class Inbox(Screen):
|
||||||
secondary_text=item['secondary_text'],
|
secondary_text=item['secondary_text'],
|
||||||
theme_text_color='Custom',
|
theme_text_color='Custom',
|
||||||
text_color=NavigateApp().theme_cls.primary_color)
|
text_color=NavigateApp().theme_cls.primary_color)
|
||||||
img_latter = item['secondary_text'][0].upper() if (
|
# img_latter = item['secondary_text'][0].upper() if (
|
||||||
item['secondary_text'][0].upper() >= 'A' and item[
|
# item['secondary_text'][0].upper() >= 'A' and item[
|
||||||
'secondary_text'][0].upper() <= 'Z') else '!'
|
# 'secondary_text'][0].upper() <= 'Z') else '!'
|
||||||
meny.add_widget(AvatarSampleWidget(
|
meny.add_widget(AvatarSampleWidget(
|
||||||
source='./images/text_images/{}.png'.format(img_latter)))
|
source='./images/text_images/{}.png'.format(avatarImageFirstLetter(item['secondary_text'].strip()))))
|
||||||
meny.bind(on_press=partial(
|
meny.bind(on_press=partial(
|
||||||
self.inbox_detail, item['receivedTime']))
|
self.inbox_detail, item['receivedTime']))
|
||||||
carousel = Carousel(direction='right')
|
carousel = Carousel(direction='right')
|
||||||
|
@ -269,11 +272,8 @@ class MyAddress(Screen):
|
||||||
secondary_text=item['secondary_text'],
|
secondary_text=item['secondary_text'],
|
||||||
theme_text_color='Custom',
|
theme_text_color='Custom',
|
||||||
text_color=NavigateApp().theme_cls.primary_color)
|
text_color=NavigateApp().theme_cls.primary_color)
|
||||||
img_latter = item['text'][0].upper() if (
|
|
||||||
item['text'][0].upper() >= 'A' and item['text'][
|
|
||||||
0].upper() <= 'Z') else '!'
|
|
||||||
meny.add_widget(AvatarSampleWidget(
|
meny.add_widget(AvatarSampleWidget(
|
||||||
source='./images/text_images/{}.png'.format(img_latter)))
|
source='./images/text_images/{}.png'.format(avatarImageFirstLetter(item['text'].strip()))))
|
||||||
meny.bind(on_press=partial(
|
meny.bind(on_press=partial(
|
||||||
self.myadd_detail, item['secondary_text'], item['text']))
|
self.myadd_detail, item['secondary_text'], item['text']))
|
||||||
self.ids.ml.add_widget(meny)
|
self.ids.ml.add_widget(meny)
|
||||||
|
@ -356,11 +356,8 @@ class AddressBook(Screen):
|
||||||
secondary_text=item[1],
|
secondary_text=item[1],
|
||||||
theme_text_color='Custom',
|
theme_text_color='Custom',
|
||||||
text_color=NavigateApp().theme_cls.primary_color)
|
text_color=NavigateApp().theme_cls.primary_color)
|
||||||
img_latter = item[0][0].upper() if (
|
|
||||||
item[0][0].upper() >= 'A' and item[0][
|
|
||||||
0].upper() <= 'Z') else '!'
|
|
||||||
meny.add_widget(AvatarSampleWidget(
|
meny.add_widget(AvatarSampleWidget(
|
||||||
source='./images/text_images/{}.png'.format(img_latter)))
|
source='./images/text_images/{}.png'.format(avatarImageFirstLetter(item[0].strip()))))
|
||||||
meny.bind(on_press=partial(
|
meny.bind(on_press=partial(
|
||||||
self.addBook_detail, item[1], item[0]))
|
self.addBook_detail, item[1], item[0]))
|
||||||
carousel = Carousel(direction='right')
|
carousel = Carousel(direction='right')
|
||||||
|
@ -470,8 +467,8 @@ class DropDownWidget(BoxLayout):
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
fromAddress = str(self.ids.ti.text)
|
fromAddress = str(self.ids.ti.text)
|
||||||
toAddress = str(self.ids.txt_input.text)
|
toAddress = str(self.ids.txt_input.text)
|
||||||
subject = str(self.ids.subject.text)
|
subject = self.ids.subject.text.encode('utf-8').strip()
|
||||||
message = str(self.ids.body.text)
|
message = self.ids.body.text.encode('utf-8').strip()
|
||||||
encoding = 3
|
encoding = 3
|
||||||
print "message: ", self.ids.body.text
|
print "message: ", self.ids.body.text
|
||||||
sendMessageToPeople = True
|
sendMessageToPeople = True
|
||||||
|
@ -542,13 +539,7 @@ class DropDownWidget(BoxLayout):
|
||||||
|
|
||||||
queues.workerQueue.put(('sendmessage', toAddress))
|
queues.workerQueue.put(('sendmessage', toAddress))
|
||||||
print "sqlExecute successfully #######################"
|
print "sqlExecute successfully #######################"
|
||||||
self.ids.body.text = ''
|
|
||||||
self.ids.ti.text = ''
|
|
||||||
self.ids.subject.text = ''
|
|
||||||
self.ids.txt_input.text = ''
|
|
||||||
self.parent.parent.current = 'inbox'
|
self.parent.parent.current = 'inbox'
|
||||||
self.ids.btn.text = 'select'
|
|
||||||
self.ids.ti.text = ''
|
|
||||||
navApp.back_press()
|
navApp.back_press()
|
||||||
toast('send')
|
toast('send')
|
||||||
return None
|
return None
|
||||||
|
@ -709,7 +700,7 @@ class Random(Screen):
|
||||||
eighteenByteRipe = False
|
eighteenByteRipe = False
|
||||||
nonceTrialsPerByte = 1000
|
nonceTrialsPerByte = 1000
|
||||||
payloadLengthExtraBytes = 1000
|
payloadLengthExtraBytes = 1000
|
||||||
if self.ids.label.text:
|
if str(self.ids.label.text).strip():
|
||||||
queues.addressGeneratorQueue.put((
|
queues.addressGeneratorQueue.put((
|
||||||
'createRandomAddress',
|
'createRandomAddress',
|
||||||
4, streamNumberForAddress,
|
4, streamNumberForAddress,
|
||||||
|
@ -774,13 +765,9 @@ class Sent(Screen):
|
||||||
|
|
||||||
if queryreturn:
|
if queryreturn:
|
||||||
for mail in queryreturn:
|
for mail in queryreturn:
|
||||||
third_text = mail[3].replace('\n', ' ')
|
|
||||||
self.data.append({
|
self.data.append({
|
||||||
'text': mail[1].strip(),
|
'text': mail[1].strip(),
|
||||||
'secondary_text': mail[2][:10] + '...........' if len(
|
'secondary_text': mail[2][:50] + '........' if len(mail[2]) >= 50 else (mail[2] + ',' + mail[3].replace('\n', ''))[0:50] + '........',
|
||||||
mail[2]) > 10 else mail[2] + '\n' + " " + (
|
|
||||||
third_text[:25] + '...!') if len(
|
|
||||||
third_text) > 25 else third_text,
|
|
||||||
'lastactiontime': mail[6]})
|
'lastactiontime': mail[6]})
|
||||||
for item in self.data:
|
for item in self.data:
|
||||||
meny = ThreeLineAvatarIconListItem(
|
meny = ThreeLineAvatarIconListItem(
|
||||||
|
@ -788,11 +775,8 @@ class Sent(Screen):
|
||||||
secondary_text=item['secondary_text'],
|
secondary_text=item['secondary_text'],
|
||||||
theme_text_color='Custom',
|
theme_text_color='Custom',
|
||||||
text_color=NavigateApp().theme_cls.primary_color)
|
text_color=NavigateApp().theme_cls.primary_color)
|
||||||
img_latter = item['secondary_text'][0].upper() if (
|
|
||||||
item['secondary_text'][0].upper() >= 'A' and item[
|
|
||||||
'secondary_text'][0].upper() <= 'Z') else '!'
|
|
||||||
meny.add_widget(AvatarSampleWidget(
|
meny.add_widget(AvatarSampleWidget(
|
||||||
source='./images/text_images/{}.png'.format(img_latter)))
|
source='./images/text_images/{}.png'.format(avatarImageFirstLetter(item['secondary_text'].strip()))))
|
||||||
meny.bind(on_press=partial(
|
meny.bind(on_press=partial(
|
||||||
self.sent_detail, item['lastactiontime']))
|
self.sent_detail, item['lastactiontime']))
|
||||||
carousel = Carousel(direction='right')
|
carousel = Carousel(direction='right')
|
||||||
|
@ -806,7 +790,7 @@ class Sent(Screen):
|
||||||
carousel.min_move = 0.2
|
carousel.min_move = 0.2
|
||||||
del_btn = Button(text='Delete')
|
del_btn = Button(text='Delete')
|
||||||
del_btn.background_normal = ''
|
del_btn.background_normal = ''
|
||||||
del_btn.background_color = (1.0, 0.0, 0.0, 1.0)
|
del_btn.background_color = (1, 0, 0, 1)
|
||||||
del_btn.bind(on_press=partial(
|
del_btn.bind(on_press=partial(
|
||||||
self.delete, item['lastactiontime']))
|
self.delete, item['lastactiontime']))
|
||||||
carousel.add_widget(del_btn)
|
carousel.add_widget(del_btn)
|
||||||
|
@ -881,9 +865,13 @@ class Sent(Screen):
|
||||||
try:
|
try:
|
||||||
self.parent.screens[4].clear_widgets()
|
self.parent.screens[4].clear_widgets()
|
||||||
self.parent.screens[4].add_widget(Trash())
|
self.parent.screens[4].add_widget(Trash())
|
||||||
|
self.parent.screens[16].clear_widgets()
|
||||||
|
self.parent.screens[16].add_widget(Allmails())
|
||||||
except Exception:
|
except Exception:
|
||||||
self.parent.parent.screens[4].clear_widgets()
|
self.parent.parent.screens[4].clear_widgets()
|
||||||
self.parent.parent.screens[4].add_widget(Trash())
|
self.parent.parent.screens[4].add_widget(Trash())
|
||||||
|
self.parent.parent.screens[16].clear_widgets()
|
||||||
|
self.parent.parent.screens[16].add_widget(Allmails())
|
||||||
|
|
||||||
|
|
||||||
class Trash(Screen):
|
class Trash(Screen):
|
||||||
|
@ -903,26 +891,58 @@ class Trash(Screen):
|
||||||
state.association = BMConfigParser().addresses()[0]
|
state.association = BMConfigParser().addresses()[0]
|
||||||
|
|
||||||
inbox = sqlQuery(
|
inbox = sqlQuery(
|
||||||
"SELECT toaddress, fromaddress, subject, message, folder from \
|
"SELECT toaddress, fromaddress, subject, message, folder, received from \
|
||||||
inbox WHERE folder = 'trash' and toaddress = '{}';".format(
|
inbox WHERE folder = 'trash' and toaddress = '{}';".format(
|
||||||
state.association))
|
state.association))
|
||||||
sent = sqlQuery(
|
sent = sqlQuery(
|
||||||
"SELECT toaddress, fromaddress, subject, message, folder from \
|
"SELECT toaddress, fromaddress, subject, message, folder, lastactiontime from \
|
||||||
sent WHERE folder = 'trash' and fromaddress = '{}';".format(
|
sent WHERE folder = 'trash' and fromaddress = '{}';".format(
|
||||||
state.association))
|
state.association))
|
||||||
trash_data = inbox + sent
|
trash_data = inbox + sent
|
||||||
|
|
||||||
|
if trash_data:
|
||||||
for item in trash_data:
|
for item in trash_data:
|
||||||
meny = ThreeLineAvatarIconListItem(
|
meny = ThreeLineAvatarIconListItem(
|
||||||
text=item[1],
|
text=item[1],
|
||||||
secondary_text=item[2],
|
secondary_text=item[2][:50] + '........' if len(item[2]) >= 50 else (item[2] + ',' + item[3].replace('\n', ''))[0:50] + '........',
|
||||||
theme_text_color='Custom',
|
theme_text_color='Custom',
|
||||||
text_color=NavigateApp().theme_cls.primary_color)
|
text_color=NavigateApp().theme_cls.primary_color)
|
||||||
img_latter = './images/text_images/{}.png'.format(
|
img_latter = './images/text_images/{}.png'.format(
|
||||||
item[2][0].upper() if (item[2][0].upper() >= 'A' and item[
|
item[2][0].upper() if (item[2][0].upper() >= 'A' and item[
|
||||||
2][0].upper() <= 'Z') else '!')
|
2][0].upper() <= 'Z') else '!')
|
||||||
meny.add_widget(AvatarSampleWidget(source=img_latter))
|
meny.add_widget(AvatarSampleWidget(source=img_latter))
|
||||||
self.ids.ml.add_widget(meny)
|
carousel = Carousel(direction='right')
|
||||||
|
if platform == 'android':
|
||||||
|
carousel.height = 150
|
||||||
|
elif platform == 'linux':
|
||||||
|
carousel.height = meny.height - 10
|
||||||
|
carousel.size_hint_y = None
|
||||||
|
carousel.ignore_perpendicular_swipes = True
|
||||||
|
carousel.data_index = 0
|
||||||
|
carousel.min_move = 0.2
|
||||||
|
del_btn = Button(text='Delete')
|
||||||
|
del_btn.background_normal = ''
|
||||||
|
del_btn.background_color = (1, 0, 0, 1)
|
||||||
|
del_btn.bind(on_press=partial(
|
||||||
|
self.delete_permanently, item[5]))
|
||||||
|
carousel.add_widget(del_btn)
|
||||||
|
carousel.add_widget(meny)
|
||||||
|
carousel.index = 1
|
||||||
|
self.ids.ml.add_widget(carousel)
|
||||||
|
else:
|
||||||
|
content = MDLabel(
|
||||||
|
font_style='Body1',
|
||||||
|
theme_text_color='Primary',
|
||||||
|
text="yet no trashed message for this account!!!!!!!!!!!!!",
|
||||||
|
halign='center',
|
||||||
|
bold=True,
|
||||||
|
size_hint_y=None,
|
||||||
|
valign='top')
|
||||||
|
self.ids.ml.add_widget(content)
|
||||||
|
|
||||||
|
def delete_permanently(self, data_index, instance, *args):
|
||||||
|
"""Deleting trash mail permanently."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Page(Screen):
|
class Page(Screen):
|
||||||
|
@ -986,7 +1006,6 @@ class NavigateApp(App):
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
"""Method builds the widget."""
|
"""Method builds the widget."""
|
||||||
import os
|
|
||||||
main_widget = Builder.load_file(
|
main_widget = Builder.load_file(
|
||||||
os.path.join(os.path.dirname(__file__), 'main.kv'))
|
os.path.join(os.path.dirname(__file__), 'main.kv'))
|
||||||
self.nav_drawer = Navigatorss()
|
self.nav_drawer = Navigatorss()
|
||||||
|
@ -1018,6 +1037,7 @@ class NavigateApp(App):
|
||||||
|
|
||||||
def getCurrentAccountData(self, text):
|
def getCurrentAccountData(self, text):
|
||||||
"""Get Current Address Account Data."""
|
"""Get Current Address Account Data."""
|
||||||
|
self.set_identicon(text)
|
||||||
address_label = self.current_address_label(
|
address_label = self.current_address_label(
|
||||||
BMConfigParser().get(text, 'label'), text)
|
BMConfigParser().get(text, 'label'), text)
|
||||||
self.root_window.children[1].ids.toolbar.title = address_label
|
self.root_window.children[1].ids.toolbar.title = address_label
|
||||||
|
@ -1074,13 +1094,29 @@ class NavigateApp(App):
|
||||||
p = GrashofPopup()
|
p = GrashofPopup()
|
||||||
p.open()
|
p.open()
|
||||||
|
|
||||||
@staticmethod
|
def getDefaultAccData(self):
|
||||||
def getDefaultAccData():
|
|
||||||
"""Getting Default Account Data."""
|
"""Getting Default Account Data."""
|
||||||
if BMConfigParser().addresses():
|
if BMConfigParser().addresses():
|
||||||
|
img = identiconGeneration.generate(BMConfigParser().addresses()[0])
|
||||||
|
self.createFolder('./images/default_identicon/')
|
||||||
|
img.texture.save('./images/default_identicon/{}.png'.format(BMConfigParser().addresses()[0]))
|
||||||
return BMConfigParser().addresses()[0]
|
return BMConfigParser().addresses()[0]
|
||||||
return 'Select Address'
|
return 'Select Address'
|
||||||
|
|
||||||
|
def createFolder(self, directory):
|
||||||
|
"""This method is used to create the directory when app starts"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
os.makedirs(directory)
|
||||||
|
except OSError:
|
||||||
|
print ('Error: Creating directory. ' + directory)
|
||||||
|
|
||||||
|
def get_default_image(self):
|
||||||
|
if BMConfigParser().addresses():
|
||||||
|
# BMConfigParser().addresses()[0]
|
||||||
|
return './images/default_identicon/{}.png'.format(BMConfigParser().addresses()[0])
|
||||||
|
return ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def addressexist():
|
def addressexist():
|
||||||
"""Checking address existence."""
|
"""Checking address existence."""
|
||||||
|
@ -1264,6 +1300,17 @@ class NavigateApp(App):
|
||||||
text_field.bind(text=self.searchQuery)
|
text_field.bind(text=self.searchQuery)
|
||||||
self.root.ids.search_bar.add_widget(text_field)
|
self.root.ids.search_bar.add_widget(text_field)
|
||||||
|
|
||||||
|
def set_identicon(self, text):
|
||||||
|
"""This method is use for showing identicon in address spinner"""
|
||||||
|
img = identiconGeneration.generate(text)
|
||||||
|
img.size = 20, 20
|
||||||
|
img.y = self.root.children[2].children[0].ids.btn.children[0].y
|
||||||
|
img.x = 5
|
||||||
|
self.root.children[2].children[0].ids.btn.add_widget(img)
|
||||||
|
|
||||||
|
def address_identicon(self):
|
||||||
|
return './images/drawer_logo1.png'
|
||||||
|
|
||||||
|
|
||||||
class GrashofPopup(Popup):
|
class GrashofPopup(Popup):
|
||||||
"""Methods for saving contacts, error messages."""
|
"""Methods for saving contacts, error messages."""
|
||||||
|
@ -1280,19 +1327,16 @@ class GrashofPopup(Popup):
|
||||||
|
|
||||||
def savecontact(self):
|
def savecontact(self):
|
||||||
"""Method is used for Saving Contacts."""
|
"""Method is used for Saving Contacts."""
|
||||||
my_addresses = \
|
label = self.ids.label.text.strip()
|
||||||
self.parent.children[1].children[2].children[0].ids.btn.values
|
address = self.ids.address.text.strip()
|
||||||
entered_text = str(self.ids.address.text)
|
if label == '' and address == '':
|
||||||
if entered_text in my_addresses:
|
self.ids.label.focus = True
|
||||||
self.ids.address.focus = False
|
self.ids.address.focus = True
|
||||||
self.ids.address.helper_text = 'Please Enter corrent address'
|
elif address == '':
|
||||||
elif entered_text == '':
|
self.ids.address.focus = True
|
||||||
|
elif label == '':
|
||||||
self.ids.label.focus = True
|
self.ids.label.focus = True
|
||||||
# self.ids.address.focus = True
|
|
||||||
self.ids.label.helper_text = 'This field is required'
|
|
||||||
|
|
||||||
label = self.ids.label.text
|
|
||||||
address = self.ids.address.text
|
|
||||||
stored_address = [addr[1] for addr in kivy_helper_search.search_sql(
|
stored_address = [addr[1] for addr in kivy_helper_search.search_sql(
|
||||||
folder="addressbook")]
|
folder="addressbook")]
|
||||||
if label and address and address not in stored_address:
|
if label and address and address not in stored_address:
|
||||||
|
@ -1328,6 +1372,30 @@ class GrashofPopup(Popup):
|
||||||
"""Pop is Canceled."""
|
"""Pop is Canceled."""
|
||||||
toast('Canceled')
|
toast('Canceled')
|
||||||
|
|
||||||
|
def checkAddress_valid(self, instance):
|
||||||
|
my_addresses = self.parent.children[1].children[2].children[0].ids.btn.values
|
||||||
|
add_book = [addr[1] for addr in kivy_helper_search.search_sql(
|
||||||
|
folder="addressbook")]
|
||||||
|
entered_text = str(instance.text).strip()
|
||||||
|
if entered_text in add_book:
|
||||||
|
text = 'Address is already in the addressbook.'
|
||||||
|
elif entered_text in my_addresses:
|
||||||
|
text = 'You can not save your own address.'
|
||||||
|
|
||||||
|
if entered_text in my_addresses or entered_text in add_book:
|
||||||
|
if len(self.ids.popup_box.children) <= 2:
|
||||||
|
err_msg = MDLabel(
|
||||||
|
id='erro_msg',
|
||||||
|
font_style='Body2',
|
||||||
|
text=text,
|
||||||
|
font_size=5)
|
||||||
|
err_msg.color = 1, 0, 0, 1
|
||||||
|
self.ids.popup_box.add_widget(err_msg)
|
||||||
|
self.ids.save_addr.disabled = True
|
||||||
|
elif len(self.ids.popup_box.children) > 2:
|
||||||
|
self.ids.popup_box.remove_widget(self.ids.popup_box.children[0])
|
||||||
|
self.ids.save_addr.disabled = False
|
||||||
|
|
||||||
|
|
||||||
class AvatarSampleWidget(ILeftBody, Image):
|
class AvatarSampleWidget(ILeftBody, Image):
|
||||||
"""Avatar Sample Widget."""
|
"""Avatar Sample Widget."""
|
||||||
|
@ -1484,6 +1552,15 @@ class MailDetail(Screen):
|
||||||
self.parent.parent.current = 'create'
|
self.parent.parent.current = 'create'
|
||||||
navApp.set_navbar_for_composer()
|
navApp.set_navbar_for_composer()
|
||||||
|
|
||||||
|
def copy_composer_text(self, instance, *args):
|
||||||
|
"""This method is used for copying the data from mail detail page"""
|
||||||
|
if len(instance.parent.text.split(':')) > 1:
|
||||||
|
cpy_text = instance.parent.text.split(':')[1].strip()
|
||||||
|
else:
|
||||||
|
cpy_text = instance.parent.text
|
||||||
|
Clipboard.copy(cpy_text)
|
||||||
|
toast('Copied')
|
||||||
|
|
||||||
|
|
||||||
class MyaddDetailPopup(Popup):
|
class MyaddDetailPopup(Popup):
|
||||||
"""MyaddDetailPopup pop is used for showing my address detail."""
|
"""MyaddDetailPopup pop is used for showing my address detail."""
|
||||||
|
@ -1651,7 +1728,7 @@ class Draft(Screen):
|
||||||
carousel.min_move = 0.2
|
carousel.min_move = 0.2
|
||||||
del_btn = Button(text='Delete')
|
del_btn = Button(text='Delete')
|
||||||
del_btn.background_normal = ''
|
del_btn.background_normal = ''
|
||||||
del_btn.background_color = (1.0, 0.0, 0.0, 1.0)
|
del_btn.background_color = (1, 0, 0, 1)
|
||||||
del_btn.bind(on_press=partial(
|
del_btn.bind(on_press=partial(
|
||||||
self.delete_draft, item['lastactiontime']))
|
self.delete_draft, item['lastactiontime']))
|
||||||
carousel.add_widget(del_btn)
|
carousel.add_widget(del_btn)
|
||||||
|
@ -1694,12 +1771,12 @@ class Draft(Screen):
|
||||||
if int(state.draft_count) > 0:
|
if int(state.draft_count) > 0:
|
||||||
msg_count_objs.draft_cnt.badge_text = str(
|
msg_count_objs.draft_cnt.badge_text = str(
|
||||||
int(state.draft_count) - 1)
|
int(state.draft_count) - 1)
|
||||||
msg_count_objs.allmail_cnt.badge_text = str(
|
# msg_count_objs.allmail_cnt.badge_text = str(
|
||||||
int(state.all_count) - 1)
|
# int(state.all_count) - 1)
|
||||||
# msg_count_objs.trash_cnt.badge_text = str(
|
# msg_count_objs.trash_cnt.badge_text = str(
|
||||||
# int(state.trash_count) + 1)
|
# int(state.trash_count) + 1)
|
||||||
state.draft_count = str(int(state.draft_count) - 1)
|
state.draft_count = str(int(state.draft_count) - 1)
|
||||||
state.all_count = str(int(state.all_count) - 1)
|
# state.all_count = str(int(state.all_count) - 1)
|
||||||
# state.trash_count = str(int(state.trash_count) + 1)
|
# state.trash_count = str(int(state.trash_count) + 1)
|
||||||
self.ids.ml.remove_widget(instance.parent.parent)
|
self.ids.ml.remove_widget(instance.parent.parent)
|
||||||
toast('Deleted')
|
toast('Deleted')
|
||||||
|
@ -1764,8 +1841,9 @@ class CustomSpinner(Spinner):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Method used for setting size of spinner."""
|
"""Method used for setting size of spinner."""
|
||||||
super(CustomSpinner, self).__init__(*args, **kwargs)
|
super(CustomSpinner, self).__init__(*args, **kwargs)
|
||||||
max_value = 2.8
|
# max_value = 2.8
|
||||||
self.dropdown_cls.max_height = self.height * max_value + max_value * 4
|
# self.dropdown_cls.max_height = self.height / 2 * max_value + max_value * 4
|
||||||
|
self.dropdown_cls.max_height = Window.size[1] / 3
|
||||||
|
|
||||||
|
|
||||||
def remove_search_bar(obj):
|
def remove_search_bar(obj):
|
||||||
|
@ -1814,17 +1892,12 @@ class Allmails(Screen):
|
||||||
if all_mails:
|
if all_mails:
|
||||||
for item in all_mails:
|
for item in all_mails:
|
||||||
meny = ThreeLineAvatarIconListItem(
|
meny = ThreeLineAvatarIconListItem(
|
||||||
text='Draft' if item[4] == 'draft' else item[1],
|
text=item[1],
|
||||||
secondary_text=item[1] if item[4] == 'draft' else item[2],
|
secondary_text=item[2][:50] + '........' if len(item[2]) >= 50 else (item[2] + ',' + item[3].replace('\n', ''))[0:50] + '........',
|
||||||
theme_text_color='Custom',
|
theme_text_color='Custom',
|
||||||
text_color=NavigateApp().theme_cls.primary_color)
|
text_color=NavigateApp().theme_cls.primary_color)
|
||||||
img_latter = './images/avatar.png' if item[
|
|
||||||
4] == 'draft' else './images/text_images/{}.png'.format(
|
|
||||||
item[2][0].upper() if (
|
|
||||||
item[2][0].upper() >= 'A' and item[
|
|
||||||
2][0].upper() <= 'Z') else '!')
|
|
||||||
meny.add_widget(AvatarSampleWidget(
|
meny.add_widget(AvatarSampleWidget(
|
||||||
source=img_latter))
|
source='./images/text_images/{}.png'.format(avatarImageFirstLetter(item[2].strip()))))
|
||||||
meny.bind(on_press=partial(
|
meny.bind(on_press=partial(
|
||||||
self.mail_detail, item[5], item[4]))
|
self.mail_detail, item[5], item[4]))
|
||||||
carousel = Carousel(direction='right')
|
carousel = Carousel(direction='right')
|
||||||
|
@ -1838,7 +1911,7 @@ class Allmails(Screen):
|
||||||
carousel.min_move = 0.2
|
carousel.min_move = 0.2
|
||||||
del_btn = Button(text='Delete')
|
del_btn = Button(text='Delete')
|
||||||
del_btn.background_normal = ''
|
del_btn.background_normal = ''
|
||||||
del_btn.background_color = (1.0, 0.0, 0.0, 1.0)
|
del_btn.background_color = (1, 0, 0, 1)
|
||||||
del_btn.bind(on_press=partial(
|
del_btn.bind(on_press=partial(
|
||||||
self.swipe_delete, item[5], item[4]))
|
self.swipe_delete, item[5], item[4]))
|
||||||
carousel.add_widget(del_btn)
|
carousel.add_widget(del_btn)
|
||||||
|
@ -1927,3 +2000,15 @@ class Allmails(Screen):
|
||||||
self.ids.refresh_layout.refresh_done()
|
self.ids.refresh_layout.refresh_done()
|
||||||
self.tick = 0
|
self.tick = 0
|
||||||
Clock.schedule_once(refresh_callback, 1)
|
Clock.schedule_once(refresh_callback, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def avatarImageFirstLetter(letter_string):
|
||||||
|
"""This method is used to the first letter for the avatar image"""
|
||||||
|
if letter_string[0].upper() >= 'A' and letter_string[0].upper() <= 'Z':
|
||||||
|
img_latter = letter_string[0].upper()
|
||||||
|
elif int(letter_string[0]) >= 0 and int(letter_string[0]) <= 9:
|
||||||
|
img_latter = letter_string[0]
|
||||||
|
else:
|
||||||
|
img_latter = '!'
|
||||||
|
|
||||||
|
return img_latter
|
82
src/identiconGeneration.py
Normal file
82
src/identiconGeneration.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import hashlib
|
||||||
|
from PIL import Image
|
||||||
|
from kivy.core.image import Image as CoreImage
|
||||||
|
# Core classes for loading images and converting them to a Texture. The raw image data can be keep in memory for further access
|
||||||
|
from kivy.uix.image import Image as kiImage
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
# constants
|
||||||
|
RESOLUTION = 128, 128
|
||||||
|
V_RESOLUTION = 7, 7
|
||||||
|
BACKGROUND_COLOR = 255, 255, 255, 255
|
||||||
|
MODE = "RGB"
|
||||||
|
|
||||||
|
|
||||||
|
def generate(Generate_string=None):
|
||||||
|
hash_string = generate_hash(Generate_string)
|
||||||
|
color = random_color(hash_string)
|
||||||
|
image = Image.new(MODE, V_RESOLUTION, BACKGROUND_COLOR)
|
||||||
|
image = generate_image(image, color, hash_string)
|
||||||
|
image = image.resize(RESOLUTION, 0)
|
||||||
|
|
||||||
|
data = BytesIO()
|
||||||
|
image.save(data, format='png')
|
||||||
|
data.seek(0)
|
||||||
|
# yes you actually need this
|
||||||
|
im = CoreImage(BytesIO(data.read()), ext='png')
|
||||||
|
beeld = kiImage()
|
||||||
|
# only use this line in first code instance
|
||||||
|
beeld.texture = im.texture
|
||||||
|
return beeld
|
||||||
|
# image.show()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_hash(string):
|
||||||
|
try:
|
||||||
|
# make input case insensitive
|
||||||
|
string = str.lower(string)
|
||||||
|
hash_object = hashlib.md5(str.encode(string))
|
||||||
|
print(hash_object.hexdigest())
|
||||||
|
|
||||||
|
# returned object is a hex string
|
||||||
|
return hash_object.hexdigest()
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
print("Error: Please enter a string as an argument.")
|
||||||
|
|
||||||
|
|
||||||
|
def random_color(hash_string):
|
||||||
|
# remove first three digits from hex string
|
||||||
|
split = 6
|
||||||
|
rgb = hash_string[:split]
|
||||||
|
|
||||||
|
split = 2
|
||||||
|
r = rgb[:split]
|
||||||
|
g = rgb[split:2 * split]
|
||||||
|
b = rgb[2 * split:3 * split]
|
||||||
|
|
||||||
|
color = (int(r, 16), int(g, 16),
|
||||||
|
int(b, 16), 0xFF)
|
||||||
|
|
||||||
|
return color
|
||||||
|
|
||||||
|
|
||||||
|
def generate_image(image, color, hash_string):
|
||||||
|
hash_string = hash_string[6:]
|
||||||
|
|
||||||
|
lower_x = 1
|
||||||
|
lower_y = 1
|
||||||
|
upper_x = int(V_RESOLUTION[0] / 2) + 1
|
||||||
|
upper_y = V_RESOLUTION[1] - 1
|
||||||
|
limit_x = V_RESOLUTION[0] - 1
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
for x in range(lower_x, upper_x):
|
||||||
|
for y in range(lower_y, upper_y):
|
||||||
|
if int(hash_string[index], 16) % 2 == 0:
|
||||||
|
image.putpixel((x, y), color)
|
||||||
|
image.putpixel((limit_x - x, y), color)
|
||||||
|
|
||||||
|
index = index + 1
|
||||||
|
|
||||||
|
return image
|
BIN
src/images/copy_text.png
Normal file
BIN
src/images/copy_text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Reference in New Issue
Block a user