2018-09-06 10:04:12 +00:00
|
|
|
(ns status-im.events
|
2022-12-20 14:45:37 +00:00
|
|
|
(:require
|
2023-02-17 12:10:00 +00:00
|
|
|
clojure.set
|
|
|
|
[re-frame.core :as re-frame]
|
|
|
|
[status-im.async-storage.core :as async-storage]
|
|
|
|
status-im.backup.core
|
|
|
|
status-im.bootnodes.core
|
|
|
|
status-im.browser.core
|
|
|
|
status-im.browser.permissions
|
|
|
|
status-im.chat.models
|
|
|
|
status-im.chat.models.images
|
|
|
|
status-im.chat.models.input
|
|
|
|
status-im.chat.models.loading
|
|
|
|
status-im.chat.models.transport
|
|
|
|
[status-im2.constants :as constants]
|
|
|
|
status-im.contact.block
|
|
|
|
status-im.contact.chat
|
|
|
|
status-im.contact.core
|
|
|
|
status-im.currency.core
|
|
|
|
status-im.ethereum.subscriptions
|
|
|
|
status-im.fleet.core
|
|
|
|
status-im.http.core
|
|
|
|
[utils.i18n :as i18n]
|
|
|
|
[status-im.keycard.core :as keycard]
|
|
|
|
status-im.log-level.core
|
|
|
|
status-im.mailserver.constants
|
|
|
|
[status-im.mailserver.core :as mailserver]
|
|
|
|
[status-im.multiaccounts.biometric.core :as biometric]
|
|
|
|
status-im.multiaccounts.login.core
|
|
|
|
status-im.multiaccounts.logout.core
|
|
|
|
[status-im.multiaccounts.model :as multiaccounts.model]
|
|
|
|
status-im.multiaccounts.update.core
|
|
|
|
[status-im.native-module.core :as status]
|
|
|
|
status-im.network.net-info
|
|
|
|
status-im.pairing.core
|
|
|
|
status-im.profile.core
|
|
|
|
status-im.search.core
|
|
|
|
status-im.signals.core
|
|
|
|
status-im.stickers.core
|
|
|
|
status-im.transport.core
|
|
|
|
[status-im.ui.components.permissions :as permissions]
|
|
|
|
[status-im.ui.components.react :as react]
|
|
|
|
status-im.ui.screens.privacy-and-security-settings.events
|
|
|
|
[status-im.utils.dimensions :as dimensions]
|
|
|
|
[utils.re-frame :as rf]
|
|
|
|
status-im.utils.logging.core
|
|
|
|
[status-im.utils.universal-links.core :as universal-links]
|
|
|
|
[status-im.utils.utils :as utils]
|
|
|
|
status-im.visibility-status-popover.core
|
|
|
|
status-im.visibility-status-updates.core
|
|
|
|
status-im.waku.core
|
|
|
|
status-im.wallet-connect.core
|
|
|
|
status-im.wallet.accounts.core
|
|
|
|
status-im.wallet.choose-recipient.core
|
|
|
|
[status-im.wallet.core :as wallet]
|
|
|
|
status-im.wallet.custom-tokens.core
|
|
|
|
status-im2.contexts.activity-center.events
|
Swipe gestures for Activity Center notifications with CTA (#15284)
Implements swipe actions for notifications with call to action (e.g. pending
contact requests, unverified identity verifications, etc).
Fixes https://github.com/status-im/status-mobile/issues/15118
According to the Design team, the goal is to deliver a consistent experience to
users, so whenever the user sees a notification with buttons, the same actions
can be taken via the swipe buttons.
Note: swipe buttons are using placeholder icons while the Design team works out
which ones to use
Additionally, a bunch of fixes:
- Fix: outgoing pending contact requests were not being removed from the UI when
cancelled.
- Fix: Membership tab not showing unread indicator.
- Fix: dismissed membership notification not marked as read.
- Fix: dismissed membership notification was displaying decline/accept buttons.
Regression came from changes in status-go related to soft deletion of
notifications.
- Fix: incorrect check for the pending state of a contact request.
- Fixed lots of bugs for identity verification notifications, as it was
completely broken. Unfortunately, somebody made lots of changes without
actually testing the flows.
- Add basic error handling and log if accepting, declining or canceling contact
requests fail.
The demo shows an identity verification with swipe actions to reply or decline.
[identity-verification-swipe-to-reply.webm](https://user-images.githubusercontent.com/46027/223565755-b2ca3f68-12e2-4e1e-9e52-edd52cfcc971.webm)
Out of scope: The old quo input is still in use in the identity verification
notification. This will eventually be solved by issue
https://github.com/status-im/status-mobile/issues/14364
### Steps to test
Notifications with one or more buttons (actions) are affected by this change,
because now the user can also swipe left/right to act on them.
- Membership notifications: private group chat. The following PR explains how to
generate them https://github.com/status-im/status-mobile/pull/14785
- Contact requests, and community gated requests to join (Admin tab).
- Identity verifications. I believe the only way to test identity verification
flows at the moment is to use the Desktop app, since initiating the challenge
is not implemented in Mobile yet.
- Mentions and replies don't have new swipe buttons because they don't have call
to action buttons throughout their lifecycle.
Steps to test identity verification flows:
#### Identity verification flow 1
- `A` and `B` are mutual contacts.
- `A` sends a verification request to `B`.
- `A` should not see any notification yet.
- `B` should receive an identity verification notification. `B` can either
decline or reply.
- `B` declines and the status `Declined` is shown instead of buttons.
- `B` can now either swipe to toggle read/unread or swipe delete the
notification.
- `A` should not receive any notification after `A` declined.
#### Identity verification flow 2
- `A` and `B` are mutual contacts.
- `A` sends a verification request to `B`.
- `A` should not see any notification yet.
- `B` should receive an identity verification notification. `B` can either
decline or reply.
- `B` press `Reply` and a bottom sheet is displayed with a text input.
- `B` sends the reply/answer message and the status `Replied` is shown instead
of buttons.
- `B` can now either swipe to toggle read/unread or swipe to delete the
notification.
- `A` should receive a notification with the reply from `B`.
- `A` can either mark the answer as untrustworthy or accept it (trust it) via
the normal buttons, as well as via the swipe left/right buttons.
- If `A` accepts the answer, then the status `Confirmed` is shown instead of
buttons. On the other hand, if `A` marks as untrustworthy, then the status
`Untrustworthy` is shown instead of buttons.
- `B` should receive no further notifications due to `A`s actions.
- `A` can now either swipe to toggle read/unread or swipe delete the
notification.
2023-03-14 15:34:13 +00:00
|
|
|
status-im2.contexts.activity-center.notification.contact-requests.events
|
2023-02-17 12:10:00 +00:00
|
|
|
status-im2.contexts.shell.events
|
2023-03-22 06:17:57 +00:00
|
|
|
status-im2.contexts.onboarding.events
|
2023-02-17 12:10:00 +00:00
|
|
|
status-im.chat.models.gaps
|
|
|
|
[status-im2.navigation.events :as navigation]))
|
2019-06-05 11:11:47 +00:00
|
|
|
|
|
|
|
(re-frame/reg-fx
|
|
|
|
:dismiss-keyboard
|
|
|
|
(fn []
|
2019-06-03 07:42:29 +00:00
|
|
|
(react/dismiss-keyboard!)))
|
2019-06-17 09:41:37 +00:00
|
|
|
|
2020-04-29 13:11:24 +00:00
|
|
|
(re-frame/reg-fx
|
|
|
|
:request-permissions-fx
|
|
|
|
(fn [options]
|
|
|
|
(permissions/request-permissions options)))
|
|
|
|
|
|
|
|
(re-frame/reg-fx
|
|
|
|
:ui/show-error
|
|
|
|
(fn [content]
|
|
|
|
(utils/show-popup "Error" content)))
|
|
|
|
|
|
|
|
(re-frame/reg-fx
|
|
|
|
:ui/show-confirmation
|
|
|
|
(fn [options]
|
|
|
|
(utils/show-confirmation options)))
|
|
|
|
|
|
|
|
(re-frame/reg-fx
|
|
|
|
:ui/close-application
|
|
|
|
(fn [_]
|
|
|
|
(status/close-application)))
|
|
|
|
|
|
|
|
(re-frame/reg-fx
|
|
|
|
::app-state-change-fx
|
|
|
|
(fn [state]
|
|
|
|
(status/app-state-change state)))
|
|
|
|
|
2021-02-12 14:58:43 +00:00
|
|
|
(re-frame/reg-fx
|
|
|
|
:ui/listen-to-window-dimensions-change
|
|
|
|
(fn []
|
|
|
|
(dimensions/add-event-listener)))
|
2020-04-29 13:11:24 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn dismiss-keyboard
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:dismiss-keyboard]}
|
|
|
|
[_]
|
|
|
|
{:dismiss-keyboard nil})
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn identicon-generated
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:identicon-generated]}
|
|
|
|
[{:keys [db]} path identicon]
|
|
|
|
{:db (assoc-in db path identicon)})
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn gfycat-generated
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:gfycat-generated]}
|
|
|
|
[{:keys [db]} path gfycat]
|
|
|
|
{:db (assoc-in db path gfycat)})
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn system-theme-mode-changed
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:system-theme-mode-changed]}
|
2023-01-24 14:58:41 +00:00
|
|
|
[{:keys [db] :as cofx} _]
|
|
|
|
(let [current-theme-type (get-in cofx [:db :multiaccount :appearance])]
|
|
|
|
(when (and (multiaccounts.model/logged-in? cofx)
|
|
|
|
(= current-theme-type status-im2.constants/theme-type-system))
|
2023-03-24 16:34:55 +00:00
|
|
|
{:multiaccounts.ui/switch-theme-fx
|
2023-01-24 14:58:41 +00:00
|
|
|
[(get-in db [:multiaccount :appearance])
|
|
|
|
(:view-id db) true]})))
|
2020-04-29 13:11:24 +00:00
|
|
|
|
|
|
|
(def authentication-options
|
|
|
|
{:reason (i18n/label :t/biometric-auth-reason-login)})
|
|
|
|
|
2022-12-20 14:45:37 +00:00
|
|
|
(defn- on-biometric-auth-result
|
|
|
|
[{:keys [bioauth-success bioauth-code bioauth-message]}]
|
2020-04-29 13:11:24 +00:00
|
|
|
(when-not bioauth-success
|
|
|
|
(if (= bioauth-code "USER_FALLBACK")
|
|
|
|
(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
|
2022-12-20 14:45:37 +00:00
|
|
|
(utils/show-confirmation
|
|
|
|
{:title (i18n/label :t/biometric-auth-confirm-title)
|
|
|
|
:content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message))
|
|
|
|
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
|
|
|
|
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout)
|
|
|
|
:on-accept #(biometric/authenticate nil
|
|
|
|
on-biometric-auth-result
|
|
|
|
authentication-options)
|
|
|
|
:on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])}))))
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn on-return-from-background
|
2022-12-20 14:45:37 +00:00
|
|
|
[{:keys [db now] :as cofx}]
|
2020-04-29 13:11:24 +00:00
|
|
|
(let [app-in-background-since (get db :app-in-background-since)
|
2022-12-20 14:45:37 +00:00
|
|
|
signed-up? (get-in db [:multiaccount :signed-up?])
|
|
|
|
biometric-auth? (= (:auth-method db) "biometric")
|
|
|
|
requires-bio-auth (and
|
|
|
|
signed-up?
|
|
|
|
biometric-auth?
|
|
|
|
(some? app-in-background-since)
|
|
|
|
(>= (- now app-in-background-since)
|
|
|
|
constants/ms-in-bg-for-require-bioauth))]
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/merge cofx
|
2022-11-14 18:16:55 +00:00
|
|
|
{:db (dissoc db :app-in-background-since)}
|
2020-04-29 13:11:24 +00:00
|
|
|
(mailserver/process-next-messages-request)
|
2020-12-10 16:02:45 +00:00
|
|
|
(wallet/restart-wallet-service-after-background app-in-background-since)
|
2021-08-04 13:27:25 +00:00
|
|
|
(universal-links/process-stored-event)
|
2021-08-19 10:36:25 +00:00
|
|
|
#(when-let [chat-id (:current-chat-id db)]
|
|
|
|
{:dispatch [:chat/mark-all-as-read chat-id]})
|
2020-04-29 13:11:24 +00:00
|
|
|
#(when requires-bio-auth
|
|
|
|
(biometric/authenticate % on-biometric-auth-result authentication-options)))))
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn on-going-in-background
|
2022-11-14 18:16:55 +00:00
|
|
|
[{:keys [db now]}]
|
2023-02-27 11:51:53 +00:00
|
|
|
{:db (assoc db :app-in-background-since now)})
|
2023-02-01 13:01:03 +00:00
|
|
|
;; event not implemented
|
|
|
|
;; :dispatch-n [[:audio-recorder/on-background] [:audio-message/on-background]]
|
2023-02-27 11:51:53 +00:00
|
|
|
|
2020-04-29 13:11:24 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn app-state-change
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:app-state-change]}
|
|
|
|
[{:keys [db] :as cofx} state]
|
2020-04-29 13:11:24 +00:00
|
|
|
(let [app-coming-from-background? (= state "active")
|
2022-12-20 14:45:37 +00:00
|
|
|
app-going-in-background? (= state "background")]
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/merge cofx
|
2020-04-29 13:11:24 +00:00
|
|
|
{::app-state-change-fx state
|
|
|
|
:db (assoc db :app-state state)}
|
|
|
|
#(when app-coming-from-background?
|
|
|
|
(on-return-from-background %))
|
|
|
|
#(when app-going-in-background?
|
|
|
|
(on-going-in-background %)))))
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn request-permissions
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:request-permissions]}
|
|
|
|
[_ options]
|
|
|
|
{:request-permissions-fx options})
|
2020-04-29 13:11:24 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn update-window-dimensions
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:update-window-dimensions]}
|
|
|
|
[{:keys [db]} dimensions]
|
|
|
|
{:db (assoc db :dimensions/window (dimensions/window dimensions))})
|
2020-04-29 13:11:24 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn on-will-focus
|
2022-05-16 09:28:03 +00:00
|
|
|
{:events [:screens/on-will-focus]}
|
2021-12-02 10:45:00 +00:00
|
|
|
[{:keys [db] :as cofx} view-id]
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/merge cofx
|
2021-12-02 10:45:00 +00:00
|
|
|
(cond
|
|
|
|
(= :chat view-id)
|
|
|
|
{::async-storage/set! {:chat-id (get-in cofx [:db :current-chat-id])
|
|
|
|
:key-uid (get-in cofx [:db :multiaccount :key-uid])}
|
2022-12-20 14:45:37 +00:00
|
|
|
:db (assoc db :screens/was-focused-once? true)}
|
2021-12-02 10:45:00 +00:00
|
|
|
|
|
|
|
(= :login view-id)
|
|
|
|
{}
|
|
|
|
|
|
|
|
(not (get db :screens/was-focused-once?))
|
|
|
|
{:db (assoc db :screens/was-focused-once? true)}
|
|
|
|
|
|
|
|
:else
|
|
|
|
{::async-storage/set! {:chat-id nil
|
|
|
|
:key-uid nil}
|
2022-12-20 14:45:37 +00:00
|
|
|
:db (assoc db :screens/was-focused-once? true)})
|
2021-02-12 14:58:43 +00:00
|
|
|
#(case view-id
|
2022-12-20 14:45:37 +00:00
|
|
|
:keycard-settings (keycard/settings-screen-did-load %)
|
|
|
|
:reset-card (keycard/reset-card-screen-did-load %)
|
|
|
|
:enter-pin-settings (keycard/enter-pin-screen-did-load %)
|
|
|
|
:keycard-login-pin (keycard/login-pin-screen-did-load %)
|
|
|
|
:add-new-account-pin (keycard/enter-pin-screen-did-load %)
|
2021-02-12 14:58:43 +00:00
|
|
|
:keycard-authentication-method (keycard/authentication-method-screen-did-load %)
|
2022-12-20 14:45:37 +00:00
|
|
|
:multiaccounts (keycard/multiaccounts-screen-did-load %)
|
|
|
|
:wallet (wallet/wallet-will-focus %)
|
2021-02-12 14:58:43 +00:00
|
|
|
nil)))
|
|
|
|
|
|
|
|
;;TODO :replace by named events
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn set-event
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:set]}
|
|
|
|
[{:keys [db]} k v]
|
|
|
|
{:db (assoc db k v)})
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn set-view-id
|
2022-09-20 16:52:41 +00:00
|
|
|
{:events [:set-view-id]}
|
|
|
|
[{:keys [db]} view-id]
|
|
|
|
{:db (assoc db :view-id view-id)})
|
|
|
|
|
2021-02-12 14:58:43 +00:00
|
|
|
;;TODO :replace by named events
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn set-once-event
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:set-once]}
|
|
|
|
[{:keys [db]} k v]
|
|
|
|
(when-not (get db k)
|
|
|
|
{:db (assoc db k v)}))
|
|
|
|
|
|
|
|
;;TODO :replace by named events
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn set-in-event
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:set-in]}
|
|
|
|
[{:keys [db]} path v]
|
|
|
|
{:db (assoc-in db path v)})
|
2021-01-29 14:08:48 +00:00
|
|
|
|
2022-12-20 14:45:37 +00:00
|
|
|
(defn on-ramp<-rpc
|
|
|
|
[on-ramp]
|
|
|
|
(clojure.set/rename-keys on-ramp
|
|
|
|
{:logoUrl :logo-url
|
|
|
|
:siteUrl :site-url}))
|
2021-01-29 14:08:48 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn crypto-loaded-event
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [::crypto-loaded]}
|
|
|
|
[{:keys [db]} on-ramps]
|
|
|
|
{:db (assoc
|
|
|
|
db
|
|
|
|
:buy-crypto/on-ramps
|
|
|
|
(map on-ramp<-rpc on-ramps))})
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn buy-crypto-ui-loaded
|
2021-02-12 14:58:43 +00:00
|
|
|
{:events [:buy-crypto.ui/loaded]}
|
|
|
|
[_]
|
2022-12-22 06:03:55 +00:00
|
|
|
{:json-rpc/call [{:method "wallet_getCryptoOnRamps"
|
|
|
|
:params []
|
|
|
|
:on-success (fn [on-ramps]
|
|
|
|
(re-frame/dispatch [::crypto-loaded on-ramps]))}]})
|
2021-03-24 15:32:37 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn open-buy-crypto-screen
|
2021-03-24 15:32:37 +00:00
|
|
|
{:events [:buy-crypto.ui/open-screen]}
|
|
|
|
[cofx]
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/merge
|
2021-03-24 15:32:37 +00:00
|
|
|
cofx
|
2021-05-24 14:25:05 +00:00
|
|
|
(navigation/open-modal :buy-crypto nil)
|
2021-03-24 15:32:37 +00:00
|
|
|
(wallet/keep-watching-history)))
|
2022-08-11 16:18:13 +00:00
|
|
|
|
|
|
|
;; Information Box
|
|
|
|
|
|
|
|
(def closable-information-boxes
|
2022-10-06 07:01:07 +00:00
|
|
|
"[{:id information box id
|
|
|
|
:global? true/false (close information box across all profiles)}]"
|
|
|
|
[])
|
2022-08-11 16:18:13 +00:00
|
|
|
|
2022-12-20 14:45:37 +00:00
|
|
|
(defn information-box-id-hash
|
|
|
|
[id public-key global?]
|
2022-08-11 16:18:13 +00:00
|
|
|
(if global?
|
|
|
|
(hash id)
|
|
|
|
(hash (str public-key id))))
|
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn close-information-box
|
2022-08-11 16:18:13 +00:00
|
|
|
{:events [:close-information-box]}
|
|
|
|
[{:keys [db]} id global?]
|
|
|
|
(let [public-key (get-in db [:multiaccount :public-key])
|
|
|
|
hash (information-box-id-hash id public-key global?)]
|
|
|
|
{::async-storage/set! {hash true}
|
2022-12-20 14:45:37 +00:00
|
|
|
:db (assoc-in db [:information-box-states id] true)}))
|
2022-08-11 16:18:13 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn information-box-states-loaded
|
2022-08-11 16:18:13 +00:00
|
|
|
{:events [:information-box-states-loaded]}
|
|
|
|
[{:keys [db]} hashes states]
|
2022-12-20 14:45:37 +00:00
|
|
|
{:db (assoc db
|
|
|
|
:information-box-states
|
|
|
|
(reduce
|
|
|
|
(fn [acc [id hash]]
|
|
|
|
(assoc acc id (get states hash)))
|
|
|
|
{}
|
|
|
|
hashes))})
|
2022-08-11 16:18:13 +00:00
|
|
|
|
2022-12-26 15:00:17 +00:00
|
|
|
(rf/defn load-information-box-states
|
2022-08-11 16:18:13 +00:00
|
|
|
{:events [:load-information-box-states]}
|
|
|
|
[{:keys [db]}]
|
|
|
|
(let [public-key (get-in db [:multiaccount :public-key])
|
|
|
|
{:keys [keys hashes]} (reduce (fn [acc {:keys [id global?]}]
|
|
|
|
(let [hash (information-box-id-hash
|
2022-12-20 14:45:37 +00:00
|
|
|
id
|
|
|
|
public-key
|
|
|
|
global?)]
|
2022-08-11 16:18:13 +00:00
|
|
|
(-> acc
|
|
|
|
(assoc-in [:hashes id] hash)
|
|
|
|
(update :keys #(conj % hash)))))
|
2022-12-20 14:45:37 +00:00
|
|
|
{}
|
|
|
|
closable-information-boxes)]
|
2022-08-11 16:18:13 +00:00
|
|
|
{::async-storage/get {:keys keys
|
|
|
|
:cb #(re-frame/dispatch
|
|
|
|
[:information-box-states-loaded hashes %])}}))
|