1from typing import TYPE_CHECKING
2
3from kivy.app import App
4from kivy.clock import Clock
5from kivy.factory import Factory
6from kivy.properties import ObjectProperty
7from kivy.lang import Builder
8from decimal import Decimal
9from kivy.uix.popup import Popup
10
11from electrum.gui.kivy.i18n import _
12from ...util import address_colors
13
14if TYPE_CHECKING:
15    from ...main_window import ElectrumWindow
16
17
18Builder.load_string('''
19<AddressLabel@Label>
20    text_size: self.width, None
21    halign: 'left'
22    valign: 'top'
23
24<AddressItem@CardItem>
25    address: ''
26    memo: ''
27    amount: ''
28    status: ''
29    is_frozen: False
30    BoxLayout:
31        spacing: '8dp'
32        height: '32dp'
33        orientation: 'vertical'
34        Widget
35        AddressLabel:
36            text: root.address
37            shorten: True
38        Widget
39        AddressLabel:
40            text:
41                (("({}) ".format(_("Frozen")) if root.is_frozen else "")
42                + (root.amount if root.status == 'Funded' else root.status) + '     ' + root.memo)
43            color: .699, .699, .699, 1
44            font_size: '13sp'
45            shorten: True
46        Widget
47
48<AddressButton@Button>:
49    background_color: 1, .585, .878, 0
50    halign: 'center'
51    text_size: (self.width, None)
52    shorten: True
53    size_hint: 0.5, None
54    default_text: ''
55    text: self.default_text
56    padding: '5dp', '5dp'
57    height: '40dp'
58    text_color: self.foreground_color
59    disabled_color: 1, 1, 1, 1
60    foreground_color: 1, 1, 1, 1
61    canvas.before:
62        Color:
63            rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
64        Rectangle:
65            size: self.size
66            pos: self.pos
67
68<AddressesDialog@Popup>
69    id: popup
70    title: _('Addresses')
71    message: ''
72    pr_status: 'Pending'
73    show_change: 0
74    show_used: 0
75    on_message:
76        self.update()
77    BoxLayout:
78        id:box
79        padding: '12dp', '12dp', '12dp', '12dp'
80        spacing: '12dp'
81        orientation: 'vertical'
82        BoxLayout:
83            spacing: '6dp'
84            height: self.minimum_height
85            size_hint: 1, None
86            orientation: 'horizontal'
87            AddressFilter:
88                opacity: 1
89                size_hint: 1, None
90                height: self.minimum_height
91                spacing: '5dp'
92                AddressButton:
93                    id: search
94                    text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change]
95                    on_release:
96                        root.show_change = (root.show_change + 1) % 3
97                        Clock.schedule_once(lambda dt: root.update())
98            AddressFilter:
99                opacity: 1
100                size_hint: 1, None
101                height: self.minimum_height
102                spacing: '5dp'
103                AddressButton:
104                    id: search
105                    text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used'), 4:(_('Funded')+'|'+_('Unused'))}[root.show_used]
106                    on_release:
107                        root.show_used = (root.show_used + 1) % 5
108                        Clock.schedule_once(lambda dt: root.update())
109            AddressFilter:
110                opacity: 1
111                size_hint: 1, None
112                height: self.minimum_height
113                spacing: '5dp'
114                canvas.before:
115                    Color:
116                        rgba: 0.9, 0.9, 0.9, 1
117                AddressButton:
118                    id: change
119                    text: root.message if root.message else _('Search')
120                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(popup))
121        RecycleView:
122            scroll_type: ['bars', 'content']
123            bar_width: '15dp'
124            viewclass: 'AddressItem'
125            id: search_container
126            RecycleBoxLayout:
127                orientation: 'vertical'
128                default_size: None, dp(56)
129                default_size_hint: 1, None
130                size_hint_y: None
131                height: self.minimum_height
132
133<AddressPopup@Popup>:
134    address: ''
135    balance: ''
136    status: ''
137    script_type: ''
138    pk: ''
139    is_frozen: False
140    address_color: 1, 1, 1, 1
141    address_background_color: 0.3, 0.3, 0.3, 1
142    BoxLayout:
143        orientation: 'vertical'
144        ScrollView:
145            GridLayout:
146                cols: 1
147                height: self.minimum_height
148                size_hint_y: None
149                padding: '10dp'
150                spacing: '10dp'
151                TopLabel:
152                    text: _('Address')
153                RefLabel:
154                    color: root.address_color
155                    background_color: root.address_background_color
156                    data: root.address
157                    name: _('Address')
158                GridLayout:
159                    cols: 1
160                    size_hint_y: None
161                    height: self.minimum_height
162                    spacing: '10dp'
163                    BoxLabel:
164                        text: _('Balance')
165                        value: root.balance
166                    BoxLabel:
167                        text: _('Script type')
168                        value: root.script_type
169                    BoxLabel:
170                        text: _('Status')
171                        value: root.status
172                    BoxLabel:
173                        text: _('Frozen')
174                        value: str(root.is_frozen)
175                TopLabel:
176                    text: _('Private Key')
177                RefLabel:
178                    data: root.pk
179                    name: _('Private key')
180                    on_touched: if not self.data: root.do_export(self)
181        Widget:
182            size_hint: 1, 0.1
183        BoxLayout:
184            size_hint: 1, None
185            height: '48dp'
186            Button:
187                size_hint: 0.5, None
188                height: '48dp'
189                text: _('Receive')
190                on_release: root.receive_at()
191            Button:
192                size_hint: 0.5, None
193                height: '48dp'
194                text: _('Freeze') if not root.is_frozen else _('Unfreeze')
195                on_release: root.freeze_address()
196            Button:
197                size_hint: 0.5, None
198                height: '48dp'
199                text: _('Close')
200                on_release: root.dismiss()
201''')
202
203
204
205class AddressPopup(Popup):
206
207    def __init__(self, parent, address, balance, status, **kwargs):
208        super(AddressPopup, self).__init__(**kwargs)
209        self.title = _('Address Details')
210        self.parent_dialog = parent
211        self.app = parent.app    # type: ElectrumWindow
212        self.address = address
213        self.status = status
214        self.script_type = self.app.wallet.get_txin_type(self.address)
215        self.balance = self.app.format_amount_and_units(balance)
216        self.address_color, self.address_background_color = address_colors(self.app.wallet, address)
217        self.is_frozen = self.app.wallet.is_frozen_address(address)
218
219    def receive_at(self):
220        self.dismiss()
221        self.parent_dialog.dismiss()
222        self.app.switch_to('receive')
223        # retry until receive_screen is set
224        Clock.schedule_interval(lambda dt: bool(self.app.receive_screen.set_address(self.address) and False) if self.app.receive_screen else True, 0.1)
225
226    def do_export(self, pk_label):
227        self.app.export_private_keys(pk_label, self.address)
228
229    def freeze_address(self):
230        self.is_frozen = not self.is_frozen
231        self.app.wallet.set_frozen_state_of_addresses([self.address], freeze=self.is_frozen)
232        self.parent_dialog.update()
233
234
235class AddressesDialog(Factory.Popup):
236
237    def __init__(self, app: 'ElectrumWindow'):
238        Factory.Popup.__init__(self)
239        self.app = app
240        self.update()
241
242    def get_card(self, addr, balance, is_used, label):
243        ci = {}
244        ci['screen'] = self
245        ci['address'] = addr
246        ci['memo'] = label
247        ci['amount'] = self.app.format_amount_and_units(balance)
248        ci['status'] = _('Used') if is_used else _('Funded') if balance > 0 else _('Unused')
249        ci['is_frozen'] = self.app.wallet.is_frozen_address(addr)
250        return ci
251
252    def update(self):
253        wallet = self.app.wallet
254        if self.show_change == 0:
255            _list = wallet.get_receiving_addresses()
256        elif self.show_change == 1:
257            _list = wallet.get_change_addresses()
258        else:
259            _list = wallet.get_addresses()
260        search = self.message
261        container = self.ids.search_container
262        n = 0
263        cards = []
264        for address in _list:
265            label = wallet.get_label(address)
266            balance = sum(wallet.get_addr_balance(address))
267            is_used_and_empty = wallet.is_used(address) and balance == 0
268            if self.show_used == 1 and (balance or is_used_and_empty):
269                continue
270            if self.show_used == 2 and balance == 0:
271                continue
272            if self.show_used == 3 and not is_used_and_empty:
273                continue
274            if self.show_used == 4 and is_used_and_empty:
275                continue
276            card = self.get_card(address, balance, is_used_and_empty, label)
277            if search and not self.ext_search(card, search):
278                continue
279            cards.append(card)
280            n += 1
281        container.data = cards
282        if not n:
283            self.app.show_error('No address matching your search')
284
285    def show_item(self, obj):
286        address = obj.address
287        c, u, x = self.app.wallet.get_addr_balance(address)
288        balance = c + u + x
289        d = AddressPopup(self, address, balance, obj.status)
290        d.open()
291
292    def ext_search(self, card, search):
293        return card['memo'].find(search) >= 0 or card['amount'].find(search) >= 0
294