Compare commits

...
Author SHA1 Message Date
Prem Chaitanya Prathi 4da4ef2cc7 feat: phase-1 changes to use 1 content-topic for all chats within a community
https://github.com/status-im/status-go/compare/84493728...a8951fd5
2025-01-08 14:45:22 +05:30
Parvesh Monu 3697f63ab0 Fix syncing flow navigation and designs (#21884) 2025-01-08 11:22:19 +05:30
Mohamed Javid ba8cf8e863 fix(wallet)_: collectible route cleanup on navigating back from TX confirmation page (#21883)
This commit fixes routes not cleaned properly ("Context Canceled" and "No Routes Found" errors thrown on generating the next route) when the user navigates back from the TX confirmation screen using the device's back button/gesture.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2025-01-07 19:04:01 +05:30
Siddarth Kumar ce1d2c4ed8 android: Kotlinify push notifications module (#21881) 2025-01-07 15:45:54 +05:30
Yevheniia Berdnyk 813c60f9e1 e2e: requirements cleanup 2025-01-07 03:07:01 +02:00
Volodymyr Kozieiev 28a81f2dd8 🎄 🧹 New Year cleanup (#21868)
* big cleannup: warnings fixed, unused code removed
2025-01-06 12:03:36 +00:00
Yevheniia Berdnyk 826410b687 e2e: cleanup base_test_case and conftest 2025-01-03 18:14:20 +02:00
Siddarth Kumar 1aa7594c41 android: kotlinify encryption utils (#21878) 2025-01-03 21:21:11 +05:30
Siddarth Kumar 5ff1d70357 android: kotlinify ui helper (#21879) 2025-01-03 21:03:06 +05:30
Siddarth Kumar 2a95f7cfac android: kotlinify root util (#21877) 2025-01-03 20:30:48 +05:30
frank 4654c4a543 fix(sha3)_: support hex string (#21837)
* fix(sha3)_: support hex string

https://github.com/status-im/status-go/compare/84493728...e01e489e
2025-01-03 08:44:02 +08:00
Mohamed Javid baf3b8c9ed fix(wallet)_: Send flow for collectibles (#21871)
This commit fixes

- the send flow for collectibles (ERC721 and ERC1155)
- picking the right balance of the ERC1155 in the send flow from the account page, based on the account viewing by the user
- the balance of ERC1155 is sometimes incorrect if the collectible is present in more than one account of the user

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2025-01-02 23:40:11 +05:30
Brian Sztamfater c6ff90954f fix_: add token and selected network on "Select network to receive" screen (#21872) 2025-01-02 11:40:04 -03:00
Tetiana Churikova c37498a6b2 e2e: big cleanup (#21875) 2024-12-30 21:10:54 +01:00
Brian Sztamfater c1b0f3ae98 fix_: include assets only related to selected network on edit asset drawer on send and bridge flows (#21869)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-12-30 11:36:43 -03:00
Icaro Motta 5007dc757f chore: Bump status-go version to include fix for bad peer check for lightmode (#21865)
Upgrades status-go to version v8.0.0, which includes the fix from PR
https://github.com/status-im/status-go/pull/5964 fix_: bad peer check logic for
lightmode and some fixes.

Might resolve https://github.com/status-im/status-mobile/issues/18031

Areas that may be impacted: we are moving from status-go version 7.0.0 to 8.0.0.
These are the changes:

- https://github.com/status-im/status-go/commit/a4e36d49c * fix_: missing message verifier start-stop ,go-waku updates, lightpush service rate limits (#5964)
- https://github.com/status-im/status-go/commit/810468a57 * test_: create private group tests (#6225)
- https://github.com/status-im/status-go/commit/0cf556bdb * test_: unskip test initialize logging (#6229)
- https://github.com/status-im/status-go/commit/b803918c2 * ci_: pytest-lint correct target pr branch (#6230)
- https://github.com/status-im/status-go/commit/d29120447 * chore(block)!: remove desktop BlockContact function for parity (#6172)
- https://github.com/status-im/status-go/commit/309d17ae5 * feat_: status-backend health endpoint (#6201)
- https://github.com/status-im/status-go/commit/6a5623bac * test_: set proxy creds (#6211)
- https://github.com/status-im/status-go/commit/2172abe95 * fix_: add infuraSecret and statusProxyBlockchainPassword to secrets (#6217)
- https://github.com/status-im/status-go/commit/0f7c26dc9 * test_: skip flaky TestService_IncrementalUpdateMixed (#6205)
- https://github.com/status-im/status-go/commit/75bdda271 * fix_: correct sensitive regex (#6188)
- https://github.com/status-im/status-go/commit/08eee8a64 * test_: add python linters (#6212)
- https://github.com/status-im/status-go/commit/66850321e * fix_: remove logging of rpc endpoint (#6195)
- https://github.com/status-im/status-go/commit/c27c773c2 * chore(wallet)_: from and to chains added to send details
- https://github.com/status-im/status-go/commit/74db63143 * test_: contact requests tests (#6167)
- https://github.com/status-im/status-go/commit/5a8310d5a * fix(pins)_: delete pins when the og message is deleted (#6173)
- https://github.com/status-im/status-go/commit/137698e02 * ci_: bring back cmd coverage (#6204)
- https://github.com/status-im/status-go/commit/1795620df * test_: run functional tests on host (no container) (#6159)
- https://github.com/status-im/status-go/commit/ef177c1c6 * feat_: SensitiveString type (#6190)
- https://github.com/status-im/status-go/commit/8b95c8148 * feat_: graceful shutdown with status-backend (#6189)
- https://github.com/status-im/status-go/commit/e6c2f89f0 * chore_: remove endpoint InitLogging (#6182)
- https://github.com/status-im/status-go/commit/4ccb08f11 * test_: WalletService class (#6171)
- https://github.com/status-im/status-go/commit/943ae1335 * fix(functional-tests)_: disable testnet (#6181)
- https://github.com/status-im/status-go/commit/616a76098 * feat_: api logging signals (#6150)
- https://github.com/status-im/status-go/commit/dfb591874 * fix_: check group error in GetBalancesAtByChain (#6169)
- https://github.com/status-im/status-go/commit/3a0080281 * fix_: create/restore account error signal (#6174)
- https://github.com/status-im/status-go/commit/e7cc53529 * feat(telem)_: track total waku message bandwidth (#6157)
- https://github.com/status-im/status-go/commit/d07e61f61 * fix_: nil *MessengerResponse is a valid result (#6175))
2024-12-26 22:20:43 -03:00
Tetiana Churikova b09f3fa6da logs: enable nightly (#21864) 2024-12-26 13:46:21 +01:00
Yevheniia Berdnyk b11c817392 e2e: LambdaTest 2024-12-26 11:44:48 +02:00
Parvesh Monu 247674b280 Onboarding remove identifiers code and refactor create_or_sync_profile screen (#21668) 2024-12-24 21:34:45 +05:30
Mohsen 07ea097bb4 [#21336] feat: upgrade wallet connect (#21833) 2024-12-24 13:13:52 +03:00
Brian Sztamfater 23d3a2db2a feat(wallet): simplify send and bridge flow ux (#21595)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-12-23 12:00:17 -03:00
Shivek KhuranaandIcaro Motta a85ca5bd3d chore: Give non-destructive instructions when Keycard migration failed (#21844)
Co-authored-by: Icaro Motta <icaro.ldm@gmail.com>
2024-12-20 18:05:11 +05:30
Mohamed Javid 0f036ca9c4 fix_: slider reset in standard authentication (#21831)
This commit fixes the slider-button doesn't reset after the completion of slider in standard authentication with keycard keypairs.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-12-17 17:46:16 +05:30
flexsurfer c19128e8b1 [#21828] Close the flows for adding accounts in keycard accounts (#21829) 2024-12-16 17:49:59 +01:00
Brian Sztamfater 8d615801d1 feat_: optimize endpoint calls when fetching balances (#21802)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-12-16 12:11:09 -03:00
Mohsen 4f6e1897fe [#21135] fix: handle unsupported wallet address (#21820) 2024-12-16 17:54:16 +03:00
Ulises ManuelandShivek Khurana 93b5f8d180 Hide viewing community token requirements under a feature flag (#21814)
Co-authored-by: Shivek Khurana <khuranashivek@outlook.com>
2024-12-16 18:17:06 +05:30
Parvesh Monu 0e607dc293 fix Privacy policy button in learn more sheet open terms-of-use sheet (#21823) 2024-12-16 15:42:17 +05:30
flexsurfer 9ced0e4131 [#21708] [Android] Login with Keycard stucks on Preparing screen from time to time (#21811) 2024-12-13 19:57:26 +01:00
Sean Hagstrom 55ee800e18 feature: add keycard metric events (#21794)
This change adds some metrics events for tracking when keycard users have reached an unavailable feature, when keycard users have successfully logged in, and when keycard users have succeeded or failed to migrate to keycard.
2024-12-12 09:21:09 -08:00
505 changed files with 4232 additions and 24424 deletions
-185
View File
@@ -1,185 +0,0 @@
legacy.status-im.utils.build/warning-handler
legacy.status-im.utils.build/get-current-sha
status-im.constants/spacing-char
status-im.constants/arg-wrapping-char
legacy.status-im.ios.core/init
legacy.status-im.ui.components.camera/aspects
legacy.status-im.ui.components.camera/capture-targets
legacy.status-im.ui.components.camera/set-torch
legacy.status-im.ui.components.camera/request-access-ios
legacy.status-im.chat.models.message-content/stylings
legacy.status-im.chat.models.message-content/actions
legacy.status-im.chat.models.message-content/blank-string
legacy.status-im.chat.models.message-content/sorted-ranges
legacy.status-im.ethereum.mnemonic/words->passphrase
native-module.core/listener
native-module.core/multiaccount-reset
native-module.core/extract-group-membership-signatures
native-module.core/sign-group-membership
native-module.core/update-mailservers
legacy.status-im.ethereum.abi-spec/bytes-to-hex
legacy.status-im.android.core/init
legacy.status-im.chat.models.message/transport-keys
legacy.status-im.utils.datetime/week
legacy.status-im.utils.datetime/time-ago
legacy.status-im.utils.datetime/format-date
legacy.status-im.utils.datetime/get-ordinal-date
reagent.dom/render
reagent.dom/unmount-component-at-node
reagent.dom/dom-node
reagent.dom/force-update-all
legacy.status-im.multiaccounts.create.core/get-new-key-code
legacy.status-im.ethereum.decode/string
legacy.status-im.ui.screens.network.views/mainnet?
legacy.status-im.data-store.chats/event->string
legacy.status-im.mailserver.constants/seven-days
legacy.status-im.mailserver.constants/connection-timeout
legacy.status-im.hardwallet.card/install-applet
legacy.status-im.hardwallet.card/install-cash-applet
legacy.status-im.multiaccount.login.data-test/all-contacts
legacy.status-im.multiaccount.login.data-test/multiaccounts
legacy.status-im.multiaccount.login.data-test/get-chats
legacy.status-im.multiaccount.login.data-test/transport
legacy.status-im.multiaccount.login.data-test/topics
legacy.status-im.utils.utils/clear-timeout
legacy.status-im.utils.handlers/logged-in
legacy.status-im.multiaccounts.model/credentials
legacy.status-im.multiaccounts.login.core/contract-fleet?
legacy.status-im.multiaccounts.login.core/fetch-nodes
status-im.config/rpc-networks-only?
status-im.config/waku-enabled?
legacy.status-im.utils.pairing/has-paired-installations?
legacy.status-im.tribute-to-talk.core-test/user-cofx
mocks.js-dependencies/action-button
mocks.js-dependencies/camera
mocks.js-dependencies/dismiss-keyboard
mocks.js-dependencies/emoji-picker
mocks.js-dependencies/i18n
mocks.js-dependencies/qr-code
mocks.js-dependencies/svg
mocks.js-dependencies/icons
mocks.js-dependencies/webview
mocks.js-dependencies/desktop-linking
mocks.js-dependencies/desktop-shortcuts
mocks.js-dependencies/snoopy
mocks.js-dependencies/snoopy-filter
mocks.js-dependencies/snoopy-bars
mocks.js-dependencies/snoopy-buffer
mocks.js-dependencies/desktop-menu
mocks.js-dependencies/desktop-config
mocks.js-dependencies/react-native-navigation-twopane
mocks.js-dependencies/react-native-screens
mocks.js-dependencies/react-native-shake
mocks.js-dependencies/back-handler
mocks.js-dependencies/react
mocks.js-dependencies/react-navigation-native
mocks.js-dependencies/react-navigation-stack
mocks.js-dependencies/react-navigation-bottom-tabs
mocks.js-dependencies/react-native-haptic-feedback
mocks.js-dependencies/mock
quo.previews.main/init
legacy.status-im.chat.models.message-list-test/descending-range
legacy.status-im.chat.models.message-list-test/random-range
legacy.status-im.ethereum.eip165/supports?
legacy.status-im.utils.hex/valid-hex?
legacy.status-im.multiaccounts.core/chat-send?
legacy.status-im.hardwallet.simulated-keycard/install-cash-applet
legacy.status-im.hardwallet.simulated-keycard/sign-typed-data
legacy.status-im.utils.core/safe-read-message-content
legacy.status-im.ui.components.react/native-modules
legacy.status-im.ui.components.react/progress-bar
react-native.fs/move-file
react-native.fs/read-dir
react-native.fs/mkdir
react-native.fs/unlink
react-native.fs/file-exists?
legacy.status-im.ui.components.colors/white
legacy.status-im.ui.components.colors/black
legacy.status-im.ui.components.core/animated-header
legacy.status-im.ui.components.core/safe-area-provider
legacy.status-im.ui.components.core/safe-area-consumer
legacy.status-im.ui.components.core/safe-area-view
legacy.status-im.utils.universal-links.core/open!
legacy.status-im.transport.filters.core-test/member-2
legacy.status-im.ui.screens.chat.ttt/one-to-one-chat-description-container
legacy.status-im.ethereum.ens/ttl
legacy.status-im.ethereum.ens/addr-hash
legacy.status-im.ethereum.ens/name-hash
legacy.status-im.ethereum.ens/ABI-hash
legacy.status-im.ethereum.ens/pubkey-hash
legacy.status-im.network.core/get-network
status-im.constants/desktop-content-types
status-im.constants/blocks-per-hour
status-im.constants/one-earth-day
status-im.constants/left-pane-min-width
status-im.constants/system
status-im.constants/contact-discovery
status-im.constants/send-transaction-failed-parse-response
status-im.constants/send-transaction-failed-parse-params
status-im.constants/send-transaction-no-account-selected
status-im.constants/send-transaction-invalid-tx-sender
status-im.constants/web3-get-logs
status-im.constants/web3-transaction-receipt
status-im.constants/web3-new-filter
status-im.constants/web3-new-pending-transaction-filter
status-im.constants/web3-new-block-filter
status-im.constants/web3-uninstall-filter
status-im.constants/web3-get-filter-changes
status-im.constants/web3-shh-post
status-im.constants/web3-shh-new-identity
status-im.constants/web3-shh-has-identity
status-im.constants/web3-shh-new-group
status-im.constants/web3-shh-add-to-group
status-im.constants/web3-shh-new-filter
status-im.constants/web3-shh-uninstall-filter
status-im.constants/web3-shh-get-filter-changes
status-im.constants/web3-shh-get-messages
status-im.constants/status-create-address
status-im.constants/event-transfer-hash
status-im.constants/regx-rtl-characters
status-im.constants/desktop-msg-chars-hard-limit
status-im.constants/debug-metrics
status-im.constants/scan-qr-code
status-im.constants/ipfs-proto-code
status-im.constants/swarm-proto-code
legacy.status-im.multiaccounts.update.publisher/publish-update!
legacy.status-im.utils.async/task-queue
legacy.status-im.utils.async/async-periodic-run!
legacy.status-im.desktop.core/app-root
legacy.status-im.desktop.core/init
legacy.status-im.chat.models-test/test-db
legacy.status-im.ui.components.spacing/padding-vertical
legacy.status-im.utils.money/percent-change
utils.debounce/clear-all
legacy.status-im.transport.db/create-chat
legacy.status-im.utils.priority-map/priority-map
legacy.status-im.utils.priority-map/priority-map-by
legacy.status-im.utils.priority-map/priority-map-keyfn
legacy.status-im.utils.priority-map/empty-message-map
legacy.status-im.wallet.choose-recipient.core/use-default-eth-gas
legacy.status-im.ui.components.animation/decay
legacy.status-im.ui.components.animation/remove-all-listeners
legacy.status-im.ui.components.animation/stop-animation
legacy.status-im.ui.components.animation/animated
legacy.status-im.ui.components.animation/add
legacy.status-im.ui.components.animation/subtract
legacy.status-im.ui.components.animation/get-layout
legacy.status-im.wallet.transactions-test/uri-query-data
legacy.status-im.utils.name/shortened-name
legacy.status-im.ui.components.button.haptic/trigger
legacy.status-im.contact.db/filter-dapps
legacy.status-im.contact.db/filter-group-contacts
legacy.status-im.contact.db/legacy-pending?
legacy.status-im.utils.transducers-test/preview-call-1
legacy.status-im.utils.transducers-test/preview-call-2
legacy.status-im.utils.types/to-string
legacy.status-im.chat.db/datemark?
legacy.status-im.chat.db/gap?
legacy.status-im.chat.db/map->sorted-seq
legacy.status-im.utils.snoopy/status-module-filter
legacy.status-im.utils.snoopy/websocket-filter
legacy.status-im.utils.snoopy/touches-filter
legacy.status-im.utils.snoopy/native-animation-filter
legacy.status-im.utils.snoopy/keyboard-observer-filter
legacy.status-im.utils.multihash/hex
legacy.status-im.utils.multihash/decode-array
+28 -3
View File
@@ -4,7 +4,7 @@
legacy.status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
legacy.status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clojure.core/defn
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for
legacy.status-im.utils.styles/def clojure.core/def
@@ -13,7 +13,29 @@
test-helpers.unit/deftest-event clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:case-duplicate-test {:level :error}
:linters {:clojure-lsp/unused-public-var {:exclude-when-defined-by #{utils.re-frame/defn}
:exclude #{
;; even if we are not
;; using color now, we
;; will need it later
quo.foundations.colors
;; same for funcs from
;; native-module
native-module.core
;; some funcs used
;; temporarily during
;; development
test-helpers.component
test-helpers.unit
;; not sure if unused
;; functions here should
;; be removed:
keycard.keycard
react-native.fs}}
:case-duplicate-test {:level :error}
:case-quoted-test {:level :error}
:case-symbol-test {:level :error}
:clj-kondo-config {:level :error}
@@ -40,7 +62,8 @@
:equals-true {:level :error}
:inline-def {:level :error}
:invalid-arity {:skip-args [legacy.status-im.utils.fx/defn
utils.re-frame/defn]}
utils.re-frame/defn
utils.re-frame/merge]}
:loop-without-recur {:level :error}
:minus-one {:level :error}
:misplaced-docstring {:level :error}
@@ -53,6 +76,8 @@
:redundant-do {:level :error}
:redundant-let {:level :error}
:refer-all {:level :error}
;; ignore error in config itself saying that `:shadowed-fn-param` is unknown
#_{:clj-kondo/ignore [:clj-kondo-config]}
:shadowed-fn-param {:level :error}
:shadowed-var {:level :error
;; We temporarily use :include to define an
+2 -2
View File
@@ -2,7 +2,7 @@ DEBUG_WEBVIEW=1
ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
GROUP_CHATS_ENABLED=1
LOG_LEVEL=info
LOG_LEVEL=debug
MAILSERVER_CONFIRMATIONS_ENABLED=1
MAINNET_WARNING_ENABLED=1
PFS_ENCRYPTION_ENABLED=1
@@ -23,4 +23,4 @@ FAST_CREATE_COMMUNITY_ENABLED=0
TEST_NETWORKS_ENABLED=0
ENABLE_ALERT_BANNER=1
FLAG_WALLET_CONNECT_ENABLED=1
API_LOGGING_ENABLED=0
API_LOGGING_ENABLED=1
@@ -1,40 +0,0 @@
package im.status.ethereum;
import java.io.File;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/** @author Kevin Kowalewski */
public class RootUtil {
public static boolean isDeviceRooted() {
return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
}
private static boolean checkRootMethod1() {
String buildTags = android.os.Build.TAGS;
return buildTags != null && buildTags.contains("test-keys");
}
private static boolean checkRootMethod2() {
String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
"/system/bin/failsafe/su", "/data/local/su" };
for (String path : paths) {
if (new File(path).exists()) return true;
}
return false;
}
private static boolean checkRootMethod3() {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
return in.readLine() != null;
} catch (Throwable t) {
return false;
} finally {
if (process != null) process.destroy();
}
}
}
@@ -0,0 +1,42 @@
package im.status.ethereum
import android.os.Build
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
object RootUtil {
fun isDeviceRooted(): Boolean =
checkRootMethod1() || checkRootMethod2() || checkRootMethod3()
private fun checkRootMethod1(): Boolean =
Build.TAGS?.contains("test-keys") ?: false
private fun checkRootMethod2(): Boolean {
val paths = arrayOf(
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su"
)
return paths.any { File(it).exists() }
}
private fun checkRootMethod3(): Boolean {
var process: Process? = null
return try {
process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
BufferedReader(InputStreamReader(process.inputStream)).readLine() != null
} catch (t: Throwable) {
false
} finally {
process?.destroy()
}
}
}
+4 -20
View File
@@ -73,9 +73,9 @@ pipeline {
passwordVariable: 'TESTRAIL_PASS'
),
usernamePassword(
credentialsId: 'sauce-labs-api',
usernameVariable: 'SAUCE_USERNAME',
passwordVariable: 'SAUCE_ACCESS_KEY'
credentialsId: 'lambda-test-api',
usernameVariable: 'LAMBDA_TEST_USERNAME',
passwordVariable: 'LAMBDA_TEST_ACCESS_KEY'
),
string(
credentialsId: 'etherscan-api-key',
@@ -95,8 +95,7 @@ pipeline {
sh 'cp -f $TEST_ETH_ACCOUNTS_FILE users.py'
sh """
python3 -m pytest \
--numprocesses 8 \
--rerun_count=2 \
--numprocesses 9 \
--testrail_report=True \
-m testrail_id \
-m \"nightly\" \
@@ -110,21 +109,6 @@ pipeline {
}
post {
always {
script {
sauce('sauce-labs-cred') {
saucePublisher()
}
}
}
success {
script {
junit(
testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']],
testResults: 'test/appium/tests/*.xml'
)
}
}
cleanup {
sh 'make purge'
}
+4 -5
View File
@@ -111,9 +111,9 @@ pipeline {
passwordVariable: 'TESTRAIL_PASS'
),
usernamePassword(
credentialsId: 'sauce-labs-api',
usernameVariable: 'SAUCE_USERNAME',
passwordVariable: 'SAUCE_ACCESS_KEY'
credentialsId: 'lambda-test-api',
usernameVariable: 'LAMBDA_TEST_USERNAME',
passwordVariable: 'LAMBDA_TEST_ACCESS_KEY'
),
string(
credentialsId: 'etherscan-api-key',
@@ -133,8 +133,7 @@ pipeline {
sh 'cp -f $TEST_ETH_ACCOUNTS_FILE users.py'
sh """
python3 -m pytest \
--numprocesses 8 \
--rerun_count=2 \
--numprocesses 9 \
--testrail_report=True \
-k \"${params.KEYWORD_EXPRESSION}\" \
--apk=${params.APK_URL ?: apk_path} \
@@ -1,297 +0,0 @@
package im.status.ethereum.module;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.Callback;
import android.util.Log;
import statusgo.Statusgo;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.function.Function;
import android.app.Activity;
import android.view.WindowManager;
import android.os.Build;
import android.view.Window;
import android.preference.PreferenceManager;
import android.content.SharedPreferences;
public class EncryptionUtils extends ReactContextBaseJavaModule {
private static final String TAG = "EncryptionUtils";
private ReactApplicationContext reactContext;
private Utils utils;
public EncryptionUtils(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
this.utils = new Utils(reactContext);
}
@Override
public String getName() {
return "EncryptionUtils";
}
@ReactMethod
private void initKeystore(final String keyUID, final Callback callback) throws JSONException {
Log.d(TAG, "initKeystore");
final String commonKeydir = this.utils.pathCombine(this.utils.getNoBackupDirectory(), "/keystore");
final String keydir = this.utils.pathCombine(commonKeydir, keyUID);
StatusBackendClient.executeStatusGoRequestWithCallback(
"InitKeystore",
keydir,
() -> Statusgo.initKeystore(keydir),
callback
);
}
@ReactMethod
public void reEncryptDbAndKeystore(final String keyUID, final String password, final String newPassword, final Callback callback) throws JSONException {
JSONObject params = new JSONObject();
params.put("keyUID", keyUID);
params.put("oldPassword", password);
params.put("newPassword", newPassword);
String jsonParams = params.toString();
StatusBackendClient.executeStatusGoRequestWithCallback(
"ChangeDatabasePasswordV2",
jsonParams,
() -> Statusgo.changeDatabasePasswordV2(jsonParams),
callback
);
}
@ReactMethod
public void convertToKeycardAccount(final String keyUID, final String accountData, final String options, final String keycardUID, final String password,
final String newPassword, final Callback callback) throws JSONException {
final String keyStoreDir = this.utils.getKeyStorePath(keyUID);
JSONObject params = new JSONObject();
params.put("keyUID", keyUID);
params.put("account", new JSONObject(accountData));
params.put("settings", new JSONObject(options));
params.put("keycardUID", keycardUID);
params.put("oldPassword", password);
params.put("newPassword", newPassword);
final String jsonParams = params.toString();
StatusBackendClient.executeStatusGoRequest(
"InitKeystore",
keyStoreDir,
() -> Statusgo.initKeystore(keyStoreDir)
);
StatusBackendClient.executeStatusGoRequestWithCallback(
"ConvertToKeycardAccountV2",
jsonParams,
() -> Statusgo.convertToKeycardAccountV2(jsonParams),
callback
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String encodeTransfer(final String to, final String value) {
try {
JSONObject params = new JSONObject();
params.put("to", to);
params.put("value", value);
String jsonParams = params.toString();
return StatusBackendClient.executeStatusGoRequestWithResult(
"EncodeTransferV2",
jsonParams,
() -> Statusgo.encodeTransferV2(jsonParams)
);
} catch (JSONException e) {
Log.e(TAG, "Error creating JSON for encodeTransfer: " + e.getMessage());
return null;
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String encodeFunctionCall(final String method, final String paramsJSON) {
try {
JSONObject params = new JSONObject();
params.put("method", method);
params.put("paramsJSON", new JSONObject(paramsJSON));
String jsonString = params.toString();
return StatusBackendClient.executeStatusGoRequestWithResult(
"EncodeFunctionCallV2",
jsonString,
() -> Statusgo.encodeFunctionCallV2(jsonString)
);
} catch (JSONException e) {
Log.e(TAG, "Error creating JSON for encodeFunctionCall: " + e.getMessage());
return null;
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String decodeParameters(final String decodeParamJSON) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"DecodeParameters",
decodeParamJSON,
() -> Statusgo.decodeParameters(decodeParamJSON)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String hexToNumber(final String hex) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"HexToNumber",
hex,
() -> Statusgo.hexToNumber(hex)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String numberToHex(final String numString) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"NumberToHex",
numString,
() -> Statusgo.numberToHex(numString)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String sha3(final String str) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"Sha3",
str,
() -> Statusgo.sha3(str)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String utf8ToHex(final String str) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"Utf8ToHex",
str,
() -> Statusgo.utf8ToHex(str)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String hexToUtf8(final String str) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"HexToUtf8",
str,
() -> Statusgo.hexToUtf8(str)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String serializeLegacyKey(final String publicKey) {
return StatusBackendClient.executeStatusGoRequestWithResult(
"SerializeLegacyKey",
publicKey,
() -> Statusgo.serializeLegacyKey(publicKey)
);
}
@ReactMethod
public void setBlankPreviewFlag(final Boolean blankPreview) {
final SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.reactContext);
sharedPrefs.edit().putBoolean("BLANK_PREVIEW", blankPreview).commit();
setSecureFlag();
}
private void setSecureFlag() {
final SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.reactContext);
final boolean setSecure = sharedPrefs.getBoolean("BLANK_PREVIEW", true);
final Activity activity = this.reactContext.getCurrentActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
final Window window = activity.getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && setSecure) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
});
}
}
@ReactMethod
public void hashTransaction(final String txArgsJSON, final Callback callback) throws JSONException {
StatusBackendClient.executeStatusGoRequestWithCallback(
"HashTransaction",
txArgsJSON,
() -> Statusgo.hashTransaction(txArgsJSON),
callback
);
}
@ReactMethod
public void hashMessage(final String message, final Callback callback) throws JSONException {
StatusBackendClient.executeStatusGoRequestWithCallback(
"HashMessage",
message,
() -> Statusgo.hashMessage(message),
callback
);
}
@ReactMethod
public void multiformatDeserializePublicKey(final String multiCodecKey, final String base58btc, final Callback callback) throws JSONException {
JSONObject params = new JSONObject();
params.put("key", multiCodecKey);
params.put("outBase", base58btc);
String jsonParams = params.toString();
StatusBackendClient.executeStatusGoRequestWithCallback(
"MultiformatDeserializePublicKeyV2",
jsonParams,
() -> Statusgo.multiformatDeserializePublicKeyV2(jsonParams),
callback
);
}
@ReactMethod
public void deserializeAndCompressKey(final String desktopKey, final Callback callback) throws JSONException {
StatusBackendClient.executeStatusGoRequestWithCallback(
"DeserializeAndCompressKey",
desktopKey,
() -> Statusgo.deserializeAndCompressKey(desktopKey),
callback
);
}
@ReactMethod
public void hashTypedData(final String data, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.hashTypedData(data), callback);
}
@ReactMethod
public void hashTypedDataV4(final String data, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.hashTypedDataV4(data), callback);
}
@ReactMethod
public void signMessage(final String rpcParams, final Callback callback) throws JSONException {
StatusBackendClient.executeStatusGoRequestWithCallback(
"SignMessage",
rpcParams,
() -> Statusgo.signMessage(rpcParams),
callback
);
}
@ReactMethod
public void signTypedData(final String data, final String account, final String password, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.signTypedData(data, account, password), callback);
}
@ReactMethod
public void signTypedDataV4(final String data, final String account, final String password, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.signTypedDataV4(data, account, password), callback);
}
@ReactMethod
public void signGroupMembership(final String content, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.signGroupMembership(content), callback);
}
}
@@ -0,0 +1,294 @@
package im.status.ethereum.module
import android.app.Activity
import android.os.Build
import android.preference.PreferenceManager
import android.util.Log
import android.view.WindowManager
import com.facebook.react.bridge.*
import org.json.JSONException
import org.json.JSONObject
import statusgo.Statusgo
import java.lang.Exception
import java.lang.RuntimeException
class EncryptionUtils(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
private val utils = Utils(reactContext)
companion object {
private const val TAG = "EncryptionUtils"
}
override fun getName(): String = "EncryptionUtils"
@ReactMethod
@Throws(Exception::class)
private fun initKeystore(keyUID: String, callback: Callback) {
Log.d(TAG, "initKeystore")
val commonKeydir = utils.pathCombine(utils.getNoBackupDirectory(), "/keystore")
val keydir = utils.pathCombine(commonKeydir, keyUID)
StatusBackendClient.executeStatusGoRequestWithCallback(
"InitKeystore",
keydir,
{ Statusgo.initKeystore(keydir) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun reEncryptDbAndKeystore(keyUID: String, password: String, newPassword: String, callback: Callback) {
val params = JSONObject().apply {
put("keyUID", keyUID)
put("oldPassword", password)
put("newPassword", newPassword)
}
StatusBackendClient.executeStatusGoRequestWithCallback(
"ChangeDatabasePasswordV2",
params.toString(),
{ Statusgo.changeDatabasePasswordV2(params.toString()) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun convertToKeycardAccount(
keyUID: String,
accountData: String,
options: String,
keycardUID: String,
password: String,
newPassword: String,
callback: Callback
) {
val keyStoreDir = utils.getKeyStorePath(keyUID)
val params = JSONObject().apply {
put("keyUID", keyUID)
put("account", JSONObject(accountData))
put("settings", JSONObject(options))
put("keycardUID", keycardUID)
put("oldPassword", password)
put("newPassword", newPassword)
}
val jsonParams = params.toString()
StatusBackendClient.executeStatusGoRequest(
"InitKeystore",
keyStoreDir,
{ Statusgo.initKeystore(keyStoreDir) }
)
StatusBackendClient.executeStatusGoRequestWithCallback(
"ConvertToKeycardAccountV2",
jsonParams,
{ Statusgo.convertToKeycardAccountV2(jsonParams) },
callback
)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun encodeTransfer(to: String, value: String): String? {
return try {
val params = JSONObject().apply {
put("to", to)
put("value", value)
}
StatusBackendClient.executeStatusGoRequestWithResult(
"EncodeTransferV2",
params.toString(),
{ Statusgo.encodeTransferV2(params.toString()) }
)
} catch (e: JSONException) {
Log.e(TAG, "Error creating JSON for encodeTransfer: ${e.message}")
null
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun encodeFunctionCall(method: String, paramsJSON: String): String? {
return try {
val params = JSONObject().apply {
put("method", method)
put("paramsJSON", JSONObject(paramsJSON))
}
StatusBackendClient.executeStatusGoRequestWithResult(
"EncodeFunctionCallV2",
params.toString(),
{ Statusgo.encodeFunctionCallV2(params.toString()) }
)
} catch (e: JSONException) {
Log.e(TAG, "Error creating JSON for encodeFunctionCall: ${e.message}")
null
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun decodeParameters(decodeParamJSON: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"DecodeParameters",
decodeParamJSON,
{ Statusgo.decodeParameters(decodeParamJSON) }
)
@ReactMethod(isBlockingSynchronousMethod = true)
fun hexToNumber(hex: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"HexToNumber",
hex,
{ Statusgo.hexToNumber(hex) }
)
@ReactMethod(isBlockingSynchronousMethod = true)
fun numberToHex(numString: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"NumberToHex",
numString,
{ Statusgo.numberToHex(numString) }
)
@ReactMethod(isBlockingSynchronousMethod = true)
fun sha3(str: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"Sha3",
str,
{ Statusgo.sha3(str) }
)
@ReactMethod(isBlockingSynchronousMethod = true)
fun utf8ToHex(str: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"Utf8ToHex",
str,
{ Statusgo.utf8ToHex(str) }
)
@ReactMethod(isBlockingSynchronousMethod = true)
fun hexToUtf8(str: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"HexToUtf8",
str,
{ Statusgo.hexToUtf8(str) }
)
@ReactMethod(isBlockingSynchronousMethod = true)
fun serializeLegacyKey(publicKey: String): String =
StatusBackendClient.executeStatusGoRequestWithResult(
"SerializeLegacyKey",
publicKey,
{ Statusgo.serializeLegacyKey(publicKey) }
)
@ReactMethod
fun setBlankPreviewFlag(blankPreview: Boolean) {
PreferenceManager.getDefaultSharedPreferences(reactContext)
.edit()
.putBoolean("BLANK_PREVIEW", blankPreview)
.commit()
setSecureFlag()
}
private fun setSecureFlag() {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(reactContext)
val setSecure = sharedPrefs.getBoolean("BLANK_PREVIEW", true)
reactContext.currentActivity?.run {
runOnUiThread {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && setSecure) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}
}
@ReactMethod
@Throws(Exception::class)
fun hashTransaction(txArgsJSON: String, callback: Callback) {
StatusBackendClient.executeStatusGoRequestWithCallback(
"HashTransaction",
txArgsJSON,
{ Statusgo.hashTransaction(txArgsJSON) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun hashMessage(message: String, callback: Callback) {
StatusBackendClient.executeStatusGoRequestWithCallback(
"HashMessage",
message,
{ Statusgo.hashMessage(message) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun multiformatDeserializePublicKey(multiCodecKey: String, base58btc: String, callback: Callback) {
val params = JSONObject().apply {
put("key", multiCodecKey)
put("outBase", base58btc)
}
StatusBackendClient.executeStatusGoRequestWithCallback(
"MultiformatDeserializePublicKeyV2",
params.toString(),
{ Statusgo.multiformatDeserializePublicKeyV2(params.toString()) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun deserializeAndCompressKey(desktopKey: String, callback: Callback) {
StatusBackendClient.executeStatusGoRequestWithCallback(
"DeserializeAndCompressKey",
desktopKey,
{ Statusgo.deserializeAndCompressKey(desktopKey) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun hashTypedData(data: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.hashTypedData(data) }, callback)
}
@ReactMethod
@Throws(Exception::class)
fun hashTypedDataV4(data: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.hashTypedDataV4(data) }, callback)
}
@ReactMethod
@Throws(Exception::class)
fun signMessage(rpcParams: String, callback: Callback) {
StatusBackendClient.executeStatusGoRequestWithCallback(
"SignMessage",
rpcParams,
{ Statusgo.signMessage(rpcParams) },
callback
)
}
@ReactMethod
@Throws(Exception::class)
fun signTypedData(data: String, account: String, password: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.signTypedData(data, account, password) }, callback)
}
@ReactMethod
@Throws(Exception::class)
fun signTypedDataV4(data: String, account: String, password: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.signTypedDataV4(data, account, password) }, callback)
}
@ReactMethod
@Throws(Exception::class)
fun signGroupMembership(content: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.signGroupMembership(content) }, callback)
}
}
@@ -1,124 +0,0 @@
package im.status.ethereum.module;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import android.app.Activity;
import android.util.Log;
import android.os.Build;
import android.webkit.WebView;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebStorage;
import android.view.WindowManager;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import android.view.inputmethod.InputMethodManager;
import android.view.View;
import android.widget.EditText;
import android.content.Context;
public class UIHelper extends ReactContextBaseJavaModule {
private static final String TAG = "UIHelper";
private ReactApplicationContext reactContext;
public UIHelper(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "UIHelper";
}
@ReactMethod
public void setSoftInputMode(final int mode) {
Log.d(TAG, "setSoftInputMode");
final Activity activity = getCurrentActivity();
if (activity == null) {
return;
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.getWindow().setSoftInputMode(mode);
}
});
}
@SuppressWarnings("deprecation")
@ReactMethod
public void clearCookies() {
Log.d(TAG, "clearCookies");
final Activity activity = getCurrentActivity();
if (activity == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
} else {
CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(activity);
cookieSyncManager.startSync();
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieSyncManager.stopSync();
cookieSyncManager.sync();
}
}
@ReactMethod
public void toggleWebviewDebug(final boolean val) {
Log.d(TAG, "toggleWebviewDebug");
final Activity activity = getCurrentActivity();
if (activity == null) {
return;
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
WebView.setWebContentsDebuggingEnabled(val);
}
});
}
@ReactMethod
public void clearStorageAPIs() {
Log.d(TAG, "clearStorageAPIs");
final Activity activity = getCurrentActivity();
if (activity == null) {
return;
}
WebStorage storage = WebStorage.getInstance();
if (storage != null) {
storage.deleteAllData();
}
}
@ReactMethod
public void resetKeyboardInputCursor(final int reactTagToReset, final int selection) {
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
InputMethodManager imm = (InputMethodManager) getReactApplicationContext().getBaseContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
View viewToReset = nativeViewHierarchyManager.resolveView(reactTagToReset);
imm.restartInput(viewToReset);
try {
EditText textView = (EditText) viewToReset;
textView.setSelection(selection);
} catch (Exception e) {}
}
}
});
}
}
@@ -0,0 +1,97 @@
package im.status.ethereum.module
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.webkit.CookieManager
import android.webkit.CookieSyncManager
import android.webkit.WebStorage
import android.webkit.WebView
import android.widget.EditText
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.uimanager.NativeViewHierarchyManager
import com.facebook.react.uimanager.UIBlock
import com.facebook.react.uimanager.UIManagerModule
class UIHelper(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
private const val TAG = "UIHelper"
}
override fun getName(): String = "UIHelper"
@ReactMethod
fun setSoftInputMode(mode: Int) {
Log.d(TAG, "setSoftInputMode")
getCurrentActivity()?.run {
runOnUiThread {
window.setSoftInputMode(mode)
}
}
}
@Suppress("DEPRECATION")
@ReactMethod
fun clearCookies() {
Log.d(TAG, "clearCookies")
getCurrentActivity()?.let { activity ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
CookieManager.getInstance().apply {
removeAllCookies(null)
flush()
}
} else {
CookieSyncManager.createInstance(activity).also { cookieSyncManager ->
cookieSyncManager.startSync()
CookieManager.getInstance().apply {
removeAllCookie()
removeSessionCookie()
}
cookieSyncManager.stopSync()
cookieSyncManager.sync()
}
}
}
}
@ReactMethod
fun toggleWebviewDebug(enabled: Boolean) {
Log.d(TAG, "toggleWebviewDebug")
getCurrentActivity()?.run {
runOnUiThread {
WebView.setWebContentsDebuggingEnabled(enabled)
}
}
}
@ReactMethod
fun clearStorageAPIs() {
Log.d(TAG, "clearStorageAPIs")
getCurrentActivity()?.let {
WebStorage.getInstance()?.deleteAllData()
}
}
@ReactMethod
fun resetKeyboardInputCursor(reactTagToReset: Int, selection: Int) {
reactApplicationContext.getNativeModule(UIManagerModule::class.java)?.addUIBlock(object : UIBlock {
override fun execute(nativeViewHierarchyManager: NativeViewHierarchyManager) {
(reactApplicationContext.baseContext.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.let { imm ->
val viewToReset = nativeViewHierarchyManager.resolveView(reactTagToReset)
imm.restartInput(viewToReset)
try {
(viewToReset as? EditText)?.setSelection(selection)
} catch (e: Exception) {
// Ignore exceptions during selection setting
}
}
}
})
}
}
@@ -1,109 +0,0 @@
package im.status.ethereum.pushnotifications;
import android.content.Context;
import android.content.Intent;
import android.app.Service;
import android.content.pm.ServiceInfo;
import android.os.IBinder;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.provider.Settings;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.os.Build;
import im.status.ethereum.module.R;
public class ForegroundService extends Service {
private static final String CHANNEL_ID = "status-service";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent i, int flags, int startId) {
// NOTE: recent versions of Android require the service to display
// a sticky notification to inform the user that the service is running
Context context = getApplicationContext();
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
Intent intent = null;
String notificationContentText = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
context.getResources().getString(R.string.status_service),
NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
// Create intent that takes the user to the notification channel settings so they can hide it
intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, CHANNEL_ID);
notificationContentText = context.getResources().getString(R.string.tap_to_hide_notification);
} else {
// For older versions of android intent takes the user to the Status app
Class intentClass;
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
intentClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return 0;
}
intent = new Intent(context, intentClass);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
notificationContentText = context.getResources().getString(R.string.keep_status_running);
}
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Intent stopIntent = new Intent(PushNotificationHelper.ACTION_TAP_STOP);
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setContentTitle(context.getResources().getString(R.string.background_service_opened))
.setContentText(notificationContentText)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setContentIntent(pendingIntent)
.setNumber(0)
.addAction(R.drawable.ic_stat_notify_status,
context.getResources().getString(R.string.stop),
stopPendingIntent)
.build();
// the id of the foreground notification MUST NOT be 0
if (Build.VERSION.SDK_INT >= 33) {
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
startForeground(1, notification);
}
return START_STICKY;
}
}
@@ -0,0 +1,122 @@
package im.status.ethereum.pushnotifications
import android.content.Context
import android.content.Intent
import android.app.Service
import android.content.pm.ServiceInfo
import android.os.IBinder
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.provider.Settings
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import android.os.Build
import im.status.ethereum.module.R
class ForegroundService : Service() {
companion object {
private const val CHANNEL_ID = "status-service"
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(i: Intent?, flags: Int, startId: Int): Int {
// NOTE: recent versions of Android require the service to display
// a sticky notification to inform the user that the service is running
val context = applicationContext
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
// Create notification channel for Android O and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(NotificationManager::class.java)
val channel = NotificationChannel(
CHANNEL_ID,
context.resources.getString(R.string.status_service),
NotificationManager.IMPORTANCE_HIGH
).apply {
setShowBadge(false)
}
notificationManager.createNotificationChannel(channel)
}
// Initialize intent and notification text based on Android version
val notificationIntent = createNotificationIntent(context)
val notificationText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.resources.getString(R.string.tap_to_hide_notification)
} else {
context.resources.getString(R.string.keep_status_running)
}
val pendingIntent = PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)
val stopIntent = Intent(PushNotificationHelper.ACTION_TAP_STOP)
val stopPendingIntent = PendingIntent.getBroadcast(
context,
0,
stopIntent,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setContentTitle(context.resources.getString(R.string.background_service_opened))
.setContentText(notificationText)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setContentIntent(pendingIntent)
.setNumber(0)
.addAction(
R.drawable.ic_stat_notify_status,
context.resources.getString(R.string.stop),
stopPendingIntent
)
.build()
// the id of the foreground notification MUST NOT be 0
if (Build.VERSION.SDK_INT >= 33) {
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(1, notification)
}
return START_STICKY
}
private fun createNotificationIntent(context: Context): Intent {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create intent that takes the user to the notification channel settings
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
putExtra(Settings.EXTRA_CHANNEL_ID, CHANNEL_ID)
}
} else {
// For older versions of android, intent takes the user to the Status app
val packageName = context.packageName
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
val className = launchIntent?.component?.className
try {
val intentClass = Class.forName(className!!)
Intent(context, intentClass).apply {
addCategory(Intent.CATEGORY_BROWSABLE)
action = Intent.ACTION_VIEW
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
// Return an empty intent as fallback
Intent()
}
}
}
}
@@ -1,146 +0,0 @@
package im.status.ethereum.pushnotifications;
import android.app.Activity;
import android.app.Application;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationManagerCompat;
import java.security.SecureRandom;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import android.util.Log;
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery;
public class PushNotification extends ReactContextBaseJavaModule implements ActivityEventListener {
public static final String LOG_TAG = "PushNotification";
private final SecureRandom mRandomNumberGenerator = new SecureRandom();
private PushNotificationHelper pushNotificationHelper;
private PushNotificationJsDelivery delivery;
private ReactApplicationContext reactContext;
private boolean started;
public PushNotification(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
reactContext.addActivityEventListener(this);
Application applicationContext = (Application) reactContext.getApplicationContext();
IntentFilter intentFilter = new IntentFilter();
pushNotificationHelper = new PushNotificationHelper(applicationContext, intentFilter);
delivery = new PushNotificationJsDelivery(reactContext);
}
@Override
public String getName() {
return "PushNotification";
}
// removed @Override temporarily just to get it working on different versions of RN
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
onActivityResult(requestCode, resultCode, data);
}
// removed @Override temporarily just to get it working on different versions of RN
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Ignored, required to implement ActivityEventListener for RN 0.33
}
private Bundle getBundleFromIntent(Intent intent) {
Bundle bundle = null;
if (intent.hasExtra("notification")) {
bundle = intent.getBundleExtra("notification");
} else if (intent.hasExtra("google.message_id")) {
bundle = new Bundle();
bundle.putBundle("data", intent.getExtras());
}
if(null != bundle && !bundle.getBoolean("foreground", false) && !bundle.containsKey("userInteraction")) {
bundle.putBoolean("userInteraction", true);
}
return bundle;
}
@Override
public void onNewIntent(Intent intent) {
Bundle bundle = this.getBundleFromIntent(intent);
if (bundle != null) {
delivery.notifyNotification(bundle);
}
}
@ReactMethod
/**
* Creates a channel if it does not already exist. Returns whether the channel was created.
*/
public void createChannel(ReadableMap channelInfo, Callback callback) {
boolean created = pushNotificationHelper.createChannel(channelInfo);
if(callback != null) {
callback.invoke(created);
}
}
@ReactMethod
public void presentLocalNotification(ReadableMap details) {
if (!this.started) {
return;
}
Bundle bundle = Arguments.toBundle(details);
// If notification ID is not provided by the user, generate one at random
if (bundle.getString("id") == null) {
bundle.putString("id", String.valueOf(mRandomNumberGenerator.nextInt()));
}
pushNotificationHelper.sendToNotificationCentre(bundle);
}
@ReactMethod
public void clearMessageNotifications(String conversationId) {
if (this.started) {
pushNotificationHelper.clearMessageNotifications(conversationId);
}
}
@ReactMethod
public void clearAllMessageNotifications() {
pushNotificationHelper.clearAllMessageNotifications();
}
@ReactMethod
public void enableNotifications() {
this.started = true;
this.pushNotificationHelper.start();
}
@ReactMethod
public void disableNotifications() {
if (this.started) {
this.started = false;
this.pushNotificationHelper.stop();
}
}
}
@@ -0,0 +1,118 @@
package im.status.ethereum.pushnotifications
import android.app.Activity
import android.app.Application
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import androidx.annotation.NonNull
import androidx.core.app.NotificationManagerCompat
import com.facebook.react.bridge.*
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery
import java.security.SecureRandom
class PushNotification(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
companion object {
const val LOG_TAG = "PushNotification"
}
private val mRandomNumberGenerator = SecureRandom()
private val pushNotificationHelper: PushNotificationHelper
private val delivery: PushNotificationJsDelivery
private val reactContext: ReactApplicationContext = reactContext
private var started: Boolean = false
init {
reactContext.addActivityEventListener(this)
val applicationContext = reactContext.applicationContext as Application
val intentFilter = IntentFilter()
pushNotificationHelper = PushNotificationHelper(applicationContext, intentFilter)
delivery = PushNotificationJsDelivery(reactContext)
}
override fun getName(): String = "PushNotification"
// Primary implementation of ActivityEventListener interface
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
// Call the helper method with the correct parameter order
handleActivityResult(requestCode, resultCode, data)
}
// Private helper method for handling activity results
private fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// Ignored, required to implement ActivityEventListener for RN 0.33
}
private fun getBundleFromIntent(intent: Intent): Bundle? {
return when {
intent.hasExtra("notification") -> intent.getBundleExtra("notification")
intent.hasExtra("google.message_id") -> Bundle().apply {
putBundle("data", intent.extras)
}
else -> null
}?.apply {
if (!getBoolean("foreground", false) && !containsKey("userInteraction")) {
putBoolean("userInteraction", true)
}
}
}
override fun onNewIntent(intent: Intent) {
getBundleFromIntent(intent)?.let { bundle ->
delivery.notifyNotification(bundle)
}
}
@ReactMethod
fun createChannel(channelInfo: ReadableMap, callback: Callback?) {
val created = pushNotificationHelper.createChannel(channelInfo)
callback?.invoke(created)
}
@ReactMethod
fun presentLocalNotification(details: ReadableMap) {
if (!started) return
Arguments.toBundle(details)?.apply {
// If notification ID is not provided by the user, generate one at random
if (getString("id") == null) {
putString("id", mRandomNumberGenerator.nextInt().toString())
}
pushNotificationHelper.sendToNotificationCentre(this)
}
}
@ReactMethod
fun clearMessageNotifications(conversationId: String) {
if (started) {
pushNotificationHelper.clearMessageNotifications(conversationId)
}
}
@ReactMethod
fun clearAllMessageNotifications() {
pushNotificationHelper.clearAllMessageNotifications()
}
@ReactMethod
fun enableNotifications() {
started = true
pushNotificationHelper.start()
}
@ReactMethod
fun disableNotifications() {
if (started) {
started = false
pushNotificationHelper.stop()
}
}
}
@@ -1,93 +0,0 @@
package im.status.ethereum.pushnotifications;
import android.os.Build;
import android.app.Application;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationActions extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
String intentActionPrefix = context.getPackageName() + ".ACTION_";
Log.i(LOG_TAG, "PushNotificationBootEventReceiver loading scheduled notifications");
if (null == intent.getAction() || !intent.getAction().startsWith(intentActionPrefix)) {
return;
}
final Bundle bundle = intent.getBundleExtra("notification");
// Dismiss the notification popup.
NotificationManager manager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
int notificationID = Integer.parseInt(bundle.getString("id"));
boolean autoCancel = bundle.getBoolean("autoCancel", true);
if(autoCancel) {
if (bundle.containsKey("tag")) {
String tag = bundle.getString("tag");
manager.cancel(tag, notificationID);
} else {
manager.cancel(notificationID);
}
}
boolean invokeApp = bundle.getBoolean("invokeApp", true);
// Notify the action.
if(invokeApp) {
IntentFilter intentFilter = new IntentFilter();
PushNotificationHelper helper = new PushNotificationHelper((Application) context.getApplicationContext(), intentFilter);
helper.invokeApp(bundle);
} else {
// We need to run this on the main thread, as the React code assumes that is true.
// Namely, DevServerHelper constructs a Handler() without a Looper, which triggers:
// "Can't create handler inside thread that has not called Looper.prepare()"
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
// Construct and load our normal React JS code bundle
final ReactInstanceManager mReactInstanceManager = ((ReactApplication) context.getApplicationContext()).getReactNativeHost().getReactInstanceManager();
ReactContext context = mReactInstanceManager.getCurrentReactContext();
PushNotificationJsDelivery delivery = new PushNotificationJsDelivery(context);
// If it's constructed, send a notification
if (context != null) {
delivery.notifyNotificationAction(bundle);
} else {
// Otherwise wait for construction, then send the notification
mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
public void onReactContextInitialized(ReactContext context) {
PushNotificationJsDelivery delivery = new PushNotificationJsDelivery(context);
delivery.notifyNotificationAction(bundle);
mReactInstanceManager.removeReactInstanceEventListener(this);
}
});
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
// Construct it in the background
mReactInstanceManager.createReactContextInBackground();
}
}
}
});
}
}
}
@@ -0,0 +1,100 @@
package im.status.ethereum.pushnotifications
import android.os.Build
import android.app.Application
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.facebook.react.ReactApplication
import com.facebook.react.ReactInstanceManager
import com.facebook.react.bridge.ReactContext
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery
import im.status.ethereum.pushnotifications.PushNotification.Companion.LOG_TAG
/**
* BroadcastReceiver responsible for handling push notification actions.
* This class processes notification interactions and manages their lifecycle.
*/
class PushNotificationActions : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val intentActionPrefix = "${context.packageName}.ACTION_"
Log.i(LOG_TAG, "PushNotificationBootEventReceiver loading scheduled notifications")
if (intent.action == null || !intent.action!!.startsWith(intentActionPrefix)) {
return
}
val bundle = intent.getBundleExtra("notification") ?: return
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationID = bundle.getString("id")?.toInt() ?: return
// Dismiss the notification popup.
val autoCancel = bundle.getBoolean("autoCancel", true)
if (autoCancel) {
bundle.getString("tag")?.let { tag ->
manager.cancel(tag, notificationID)
} ?: run {
manager.cancel(notificationID)
}
}
// Notify the action.
val invokeApp = bundle.getBoolean("invokeApp", true)
if (invokeApp) {
val intentFilter = IntentFilter()
val helper = PushNotificationHelper(
context.applicationContext as Application,
intentFilter
)
helper.invokeApp(bundle)
} else {
// We need to run this on the main thread, as the React code assumes that is true.
// Namely, DevServerHelper constructs a Handler() without a Looper, which triggers:
// "Can't create handler inside thread that has not called Looper.prepare()"
Handler(Looper.getMainLooper()).post {
// Construct and load our normal React JS code bundle
val reactInstanceManager = (context.applicationContext as ReactApplication)
.reactNativeHost
.reactInstanceManager
val currentContext = reactInstanceManager.currentReactContext
when {
// If context exists, deliver notification immediately
currentContext != null -> {
PushNotificationJsDelivery(currentContext)
.notifyNotificationAction(bundle)
}
// Otherwise wait for construction, then send the notification
else -> {
val listener = object : ReactInstanceManager.ReactInstanceEventListener {
override fun onReactContextInitialized(reactContext: ReactContext) {
PushNotificationJsDelivery(reactContext)
.notifyNotificationAction(bundle)
reactInstanceManager.removeReactInstanceEventListener(this)
}
}
// Add the listener and create context if needed
reactInstanceManager.addReactInstanceEventListener(listener)
if (!reactInstanceManager.hasStartedCreatingInitialContext()) {
// Construct it in the background
reactInstanceManager.createReactContextInBackground()
}
}
}
}
}
}
}
@@ -1,890 +0,0 @@
// https://github.com/zo0r/react-native-push-notification/blob/bedc8f646aab67d594f291449fbfa24e07b64fe8/android/src/main/java/com/dieam/reactnativepushnotification/modules/RNPushNotificationHelper.java Copy-Paste with removed firebase
package im.status.ethereum.pushnotifications;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AlarmManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Base64;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.Person;
import androidx.core.graphics.drawable.IconCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import im.status.ethereum.module.R;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationHelper {
private Context context;
private static final long DEFAULT_VIBRATION = 300L;
private static final String CHANNEL_ID = "status-im-notifications";
public static final String ACTION_DELETE_NOTIFICATION = "im.status.ethereum.module.DELETE_NOTIFICATION";
public static final String ACTION_TAP_STOP = "im.status.ethereum.module.TAP_STOP";
final int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_CANCEL_CURRENT;
private NotificationManager notificationManager;
private HashMap<String, Person> persons;
private HashMap<String, StatusMessageGroup> messageGroups;
private IntentFilter intentFilter;
public PushNotificationHelper(Application context, IntentFilter intentFilter) {
this.context = context;
this.intentFilter = intentFilter;
this.persons = new HashMap<String, Person>();
this.messageGroups = new HashMap<String, StatusMessageGroup>();
this.notificationManager = context.getSystemService(NotificationManager.class);
this.registerBroadcastReceiver();
}
public Intent getOpenAppIntent(String deepLink) {
Class intentClass;
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
intentClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
Intent intent = new Intent(context, intentClass);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setAction(Intent.ACTION_VIEW);
//NOTE: you might wonder, why the heck did he decide to set these flags in particular. Well,
//the answer is a simple as it can get in the Android native development world. I noticed
//that my initial setup was opening the app but wasn't triggering any events on the js side, like
//the links do from the browser. So I compared both intents and noticed that the link from
//the browser produces an intent with the flag 0x14000000. I found out that it was the following
//flags in this link:
//https://stackoverflow.com/questions/52390129/android-intent-setflags-issue
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (deepLink != null) {
intent.setData(Uri.parse(deepLink));
}
return intent;
}
//NOTE: we use a dynamically created BroadcastReceiver here so that we can capture
//intents from notifications and act on them. For instance when tapping/dismissing
//a chat notification we want to clear the chat so that next messages don't show
//the messages that we have seen again
private final BroadcastReceiver notificationActionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == ACTION_DELETE_NOTIFICATION) {
String groupId = intent.getExtras().getString("im.status.ethereum.groupId");
if (groupId != null) {
cleanGroup(groupId);
}
}
if (intent.getAction() == ACTION_TAP_STOP) {
stop();
System.exit(0);
}
Log.e(LOG_TAG, "intent received: " + intent.getAction());
}
};
public void registerBroadcastReceiver() {
this.intentFilter.addAction(ACTION_DELETE_NOTIFICATION);
this.intentFilter.addAction(ACTION_TAP_STOP);
context.registerReceiver(notificationActionReceiver, this.intentFilter, Context.RECEIVER_EXPORTED);
Log.e(LOG_TAG, "Broadcast Receiver registered");
}
private NotificationManager notificationManager() {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
public void invokeApp(Bundle bundle) {
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
Class<?> activityClass = Class.forName(className);
Intent activityIntent = new Intent(context, activityClass);
if(bundle != null) {
activityIntent.putExtra("notification", bundle);
}
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
} catch(Exception e) {
Log.e(LOG_TAG, "Class not found", e);
return;
}
}
public void sendToNotificationCentre(final Bundle bundle) {
PushNotificationPicturesAggregator aggregator = new PushNotificationPicturesAggregator(new PushNotificationPicturesAggregator.Callback() {
public void call(Bitmap largeIconImage, Bitmap bigPictureImage) {
sendToNotificationCentreWithPicture(bundle, largeIconImage, bigPictureImage);
}
});
aggregator.setLargeIconUrl(context, bundle.getString("largeIconUrl"));
aggregator.setBigPictureUrl(context, bundle.getString("bigPictureUrl"));
}
public void handleConversation(final Bundle bundle) {
if (bundle.getBoolean("deleted")){
this.removeStatusMessage(bundle);
} else {
this.addStatusMessage(bundle);
}
}
public void clearMessageNotifications(String conversationId) {
notificationManager.cancel(conversationId.hashCode());
cleanGroup(conversationId);
}
public void clearAllMessageNotifications() {
notificationManager.cancelAll();
}
public void sendToNotificationCentreWithPicture(final Bundle bundle, Bitmap largeIconBitmap, Bitmap bigPictureBitmap) {
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
Log.e(LOG_TAG, "No activity class found for the notification");
return;
}
if (bundle.getBoolean("isConversation")) {
this.handleConversation(bundle);
return;
}
if (bundle.getString("message") == null) {
// this happens when a 'data' notification is received - we do not synthesize a local notification in this case
Log.d(LOG_TAG, "Ignore this message if you sent data-only notification. Cannot send to notification centre because there is no 'message' field in: " + bundle);
return;
}
String notificationIdString = bundle.getString("id");
if (notificationIdString == null) {
Log.e(LOG_TAG, "No notification ID specified for the notification");
return;
}
Resources res = context.getResources();
String packageName = context.getPackageName();
String title = bundle.getString("title");
if (title == null) {
ApplicationInfo appInfo = context.getApplicationInfo();
title = context.getPackageManager().getApplicationLabel(appInfo).toString();
}
int priority = NotificationCompat.PRIORITY_HIGH;
final String priorityString = bundle.getString("priority");
if (priorityString != null) {
switch (priorityString.toLowerCase()) {
case "max":
priority = NotificationCompat.PRIORITY_MAX;
break;
case "high":
priority = NotificationCompat.PRIORITY_HIGH;
break;
case "low":
priority = NotificationCompat.PRIORITY_LOW;
break;
case "min":
priority = NotificationCompat.PRIORITY_MIN;
break;
case "default":
priority = NotificationCompat.PRIORITY_DEFAULT;
break;
default:
priority = NotificationCompat.PRIORITY_HIGH;
}
}
int visibility = NotificationCompat.VISIBILITY_PRIVATE;
final String visibilityString = bundle.getString("visibility");
if (visibilityString != null) {
switch (visibilityString.toLowerCase()) {
case "private":
visibility = NotificationCompat.VISIBILITY_PRIVATE;
break;
case "public":
visibility = NotificationCompat.VISIBILITY_PUBLIC;
break;
case "secret":
visibility = NotificationCompat.VISIBILITY_SECRET;
break;
default:
visibility = NotificationCompat.VISIBILITY_PRIVATE;
}
}
String channel_id = bundle.getString("channelId");
if(channel_id == null) {
channel_id = this.getNotificationDefaultChannelId();
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(context, channel_id)
.setContentTitle(title)
.setTicker(bundle.getString("ticker"))
.setVisibility(visibility)
.setPriority(priority)
.setAutoCancel(bundle.getBoolean("autoCancel", true))
.setOnlyAlertOnce(bundle.getBoolean("onlyAlertOnce", false));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24 and higher
// Restore showing timestamp on Android 7+
// Source: https://developer.android.com/reference/android/app/Notification.Builder.html#setShowWhen(boolean)
boolean showWhen = bundle.getBoolean("showWhen", true);
notification.setShowWhen(showWhen);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // API 26 and higher
// Changing Default mode of notification
notification.setDefaults(Notification.DEFAULT_LIGHTS);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { // API 20 and higher
String group = bundle.getString("group");
if (group != null) {
notification.setGroup(group);
}
if (bundle.containsKey("groupSummary") || bundle.getBoolean("groupSummary")) {
notification.setGroupSummary(bundle.getBoolean("groupSummary"));
}
}
String numberString = bundle.getString("number");
if (numberString != null) {
notification.setNumber(Integer.parseInt(numberString));
}
// Small icon
int smallIconResId = 0;
String smallIcon = bundle.getString("smallIcon");
if (smallIcon != null && !smallIcon.isEmpty()) {
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
} else if(smallIcon == null) {
smallIconResId = res.getIdentifier("ic_stat_notify_status", "drawable", packageName);
}
if (smallIconResId == 0) {
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
if (smallIconResId == 0) {
smallIconResId = android.R.drawable.ic_dialog_info;
}
}
notification.setSmallIcon(smallIconResId);
// Large icon
if(largeIconBitmap == null) {
int largeIconResId = 0;
String largeIcon = bundle.getString("largeIcon");
if (largeIcon != null && !largeIcon.isEmpty()) {
largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
} else if(largeIcon == null) {
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
}
// Before Lolipop there was no large icon for notifications.
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
}
}
Bundle author = bundle.getBundle("notificationAuthor");
if (largeIconBitmap == null && author != null) {
String base64Image = author.getString("icon").split(",")[1];
byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
notification.setLargeIcon(getCircleBitmap(decodedByte));
} else if (largeIconBitmap != null){
notification.setLargeIcon(largeIconBitmap);
}
String message = bundle.getString("message");
notification.setContentText(message);
String subText = bundle.getString("subText");
if (subText != null) {
notification.setSubText(subText);
}
String bigText = bundle.getString("bigText");
if (bigText == null) {
bigText = message;
}
NotificationCompat.Style style;
if(bigPictureBitmap != null) {
style = new NotificationCompat.BigPictureStyle()
.bigPicture(bigPictureBitmap)
.setBigContentTitle(title)
.setSummaryText(message);
} else {
style = new NotificationCompat.BigTextStyle().bigText(bigText);
}
notification.setStyle(style);
Intent intent = new Intent(context, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
bundle.putBoolean("foreground", this.isApplicationInForeground());
bundle.putBoolean("userInteraction", true);
intent.putExtra("notification", bundle);
Uri soundUri = null;
if (!bundle.containsKey("playSound") || bundle.getBoolean("playSound")) {
String soundName = bundle.getString("soundName");
if (soundName == null) {
soundName = "default";
}
soundUri = getSoundUri(soundName);
notification.setSound(soundUri);
}
if (soundUri == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setSound(null);
}
if (bundle.containsKey("ongoing") || bundle.getBoolean("ongoing")) {
notification.setOngoing(bundle.getBoolean("ongoing"));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setCategory(NotificationCompat.CATEGORY_CALL);
String color = bundle.getString("color");
int defaultColor = -1;
if (color != null) {
notification.setColor(Color.parseColor(color));
} else if (defaultColor != -1) {
notification.setColor(defaultColor);
}
}
int notificationID = notificationIdString.hashCode();
notification.setContentIntent(createOnTapIntent(context, notificationID, bundle.getString("deepLink")))
.setDeleteIntent(createOnDismissedIntent(context, notificationID, bundle.getString("deepLink")));
NotificationManager notificationManager = notificationManager();
long[] vibratePattern = new long[]{0};
if (!bundle.containsKey("vibrate") || bundle.getBoolean("vibrate")) {
long vibration = bundle.containsKey("vibration") ? (long) bundle.getDouble("vibration") : DEFAULT_VIBRATION;
if (vibration == 0)
vibration = DEFAULT_VIBRATION;
vibratePattern = new long[]{0, vibration};
notification.setVibrate(vibratePattern);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Define the shortcutId
String shortcutId = bundle.getString("shortcutId");
if (shortcutId != null) {
notification.setShortcutId(shortcutId);
}
Long timeoutAfter = (long) bundle.getDouble("timeoutAfter");
if (timeoutAfter != null && timeoutAfter >= 0) {
notification.setTimeoutAfter(timeoutAfter);
}
}
Long when = (long) bundle.getDouble("when");
if (when != null && when >= 0) {
notification.setWhen(when);
}
notification.setUsesChronometer(bundle.getBoolean("usesChronometer", false));
notification.setChannelId(channel_id);
JSONArray actionsArray = null;
try {
actionsArray = bundle.getString("actions") != null ? new JSONArray(bundle.getString("actions")) : null;
} catch (JSONException e) {
Log.e(LOG_TAG, "Exception while converting actions to JSON object.", e);
}
if (actionsArray != null) {
// No icon for now. The icon value of 0 shows no icon.
int icon = 0;
// Add button for each actions.
for (int i = 0; i < actionsArray.length(); i++) {
String action;
try {
action = actionsArray.getString(i);
} catch (JSONException e) {
Log.e(LOG_TAG, "Exception while getting action from actionsArray.", e);
continue;
}
Intent actionIntent = new Intent(context, PushNotificationActions.class);
actionIntent.setAction(packageName + ".ACTION_" + i);
actionIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
// Add "action" for later identifying which button gets pressed.
bundle.putString("action", action);
actionIntent.putExtra("notification", bundle);
actionIntent.setPackage(packageName);
PendingIntent pendingActionIntent = PendingIntent.getBroadcast(context, notificationID, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notification.addAction(new NotificationCompat.Action.Builder(icon, action, pendingActionIntent).build());
} else {
notification.addAction(icon, action, pendingActionIntent);
}
}
}
if (!(this.isApplicationInForeground() && bundle.getBoolean("ignoreInForeground"))) {
Notification info = notification.build();
info.defaults |= Notification.DEFAULT_LIGHTS;
if (bundle.containsKey("tag")) {
String tag = bundle.getString("tag");
notificationManager.notify(tag, notificationID, info);
} else {
notificationManager.notify(notificationID, info);
}
}
} catch (Exception e) {
Log.e(LOG_TAG, "failed to send push notification", e);
}
}
private boolean checkOrCreateChannel(NotificationManager manager, String channel_id, String channel_name, String channel_description, Uri soundUri, int importance, long[] vibratePattern, boolean showBadge) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return false;
if (manager == null)
return false;
NotificationChannel channel = manager.getNotificationChannel(channel_id);
if (
channel == null && channel_name != null && channel_description != null ||
channel != null &&
(
channel_name != null && !channel.getName().equals(channel_name) ||
channel_description != null && !channel.getDescription().equals(channel_description)
)
) {
// If channel doesn't exist create a new one.
// If channel name or description is updated then update the existing channel.
channel = new NotificationChannel(channel_id, channel_name, importance);
channel.setDescription(channel_description);
channel.enableLights(true);
channel.enableVibration(vibratePattern != null);
channel.setVibrationPattern(vibratePattern);
channel.setShowBadge(showBadge);
if (soundUri != null) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build();
channel.setSound(soundUri, audioAttributes);
} else {
channel.setSound(null, null);
}
manager.createNotificationChannel(channel);
return true;
}
return false;
}
public boolean createChannel(ReadableMap channelInfo) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return false;
String channelId = channelInfo.getString("channelId");
String channelName = channelInfo.getString("channelName");
String channelDescription = channelInfo.hasKey("channelDescription") ? channelInfo.getString("channelDescription") : "";
String soundName = channelInfo.hasKey("soundName") ? channelInfo.getString("soundName") : "default";
int importance = channelInfo.hasKey("importance") ? channelInfo.getInt("importance") : 4;
boolean vibrate = channelInfo.hasKey("vibrate") && channelInfo.getBoolean("vibrate");
long[] vibratePattern = vibrate ? new long[] { DEFAULT_VIBRATION } : null;
boolean showBadge = channelInfo.hasKey("showBadge") && channelInfo.getBoolean("showBadge");
NotificationManager manager = notificationManager();
Uri soundUri = getSoundUri(soundName);
return checkOrCreateChannel(manager, channelId, channelName, channelDescription, soundUri, importance, vibratePattern, showBadge);
}
public String getNotificationDefaultChannelId() {
return this.CHANNEL_ID;
}
private Uri getSoundUri(String soundName) {
if (soundName == null || "default".equalsIgnoreCase(soundName)) {
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
} else {
// sound name can be full filename, or just the resource name.
// So the strings 'my_sound.mp3' AND 'my_sound' are accepted
// The reason is to make the iOS and android javascript interfaces compatible
int resId;
if (context.getResources().getIdentifier(soundName, "raw", context.getPackageName()) != 0) {
resId = context.getResources().getIdentifier(soundName, "raw", context.getPackageName());
} else {
soundName = soundName.substring(0, soundName.lastIndexOf('.'));
resId = context.getResources().getIdentifier(soundName, "raw", context.getPackageName());
}
return Uri.parse("android.resource://" + context.getPackageName() + "/" + resId);
}
}
public Class getMainActivityClass() {
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
public boolean isApplicationInForeground() {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> processInfos = activityManager.getRunningAppProcesses();
if (processInfos != null) {
for (RunningAppProcessInfo processInfo : processInfos) {
if (processInfo.processName.equals(context.getPackageName())
&& processInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&& processInfo.pkgList.length > 0) {
return true;
}
}
}
return false;
}
private Bitmap getCircleBitmap(Bitmap bitmap) {
final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(output);
final int color = Color.RED;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawOval(rectF, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
bitmap.recycle();
return output;
}
private Person getPerson(Bundle bundle) {
String name = bundle.getString("name");
return new Person.Builder().setName(name).build();
}
private StatusMessage createMessage(Bundle data) {
Person author = getPerson(data.getBundle("notificationAuthor"));
long timeStampLongValue = (long) data.getDouble("timestamp");
return new StatusMessage(data.getString("id"), author, timeStampLongValue, data.getString("message"));
}
private PendingIntent createGroupOnDismissedIntent(Context context, int notificationId, String groupId, String deepLink) {
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
intent.putExtra("im.status.ethereum.deepLink", deepLink);
intent.putExtra("im.status.ethereum.groupId", groupId);
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag);
}
private PendingIntent createGroupOnTapIntent(Context context, int notificationId, String groupId, String deepLink) {
Intent intent = getOpenAppIntent(deepLink);
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag);
}
private PendingIntent createOnTapIntent(Context context, int notificationId, String deepLink) {
Intent intent = getOpenAppIntent(deepLink);
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag);
}
private PendingIntent createOnDismissedIntent(Context context, int notificationId, String deepLink) {
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
intent.putExtra("im.status.ethereum.deepLink", deepLink);
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag);
}
public void removeStatusMessage(Bundle bundle) {
String conversationId = bundle.getString("conversationId");
StatusMessageGroup group = this.messageGroups.get(conversationId);
NotificationManager notificationManager = notificationManager();
if (group == null) {
group = new StatusMessageGroup(conversationId);
}
this.messageGroups.put(conversationId, group);
String id = bundle.getString("id");
group.removeMessage(id);
this.showMessages(bundle);
}
public StatusMessageGroup getMessageGroup(String conversationId) {
return this.messageGroups.get(conversationId);
}
public void addStatusMessage(Bundle bundle) {
String conversationId = bundle.getString("conversationId");
StatusMessageGroup group = this.messageGroups.get(conversationId);
NotificationManager notificationManager = notificationManager();
if (group == null) {
group = new StatusMessageGroup(conversationId);
}
this.messageGroups.put(conversationId, group);
group.addMessage(createMessage(bundle));
this.showMessages(bundle);
}
public void showMessages(Bundle bundle) {
String conversationId = bundle.getString("conversationId");
StatusMessageGroup group = this.messageGroups.get(conversationId);
NotificationManager notificationManager = notificationManager();
NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle("Me");
ArrayList<StatusMessage> messages = group.getMessages();
if (messages.size() == 0) {
notificationManager.cancel(conversationId.hashCode());
return;
}
for (int i = 0; i < messages.size(); i++) {
StatusMessage message = messages.get(i);
messagingStyle.addMessage(message.getText(),
message.getTimestamp(),
message.getAuthor());
}
if (bundle.getString("title") != null) {
messagingStyle.setConversationTitle(bundle.getString("title"));
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setStyle(messagingStyle)
.setGroup(conversationId)
.setOnlyAlertOnce(true)
.setGroupSummary(true)
.setContentIntent(createGroupOnTapIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
.setDeleteIntent(createGroupOnDismissedIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
.setNumber(messages.size() + 1)
.setAutoCancel(true);
if (Build.VERSION.SDK_INT >= 21) {
builder.setVibrate(new long[0]);
}
notificationManager.notify(conversationId.hashCode(), builder.build());
}
class StatusMessageGroup {
private ArrayList<StatusMessage> messages;
private String id;
StatusMessageGroup(String id) {
this.id = id;
this.messages = new ArrayList<StatusMessage>();
}
public ArrayList<StatusMessage> getMessages() {
return messages;
}
public void addMessage(StatusMessage message) {
this.messages.add(message);
}
public void removeMessage(String id) {
ArrayList<StatusMessage> newMessages = new ArrayList<StatusMessage>();
for(StatusMessage message: this.messages) {
if(!message.id.equals(id)) {
newMessages.add(message);
}
}
this.messages = newMessages;
}
public String getId() {
return this.id;
}
}
class StatusMessage {
public Person getAuthor() {
return author;
}
public long getTimestamp() {
return timestamp;
}
public String getText() {
return text;
}
private String id;
private Person author;
private long timestamp;
private String text;
StatusMessage(String id, Person author, long timestamp, String text) {
this.id = id;
this.author = author;
this.timestamp = timestamp;
this.text = text;
}
}
private void removeGroup(String groupId) {
this.messageGroups.remove(groupId);
}
private void cleanGroup(String groupId) {
removeGroup(groupId);
if (messageGroups.size() == 0) {
notificationManager.cancelAll();
}
}
public void start() {
Log.e(LOG_TAG, "Starting Foreground Service");
Intent serviceIntent = new Intent(context, ForegroundService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
this.registerBroadcastReceiver();
}
public void stop() {
Log.e(LOG_TAG, "Stopping Foreground Service");
//NOTE: we cancel all the current notifications, because the intents can't be used anymore
//since the broadcast receiver will be killed as well and won't be able to handle any intent
notificationManager.cancelAll();
Intent serviceIntent = new Intent(context, ForegroundService.class);
context.stopService(serviceIntent);
context.unregisterReceiver(notificationActionReceiver);
}
}
@@ -0,0 +1,750 @@
// https://github.com/zo0r/react-native-push-notification/blob/bedc8f646aab67d594f291449fbfa24e07b64fe8/android/src/main/
// java/com/dieam/reactnativepushnotification/modules/RNPushNotificationHelper.java Copy-Paste with removed firebase
package im.status.ethereum.pushnotifications
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.app.AlarmManager
import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.BroadcastReceiver
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.media.AudioAttributes
import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.service.notification.StatusBarNotification
import android.util.Log
import android.util.Base64
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.graphics.drawable.IconCompat
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import org.json.JSONArray
import org.json.JSONException
import im.status.ethereum.module.R
import im.status.ethereum.pushnotifications.PushNotification.Companion.LOG_TAG
class PushNotificationHelper(private val context: Application, private val intentFilter: IntentFilter) {
companion object {
private const val DEFAULT_VIBRATION: Long = 300L
private const val CHANNEL_ID = "status-im-notifications"
const val ACTION_DELETE_NOTIFICATION = "im.status.ethereum.module.DELETE_NOTIFICATION"
const val ACTION_TAP_STOP = "im.status.ethereum.module.TAP_STOP"
}
private val flag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_CANCEL_CURRENT
}
private val notificationManager: NotificationManager by lazy {
context.getSystemService(NotificationManager::class.java)
}
private val persons: MutableMap<String, Person> = mutableMapOf()
private val messageGroups: MutableMap<String, StatusMessageGroup> = mutableMapOf()
init {
registerBroadcastReceiver()
}
private val notificationActionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_DELETE_NOTIFICATION -> {
intent.extras?.getString("im.status.ethereum.groupId")?.let { groupId ->
cleanGroup(groupId)
}
}
ACTION_TAP_STOP -> {
stop()
System.exit(0)
}
}
Log.e(LOG_TAG, "intent received: ${intent.action}")
}
}
private fun registerBroadcastReceiver() {
intentFilter.apply {
addAction(ACTION_DELETE_NOTIFICATION)
addAction(ACTION_TAP_STOP)
}
context.registerReceiver(notificationActionReceiver, intentFilter, Context.RECEIVER_EXPORTED)
Log.e(LOG_TAG, "Broadcast Receiver registered")
}
fun getOpenAppIntent(deepLink: String?): Intent? {
val packageName = context.packageName
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
val className = launchIntent?.component?.className ?: return null
return try {
val intentClass = Class.forName(className)
Intent(context, intentClass).apply {
addCategory(Intent.CATEGORY_BROWSABLE)
action = Intent.ACTION_VIEW
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
deepLink?.let { data = Uri.parse(it) }
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
null
}
}
fun invokeApp(bundle: Bundle?) {
val packageName = context.packageName
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
val className = launchIntent?.component?.className ?: return
try {
val activityClass = Class.forName(className)
Intent(context, activityClass).apply {
bundle?.let { putExtra("notification", it) }
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(this)
}
} catch (e: ClassNotFoundException) {
Log.e(LOG_TAG, "Class not found", e)
}
}
fun getMainActivityClass(): Class<*>? {
val packageName = context.packageName
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
val className = launchIntent?.component?.className ?: return null
return try {
Class.forName(className)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
null
}
}
fun isApplicationInForeground(): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val processInfos = activityManager.runningAppProcesses ?: return false
return processInfos.any { processInfo ->
processInfo.processName == context.packageName &&
processInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
processInfo.pkgList.isNotEmpty()
}
}
private fun getCircleBitmap(bitmap: Bitmap): Bitmap {
val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint().apply {
isAntiAlias = true
color = Color.RED
}
val rect = Rect(0, 0, bitmap.width, bitmap.height)
val rectF = RectF(rect)
canvas.drawARGB(0, 0, 0, 0)
canvas.drawOval(rectF, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
bitmap.recycle()
return output
}
// Represents a message in a Status conversation
data class StatusMessage(
val id: String,
val author: Person,
val timestamp: Long,
val text: String
)
// Represents a group of messages in a Status conversation
inner class StatusMessageGroup(val id: String) {
private val _messages = mutableListOf<StatusMessage>()
val messages: List<StatusMessage> get() = _messages.toList()
fun addMessage(message: StatusMessage) {
_messages.add(message)
}
fun removeMessage(id: String) {
_messages.removeAll { it.id == id }
}
}
private fun getPerson(bundle: Bundle): Person {
return Person.Builder()
.setName(bundle.getString("name"))
.build()
}
private fun createMessage(data: Bundle): StatusMessage {
val author = getPerson(data.getBundle("notificationAuthor") ?: Bundle())
val timeStampLongValue = data.getDouble("timestamp").toLong()
return StatusMessage(
id = data.getString("id") ?: "",
author = author,
timestamp = timeStampLongValue,
text = data.getString("message") ?: ""
)
}
private fun createGroupOnDismissedIntent(
context: Context,
notificationId: Int,
groupId: String,
deepLink: String?
): PendingIntent {
return Intent(ACTION_DELETE_NOTIFICATION).apply {
putExtra("im.status.ethereum.deepLink", deepLink)
putExtra("im.status.ethereum.groupId", groupId)
}.let { intent ->
PendingIntent.getBroadcast(
context.applicationContext,
notificationId,
intent,
flag
)
}
}
private fun createGroupOnTapIntent(
context: Context,
notificationId: Int,
groupId: String,
deepLink: String?
): PendingIntent {
return getOpenAppIntent(deepLink)?.let { intent ->
PendingIntent.getActivity(
context.applicationContext,
notificationId,
intent,
flag
)
} ?: throw IllegalStateException("Could not create open app intent")
}
private fun createOnTapIntent(
context: Context,
notificationId: Int,
deepLink: String?
): PendingIntent {
return getOpenAppIntent(deepLink)?.let { intent ->
PendingIntent.getActivity(
context.applicationContext,
notificationId,
intent,
flag
)
} ?: throw IllegalStateException("Could not create open app intent")
}
private fun createOnDismissedIntent(
context: Context,
notificationId: Int,
deepLink: String?
): PendingIntent {
return Intent(ACTION_DELETE_NOTIFICATION).apply {
putExtra("im.status.ethereum.deepLink", deepLink)
}.let { intent ->
PendingIntent.getBroadcast(
context.applicationContext,
notificationId,
intent,
flag
)
}
}
fun removeStatusMessage(bundle: Bundle) {
val conversationId = bundle.getString("conversationId") ?: return
val group = messageGroups.getOrPut(conversationId) {
StatusMessageGroup(conversationId)
}
bundle.getString("id")?.let { id ->
group.removeMessage(id)
}
showMessages(bundle)
}
fun getMessageGroup(conversationId: String): StatusMessageGroup? {
return messageGroups[conversationId]
}
fun addStatusMessage(bundle: Bundle) {
val conversationId = bundle.getString("conversationId") ?: return
val group = messageGroups.getOrPut(conversationId) {
StatusMessageGroup(conversationId)
}
group.addMessage(createMessage(bundle))
showMessages(bundle)
}
fun showMessages(bundle: Bundle) {
val conversationId = bundle.getString("conversationId") ?: return
val group = messageGroups[conversationId] ?: return
val messagingStyle = NotificationCompat.MessagingStyle("Me")
// If there are no messages, cancel the notification and return
if (group.messages.isEmpty()) {
notificationManager.cancel(conversationId.hashCode())
return
}
group.messages.forEach { message ->
messagingStyle.addMessage(
message.text,
message.timestamp,
message.author
)
}
// Set conversation title if available
bundle.getString("title")?.let { title ->
messagingStyle.conversationTitle = title
}
val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply {
setSmallIcon(R.drawable.ic_stat_notify_status)
setPriority(NotificationCompat.PRIORITY_HIGH)
setCategory(NotificationCompat.CATEGORY_MESSAGE)
setStyle(messagingStyle)
setGroup(conversationId)
setOnlyAlertOnce(true)
setGroupSummary(true)
setContentIntent(createGroupOnTapIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
setDeleteIntent(createGroupOnDismissedIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
setNumber(group.messages.size + 1)
setAutoCancel(true)
// Set empty vibration pattern for Android 5.0 and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setVibrate(LongArray(0))
}
}
// Show the notification
notificationManager.notify(conversationId.hashCode(), builder.build())
}
fun checkOrCreateChannel(
manager: NotificationManager?,
channelId: String,
channelName: String?,
channelDescription: String?,
soundUri: Uri?,
importance: Int,
vibratePattern: LongArray?,
showBadge: Boolean
): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || manager == null) {
return false
}
val existingChannel = manager.getNotificationChannel(channelId)
val shouldCreateOrUpdate = when {
// Channel doesn't exist and we have required info
existingChannel == null && channelName != null && channelDescription != null -> true
// Channel exists but needs update
existingChannel != null && (
(channelName != null && existingChannel.name != channelName) ||
(channelDescription != null && existingChannel.description != channelDescription)
) -> true
else -> false
}
if (shouldCreateOrUpdate) {
NotificationChannel(channelId, channelName, importance).apply {
description = channelDescription
enableLights(true)
enableVibration(vibratePattern != null)
vibrationPattern = vibratePattern
setShowBadge(showBadge)
// Configure sound
if (soundUri != null) {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
setSound(soundUri, audioAttributes)
} else {
setSound(null, null)
}
manager.createNotificationChannel(this)
}
return true
}
return false
}
fun createChannel(channelInfo: ReadableMap): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false
}
return checkOrCreateChannel(
manager = notificationManager,
channelId = channelInfo.getString("channelId") ?: return false,
channelName = channelInfo.getString("channelName"),
channelDescription = if (channelInfo.hasKey("channelDescription")) {
channelInfo.getString("channelDescription")
} else "",
soundUri = getSoundUri(
if (channelInfo.hasKey("soundName")) {
channelInfo.getString("soundName")
} else "default"
),
importance = if (channelInfo.hasKey("importance")) {
channelInfo.getInt("importance")
} else 4,
vibratePattern = if (channelInfo.hasKey("vibrate") && channelInfo.getBoolean("vibrate")) {
longArrayOf(DEFAULT_VIBRATION)
} else null,
showBadge = channelInfo.hasKey("showBadge") && channelInfo.getBoolean("showBadge")
)
}
fun getNotificationDefaultChannelId(): String = CHANNEL_ID
private fun getSoundUri(soundName: String?): Uri {
return when {
soundName == null || soundName.equals("default", ignoreCase = true) -> {
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
}
else -> {
val resources = context.resources
val packageName = context.packageName
val resId = when {
// Try to find the resource as is
resources.getIdentifier(soundName, "raw", packageName) != 0 -> {
resources.getIdentifier(soundName, "raw", packageName)
}
// Try without file extension
else -> {
val nameWithoutExtension = soundName.substringBeforeLast('.')
resources.getIdentifier(nameWithoutExtension, "raw", packageName)
}
}
Uri.parse("android.resource://$packageName/$resId")
}
}
}
private fun removeGroup(groupId: String) {
messageGroups.remove(groupId)
}
private fun cleanGroup(groupId: String) {
removeGroup(groupId)
if (messageGroups.isEmpty()) {
notificationManager.cancelAll()
}
}
fun start() {
Log.e(LOG_TAG, "Starting Foreground Service")
Intent(context, ForegroundService::class.java).also { serviceIntent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
registerBroadcastReceiver()
}
fun stop() {
Log.e(LOG_TAG, "Stopping Foreground Service")
// Cancel all current notifications since their intents won't be valid
// after the broadcast receiver is killed
notificationManager.cancelAll()
Intent(context, ForegroundService::class.java).also { serviceIntent ->
context.stopService(serviceIntent)
}
context.unregisterReceiver(notificationActionReceiver)
}
fun sendToNotificationCentre(bundle: Bundle) {
val aggregator = PushNotificationPicturesAggregator { largeIconImage, bigPictureImage ->
sendToNotificationCentreWithPicture(bundle, largeIconImage, bigPictureImage)
}
aggregator.apply {
setLargeIconUrl(context, bundle.getString("largeIconUrl"))
setBigPictureUrl(context, bundle.getString("bigPictureUrl"))
}
}
fun handleConversation(bundle: Bundle) {
if (bundle.getBoolean("deleted")) {
removeStatusMessage(bundle)
} else {
addStatusMessage(bundle)
}
}
fun clearMessageNotifications(conversationId: String) {
notificationManager.cancel(conversationId.hashCode())
cleanGroup(conversationId)
}
fun clearAllMessageNotifications() {
notificationManager.cancelAll()
}
fun sendToNotificationCentreWithPicture(
bundle: Bundle,
largeIconBitmap: Bitmap?,
bigPictureBitmap: Bitmap?
) {
try {
val intentClass = getMainActivityClass() ?: run {
Log.e(LOG_TAG, "No activity class found for the notification")
return
}
if (bundle.getBoolean("isConversation")) {
handleConversation(bundle)
return
}
if (bundle.getString("message") == null) {
Log.d(LOG_TAG, "Ignore this message if you sent data-only notification. " +
"Cannot send to notification centre because there is no 'message' field in: $bundle")
return
}
val notificationIdString = bundle.getString("id") ?: run {
Log.e(LOG_TAG, "No notification ID specified for the notification")
return
}
val resources = context.resources
val packageName = context.packageName
val title = bundle.getString("title") ?: run {
val appInfo = context.applicationInfo
context.packageManager.getApplicationLabel(appInfo).toString()
}
val priority = when (bundle.getString("priority")?.toLowerCase()) {
"max" -> NotificationCompat.PRIORITY_MAX
"high" -> NotificationCompat.PRIORITY_HIGH
"low" -> NotificationCompat.PRIORITY_LOW
"min" -> NotificationCompat.PRIORITY_MIN
"default" -> NotificationCompat.PRIORITY_DEFAULT
else -> NotificationCompat.PRIORITY_HIGH
}
val visibility = when (bundle.getString("visibility")?.toLowerCase()) {
"private" -> NotificationCompat.VISIBILITY_PRIVATE
"public" -> NotificationCompat.VISIBILITY_PUBLIC
"secret" -> NotificationCompat.VISIBILITY_SECRET
else -> NotificationCompat.VISIBILITY_PRIVATE
}
val channelId = bundle.getString("channelId") ?: getNotificationDefaultChannelId()
NotificationCompat.Builder(context, channelId).apply {
setContentTitle(title)
setTicker(bundle.getString("ticker"))
setVisibility(visibility)
setPriority(priority)
setAutoCancel(bundle.getBoolean("autoCancel", true))
setOnlyAlertOnce(bundle.getBoolean("onlyAlertOnce", false))
// Configure notification based on Android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setShowWhen(bundle.getBoolean("showWhen", true))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setDefaults(Notification.DEFAULT_LIGHTS)
}
// Handle group settings for Android KitKat Watch and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
bundle.getString("group")?.let { group ->
setGroup(group)
}
if (bundle.containsKey("groupSummary") || bundle.getBoolean("groupSummary")) {
setGroupSummary(bundle.getBoolean("groupSummary"))
}
}
// Set notification number if provided
bundle.getString("number")?.toIntOrNull()?.let { number ->
setNumber(number)
}
val smallIconResId = findSmallIconResourceId(resources, packageName, bundle)
setSmallIcon(smallIconResId)
configureLargeIcon(this, largeIconBitmap, bundle, resources, packageName)
val message = bundle.getString("message") ?: ""
setContentText(message)
bundle.getString("subText")?.let { subText ->
setSubText(subText)
}
val bigText = bundle.getString("bigText") ?: message
val style = when {
bigPictureBitmap != null -> NotificationCompat.BigPictureStyle()
.bigPicture(bigPictureBitmap)
.setBigContentTitle(title)
.setSummaryText(message)
else -> NotificationCompat.BigTextStyle().bigText(bigText)
}
setStyle(style)
val notificationId = notificationIdString.hashCode()
setContentIntent(createOnTapIntent(context, notificationId, bundle.getString("deepLink")))
setDeleteIntent(createOnDismissedIntent(context, notificationId, bundle.getString("deepLink")))
configureSoundAndVibration(this, bundle)
setOngoing(bundle.getBoolean("ongoing", false))
// Handle color for Lollipop and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setCategory(NotificationCompat.CATEGORY_CALL)
bundle.getString("color")?.let { color ->
setColor(Color.parseColor(color))
}
}
// Finally show the notification if conditions are met
if (!(isApplicationInForeground() && bundle.getBoolean("ignoreInForeground"))) {
val notification = build()
notification.defaults = notification.defaults or Notification.DEFAULT_LIGHTS
if (bundle.containsKey("tag")) {
notificationManager.notify(bundle.getString("tag"), notificationId, notification)
} else {
notificationManager.notify(notificationId, notification)
}
}
}
} catch (e: Exception) {
Log.e(LOG_TAG, "Failed to send push notification", e)
}
}
private fun findSmallIconResourceId(resources: Resources, packageName: String, bundle: Bundle): Int {
val smallIcon = bundle.getString("smallIcon")
return when {
!smallIcon.isNullOrEmpty() ->
resources.getIdentifier(smallIcon, "mipmap", packageName)
smallIcon == null ->
resources.getIdentifier("ic_stat_notify_status", "drawable", packageName)
else -> {
val defaultIcon = resources.getIdentifier("ic_launcher", "mipmap", packageName)
defaultIcon.takeIf { it != 0 } ?: android.R.drawable.ic_dialog_info
}
}
}
private fun configureLargeIcon(
builder: NotificationCompat.Builder,
largeIconBitmap: Bitmap?,
bundle: Bundle,
resources: Resources,
packageName: String
) {
var finalLargeIcon = largeIconBitmap
if (finalLargeIcon == null) {
val largeIcon = bundle.getString("largeIcon")
val largeIconResId = when {
!largeIcon.isNullOrEmpty() ->
resources.getIdentifier(largeIcon, "mipmap", packageName)
largeIcon == null ->
resources.getIdentifier("ic_launcher", "mipmap", packageName)
else -> 0
}
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
finalLargeIcon = BitmapFactory.decodeResource(resources, largeIconResId)
}
}
bundle.getBundle("notificationAuthor")?.getString("icon")?.let { icon ->
val base64Image = icon.split(",")[1]
val decodedString = Base64.decode(base64Image, Base64.DEFAULT)
val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
builder.setLargeIcon(getCircleBitmap(decodedByte))
} ?: finalLargeIcon?.let { icon ->
builder.setLargeIcon(icon)
}
}
private fun configureSoundAndVibration(
builder: NotificationCompat.Builder,
bundle: Bundle
) {
var soundUri: Uri? = null
if (!bundle.containsKey("playSound") || bundle.getBoolean("playSound")) {
val soundName = bundle.getString("soundName") ?: "default"
soundUri = getSoundUri(soundName)
builder.setSound(soundUri)
}
if (soundUri == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setSound(null)
}
if (!bundle.containsKey("vibrate") || bundle.getBoolean("vibrate")) {
val vibration = if (bundle.containsKey("vibration")) {
bundle.getDouble("vibration").toLong()
} else DEFAULT_VIBRATION
val finalVibration = if (vibration == 0L) DEFAULT_VIBRATION else vibration
builder.setVibrate(longArrayOf(0, finalVibration))
}
}
}
@@ -1,86 +0,0 @@
package im.status.ethereum.pushnotifications;
import android.os.Build;
import android.app.Application;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Set;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
class PushNotificationJsDelivery {
private ReactContext reactContext;
PushNotificationJsDelivery(ReactContext context){
reactContext = context;
}
String convertJSON(Bundle bundle) {
try {
JSONObject json = convertJSONObject(bundle);
return json.toString();
} catch (JSONException e) {
return null;
}
}
// a Bundle is not a map, so we have to convert it explicitly
private JSONObject convertJSONObject(Bundle bundle) throws JSONException {
JSONObject json = new JSONObject();
Set<String> keys = bundle.keySet();
for (String key : keys) {
Object value = bundle.get(key);
if (value instanceof Bundle) {
json.put(key, convertJSONObject((Bundle)value));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
json.put(key, JSONObject.wrap(value));
} else {
json.put(key, value);
}
}
return json;
}
void notifyNotification(Bundle bundle) {
String bundleString = convertJSON(bundle);
WritableMap params = Arguments.createMap();
params.putString("dataJSON", bundleString);
sendEvent("remoteNotificationReceived", params);
}
void notifyNotificationAction(Bundle bundle) {
String bundleString = convertJSON(bundle);
WritableMap params = Arguments.createMap();
params.putString("dataJSON", bundleString);
sendEvent("notificationActionReceived", params);
}
void sendEvent(String eventName, Object params) {
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
@@ -0,0 +1,93 @@
package im.status.ethereum.pushnotifications
import android.os.Build
import android.app.Application
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import org.json.JSONException
import org.json.JSONObject
import im.status.ethereum.pushnotifications.PushNotification.Companion.LOG_TAG
class PushNotificationJsDelivery(
private val reactContext: ReactContext
) {
/**
* Converts a Bundle to its JSON string representation.
* Returns null if conversion fails.
*/
fun convertJSON(bundle: Bundle): String? {
return try {
convertJSONObject(bundle).toString()
} catch (e: JSONException) {
null
}
}
/**
* Converts a Bundle to a JSONObject, handling nested bundles and different value types.
* This explicit conversion is necessary because Bundle is not a standard map.
*/
@Throws(JSONException::class)
private fun convertJSONObject(bundle: Bundle): JSONObject {
return JSONObject().apply {
// Using Kotlin's for loop syntax and 'apply' scope function
bundle.keySet().forEach { key ->
bundle.get(key)?.let { value ->
when (value) {
is Bundle -> put(key, convertJSONObject(value))
else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
put(key, JSONObject.wrap(value))
} else {
put(key, value)
}
}
}
}
}
}
/**
* Notifies about a received remote notification by sending an event to JavaScript
*/
fun notifyNotification(bundle: Bundle) {
convertJSON(bundle)?.let { bundleString ->
Arguments.createMap().apply {
putString("dataJSON", bundleString)
sendEvent("remoteNotificationReceived", this)
}
}
}
/**
* Notifies about a notification action by sending an event to JavaScript
*/
fun notifyNotificationAction(bundle: Bundle) {
convertJSON(bundle)?.let { bundleString ->
Arguments.createMap().apply {
putString("dataJSON", bundleString)
sendEvent("notificationActionReceived", this)
}
}
}
/**
* Sends an event to JavaScript if there's an active Catalyst instance
*/
fun sendEvent(eventName: String, params: Any) {
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
}
}
@@ -1,27 +0,0 @@
package im.status.ethereum.pushnotifications;
import im.status.ethereum.pushnotifications.PushNotification;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.List;
public class PushNotificationPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(new PushNotification(reactContext));
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
@@ -0,0 +1,24 @@
package im.status.ethereum.pushnotifications
import im.status.ethereum.pushnotifications.PushNotification
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.JavaScriptModule
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class PushNotificationPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(PushNotification(reactContext))
}
// Note: This function is deprecated in newer versions of React Native
// but kept for backwards compatibility
fun createJSModules(): List<Class<out JavaScriptModule>> {
return emptyList()
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
@@ -1,136 +0,0 @@
package im.status.ethereum.pushnotifications;
import androidx.annotation.Nullable;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.common.Priority;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import android.util.Log;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import java.util.concurrent.atomic.AtomicInteger;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationPicturesAggregator {
interface Callback {
public void call(Bitmap largeIconImage, Bitmap bigPictureImage);
}
private AtomicInteger count = new AtomicInteger(0);
private Bitmap largeIconImage;
private Bitmap bigPictureImage;
private Callback callback;
public PushNotificationPicturesAggregator(Callback callback) {
this.callback = callback;
}
public void setBigPicture(Bitmap bitmap) {
this.bigPictureImage = bitmap;
this.finished();
}
public void setBigPictureUrl(Context context, String url) {
if(null == url) {
this.setBigPicture(null);
return;
}
Uri uri = null;
try {
uri = Uri.parse(url);
} catch(Exception ex) {
Log.e(LOG_TAG, "Failed to parse bigPictureUrl", ex);
this.setBigPicture(null);
return;
}
final PushNotificationPicturesAggregator aggregator = this;
this.downloadRequest(context, uri, new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
aggregator.setBigPicture(bitmap);
}
@Override
public void onFailureImpl(DataSource dataSource) {
aggregator.setBigPicture(null);
}
});
}
public void setLargeIcon(Bitmap bitmap) {
this.largeIconImage = bitmap;
this.finished();
}
public void setLargeIconUrl(Context context, String url) {
if(null == url) {
this.setLargeIcon(null);
return;
}
Uri uri = null;
try {
uri = Uri.parse(url);
} catch(Exception ex) {
Log.e(LOG_TAG, "Failed to parse largeIconUrl", ex);
this.setLargeIcon(null);
return;
}
final PushNotificationPicturesAggregator aggregator = this;
this.downloadRequest(context, uri, new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
aggregator.setLargeIcon(bitmap);
}
@Override
public void onFailureImpl(DataSource dataSource) {
aggregator.setLargeIcon(null);
}
});
}
private void downloadRequest(Context context, Uri uri, BaseBitmapDataSubscriber subscriber) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setRequestPriority(Priority.HIGH)
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
.build();
if(!Fresco.hasBeenInitialized()) {
Fresco.initialize(context);
}
DataSource<CloseableReference<CloseableImage>> dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context);
dataSource.subscribe(subscriber, CallerThreadExecutor.getInstance());
}
private void finished() {
synchronized(this.count) {
int val = this.count.incrementAndGet();
if(val >= 2 && this.callback != null) {
this.callback.call(this.largeIconImage, this.bigPictureImage);
}
}
}
}
@@ -0,0 +1,109 @@
package im.status.ethereum.pushnotifications
import androidx.annotation.Nullable
import com.facebook.common.executors.CallerThreadExecutor
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.common.Priority
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.request.ImageRequestBuilder
import android.util.Log
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import java.util.concurrent.atomic.AtomicInteger
import im.status.ethereum.pushnotifications.PushNotification.Companion.LOG_TAG
class PushNotificationPicturesAggregator(
private val callback: (Bitmap?, Bitmap?) -> Unit
) {
private val count = AtomicInteger(0)
private var largeIconImage: Bitmap? = null
private var bigPictureImage: Bitmap? = null
fun setBigPicture(bitmap: Bitmap?) {
bigPictureImage = bitmap
finished()
}
fun setBigPictureUrl(context: Context, url: String?) {
if (url == null) {
setBigPicture(null)
return
}
val uri = try {
Uri.parse(url)
} catch (ex: Exception) {
Log.e(LOG_TAG, "Failed to parse bigPictureUrl", ex)
setBigPicture(null)
return
}
downloadRequest(context, uri, object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(@Nullable bitmap: Bitmap?) {
setBigPicture(bitmap)
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
setBigPicture(null)
}
})
}
fun setLargeIcon(bitmap: Bitmap?) {
largeIconImage = bitmap
finished()
}
fun setLargeIconUrl(context: Context, url: String?) {
if (url == null) {
setLargeIcon(null)
return
}
val uri = try {
Uri.parse(url)
} catch (ex: Exception) {
Log.e(LOG_TAG, "Failed to parse largeIconUrl", ex)
setLargeIcon(null)
return
}
downloadRequest(context, uri, object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(@Nullable bitmap: Bitmap?) {
setLargeIcon(bitmap)
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
setLargeIcon(null)
}
})
}
private fun downloadRequest(context: Context, uri: Uri, subscriber: BaseBitmapDataSubscriber) {
val imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setRequestPriority(Priority.HIGH)
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
.build()
if (!Fresco.hasBeenInitialized()) {
Fresco.initialize(context)
}
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
dataSource.subscribe(subscriber, CallerThreadExecutor.getInstance())
}
private fun finished() {
synchronized(count) {
if (count.incrementAndGet() >= 2) {
callback(largeIconImage, bigPictureImage)
}
}
}
}
@@ -1,54 +0,0 @@
package im.status.ethereum.pushnotifications;
import org.junit.Test;
import static org.junit.Assert.*;
import android.app.Application;
import android.app.NotificationManager;
import android.os.Bundle;
import android.content.Intent;
import android.app.PendingIntent;
import android.content.IntentFilter;
import static org.mockito.Mockito.*;
import org.mockito.Spy;
public class PushNotificationHelperTest {
PushNotificationHelper helper;
@Test
public void testAddStatusMessage() {
final Application context = mock(Application.class);
final NotificationManager manager = mock(NotificationManager.class);
final IntentFilter filter = mock(IntentFilter.class);
when(context.getSystemService(NotificationManager.class)).thenReturn(manager);
// Create a spy for PushNotificationHelper
PushNotificationHelper realHelper = new PushNotificationHelper(context, filter);
helper = spy(realHelper);
Bundle bundle = mock(Bundle.class);
Bundle personBundle = mock(Bundle.class);
String conversationId = "conversation-id";
String deepLink = "deep-link";
when(bundle.getString("conversationId")).thenReturn(conversationId);
when(bundle.getString("id")).thenReturn("id");
when(bundle.getString("message")).thenReturn("message");
when(bundle.getDouble("timestamp")).thenReturn(100000.0);
when(bundle.getString("deepLink")).thenReturn(deepLink);
when(personBundle.getString("name")).thenReturn("name");
when(bundle.getBundle("notificationAuthor")).thenReturn(personBundle);
doNothing().when(helper).showMessages(bundle);
helper.addStatusMessage(bundle);
assertNotNull(helper.getMessageGroup(conversationId));
}
}
@@ -0,0 +1,49 @@
package im.status.ethereum.pushnotifications
import android.app.Application
import android.app.NotificationManager
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.mockito.Mockito.*
import org.mockito.Spy
class PushNotificationHelperTest {
private lateinit var helper: PushNotificationHelper
@Test
fun testAddStatusMessage() {
val context = mock(Application::class.java)
val manager = mock(NotificationManager::class.java)
val filter = mock(IntentFilter::class.java)
`when`(context.getSystemService(NotificationManager::class.java)).thenReturn(manager)
// Create a spy for PushNotificationHelper
val realHelper = PushNotificationHelper(context, filter)
helper = spy(realHelper)
val bundle = mock(Bundle::class.java)
val personBundle = mock(Bundle::class.java)
val conversationId = "conversation-id"
val deepLink = "deep-link"
`when`(bundle.getString("conversationId")).thenReturn(conversationId)
`when`(bundle.getString("id")).thenReturn("id")
`when`(bundle.getString("message")).thenReturn("message")
`when`(bundle.getDouble("timestamp")).thenReturn(100000.0)
`when`(bundle.getString("deepLink")).thenReturn(deepLink)
`when`(personBundle.getString("name")).thenReturn("name")
`when`(bundle.getBundle("notificationAuthor")).thenReturn(personBundle)
doNothing().`when`(helper).showMessages(bundle)
helper.addStatusMessage(bundle)
assertNotNull(helper.getMessageGroup(conversationId))
}
}
+3 -3
View File
@@ -20,8 +20,8 @@
"@react-native-community/netinfo": "^4.4.0",
"@react-native-community/push-notification-ios": "^1.4.1",
"@react-native-community/slider": "^3.0.0",
"@walletconnect/react-native-compat": "2.11.2",
"@walletconnect/web3wallet": "1.10.2",
"@walletconnect/react-native-compat": "2.17.3",
"@reown/walletkit": "^1.0.0",
"base-64": "^1.0.0",
"big-integer": "1.6.51",
"bignumber.js": "git+https://github.com/status-im/bignumber.js.git#refs/tags/v4.0.2-status",
@@ -66,7 +66,7 @@
"react-native-shake": "^3.3.1",
"react-native-share": "10.0.2",
"react-native-static-safe-area-insets": "^2.2.0",
"react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#refs/tags/v2.6.3",
"react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#refs/tags/v2.6.4",
"react-native-svg": "13.10.0",
"react-native-webview": "13.6.3",
"react-syntax-highlighter": "^15.5.0"
+4
View File
@@ -71,6 +71,10 @@
[callback]
(.addListener ^js event-emitter "keyCardOnDisconnected" callback))
(defn on-card-new-pairing
[callback]
(.addListener ^js event-emitter "keyCardNewPairing" callback))
(defn on-nfc-user-cancelled
[callback]
(.addListener ^js event-emitter "keyCardOnNFCUserCancelled" callback))
+7 -55
View File
@@ -19,8 +19,6 @@
[status-im.constants :as constants]
[status-im.contexts.chat.events :as chat.events]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.debounce :as debounce]
[utils.ens.core :as utils.ens]
[utils.ethereum.chain :as chain]
@@ -139,13 +137,11 @@
{:events [:browser/delete-bookmark]}
[{:keys [db] :as cofx}
url]
(let [old-bookmark (get-in db [:bookmarks/bookmarks url])
removed-bookmark (merge old-bookmark {:removed true})]
(rf/merge cofx
{:db (update db :bookmarks/bookmarks dissoc url)
:json-rpc/call [{:method "wakuext_removeBookmark"
:params [url]
:on-success #()}]})))
(rf/merge cofx
{:db (update db :bookmarks/bookmarks dissoc url)
:json-rpc/call [{:method "wakuext_removeBookmark"
:params [url]
:on-success #()}]}))
(defn can-go-back?
[{:keys [history-index]}]
@@ -357,43 +353,11 @@
:id (int id)
:result result}}})
(defn utf8-to-hex
[s]
(let [hex (native-module/utf8-to-hex (str s))]
(if (empty? hex)
nil
hex)))
(defn normalize-message
"NOTE (andrey) there is no spec for this, so this implementation just to be compatible with MM"
[message]
(if (string/starts-with? message "0x")
message
(utf8-to-hex message)))
(defn normalize-sign-message-params
"NOTE (andrey) we need this function, because params may be mixed up"
[params typed?]
(let [[first-param second-param] params]
(when (and (string? first-param) (string? second-param))
(cond
(address/address? first-param)
[first-param (if typed? second-param (normalize-message second-param))]
(address/address? second-param)
[second-param (if typed? first-param (normalize-message first-param))]))))
(rf/defn send-to-bridge
{:events [:browser.callback/call-rpc]}
[_ message]
{:browser/send-to-bridge message})
(defn web3-sign-message?
[method]
(#{constants/web3-sign-typed-data constants/web3-sign-typed-data-v3 constants/web3-sign-typed-data-v4
constants/web3-personal-sign
constants/web3-eth-sign constants/web3-keycard-sign-typed-data}
method))
(rf/defn handle-no-permissions
[cofx {:keys [method id]} message-id]
(if (= method "eth_accounts")
@@ -484,18 +448,6 @@
(when (and message webview)
(.injectJavaScript webview msg)))))
(re-frame/reg-fx
:browser/call-rpc
(fn [[payload callback]]
(native-module/call-rpc
(types/clj->json payload)
(fn [response]
(if (= "" response)
(do
(log/warn :web3-response-error)
(callback "web3-response-error" nil))
(callback nil (.parse js/JSON response)))))))
(re-frame/reg-fx
:browser/show-browser-selection
(fn [link]
@@ -548,8 +500,8 @@
(rf/defn lock-pressed
{:events [:browser.ui/lock-pressed]}
[cofx secure?]
(update-browser-option cofx :show-tooltip (if secure? :secure :not-secure)))
[cofx is-secure?]
(update-browser-option cofx :show-tooltip (if is-secure? :secure :not-secure)))
(rf/defn close-tooltip-pressed
{:events [:browser.ui/close-tooltip-pressed]}
-120
View File
@@ -1,120 +0,0 @@
(ns legacy.status-im.browser.core-test
(:require
[utils.url :as url]))
(defn has-wrong-properties?
[result dapp-url expected-browser]
(let [browser (get-in result [:db :browser/browsers dapp-url])]
(reduce (fn [acc k]
(if (= (k browser)
(k expected-browser))
acc
(conj acc [k (str "was expecting " (k expected-browser) " got " (k browser))])))
nil
(keys expected-browser))))
(defn get-dapp-id
[result dapp-url]
(some #(when (= (url/normalize-and-decode-url dapp-url) (first (:history %))) (:browser-id %))
(vals (get-in result [:db :browser/browsers]))))
#_(deftest browser-test
(let [dapp1-url "cryptokitties.co"
dapp2-url "http://test2.com"]
(testing "user opens a dapp"
(let [result-open (browser/open-url {:db {} :now 1} dapp1-url)
dapp1-id (get-dapp-id result-open dapp1-url)]
(is (= dapp1-id (get-in result-open [:db :browser/options :browser-id]))
"browser-id should be dapp1-url")
(is (not (has-wrong-properties? result-open
dapp1-id
{:browser-id dapp1-id
:history-index 0
:history ["https://cryptokitties.co"]
:dapp? false
:name (i18n/label :t/browser)}))
"some properties of the browser are not correct")
(testing "then a second dapp"
(let [result-open-2 (browser/open-url {:db (:db result-open)
:now 2}
dapp2-url)
dapp2-id (get-dapp-id result-open-2 dapp2-url)]
(is (= dapp2-id (get-in result-open-2 [:db :browser/options :browser-id]))
"browser-id should be dapp2 host")
(is (not (has-wrong-properties? result-open-2
dapp2-id
{:browser-id dapp2-id
:history-index 0
:history ["http://test2.com"]
:dapp? false}))
"some properties of the browser are not correct")
(testing "then removes the second dapp"
(let [result-remove-2 (browser/remove-browser {:db (:db result-open-2)} dapp2-id)]
(is (= #{dapp1-id}
(set (keys (get-in result-remove-2 [:db :browser/browsers]))))
"the second dapp shouldn't be in the browser list anymore")))))
(testing "then opens the dapp again"
(let [result-open-existing (browser/open-existing-browser {:db (:db result-open)
:now 2}
dapp1-id)
dapp1-url2 (str "https://" dapp1-url "/nav2")]
(is (not (has-wrong-properties? result-open-existing
dapp1-id
{:browser-id dapp1-id
:history-index 0
:history ["https://cryptokitties.co"]
:dapp? false
:name (i18n/label :t/browser)}))
"some properties of the browser are not correct")
(is (nil? (browser/navigate-to-next-page result-open-existing))
"nothing should happen if user tries to navigate to next page")
(is (nil? (browser/navigate-to-previous-page result-open-existing))
"nothing should happen if user tries to navigate to previous page")
(testing "then navigates to a new url in the dapp"
(let [result-navigate (browser/navigation-state-changed
{:db (:db result-open-existing)
:now 4}
(clj->js {"url" dapp1-url2
"loading" false})
false)]
(is (not (has-wrong-properties? result-navigate
dapp1-id
{:browser-id dapp1-id
:history-index 1
:history ["https://cryptokitties.co" dapp1-url2]
:dapp? false
:name (i18n/label :t/browser)}))
"some properties of the browser are not correct")
(testing "then navigates to previous page"
(let [result-previous (browser/navigate-to-previous-page {:db (:db result-navigate)
:now 5})]
(is
(not (has-wrong-properties? result-previous
dapp1-id
{:browser-id dapp1-id
:history-index 0
:history ["https://cryptokitties.co"
dapp1-url2]
:dapp? false
:name (i18n/label :t/browser)}))
"some properties of the browser are not correct")
(testing "then navigates to next page")
(let [result-next (browser/navigate-to-next-page {:db (:db result-previous)
:now 6})]
(is (not
(has-wrong-properties? result-next
dapp1-id
{:browser-id dapp1-id
:history-index 1
:history ["https://cryptokitties.co"
dapp1-url2]
:dapp? false
:name (i18n/label :t/browser)}))
"some properties of the browser are not correct"))))))))))))
@@ -22,7 +22,7 @@
:icon :main-icons/wallet}})
(rf/defn permission-yield-control
[{:keys [db] :as cofx} dapp-name permission message-id params]
[cofx dapp-name permission message-id]
(cond
(= permission constants/dapp-permission-qr-code)
(rf/merge (assoc-in cofx [:db :browser/options :yielding-control?] true)
@@ -33,7 +33,7 @@
:message-id message-id}}))))
(rf/defn permission-show-permission
[{:keys [db] :as cofx} dapp-name permission message-id yield-control?]
[{:keys [db]} dapp-name permission message-id yield-control?]
{:db (assoc-in db
[:browser/options :show-permission]
{:requested-permission permission
@@ -51,7 +51,7 @@
(rf/defn send-response-to-bridge
"Send response to the bridge. If the permission is allowed, send data associated
with the permission"
[{:keys [db] :as cofx} permission message-id allowed? data]
[_cofx permission message-id allowed? data]
{:browser/send-to-bridge (cond-> {:type constants/api-response
:isAllowed allowed?
:permission permission
@@ -108,13 +108,13 @@
(let [pending-permissions (get-in db [:browser/options :pending-permissions])
next-permission (last pending-permissions)
new-cofx (update-in cofx [:db :browser/options :pending-permissions] butlast)]
(when-let [{:keys [yield-control? permission message-id allowed? params]} next-permission]
(when-let [{:keys [yield-control? permission message-id allowed?]} next-permission]
(if (and yield-control? allowed?)
(permission-yield-control new-cofx dapp-name permission message-id params)
(permission-yield-control new-cofx dapp-name permission message-id)
(permission-show-permission new-cofx dapp-name permission message-id yield-control?)))))))
(rf/defn send-response-and-process-next-permission
[{:keys [db] :as cofx} dapp-name requested-permission message-id]
[cofx dapp-name requested-permission message-id]
(rf/merge cofx
(send-response-to-bridge requested-permission
message-id
@@ -126,12 +126,12 @@
"Add permission to set of allowed permission and process next permission"
{:events [:browser.permissions.ui/dapp-permission-allowed]}
[{:keys [db] :as cofx}]
(let [{:keys [requested-permission message-id dapp-name yield-control? params]}
(let [{:keys [requested-permission message-id dapp-name yield-control?]}
(get-in db [:browser/options :show-permission])]
(rf/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(update-dapp-permissions dapp-name requested-permission true)
(if yield-control?
(permission-yield-control dapp-name requested-permission message-id params)
(permission-yield-control dapp-name requested-permission message-id)
(send-response-and-process-next-permission dapp-name requested-permission message-id)))))
(rf/defn deny-permission
@@ -1,90 +0,0 @@
(ns legacy.status-im.browser.permissions-test)
#_(deftest permissions-test
(let [dapp-name "test.com"
dapp-name2 "test2.org"
cofx {:db (assoc-in (:db (browser/open-url {:db {}} dapp-name))
[:profile/profile :public-key]
"public-key")}
dapp-id (core.tests/get-dapp-id cofx dapp-name)]
(testing "dapps permissions are initialized"
(is (zero? (count (get-in cofx [:db :dapps/permissions]))))
(is (= dapp-id (get-in cofx [:db :browser/options :browser-id]))))
(testing "receiving an unsupported permission"
(let [result-ask (browser/process-bridge-message cofx
(types/clj->json
{:type "api-request"
:host dapp-name
:messageId 0
:permission "FAKE_PERMISSION"}))]
(is (not (get-in result-ask [:browser/send-to-bridge :isAllowed])))))
(testing "receiving a supported permission"
(let [result-ask (browser/process-bridge-message cofx
(types/clj->json {:type "api-request"
:host dapp-name
:messageId 1
:permission "contact-code"}))]
(is (= (get-in result-ask [:db :browser/options :show-permission])
{:requested-permission "contact-code"
:dapp-name "test.com"
:message-id 1
:yield-control? nil}))
(is (zero? (count (get-in result-ask [:db :dapps/permissions]))))
(testing "then user accepts the supported permission"
(let [accept-result (permissions/allow-permission {:db (:db result-ask)})]
(is (= (get accept-result :browser/send-to-bridge)
{:type "api-response"
:messageId 1
:isAllowed true
:data "public-key"
:permission "contact-code"})
"the data should have been sent to the bridge")
(is (= (get-in accept-result [:db :dapps/permissions])
{"test.com" {:dapp "test.com" :permissions ["contact-code"]}})
"the dapp should now have CONTACT_CODE permission")
(testing "then dapp asks for permission again"
(let [result-ask-again (browser/process-bridge-message {:db (:db accept-result)}
(types/clj->json
{:type "api-request"
:host dapp-name
:messageId 2
:permission "contact-code"}))]
(is (= (get result-ask-again :browser/send-to-bridge)
{:type "api-response"
:isAllowed true
:messageId 2
:data "public-key"
:permission "contact-code"})
"the response should be immediatly sent to the bridge")))
(testing "then user switch to another dapp that asks for permissions"
(let [new-dapp (browser/open-url {:db (:db accept-result)} dapp-name2)
result-ask2 (browser/process-bridge-message {:db (:db new-dapp)}
(types/clj->json
{:type "api-request"
:host dapp-name2
:messageId 3
:permission "contact-code"}))]
(is (= (get-in result-ask2 [:db :dapps/permissions])
{"test.com" {:dapp "test.com" :permissions ["contact-code"]}})
"there should only be permissions for dapp-name at that point")
(is (nil? (get result-ask2 :browser/send-to-bridge))
"no message should be sent to the bridge")
(testing "then user accepts permission for dapp-name2"
(let [accept-result2 (permissions/allow-permission {:db (:db result-ask2)})]
(is (= (get-in accept-result2 [:db :dapps/permissions])
{"test.com" {:dapp "test.com" :permissions ["contact-code"]}
"test2.org" {:dapp "test2.org" :permissions ["contact-code"]}})
"there should be permissions for both dapps now")
(is (= (get accept-result2 :browser/send-to-bridge)
{:type "api-response"
:isAllowed true
:messageId 3
:data "public-key"
:permission "contact-code"})
"the response should be sent to the bridge")))))))))))
@@ -156,7 +156,7 @@
(rf/defn select-mention
{:events [:chat.ui/select-mention]}
[{:keys [db]} {:keys [primary-name searched-text match public-key] :as user}]
[{:keys [db]} {:keys [primary-name searched-text match public-key] :as _user}]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
method "wakuext_chatMentionSelectMention"
@@ -120,7 +120,7 @@
{:db (assoc-in db [:messages chat-id message-id :outgoing-status] status)}))
(rf/defn handle-removed-messages
[{:keys [db] :as cofx} removed-messages]
[cofx removed-messages]
(let [mark-as-deleted-fx (->> removed-messages
(map #(assoc %
:message-id (:messageId %)
@@ -136,7 +136,7 @@
[message-id]
nil)))
removed-messages)
remove-messages-fx (fn [{:keys [db]}]
remove-messages-fx (fn [_cofx]
{:dispatch [:activity-center.notifications/fetch-unread-count]})]
(apply rf/merge
cofx
+3 -3
View File
@@ -43,7 +43,7 @@
(rf/defn member-ban
{:events [::member-ban]}
[cofx community-id public-key]
[_cofx community-id public-key]
{:json-rpc/call [{:method "wakuext_banUserFromCommunity"
:params [{:communityId community-id
:user public-key}]
@@ -63,7 +63,7 @@
(rf/defn member-kick
{:events [::member-kick]}
[cofx community-id public-key]
[_cofx community-id public-key]
{:json-rpc/call [{:method "wakuext_removeUserFromCommunity"
:params [community-id public-key]
:js-response true
@@ -97,7 +97,7 @@
(rf/defn add-role-to-member
{:events [:community.member/add-role]}
[cofx community-id public-key role-id]
[_cofx community-id public-key role-id]
{:json-rpc/call [{:method "wakuext_addRoleToMember"
:params [{:communityId community-id
:user public-key
@@ -4,12 +4,6 @@
[status-im.constants :as constants]
[utils.transforms :as transforms]))
(defn <-revealed-accounts-rpc
[accounts]
(mapv
#(set/rename-keys % {:isAirdropAddress :airdrop-address?})
(js->clj accounts :keywordize-keys true)))
(defn <-request-to-join-community-rpc
[r]
(set/rename-keys r
@@ -16,7 +16,7 @@
:community-id :communityId
:clock-value :clock})))
(defn- <-status-link-previews-rpc
(defn <-status-link-previews-rpc
[preview]
(-> preview
(update :community
@@ -40,7 +40,7 @@
(update-in [:community :banner] set/rename-keys {:data-uri :dataUri})
(update-in [:community :icon] set/rename-keys {:data-uri :dataUri})))
(defn- <-link-preview-rpc
(defn <-link-preview-rpc
[preview]
(-> preview
(update :thumbnail set/rename-keys {:dataUri :data-uri})
@@ -160,17 +160,17 @@
:on-error #(log/error "failed to delete messages by chat-id" % chat-id)}]})
(rf/defn delete-message
[cofx id]
[_cofx id]
(delete-message-rpc id))
(rf/defn delete-messages-from
[cofx author]
[_cofx author]
(delete-messages-from-rpc author))
(rf/defn mark-messages-seen
[cofx chat-id ids on-success]
[_cofx chat-id ids on-success]
(mark-seen-rpc chat-id ids on-success))
(rf/defn delete-messages-by-chat-id
[cofx chat-id]
[_cofx chat-id]
(delete-messages-by-chat-id-rpc chat-id))
@@ -29,7 +29,7 @@
:on-error on-error}]})
(rf/defn send-pin-message
[cofx pin-message]
[_cofx pin-message]
{:json-rpc/call [{:method "wakuext_sendPinMessage"
:params [(messages/->rpc pin-message)]
:js-response true
+4 -11
View File
@@ -62,7 +62,7 @@
(rf/defn update-ens-tx-state-and-redirect
{:events [:update-ens-tx-state-and-redirect]}
[{:keys [db] :as cofx} new-state username custom-domain? tx-hash]
[cofx new-state username custom-domain? tx-hash]
(rf/merge cofx
(update-ens-tx-state new-state username custom-domain? tx-hash)
(redirect-to-ens-summary)))
@@ -178,13 +178,6 @@
:else
(re-frame/dispatch [::name-resolved username :taken]))))
(defn registration-cost
[chain-id]
(case chain-id
3 50
5 10
1 10))
(rf/defn register-name
{:events [::register-name-pressed]}
[{:keys [db]} address]
@@ -239,14 +232,14 @@
(let [{:keys [custom-domain?]} (:ens/registration db)
chain-id (chain/chain-id db)
usernames (into #{} (keys (get-in db [:ens/names chain-id])))
state (state custom-domain? username usernames)]
next-state (state custom-domain? username usernames)]
(reset! resolve-last-id (random/id))
(merge
{:db (update db
:ens/registration assoc
:username username
:state state)}
(when (= state :searching)
:state next-state)}
(when (= next-state :searching)
(let [{:profile/keys [profile]} db
{:keys [public-key]} profile
addresses (addresses-without-watch db)
+1 -23
View File
@@ -1,28 +1,6 @@
(ns legacy.status-im.ethereum.macros
(:require
[clojure.java.io :as io]
[clojure.string :as string]))
(defn token-icon-path
[path]
(fn [el]
(let [el (string/replace el ".png" "")
s (str path el ".png")
s-js (str "." s)]
(when (.exists (io/file s))
[el `(js/require ~s-js)]))))
(defmacro resolve-icons
"In react-native arguments to require must be static strings.
Resolve all icons at compilation time so no variable is used."
[network]
(let [path (str "./resources/images/tokens/" (name network) "/")
files (->> (io/file path)
file-seq
(filter #(string/ends-with? % "png"))
(map #(first (string/split (.getName %) #"@")))
distinct)]
(into {} (map (token-icon-path path) files))))
[clojure.java.io :as io]))
(defn network->icon
[network]
+1 -50
View File
@@ -1,8 +1,7 @@
(ns legacy.status-im.ethereum.tokens
(:require
[clojure.string :as string]
[utils.ethereum.chain :as chain])
(:require-macros [legacy.status-im.ethereum.macros :as ethereum.macros :refer [resolve-icons]]))
(:require-macros [legacy.status-im.ethereum.macros :as ethereum.macros]))
(def default-native-currency
(memoize
@@ -13,8 +12,6 @@
:decimals 18
:icon {:source (js/require "../resources/images/tokens/default-token.png")}})))
(def snt-icon-source (js/require "../resources/images/tokens/mainnet/SNT.png"))
(def all-native-currencies
(ethereum.macros/resolve-native-currency-icons
{:mainnet {:name "Ether"
@@ -38,54 +35,8 @@
:symbol-display :BNBtest
:decimals 18}}))
(def native-currency-symbols
(set (map #(-> % val :symbol) all-native-currencies)))
(defn native-currency
[{sym :symbol :as current-network}]
(let [chain (chain/network->chain-keyword current-network)]
(get all-native-currencies chain (default-native-currency sym))))
(defn ethereum?
[sym]
(native-currency-symbols sym))
(def token-icons
{:mainnet (resolve-icons :mainnet)
:xdai (resolve-icons :xdai)
:custom []})
(def default-token (js/require "../resources/images/tokens/default-token.png"))
(defn update-icon
[network token]
(-> token
(assoc-in [:icon :source] (get-in token-icons [network (name (:symbol token))] default-token))
(update :address string/lower-case)))
(defn nfts-for
[all-tokens]
(filter :nft? (vals all-tokens)))
(defn sorted-tokens-for
[all-tokens]
(->> (vals all-tokens)
(filter #(not (:hidden? %)))
(sort #(compare (string/lower-case (:name %1))
(string/lower-case (:name %2))))))
(defn symbol->token
[all-tokens sym]
(some #(when (= sym (:symbol %)) %) (vals all-tokens)))
(defn address->token
[all-tokens address]
(get all-tokens (string/lower-case address)))
(defn asset-for
[all-tokens current-network sym]
(let [native-coin (native-currency current-network)]
(if (or (= (:symbol-display native-coin) sym)
(= (:symbol native-coin) sym))
native-coin
(symbol->token all-tokens sym))))
@@ -7,37 +7,9 @@
[re-frame.core :as re-frame]
[status-im.common.json-rpc.events :as json-rpc]
[taoensso.timbre :as log]
[utils.ethereum.chain :as chain]
[utils.ethereum.eip.eip55 :as eip55]
[utils.re-frame :as rf]))
(def confirmations-count-threshold 12)
(def etherscan-supported?
#{(chain/chain-keyword->chain-id :mainnet)
(chain/chain-keyword->chain-id :sepolia)})
(def binance-mainnet-chain-id (chain/chain-keyword->chain-id :bsc))
(def binance-testnet-chain-id (chain/chain-keyword->chain-id :bsc-testnet))
(def network->subdomain {11155111 "sepolia"})
(defn get-transaction-details-url
[chain-id tx-hash]
{:pre [(number? chain-id) (string? tx-hash)]
:post [(or (nil? %) (string? %))]}
(cond
(etherscan-supported? chain-id)
(let [network-subdomain (when-let [subdomain (network->subdomain chain-id)]
(str subdomain "."))]
(str "https://" network-subdomain "etherscan.io/tx/" tx-hash))
(= chain-id binance-mainnet-chain-id)
(str "https://bscscan.com/tx/" tx-hash)
(= chain-id binance-testnet-chain-id)
(str "https://testnet.bscscan.com/tx/" tx-hash)))
(def default-erc20-token
{:symbol :ERC20
:decimals 18
@@ -121,16 +93,16 @@
(rf/defn check-transaction
"Check if the transaction has been triggered and applies the effects returned
by `on-trigger` if that is the case"
[{:keys [db] :as cofx} {:keys [hash] :as transaction}]
[{:keys [db] :as cofx} {tx-hash :hash :as transaction}]
(when-let [watch-params
(get-in db [:ethereum/watched-transactions hash])]
(get-in db [:ethereum/watched-transactions tx-hash])]
(let [{:keys [trigger-fn on-trigger]} watch-params]
(when (trigger-fn db transaction)
(rf/merge cofx
{:db (update db
:ethereum/watched-transactions
dissoc
hash)}
tx-hash)}
(on-trigger transaction))))))
(rf/defn check-watched-transactions
@@ -152,14 +124,14 @@
"We determine a unique id for the transfer before adding it because some
transaction can contain multiple transfers and they would overwrite each other
in the transfer map if identified by hash"
[{:keys [db] :as cofx} {:keys [hash id address] :as transfer}]
(let [transfer-by-hash (get-in db [:wallet-legacy :accounts address :transactions hash])]
[{:keys [db] :as cofx} {tx-hash :hash :keys [id address] :as transfer}]
(let [transfer-by-hash (get-in db [:wallet-legacy :accounts address :transactions tx-hash])]
(when-let [unique-id (when-not (= transfer transfer-by-hash)
(if (and transfer-by-hash
(not (= :pending
(:type transfer-by-hash))))
id
hash))]
tx-hash))]
(rf/merge cofx
{:db (assoc-in db
[:wallet-legacy :accounts address :transactions unique-id]
@@ -170,11 +142,7 @@
[db address]
(get-in db [:wallet-legacy :accounts (eip55/address->checksum address) :min-block]))
(defn get-max-block-with-transfers
[db address]
(get-in db [:wallet-legacy :accounts (eip55/address->checksum address) :max-block]))
(defn min-block-transfers-count
(defn count-min-block-transfers
[db address]
(get-in db
[:wallet-legacy :accounts
@@ -187,14 +155,14 @@
{:keys [min-block min-block-transfers-count]}
(reduce
(fn [{:keys [min-block] :as acc}
{:keys [block hash]}]
{tx-hash :hash block :block}]
(cond
(or (nil? min-block) (> min-block (js/parseInt block)))
{:min-block (js/parseInt block)
:min-block-transfers-count 1}
(and (= min-block block)
(nil? (get-in db [:wallet-legacy :accounts checksum :transactions hash])))
(nil? (get-in db [:wallet-legacy :accounts checksum :transactions tx-hash])))
(update acc :min-block-transfers-count inc)
:else acc))
@@ -203,7 +171,7 @@
(js/parseInt min-block-string))
:min-block-transfers-count
(min-block-transfers-count db address)}
(count-min-block-transfers db address)}
transfers)]
(log/debug "[transactions] set-lowest-fetched-block"
"address" address
@@ -238,7 +206,7 @@
{:db (update-fetching-status db addresses :history? false)})
(rf/defn tx-history-end-reached
[{:keys [db] :as cofx} address]
[{:keys [db]} address]
(let [syncing-allowed? (utils.mobile-sync/syncing-allowed? db)]
{:db (assoc-in db
[:wallet-legacy :fetching address :all-fetched?]
@@ -247,23 +215,22 @@
:all-preloaded))}))
(rf/defn handle-new-transfer
[{:keys [db] :as cofx} transfers {:keys [address limit]}]
[cofx transfers {:keys [address limit]}]
(log/debug "[transfers] new-transfers"
"address" address
"count" (count transfers)
"limit" limit)
(let [checksum (eip55/address->checksum address)
max-known-block (get-max-block-with-transfers db address)
effects (cond-> [(when (seq transfers)
(set-lowest-fetched-block checksum transfers))]
(let [checksum (eip55/address->checksum address)
effects (cond-> [(when (seq transfers)
(set-lowest-fetched-block checksum transfers))]
(seq transfers)
(concat
[]
(mapv add-transfer transfers))
(seq transfers)
(concat
[]
(mapv add-transfer transfers))
(< (count transfers) limit)
(conj (tx-history-end-reached checksum)))]
(< (count transfers) limit)
(conj (tx-history-end-reached checksum)))]
(apply rf/merge cofx (tx-fetching-ended [checksum]) effects)))
(rf/defn new-transfers
@@ -311,16 +278,12 @@
:limit limit)])
:on-error #(re-frame/dispatch [::tx-fetching-failed address])})))))
(defn some-transactions-loaded?
[db address]
(not-empty (get-in db [:wallet-legacy :accounts address :transactions])))
(rf/defn fetch-more-tx
{:events [:transactions/fetch-more]}
[{:keys [db] :as cofx} address]
(let [min-known-block (or (get-min-known-block db address)
(:ethereum/current-block db))
min-block-transfers-count (or (min-block-transfers-count db address) 0)]
(let [min-known-block (or (get-min-known-block db address)
(:ethereum/current-block db))
min-count (or (count-min-block-transfers db address) 0)]
(rf/merge
cofx
{:transactions/get-transfers
@@ -333,7 +296,7 @@
;; the whole `default-transfers-limit` of transfers the number of transfers already received
;; for `min-known-block` is added to the page size.
:limit-per-address {address (+ default-transfers-limit
min-block-transfers-count)}}}
min-count)}}}
(tx-fetching-in-progress [address]))))
(rf/defn get-fetched-transfers
+1 -19
View File
@@ -59,21 +59,11 @@
(fn [options]
(permissions/request-notifications 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 [_]
(native-module/close-application)))
(re-frame/reg-fx
::app-state-change-fx
(fn [state]
@@ -83,11 +73,6 @@
(theme/change-device-theme (rn/get-color-scheme)))
(native-module/app-state-change state)))
(re-frame/reg-fx
:ui/listen-to-window-dimensions-change
(fn []
(dimensions/add-event-listener)))
(rf/defn dismiss-keyboard
{:events [:dismiss-keyboard]}
[_]
@@ -233,7 +218,4 @@
:on-success (fn [on-ramps]
(re-frame/dispatch [::crypto-loaded on-ramps]))}]})
(re-frame/reg-event-fx :buy-crypto.ui/open-screen
(fn []
{:fx [[:dispatch [:wallet-legacy/keep-watching]]
[:dispatch [:open-modal :buy-crypto nil]]]}))
+1 -1
View File
@@ -84,7 +84,7 @@
(rf/defn save
{:events [:fleet.ui/save-fleet-confirmed]}
[{:keys [db now] :as cofx} fleet]
[{:keys [db] :as cofx} fleet]
(let [old-fleet (get-in db [:profile/profile :fleet])]
(when (not= fleet old-fleet)
(multiaccounts.update/multiaccount-update
+6 -15
View File
@@ -40,7 +40,7 @@
(rf/defn remove-member
"Format group update message and sign membership"
{:events [:group-chats.ui/remove-member-pressed]}
[_ chat-id member do-not-navigate?]
[_ chat-id member _do-not-navigate?]
{:json-rpc/call [{:method "wakuext_removeMemberFromGroupChat"
:params [nil chat-id member]
:js-response true
@@ -48,7 +48,7 @@
(rf/defn remove-members
{:events [:group-chats.ui/remove-members-pressed]}
[{{:group-chat/keys [deselected-members]} :db :as cofx} chat-id]
[{{:group-chat/keys [deselected-members]} :db :as _cofx} chat-id]
{:json-rpc/call [{:method "wakuext_removeMembersFromGroupChat"
:params [nil chat-id deselected-members]
:js-response true
@@ -104,7 +104,7 @@
(rf/defn leave
"Leave chat"
{:events [:group-chats.ui/leave-chat-confirmed]}
[{:keys [db] :as cofx} chat-id]
[_cofx chat-id]
{:json-rpc/call [{:method "wakuext_leaveGroupChat"
:params [nil chat-id true]
:js-response true
@@ -128,7 +128,7 @@
(rf/defn name-changed
"Save chat from edited profile"
{:events [:group-chats.ui/name-changed]}
[{:keys [db] :as cofx} chat-id new-name]
[{:keys [db] :as _cofx} chat-id new-name]
(when (valid-name? new-name)
{:db (assoc-in db [:chats chat-id :name] new-name)
:json-rpc/call [{:method "wakuext_changeGroupChatName"
@@ -149,7 +149,7 @@
(rf/defn send-group-chat-membership-request
"Send group chat membership request"
{:events [:send-group-chat-membership-request]}
[{{:keys [chats] :as db} :db :as cofx} chat-id]
[{{:keys [chats] :as db} :db :as _cofx} chat-id]
(let [{:keys [invitation-admin]} (get chats chat-id)
message (get-in db [:chat/memberships chat-id :message])]
{:db (assoc-in db [:chat/memberships chat-id] nil)
@@ -161,7 +161,7 @@
(rf/defn send-group-chat-membership-rejection
"Send group chat membership rejection"
{:events [:send-group-chat-membership-rejection]}
[cofx invitation-id]
[_cofx invitation-id]
{:json-rpc/call [{:method "wakuext_sendGroupChatInvitationRejection"
:params [nil invitation-id]
:js-response true
@@ -176,15 +176,6 @@
%
invitations))})
(defn member-removed?
[{:keys [membership-update-events]} pk]
(->> membership-update-events
(filter #(contains? (set (:members %)) pk))
(sort-by :clockValue >)
first
:type
(= constants/invitation-state-removed)))
(rf/defn deselect-member
{:events [:deselect-member]}
[{:keys [db]} id]
-26
View File
@@ -1,26 +0,0 @@
(ns legacy.status-im.group-chats.db)
(def members-added-type 3)
(defn member?
[public-key {:keys [members contacts users]}]
(let [members-list (into #{} (concat (keys users) contacts (map #(:id %) members)))]
(contains? members-list public-key)))
(defn invited?
[my-public-key {:keys [contacts]}]
(contains? contacts my-public-key))
(defn get-inviter-pk
[my-public-key {:keys [membership-update-events]}]
(->> membership-update-events
reverse
(keep (fn [{:keys [from type members]}]
(when (and (= type members-added-type)
((set members) my-public-key))
from)))
first))
(defn group-chat?
[chat]
(and (:group-chat chat) (not (:public? chat))))
+1 -1
View File
@@ -7,7 +7,7 @@
(rf/defn save-log-level
{:events [:log-level.ui/change-log-level-confirmed]}
[{:keys [db now] :as cofx} log-level]
[{:keys [db] :as cofx} log-level]
(let [old-log-level (get-in db [:profile/profile :log-level])]
(when (not= old-log-level log-level)
(multiaccounts.update/multiaccount-update
@@ -1,18 +0,0 @@
(ns ^{:doc "Mailserver events and API"} legacy.status-im.mailserver.constants)
(def one-day (* 24 3600))
(def seven-days (* 7 one-day))
(def max-gaps-range (* 30 one-day))
(def max-request-range one-day)
(def maximum-number-of-attempts 2)
(def request-timeout 30)
(def min-limit 100)
(def max-limit 1000)
(def backoff-interval-ms 3000)
(def default-limit max-limit)
;; If a mailserver fails, how long before we should consider them again
;; for selection, in ms
(def cooloff-period 120000)
(def connection-timeout
"Time after which mailserver connection is considered to have failed"
10000)
@@ -1,7 +1,6 @@
(ns legacy.status-im.multiaccounts.create.core
(:require
[legacy.status-im.utils.deprecated-types :as types]
[legacy.status-im.utils.signing-phrase.core :as signing-phrase]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
@@ -29,11 +28,6 @@
derived
(update :derived normalize-derived-data-keys)))
(re-frame/reg-cofx
::get-signing-phrase
(fn [cofx _]
(assoc cofx :signing-phrase (signing-phrase/generate))))
(re-frame/reg-fx
:multiaccount-generate-and-derive-addresses
(fn []
@@ -1,31 +0,0 @@
(ns legacy.status-im.multiaccounts.recover.core
(:require
[legacy.status-im.multiaccounts.create.core :as multiaccounts.create]
[legacy.status-im.utils.deprecated-types :as types]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[taoensso.timbre :as log]))
(re-frame/reg-fx
::import-multiaccount
(fn [{:keys [passphrase password success-event]}]
(log/debug "[recover] ::import-multiaccount")
(native-module/multiaccount-import-mnemonic
passphrase
password
(fn [result]
(let [{:keys [id] :as root-data}
(multiaccounts.create/normalize-multiaccount-data-keys
(types/json->clj result))]
(native-module.core/multiaccount-derive-addresses
id
[constants/path-wallet-root
constants/path-eip1581
constants/path-whisper
constants/path-default-wallet]
(fn [result]
(let [derived-data (multiaccounts.create/normalize-derived-data-keys
(types/json->clj result))]
(re-frame/dispatch [success-event root-data derived-data])))))))))
@@ -1,94 +0,0 @@
(ns legacy.status-im.multiaccounts.reset-password.core
(:require
[clojure.string :as string]
[legacy.status-im.popover.core :as popover]
[legacy.status-im.utils.deprecated-types :as types]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/defn on-input-change
{:events [::handle-input-change]}
[{:keys [db]} input-id value]
(let [new-password (get-in db [:multiaccount/reset-password-form-vals :new-password])
error (when (and (= input-id :confirm-new-password)
(pos? (count new-password))
(pos? (count value))
(not= value new-password))
:t/password-mismatch)]
{:db (-> db
(assoc-in [:multiaccount/reset-password-form-vals input-id] value)
(assoc-in [:multiaccount/reset-password-errors input-id] error))}))
(rf/defn clear-form-vals
{:events [::clear-form-vals]}
[{:keys [db]}]
{:db (dissoc db :multiaccount/reset-password-form-vals :multiaccount/reset-password-errors)})
(rf/defn set-current-password-error
{:events [::handle-verification-error ::password-reset-error]}
[{:keys [db]} error]
{:db (assoc-in db [:multiaccount/reset-password-errors :current-password] error)})
(rf/defn password-reset-success
{:events [::password-reset-success]}
[{:keys [db] :as cofx}]
(rf/merge cofx
{:db (dissoc
db
:multiaccount/reset-password-form-vals
:multiaccount/reset-password-errors
:multiaccount/reset-password-next-enabled?
:multiaccount/resetting-password?)}))
(defn change-db-password-cb
[res]
(let [{:keys [error]} (types/json->clj res)]
(if (not (string/blank? error))
(re-frame/dispatch [::password-reset-error error])
(re-frame/dispatch [::password-reset-success]))))
(re-frame/reg-fx
::change-db-password
(fn [[key-uid {:keys [current-password new-password]}]]
(native-module/reset-password
key-uid
(native-module/sha3 (security/safe-unmask-data current-password))
(native-module/sha3 (security/safe-unmask-data new-password))
change-db-password-cb)))
(rf/defn handle-verification-success
{:events [::handle-verification-success]}
[{:keys [db] :as cofx} form-vals]
(let [{:keys [key-uid name]} (:profile/profile db)]
(rf/merge cofx
{::change-db-password [key-uid form-vals]
:db (assoc db
:multiaccount/resetting-password?
true)}
(popover/show-popover {:view :password-reset-popover
:prevent-closing? true}))))
(defn handle-verification
[form-vals result]
(let [{:keys [error]} (types/json->clj result)]
(if (not (string/blank? error))
(re-frame/dispatch [::handle-verification-error :t/wrong-password])
(re-frame/dispatch [::handle-verification-success form-vals]))))
(re-frame/reg-fx
::validate-current-password-and-reset
(fn [{:keys [address current-password] :as form-vals}]
(let [hashed-pass (native-module/sha3 (security/safe-unmask-data current-password))]
(native-module/verify address
hashed-pass
(partial handle-verification form-vals)))))
(rf/defn reset
{:events [::reset]}
[{:keys [db]} form-vals]
{::validate-current-password-and-reset
(assoc form-vals
:address
(get-in db [:profile/profile :wallet-root-address]))})
@@ -9,7 +9,7 @@
(rf/defn send-contact-update
[{:keys [db]}]
(let [{:keys [name preferred-name display-name address]} (:profile/profile db)]
(let [{:keys [name preferred-name display-name]} (:profile/profile db)]
{:json-rpc/call [{:method "wakuext_sendContactUpdates"
:params [(or preferred-name display-name name) "" ""]
:on-success #(log/debug "sent contact update")}]}))
@@ -79,30 +79,27 @@
synced-stickers))
(rf/defn optimistic
[{:keys [db] :as cofx} setting setting-value]
(let [current-multiaccount (:profile/profile db)
setting-value (if (= :currency setting)
(settings/rpc->currency setting-value)
setting-value)
db (case setting
:stickers/packs-pending
(let [packs-pending (keys (js->clj setting-value))]
(update db :stickers/packs-pending conj packs-pending))
:stickers/packs-installed
(let [packs-installed-keys (keys (js->clj setting-value))]
(reduce #(assoc-in %1
[:stickers/packs %2 :status]
constants/sticker-pack-status-installed)
db
packs-installed-keys))
:stickers/recent-stickers
(let [recent-stickers-from-remote (augment-synchronized-recent-stickers
(types/js->clj setting-value)
(:stickers/packs db))
merged (into recent-stickers-from-remote
(:stickers/recent-stickers db))]
(assoc db :stickers/recent-stickers recent-stickers-from-remote))
db)]
[{:keys [db]} setting setting-value]
(let [setting-value (if (= :currency setting)
(settings/rpc->currency setting-value)
setting-value)
db (case setting
:stickers/packs-pending
(let [packs-pending (keys (js->clj setting-value))]
(update db :stickers/packs-pending conj packs-pending))
:stickers/packs-installed
(let [packs-installed-keys (keys (js->clj setting-value))]
(reduce #(assoc-in %1
[:stickers/packs %2 :status]
constants/sticker-pack-status-installed)
db
packs-installed-keys))
:stickers/recent-stickers
(let [recent-stickers-from-remote (augment-synchronized-recent-stickers
(types/js->clj setting-value)
(:stickers/packs db))]
(assoc db :stickers/recent-stickers recent-stickers-from-remote))
db)]
{:db (if setting-value
(assoc-in db [:profile/profile setting] setting-value)
(update db :profile/profile dissoc setting))
+1 -8
View File
@@ -1,7 +1,6 @@
(ns legacy.status-im.node.core
(:require
[legacy.status-im.utils.deprecated-types :as types]
[status-im.config :as config]))
[legacy.status-im.utils.deprecated-types :as types]))
(defn fleets
[{:keys [custom-fleets]}]
@@ -9,9 +8,3 @@
(mapv #(:fleets (types/json->clj %)) $)
(conj $ custom-fleets)
(reduce merge $)))
(defn current-fleet-key
[db]
(keyword (get-in db
[:profile/profile :fleet]
config/fleet)))
+2 -2
View File
@@ -98,7 +98,7 @@
:deviceType utils.platform/os}]}))
(rf/defn init
[cofx]
[_cofx]
{:pairing/get-our-installations nil})
(rf/defn enable
@@ -254,7 +254,7 @@
(rf/defn pair-and-sync
{:events [:pairing/pair-and-sync]}
[cofx installation-id]
[_cofx installation-id]
{:fx [[:json-rpc/call
[{:method "wakuext_enableInstallationAndSync"
:params [{:installationId installation-id}]
+1 -1
View File
@@ -37,7 +37,7 @@
(rf/defn finish-success
{:events [:my-profile/finish-success]}
[{:keys [db] :as cofx}]
[{:keys [db]}]
{:db (update db :my-profile/seed assoc :step :finish :error nil :word nil)})
(rf/defn finish
+2 -2
View File
@@ -23,7 +23,7 @@
(rf/defn set-qr-code
{:events [:qr-scanner.callback/scan-qr-code-success]}
[{:keys [db]} opts data]
[_ opts data]
(when-let [handler (:handler opts)]
{:dispatch [handler data opts]}))
@@ -46,7 +46,7 @@
(= (:public-key profile) public-key))
(rf/defn handle-private-chat
[{:keys [db] :as cofx} {:keys [chat-id]}]
[{:keys [db]} {:keys [chat-id]}]
(if-not (own-public-key? db chat-id)
{:dispatch [:chat.ui/start-chat chat-id]}
{:effects.utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
@@ -1,6 +1,4 @@
(ns legacy.status-im.react-native.resources
(:require
[legacy.status-im.ui.components.colors :as colors]))
(ns legacy.status-im.react-native.resources)
(def ui
{:empty-chats-header (js/require "../resources/images/ui/empty-chats-header.png")
@@ -52,10 +50,6 @@
:no-contacts (js/require "../resources/images/ui/no-contacts.png")
:no-contacts-dark (js/require "../resources/images/ui/no-contacts-dark.png")})
(defn get-theme-image
[k]
(get ui (when (colors/dark?) (keyword (str (name k) "-dark"))) (get ui k)))
(def loaded-images (atom {}))
(defn get-image
-16
View File
@@ -2,7 +2,6 @@
(:require
[legacy.status-im.utils.utils :as utils]
[re-frame.core :as re-frame]
[status-im.common.json-rpc.events :as json-rpc]
[status-im.constants :as constants]
[utils.ethereum.chain :as chain]
[utils.re-frame :as rf]))
@@ -23,21 +22,6 @@
:params [(chain/chain-id db) id]
:on-success #()}]}))
(re-frame/reg-fx :stickers/load-packs
(fn [chain-id]
(json-rpc/call {:method "stickers_market"
:params [chain-id]
:on-success [:stickers/stickers-market-success]})
(json-rpc/call {:method "stickers_installed"
:params []
:on-success [:stickers/stickers-installed-success]})
(json-rpc/call {:method "stickers_pending"
:params []
:on-success [:stickers/stickers-pending-success]})
(json-rpc/call {:method "stickers_recent"
:params []
:on-success [:stickers/stickers-recent-success]})))
(rf/defn pending-pack
{:events [:stickers/pending-pack]}
[{db :db} id]
+1 -93
View File
@@ -1,64 +1,6 @@
(ns legacy.status-im.subs.ens
(:require
[clojure.string :as string]
[legacy.status-im.ens.core :as ens]
[re-frame.core :as re-frame]
[utils.address :as address]
[utils.ethereum.chain :as chain]
[utils.money :as money]))
(re-frame/reg-sub
:ens/preferred-name
:<- [:profile/profile]
(fn [multiaccount]
(:preferred-name multiaccount)))
(re-frame/reg-sub
:ens/search-screen
:<- [:ens/registration]
(fn [{:keys [custom-domain? username state]}]
{:state state
:username username
:custom-domain? custom-domain?}))
(defn- ens-amount-label
[chain-id]
(str (ens/registration-cost chain-id)
(case chain-id
3 " STT"
1 " SNT"
"")))
(re-frame/reg-sub
:ens/checkout-screen
:<- [:ens/registration]
:<- [:chain-keyword]
:<- [:multiaccount/default-account]
:<- [:multiaccount/public-key]
:<- [:chain-id]
:<- [:wallet-legacy]
(fn [[{:keys [custom-domain? username address]}
chain default-account public-key chain-id wallet]]
(let [address (or address (address/normalized-hex (:address default-account)))
balance (get-in wallet [:accounts address :balance])]
{:address address
:username username
:public-key public-key
:custom-domain? custom-domain?
:chain chain
:amount-label (ens-amount-label chain-id)
:sufficient-funds? (money/sufficient-funds?
(money/formatted->internal (money/bignumber 10)
(chain/chain-keyword->snt-symbol chain)
18)
(get balance (chain/chain-keyword->snt-symbol chain)))})))
(re-frame/reg-sub
:ens/confirmation-screen
:<- [:ens/registration]
(fn [{:keys [username state]}]
{:state state
:username username}))
[re-frame.core :as re-frame]))
(re-frame/reg-sub
:ens/current-names
@@ -66,37 +8,3 @@
:<- [:chain-id]
(fn [[all-names chain-id]]
(get all-names chain-id)))
(re-frame/reg-sub
:ens.name/screen
:<- [:get-screen-params :ens-name-details]
:<- [:ens/current-names]
(fn [[name ens]]
(let [{:keys [address public-key expiration-date releasable?]} (get ens name)
pending? (nil? address)]
(cond-> {:name name
:custom-domain? (not (string/ends-with? name ".stateofus.eth"))}
pending?
(assoc :pending? true)
(not pending?)
(assoc :address address
:public-key public-key
:releasable? releasable?
:expiration-date expiration-date)))))
(re-frame/reg-sub
:ens.main/screen
:<- [:ens/current-names]
:<- [:profile/profile]
:<- [:ens/preferred-name]
:<- [:ens/registrations]
(fn [[names multiaccount preferred-name registrations]]
(let [not-in-progress-names (reduce (fn [acc {:keys [username custom-domain?]}]
(let [full-name (ens/fullname custom-domain? username)]
(remove #(= % full-name) acc)))
(keys names)
(vals registrations))]
{:names not-in-progress-names
:profile/profile multiaccount
:preferred-name preferred-name
:registrations registrations})))
@@ -11,31 +11,6 @@
(fn [[current-mailserver-id current-fleet mailservers]]
(get-in mailservers [current-fleet current-mailserver-id :name])))
(re-frame/reg-sub
:mailserver/connecting?
:<- [:mailserver/state]
(fn [state]
(#{:connecting :added} state)))
(re-frame/reg-sub
:mailserver/connection-error?
:<- [:mailserver/state]
(fn [state]
(#{:error :disconnected} state)))
(re-frame/reg-sub
:mailserver/fetching?
:<- [:mailserver/state]
:<- [:mailserver/pending-requests]
:<- [:mailserver/connecting?]
:<- [:mailserver/connection-error?]
:<- [:mailserver/request-error?]
(fn [[state pending-requests connecting? connection-error? request-error?]]
(and pending-requests
(= state :connected)
(pos-int? pending-requests)
(not (or connecting? connection-error? request-error?)))))
(re-frame/reg-sub
:mailserver/fleet-mailservers
:<- [:fleets/current-fleet]
@@ -43,23 +18,6 @@
(fn [[current-fleet mailservers]]
(current-fleet mailservers)))
(re-frame/reg-sub
:mailserver.edit/connected?
:<- [:mailserver.edit/mailserver]
:<- [:mailserver/current-id]
(fn [[mailserver current-mailserver-id]]
(= (get-in mailserver [:id :value])
current-mailserver-id)))
(re-frame/reg-sub
:mailserver.edit/validation-errors
:<- [:mailserver.edit/mailserver]
(fn [mailserver]
(set (keep
(fn [[k {:keys [error]}]]
(when error k))
mailserver))))
(re-frame/reg-sub
:mailserver/preferred-id
:<- [:profile/profile]
-1
View File
@@ -3,7 +3,6 @@
legacy.status-im.subs.browser
legacy.status-im.subs.ens
legacy.status-im.subs.mailservers
legacy.status-im.subs.stickers
[re-frame.core :as re-frame]))
(defn reg-root-key-sub
-22
View File
@@ -1,22 +0,0 @@
(ns legacy.status-im.subs.stickers
(:require
[re-frame.core :as re-frame]
[status-im.constants :as constants]))
(re-frame/reg-sub
:stickers/all-packs
:<- [:stickers/packs]
(fn [packs]
(map (fn [{:keys [status] :as pack}]
(-> pack
(assoc :installed (= status constants/sticker-pack-status-installed))
(assoc :pending (= status constants/sticker-pack-status-pending))
(assoc :owned (= status constants/sticker-pack-status-owned))))
(vals packs))))
(re-frame/reg-sub
:stickers/get-current-pack
:<- [:get-screen-params]
:<- [:stickers/all-packs]
(fn [[{:keys [id]} packs]]
(first (filter #(= (:id %) id) packs))))
@@ -1,56 +0,0 @@
(ns legacy.status-im.ui.components.accordion
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.list.item :as list.item]
[legacy.status-im.ui.components.react :as react]
[reagent.core :as reagent]))
(defn drop-down-icon
[{:keys [opened? dropdown-margin-left]}]
[react/view {:flex-direction :row :align-items :center}
[icons/icon (if opened? :main-icons/dropdown-up :main-icons/dropdown)
{:container-style {:align-items :center
:margin-left dropdown-margin-left
:justify-content :center}
:resize-mode :center
:color colors/black}]])
(defn section
"Render collapsible section"
[_props]
(let [opened? (reagent/atom (get _props :default))]
(fn
[{:keys [title content icon opened disabled
padding-vertical dropdown-margin-left
open-container-style
on-open on-close]
:or {padding-vertical 8
dropdown-margin-left 8
open-container-style {}
on-open #()
on-close #()}}]
(let [on-press #(do
(apply (if @opened? on-close on-open) [])
(swap! opened? not))]
[react/view
(merge {:padding-vertical padding-vertical}
(when @opened? open-container-style))
(if (string? title)
[list.item/list-item
{:title title
:icon icon
:on-press on-press
:accessory [drop-down-icon (or @opened? opened)]}]
[react/touchable-opacity {:on-press on-press :disabled disabled}
[react/view
{:flex-direction :row
:margin-right 14
:justify-content :space-between}
title
[drop-down-icon
{:opened? (or @opened? opened)
:dropdown-margin-left dropdown-margin-left}]]])
(when (or @opened? opened)
content)]))))
@@ -1,7 +1,7 @@
(ns legacy.status-im.ui.components.action-sheet
(:require
["react-native" :refer (ActionSheetIOS)]
[legacy.status-im.utils.core :as utils]
[utils.collection :as utils]
[utils.i18n :as i18n]))
(defn- callback
@@ -1,69 +0,0 @@
(ns legacy.status-im.ui.components.animated-header
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.header :as header]
[oops.core :refer [oget]]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]))
(defn header-wrapper-style
[{:keys [offset]}]
(merge
{:background-color (:ui-background @colors/theme)}
(when (and offset platform/ios?)
{:z-index 2
:shadow-radius 16
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})))
(defn title-style
[layout]
{:flex 1
:justify-content :center
:padding-right (get-in layout [:right :width])})
(defn header-container
[]
(let [layout (reagent/atom {})
offset (reagent/atom 0)
on-layout (fn [evt]
(reset! offset (oget evt "nativeEvent" "layout" "height")))]
(fn [{:keys [extended-header refresh-control refreshing-sub refreshing-counter] :as props} children]
[rn/view
{:flex 1
:pointer-events :box-none}
[rn/view
{:pointer-events :box-none
:style (header-wrapper-style {:offset @offset})}
[header/header
(merge
{:get-layout (fn [el l] (swap! layout assoc el l))
:border-bottom false
:title-align :left}
(dissoc props :extended-header))]]
(into [rn/scroll-view
{:refreshControl (when refresh-control
(refresh-control
(and @refreshing-sub
@refreshing-counter)))
:style {:z-index 1}
:scrollEventThrottle 16}
[rn/view {:pointer-events :box-none}
[rn/view
{:pointer-events :box-none
:on-layout on-layout}
[extended-header
{:offset @offset}]]]]
children)])))
(defn header
[{:keys [use-insets] :as props} & children]
(if use-insets
[header-container
(-> props
(dissoc :use-insets)
(assoc :insets (safe-area/get-insets)))
children]
[header-container props children]))
@@ -35,12 +35,6 @@
anim-value
(clj->js (add-native-driver config))))
(defn decay
[anim-value config]
(.decay ^js react/animated
anim-value
(clj->js (add-native-driver config))))
(defn anim-sequence
[animations]
(.sequence ^js react/animated (clj->js animations)))
@@ -49,65 +43,13 @@
[animations]
(.parallel ^js react/animated (clj->js animations)))
(defn anim-delay
[duration]
(.delay ^js react/animated duration))
(defn event
[mapping config]
(.event ^js react/animated (clj->js mapping) (clj->js config)))
(defn add-listener
[^js anim-value listener]
(.addListener anim-value listener))
(defn remove-all-listeners
[^js anim-value]
(.removeAllListeners anim-value))
(defn stop-animation
[^js anim-value]
(.stopAnimation anim-value))
(defn set-value
[^js anim-value value]
(.setValue anim-value value))
(def animated (.-Animated ^js rn))
(def animated-value (-> ^js rn .-Animated .-Value))
(def animated-value-xy (-> ^js rn .-Animated .-ValueXY))
(def easing (-> ^js rn .-Easing))
(defn create-value
[value]
(new animated-value value))
(defn create-value-xy
[value]
(new animated-value-xy value))
(defn add
[anim-x anim-y]
((-> ^js rn .-Animated .add) anim-x anim-y))
(defn subtract
[anim-x anim-y]
((-> ^js rn .-Animated .-subtract) anim-x anim-y))
(defn x
[^js value-xy]
(.-x value-xy))
(defn y
[^js value-xy]
(.-y value-xy))
(defn get-layout
[^js value-xy]
(js->clj (.getLayout value-xy)))
(defn easing-in [] (.-in ^js easing))
(defn easing-out [] (.-out ^js easing))
(defn cubic [] (.-cubic ^js easing))
(def bezier (.-bezier ^js easing))
@@ -1,152 +0,0 @@
(ns legacy.status-im.ui.components.bottom-panel.views
(:require
["react-native" :refer (BackHandler)]
[legacy.status-im.ui.components.animation :as anim]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.react :as react]
[react-native.platform :as platform]
[reagent.core :as reagent])
(:require-macros [legacy.status-im.utils.views :as views]))
(def back-listener (atom nil))
(defn remove-back-listener
[]
(when @back-listener
(.remove ^js @back-listener)
(reset! back-listener nil)))
(defn add-back-listener
[]
(remove-back-listener)
(reset! back-listener (.addEventListener BackHandler
"hardwareBackPress"
(fn [] true))))
(defn hide-panel-anim
[bottom-anim-value alpha-value window-height]
(remove-back-listener)
(react/dismiss-keyboard!)
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value
{:toValue (- window-height)
:useNativeDriver true})
(anim/timing alpha-value
{:toValue 0
:duration 500
:useNativeDriver true})])))
(defn show-panel-anim
[bottom-anim-value alpha-value]
(add-back-listener)
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value
{:toValue 40
:useNativeDriver true})
(anim/timing alpha-value
{:toValue 0.4
:duration 500
:useNativeDriver true})])))
(defn bottom-panel
[_ render window-height on-close on-touch-outside show-overlay?]
(let [bottom-anim-value (anim/create-value window-height)
alpha-value (anim/create-value 0)
clear-timeout (atom nil)
update? (atom nil)
current-obj (reagent/atom nil)]
(reagent/create-class
{:UNSAFE_componentWillMount (fn [^js args]
(let [[_ obj _ _] (.-argv (.-props args))]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value
alpha-value
(- window-height)))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj
nil)
600))
(hide-panel-anim bottom-anim-value
alpha-value
(- window-height)))))))
:UNSAFE_componentWillUpdate (fn [_ [_ obj _ _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value
alpha-value
(- window-height)))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj
nil)
600))
(hide-panel-anim bottom-anim-value
alpha-value
(- window-height))))))
:reagent-render (fn []
(if @current-obj
[react/keyboard-avoiding-view
{:style {:position :absolute :top 0 :bottom 0 :left 0 :right 0}
:ignore-offset true}
[react/view {:flex 1}
(when (and platform/ios? show-overlay?)
[react/animated-view
{:flex 1
:background-color colors/black-persist
:opacity alpha-value}])
(when on-touch-outside
[react/touchable-opacity
{:active-opacity 0
:on-press on-touch-outside
:style {:flex 1}}])
[react/animated-view
{:style {:position :absolute
:transform [{:translateY bottom-anim-value}]
:bottom 0
:left 0
:right 0}}
[react/view {:flex 1}
[render @current-obj]]]]]
;;TODO this is not great, improve!
#(do (on-close)
nil)))})))
(views/defview animated-bottom-panel
[m view on-close on-touch-outside show-overlay?]
(views/letsubs [{window-height :height} [:dimensions/window]]
[bottom-panel
(when m
(select-keys m
[:from :contact :amount :token :approve? :message :cancel? :hash :name :url :icons
:wc-version :params :connector :description :topic :relay :self :peer :permissions
:state])) view window-height on-close on-touch-outside
(if-not (nil? show-overlay?) show-overlay? true)]))
@@ -67,7 +67,6 @@
:else theme)
{:keys [icon-color background-color text-color border-color]}
(themes theme')]
[rn/touchable-without-feedback
(merge {:disabled disabled
:accessibility-label accessibility-label}
@@ -3,7 +3,6 @@
[clojure.string :as string]
[legacy.status-im.ui.components.chat-icon.styles :as styles]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.screens.chat.photos :as photos]
[legacy.status-im.ui.screens.profile.visibility-status.utils :as visibility-status-utils]
[quo.components.avatars.user-avatar.style :as user-avatar.style]
@@ -12,8 +11,7 @@
[re-frame.core :as re-frame.core]
[react-native.core :as rn]
[status-im.contexts.profile.utils :as profile.utils]
[utils.ens.core :as utils.ens]
[utils.image-server :as image-server]))
[utils.ens.core :as utils.ens]))
;;TODO REWORK THIS NAMESPACE
@@ -37,20 +35,6 @@
[rn/text {:style (:default-chat-icon-text styles)}
(get-name-first-char name)]]))
(defn chat-icon-view
[chat-id group-chat name styles]
[rn/view (:container styles)
(if group-chat
[default-chat-icon name styles]
(let [photo-path @(re-frame.core/subscribe [:chats/photo-path chat-id])]
[photos/photo photo-path styles]))])
(defn emoji-chat-icon
[emoji styles]
(when-not (string/blank? emoji)
[rn/view (:default-chat-icon styles)
[rn/text {:style (:default-chat-icon-text styles)} emoji]]))
(defn profile-photo-plus-dot-view
[{:keys [public-key full-name customization-color photo-container photo-path community?]}]
(let [theme @(re-frame.core/subscribe [:theme])
@@ -88,35 +72,6 @@
{:style dot-styles
:accessibility-label dot-accessibility-label}])]))
(defn emoji-chat-icon-view
[chat-id group-chat name emoji styles]
[rn/view (:container styles)
(if group-chat
(if (string/blank? emoji)
[default-chat-icon name styles]
[emoji-chat-icon emoji styles])
[profile-photo-plus-dot-view
{:public-key chat-id
:photo-container (:default-chat-icon styles)}])])
(defn chat-icon-view-chat-list
[chat-id group-chat name color]
[chat-icon-view chat-id group-chat name
{:container styles/container-chat-list
:size 40
:chat-icon styles/chat-icon-chat-list
:default-chat-icon (styles/default-chat-icon-chat-list color)
:default-chat-icon-text (styles/default-chat-icon-text 40)}])
(defn chat-icon-view-chat-sheet
[chat-id group-chat name color]
[chat-icon-view chat-id group-chat name
{:container styles/container-chat-list
:size 40
:chat-icon styles/chat-icon-chat-list
:default-chat-icon (styles/default-chat-icon-chat-list color)
:default-chat-icon-text (styles/default-chat-icon-text 40)}])
(defn custom-icon-view-list
[name color & [size]]
[rn/view (styles/container-list-size (or size 40))
@@ -147,44 +102,4 @@
:default-chat-icon (styles/default-chat-icon-profile colors/default-chat-color size)
:default-chat-icon-text (styles/default-chat-icon-text size)}])
(defn profile-icon-view
[photo-path name color emoji edit? size override-styles public-key community?]
(let [styles (merge {:container {:width size :height size}
:size size
:chat-icon styles/chat-icon-profile
:default-chat-icon (styles/default-chat-icon-profile color size)
:default-chat-icon-text (if (string/blank? emoji)
(styles/default-chat-icon-text size)
(styles/emoji-chat-icon-text size))}
override-styles)
img-config (:config photo-path)
photo-path (if img-config
;; temp support new media server avatar for old component
{:uri (image-server/get-image-uri
img-config
{:size size
:full-name name
:font-size (get-in styles [:default-chat-icon-text :font-size])
:background-color (get-in styles [:default-chat-icon :background-color])
:indicator-size 0
:indicator-border 0
:indicator-color "#000000"
:color (get-in styles [:default-chat-icon-text :color])
:length 2
:ring? (not (utils.ens/is-valid-eth-name? name))
:ring-width 2})}
photo-path)]
[rn/view (:container styles)
(if (and photo-path (seq photo-path))
[profile-photo-plus-dot-view
{:photo-path photo-path
:public-key public-key
:photo-container (:container styles)
:community? community?}]
[rn/view {:accessibility-label :chat-icon}
(if (string/blank? emoji)
[default-chat-icon name styles]
[emoji-chat-icon emoji styles])])
(when edit?
[rn/view {:style (styles/chat-icon-profile-edit)}
[icons/tiny-icon :tiny-icons/tiny-edit {:color colors/white-persist}]])]))
@@ -1,7 +1,6 @@
(ns legacy.status-im.ui.components.chat-icon.styles
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.emoji-thumbnail.utils :as emoji-utils]))
[legacy.status-im.ui.components.colors :as colors]))
(defn default-chat-icon
[color]
@@ -13,51 +12,6 @@
:border-radius 20
:background-color color})
(defn default-chat-icon-redesign
[color size]
{:margin 0
:width size
:height size
:align-items :center
:justify-content :center
:border-radius (/ size 2)
:background-color color})
(defn default-chat-icon-chat-list
[color]
(merge (default-chat-icon color)
{:width 40
:height 40
:border-radius 20}))
(defn default-list-chat-icon-redesign
[color size]
(merge (default-chat-icon-redesign color size)
{:width size
:height size
:border-radius (/ size 2)}))
(defn default-community-icon-chat-list
[color]
(merge (default-chat-icon color)
{:width 48
:height 48
:border-radius 48}))
(defn default-token-icon-chat-list
[color]
(merge (default-chat-icon color)
{:width 20
:height 20
:border-radius 20}))
(defn default-chat-icon-chat-toolbar
[color size]
(merge (default-chat-icon color)
{:width size
:height size
:border-radius size}))
(defn default-chat-icon-profile
[color size]
(merge (default-chat-icon color)
@@ -72,71 +26,12 @@
:font-size (/ size 2)
:line-height size})
(defn emoji-chat-icon-text
[size]
{:font-size (emoji-utils/emoji-font-size size)
:line-height size
:margin-top (emoji-utils/emoji-top-margin-for-vertical-alignment size)}) ;; Required for vertical alignment bug - Check function defination for more info
(def chat-icon
{:margin 4
:border-radius 20
:width 40
:height 40})
(defn chat-icon-redesign
[size]
{:margin 4
:border-radius (/ size 2)
:width size
:height size})
(def chat-icon-chat-list
(merge chat-icon
{:width 40
:height 40
:margin 0}))
(defn community-status-icon
[size]
{:margin 4
:border-radius 10
:width size
:height size})
(def community-icon-chat-list
(merge chat-icon
{:width 48
:height 48
:margin 0}))
(defn community-icon-chat-list-redesign
[size]
(merge (chat-icon size)
{:width size
:height size
:margin 0}))
(defn community-status-chat-list-icon
[size]
(merge (community-status-icon size)
{:width size
:height size
:margin 0}))
(def token-icon-chat-list
(merge chat-icon
{:width 20
:height 20
:margin 0}))
(defn chat-icon-chat-toolbar
[size]
(merge chat-icon
{:width size
:height size
:margin 0}))
(defn custom-size-icon
[size]
(merge chat-icon
@@ -144,44 +39,11 @@
:height size
:margin 0}))
(def chat-icon-profile
(merge chat-icon
{:width 64
:height 64
:border-radius 32}))
(def container-chat-list
{:width 40
:height 40})
(def token-icon-container-chat-list
{:width 20
:height 20})
(def community-icon-container-chat-list
{:width 48
:height 48})
(defn container-list-size
[size]
{:width size
:height size})
(defn container-chat-toolbar
[size]
{:width size
:height size})
(defn chat-icon-profile-edit
[]
{:width 24
:height 24
:border-radius 12
:border-width 1
:border-color colors/white-persist
:background-color colors/blue
:justify-content :center
:align-items :center
:position :absolute
:bottom -2
:right -2})
@@ -1,15 +0,0 @@
(ns legacy.status-im.ui.components.checkbox.styles
(:require
[legacy.status-im.ui.components.colors :as colors]))
(def wrapper
{:width 24 :height 24 :align-items :center :justify-content :center})
(defn icon-check-container
[checked?]
{:background-color (if checked? colors/blue colors/gray-lighter)
:align-items :center
:justify-content :center
:border-radius 2
:width 18
:height 18})
@@ -1,27 +0,0 @@
(ns legacy.status-im.ui.components.checkbox.view
(:require
[legacy.status-im.ui.components.checkbox.styles :as styles]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.react :as react]))
(defn checkbox
"react/check-box is currently not available on iOS,
use it once it is available on all platforms"
[{:keys [on-value-change checked? accessibility-label style]
:or {accessibility-label :checkbox}}]
[(if on-value-change react/touchable-highlight react/view)
(merge {:style (merge
styles/wrapper
style)
:accessibility-label (str accessibility-label
"-"
(if checked? "on" "off"))}
(when on-value-change
{:on-press #(on-value-change (not checked?))}))
(if checked?
[icons/tiny-icon
:tiny-icons/tiny-check
{:container-style (styles/icon-check-container true)
:color colors/white-persist}]
[react/view {:style (styles/icon-check-container false)}])])
+2 -32
View File
@@ -125,16 +125,12 @@
(def white-persist (:ui-background light-theme)) ;; this doesn't with theme
(def white-transparent-10 (:interactive-03 light-theme)) ;; Used as icon background color for a dark foreground
(def white-transparent (:icon-03 light-theme)) ;; Used as icon color on dark background and input placeholder color
(def white-transparent-persist (:icon-03 light-theme))
(def white-transparent-70 (:text-03 light-theme))
(def white-transparent-70-persist (:text-03 light-theme))
(def mentioned-background (:mentioned-background old-colors-mapping-light))
(def mentioned-border (:mentioned-border old-colors-mapping-light))
(def red-light "#ffe5ea") ;; error tooltip TODO (andrey) should be white, but shadow
;; needed
;; BLACK
(def black (:text-01 light-theme)) ;; Used as the default text color
(def black-persist (:ui-background dark-theme)) ;; this doesn't with theme
@@ -143,10 +139,7 @@
;; color for containers like "Backup recovery phrase"
(def black-transparent-20 (:backdrop light-theme)) ; accounts divider
(def black-transparent-40 (:backdrop light-theme))
(def black-transparent-40-persist (:backdrop light-theme))
(def black-transparent-50 (:backdrop light-theme))
(def black-light "#2d2d2d") ;; sign-with-keycard-button
(def black-transparent-86 (:ui-03 light-theme))
;; DARK GREY
(def gray (:text-02 light-theme)) ;; Dark grey, used as a background for a light foreground and
@@ -160,7 +153,7 @@
;; ACCENT BLUE
(def blue (:interactive-01 light-theme)) ;; Accent blue, used as main wallet color, and ios home add
;; button
(def blue-persist (:interactive-01 light-theme))
;; LIGHT BLUE
(def blue-light (:interactive-02 light-theme)) ;; Light Blue
(def blue-transparent-10 (alpha blue 0.1)) ;; unknown
@@ -168,7 +161,7 @@
;; RED
(def red (:negative-01 light-theme)) ;; Used to highlight errors or "dangerous" actions
(def red-transparent-10 (alpha red 0.1)) ;;action-row ;; ttt finish
(def red-audio-recorder "#fa6565")
;; GREEN
(def green "#44d058") ;; icon for successful inboud transaction
@@ -177,32 +170,9 @@
;; YELLOW
(def pin-background (:pin-background old-colors-mapping-light)) ;; Light yellow, used as background for pinned messages
(def purple "#887af9")
(def orange "#FE8F59")
(def chat-colors
["#fa6565"
"#7cda00"
purple
"#51d0f0"
orange
"#d37ef4"])
(def account-colors
["#9B832F"
"#D37EF4"
"#1D806F"
"#FA6565"
"#7CDA00"
purple
"#8B3131"])
(def mention-incoming "#0DA4C9")
(def mention-outgoing "#9EE8FA")
(def text black)
(def text-gray gray)
(def default-community-color "#773377")
(def default-chat-color "#a187d5") ;; legacy
;; THEME
@@ -1,28 +1,10 @@
(ns legacy.status-im.ui.components.common.common
(:require
[legacy.status-im.ui.components.common.styles :as styles]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.react :as react]
[reagent.core :as reagent]
[utils.i18n :as i18n])
[reagent.core :as reagent])
(:require-macros [legacy.status-im.utils.views :refer [defview letsubs]]))
(defn logo
[{:keys [size]}]
[icons/icon :icons/logo (styles/logo size)])
;;TODO DEPRECATED, use legacy.status-im.ui.components.badge
(defn counter
([value] (counter nil value))
([{:keys [size accessibility-label] :or {size 18}} value]
(let [more-than-9 (> value 9)]
[react/view {:style (styles/counter-container size)}
[react/text
(cond-> {:style (styles/counter-label size)}
accessibility-label
(assoc :accessibility-label accessibility-label))
(if more-than-9 (i18n/label :t/counter-9-plus) value)]])))
(def small-screen-image-k 0.8)
(def small-screen-height 600)
@@ -11,29 +11,6 @@
:align-items :center
:justify-content :center})
(defn logo
[icon-size]
{:width icon-size
:height icon-size
:color :none
:container-style {}})
(defn counter-container
[size]
{:width size
:height size
:border-radius (/ size 2)
:background-color colors/blue
:align-items :center
:justify-content :center})
(defn counter-label
[size]
{:font-size (inc (/ size 2))
:typography :main-medium
:color colors/white-persist
:text-align :center})
(def image-contain
{:align-self :stretch
:align-items :center
@@ -1,129 +0,0 @@
(ns legacy.status-im.ui.components.copyable-text
(:require
[legacy.status-im.ui.components.animation :as animation]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.react :as react]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn hide-cue-atom
[anim-opacity anim-y cue-atom]
(animation/start
(animation/parallel
[(animation/timing
anim-opacity
{:toValue 0
:duration 140
:delay 1000
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-y
{:toValue 0
:duration 140
:delay 1000
:easing (.-ease ^js animation/easing)
:useNativeDriver true})])
#(reset! cue-atom false)))
(defn show-cue-atom
[anim-opacity anim-y cue-atom y]
(when @cue-atom
(animation/start
(animation/parallel
[(animation/timing
anim-opacity
{:toValue 1
:duration 140
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-y
{:toValue y
:duration 140
:easing (.-ease ^js animation/easing)
:useNativeDriver true})])
#(hide-cue-atom anim-opacity anim-y cue-atom))))
(defn copy-action-visual-cue
[anim-opacity anim-y width cue-atom]
[react/animated-view
{:style
{:opacity anim-opacity
:transform [{:translateY anim-y}]
:max-width @width
:z-index (if @cue-atom 1 -1)
:height 34
:position :absolute
:border-radius 8
:align-self :center
:align-items :center
:justify-content :center
:shadow-offset {:width 0 :height 4}
:shadow-radius 12
:elevation 8
:shadow-opacity 1
:shadow-color "rgba(0, 34, 51, 0.08)"
:background-color colors/white}}
[react/view
{:padding-horizontal 16
:padding-vertical 7
:border-radius 8
:background-color colors/white
:shadow-offset {:width 0 :height 2}
:shadow-radius 4
:shadow-opacity 1
:shadow-color "rgba(0, 34, 51, 0.16)"}
[react/text
{:style
{:typography :main-medium
;; line height specified here because of figma spec
:line-height 20
:font-size 14}}
(i18n/label :t/sharing-copied-to-clipboard)]]])
(defn copyable-text-view
[{:keys [label container-style]} content]
(let [cue-atom (reagent/atom false)
background-color (or (get container-style :background-color) colors/white)
width (reagent/atom 0)
height (reagent/atom 0)
anim-y (animation/create-value 0)
anim-opacity (animation/create-value 0)]
(reagent/create-class
{:reagent-render
(fn [{:keys [copied-text]} _]
(let [copy-fn #(when (not @cue-atom)
(reset! cue-atom true)
(show-cue-atom
anim-opacity
anim-y
cue-atom
(if (> @height 34)
(- (/ @height 2))
(- (+ 17 @height))))
(react/copy-to-clipboard copied-text))]
[react/view
{:style (if container-style container-style {})
:on-layout
#(do
(reset! width (-> ^js % .-nativeEvent .-layout .-width))
(reset! height (-> ^js % .-nativeEvent .-layout .-height)))}
(when label
[react/text
{:style
{:font-size 13
;; line height specified here because of figma spec
:line-height 18
:font-weight "500"
:color colors/gray
:margin-bottom 4}}
(i18n/label label)])
[copy-action-visual-cue anim-opacity anim-y width cue-atom]
[react/touchable-highlight
{:active-opacity (if @cue-atom 1 0.85)
:underlay-color colors/black
:on-press copy-fn
:on-long-press copy-fn}
[react/view {:background-color background-color}
content]]]))})))
@@ -1,10 +1,8 @@
(ns legacy.status-im.ui.components.core
(:require
[legacy.status-im.ui.components.animated-header :as animated-header]
[legacy.status-im.ui.components.button.view :as button]
[legacy.status-im.ui.components.controls.view :as controls]
[legacy.status-im.ui.components.header :as header]
[legacy.status-im.ui.components.list.footer :as list-footer]
[legacy.status-im.ui.components.list.header :as list-header]
[legacy.status-im.ui.components.separator :as separator]
[legacy.status-im.ui.components.text :as text]
@@ -12,11 +10,8 @@
(def text text/text)
(def header header/header)
(def animated-header animated-header/header)
(def text-input text-input/text-input)
(def button button/button)
(def list-header list-header/header)
(def list-footer list-footer/footer)
(def radio controls/radio)
(def separator separator/separator)
@@ -1,52 +0,0 @@
(ns legacy.status-im.ui.components.emoji-thumbnail.color-picker
(:require
[legacy.status-im.ui.components.emoji-thumbnail.styles :as styles]
[react-native.core :as rn]))
(def emoji-picker-colors-row1
[{:name "red" :color "#F5A3A3" :key "1"}
{:name "pink" :color "#F5A3BF" :key "2"}
{:name "magenta" :color "#E9A3F5" :key "3"}
{:name "purple" :color "#C0A3F5" :key "4"}
{:name "indigo" :color "#A3B0F5" :key "5"}
{:name "blue" :color "#A3C2F5" :key "6"}
{:name "cyan" :color "#A3DCF5" :key "7"}])
(def emoji-picker-colors-row2
[{:name "teal" :color "#A3ECF5" :key "8"}
{:name "mint" :color "#A3F5E2" :key "9"}
{:name "green" :color "#A3F5BA" :key "10"}
{:name "moss" :color "#CFF5A3" :key "11"}
{:name "lemon" :color "#EEF5A3" :key "12"}
{:name "yellow" :color "#F5F5A3" :key "13"}])
(def emoji-picker-colors-row3
[{:name "honey" :color "#F5E4A3" :key "14"}
{:name "orange" :color "#F5D7A3" :key "15"}
{:name "peach" :color "#F5B6A3" :key "16"}
{:name "brown" :color "#E0C2B8" :key "17"}
{:name "grey" :color "#CCCCCC" :key "18"}
{:name "dove" :color "#DAE2E7" :key "19"}
{:name "white" :color "#FFFFFF" :key "20"}])
(defn colors-row
[color-circle container-style colors]
[rn/view {:style container-style :accessibility-label :colors-row}
(for [x colors]
[color-circle x])])
(defn color-picker-section
[color-circle]
[:<>
[colors-row ;; Row - 1st
color-circle
(merge styles/emoji-picker-color-row-container styles/emoji-picker-row1-style)
emoji-picker-colors-row1]
[colors-row ;; Row - 2nd
color-circle
(merge styles/emoji-picker-color-row-container styles/emoji-picker-row2-style)
emoji-picker-colors-row2]
[colors-row ;; Row - 3rd
color-circle
(merge styles/emoji-picker-color-row-container styles/emoji-picker-row3-style)
emoji-picker-colors-row3]])
@@ -1,19 +0,0 @@
(ns legacy.status-im.ui.components.emoji-thumbnail.preview
(:require
[clojure.string :as string]
[legacy.status-im.ui.components.emoji-thumbnail.styles :as styles]
[legacy.status-im.ui.components.react :as react]))
(defn emoji-thumbnail
[emoji color size]
(when-not (string/blank? emoji)
[react/view (styles/emoji-thumbnail-icon color size)
[react/text
{:style (styles/emoji-thumbnail-icon-text size)
:accessibility-label :thumbnail-emoji} emoji]]))
(defn emoji-thumbnail-touchable
[emoji color size func]
(when-not (string/blank? emoji)
[react/touchable-opacity {:on-press func}
[emoji-thumbnail emoji color size]]))
@@ -1,171 +0,0 @@
(ns legacy.status-im.ui.components.emoji-thumbnail.styles
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.emoji-thumbnail.utils :as emoji-utils]
[react-native.platform :as platform]))
(defn emoji-thumbnail-icon
[color size]
{:width size
:height size
:align-items :center
:justify-content :center
:border-radius (/ size 2)
:background-color color
:border-width 0.5
:border-color "rgba(0,0,0,0.1)"
:accessibility-label :thumbnail-container-circle})
(defn emoji-thumbnail-icon-text
[size]
{:font-size (emoji-utils/emoji-font-size size)
:line-height size
:margin-top (emoji-utils/emoji-top-margin-for-vertical-alignment size)}) ;; Required for vertical alignment bug - Check function defination for more info
;; Styles Related to Emoji Thumbnail Picker
(def emoji-thumbnail-preview-size 60)
(def emoji-picker-upper-components-size
(if platform/android? 350 405))
(defn emoji-picker-gray-color
[]
(if (colors/dark?) "#c3c3bc99" "#3C3C4399"))
(defn emoji-picker-category-container
[]
(if (colors/dark?) "#110d0a" "#EEF2F5"))
(defn emoji-picker-active-category-container
[]
(if (colors/dark?) "#87877f33" "#78788033"))
(defn emoji-picker-active-category-color
[]
(if (colors/dark?) "#bbbbbb" "#000000"))
(def emoji-thumbnail-preview
{:margin-top 16
:margin-bottom 16
:align-items :center
:justify-content :center
:accessibility-label :emoji-preview})
(defn emoji-picker-keyboard-container
[]
{:border-radius 0
:background-color (colors/get-color :ui-background)})
(defn emoji-picker-search-bar
[]
{:border-radius 10
:height 36
:background-color (:ui-01 @colors/theme)})
(defn emoji-picker-search-bar-text
[]
{:color (emoji-picker-gray-color)})
(defn emoji-picker-header
[]
{:font-size 13
:color (emoji-picker-gray-color)
:font-weight "600"
:margin-top 7
:margin-bottom 0})
(defn emoji-keyboard
[func]
{:onEmojiSelected func
:emojiSize 23
:containerStyles (emoji-picker-keyboard-container)
:categoryPosition "floating"
:categoryContainerColor (emoji-picker-category-container)
:activeCategoryColor (emoji-picker-active-category-color)
:activeCategoryContainerColor (emoji-picker-active-category-container)
:categoryColor (emoji-picker-gray-color)
:enableSearchBar true
:searchBarTextStyles (emoji-picker-search-bar-text)
:searchBarStyles (emoji-picker-search-bar)
:searchBarPlaceholderColor (emoji-picker-gray-color)
:closeSearchColor (emoji-picker-gray-color)
:headerStyles (emoji-picker-header)
:disabledCategory ["symbols"]})
(def emoji-picker-color-row-container
{:flex-direction "row"
:justify-content "space-around"
:flex-grow 1})
(def emoji-picker-row1-style
{:margin-top 10})
(def emoji-picker-row2-style
{:margin-top 10
:margin-bottom 10
:margin-left 25
:margin-right 25})
(def emoji-picker-row3-style
{:margin-bottom 10})
(defn emoji-picker-color-border
[item_color color-selected?]
{:height 44
:width 44
:border-radius 22
:border-width 2
:border-color (if color-selected? item_color "#00000000")
:align-items :center
:justify-content :center})
(defn emoji-picker-color
[item_color]
{:height 36
:width 36
:border-radius 18
:border-width 0.5
:background-color item_color
:border-color "rgba(0,0,0,0.1)"})
(def emoji-picker-default-thumbnails
[{:emoji "🐺" :color "#CCCCCC"}
{:emoji "🏆" :color "#F5E4A3"}
{:emoji "🦀" :color "#F5A3A3"}
{:emoji "🍁" :color "#F5D7A3"}
{:emoji "🐳" :color "#A3DCF5"}
{:emoji "🦕" :color "#CFF5A3"}
{:emoji "🐥" :color "#F5F5A3"}
{:emoji "🐇" :color "#DAE2E7"}
{:emoji "🐒" :color "#E0C2B8"}
{:emoji "🦍" :color "#CCCCCC"}
{:emoji "🤠" :color "#E0C2B8"}
{:emoji "👾" :color "#F5A3BF"}
{:emoji "🕴️" :color "#CCCCCC"}
{:emoji "💃" :color "#F5A3A3"}
{:emoji "🦹" :color "#E9A3F5"}
{:emoji "🐕" :color "#F5D7A3"}
{:emoji "🦇" :color "#C0A3F5"}
{:emoji "🦜" :color "#F5A3A3"}
{:emoji "🐢" :color "#CFF5A3"}
{:emoji "🦎" :color "#CFF5A3"}
{:emoji "🐊" :color "#CFF5A3"}
{:emoji "🦋" :color "#F5B6A3"}
{:emoji "🕸️" :color "#CCCCCC"}
{:emoji "🦈" :color "#A3DCF5"}
{:emoji "☘️" :color "#CFF5A3"}
{:emoji "🍇" :color "#E9A3F5"}
{:emoji "🍎" :color "#F5B6A3"}
{:emoji "🥥" :color "#E0C2B8"}
{:emoji "🧀" :color "#F5E4A3"}
{:emoji "🥪" :color "#F5D7A3"}
{:emoji "🥣" :color "#A3DCF5"}
{:emoji "🍜" :color "#F5B6A3"}
{:emoji "🧁" :color "#A3DCF5"}
{:emoji "☕" :color "#DAE2E7"}
{:emoji "🍷" :color "#F5A3A3"}
{:emoji "🚆" :color "#A3DCF5"}
{:emoji "👓" :color "#CCCCCC"}
{:emoji "🎶" :color "#A3DCF5"}])
@@ -1,16 +0,0 @@
(ns legacy.status-im.ui.components.emoji-thumbnail.utils
(:require
[react-native.platform :as platform]))
(defn emoji-font-size
[container_size]
(int (* (/ container_size 10) 6)))
;; React Native Bug: Till version 0.65 React Native has a bug. It doesn't center text/emoji
;; inside the container(In Android & Web Only). Even with the textAlign: "center" and other properties.
;; So this top margin is required so that emoji will center in the emoji circle.
;; More Info: https://github.com/facebook/react-native/issues/32198
;; TODO: Remove this top margin, if future updates of react-native fix this issue.
(defn emoji-top-margin-for-vertical-alignment
[container_size]
(if platform/android? (- (int (/ container_size 20))) 0))
@@ -1,30 +0,0 @@
(ns legacy.status-im.ui.components.invite.views
(:require
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.list.item :as list.item]
[re-frame.core :as re-frame]
[utils.i18n :as i18n]))
(defn button
[]
[quo/button
{:on-press #(re-frame/dispatch [:invite.events/share-link nil])
:accessibility-label :invite-friends-button}
(i18n/label :t/invite-friends)])
(defn list-item
[{:keys [accessibility-label]}]
[list.item/list-item
{:theme :accent
:title (i18n/label :t/invite-friends)
:icon :main-icons/share
:accessibility-label accessibility-label
:on-press (fn []
(re-frame/dispatch [:bottom-sheet/hide-old])
(js/setTimeout
#(re-frame/dispatch [:invite.events/share-link nil])
250))}])
@@ -1,17 +0,0 @@
(ns legacy.status-im.ui.components.keyboard-avoid-presentation
(:require
[legacy.status-im.ui.components.react :as react]
[react-native.utils :as rn.utils]
[reagent.core :as reagent]))
(defn keyboard-avoiding-view
[& argv]
(let [[props children] (rn.utils/get-props-and-children argv)]
(reagent/as-element
(into [react/keyboard-avoiding-view
(update props
:keyboard-vertical-offset
+
20
(if (:ignore-offset props) 44 0))]
children))))
@@ -1,18 +0,0 @@
(ns legacy.status-im.ui.components.list.footer
(:require
[legacy.status-im.ui.components.spacing :as spacing]
[legacy.status-im.ui.components.text :as text]
[react-native.core :as rn]
[react-native.utils :as rn.utils]))
(defn footer
[& argv]
(let [[props children] (rn.utils/get-props-and-children argv)
{:keys [color]
:or {color :secondary}}
props]
[rn/view
{:style (merge (:base spacing/padding-horizontal)
(:small spacing/padding-vertical))}
(into [text/text {:color color}]
children)]))
@@ -3,47 +3,6 @@
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.utils.styles :as styles]))
(def item
{:flex-direction :row
:justify-content :center
:padding-horizontal 16})
(def item-content-view
{:flex 1
:flex-direction :column
:justify-content :center})
(def item-checkbox
{:flex 1
:flex-direction :column
:align-items :center
:justify-content :center})
(def icon-size 24)
(def icon-wrapper-size (+ icon-size (* 2 8)))
(def item-icon-wrapper
{:width icon-wrapper-size
:height icon-wrapper-size
:align-items :center
:justify-content :center})
(def item-icon
{:width icon-size
:height icon-size})
(def left-item-wrapper
{:justify-content :center
:margin-vertical 12})
(def content-item-wrapper
{:flex 1
:justify-content :center
:margin-horizontal 16})
(def right-item-wrapper
{:justify-content :center})
(def base-separator
{:height 1
:background-color colors/black-transparent})
@@ -1,110 +0,0 @@
(ns legacy.status-im.ui.components.profile-header.view
(:require
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.spacing :as spacing]
[react-native.core :as rn]
[react-native.reanimated :as animated]))
(def avatar-extended-size 64)
(def avatar-minimized-size 40)
(def subtitle-margin 4)
(defn container-style
[{:keys [animation minimized]}]
(merge {:flex-direction :row
:padding-vertical 4
:align-items :center}
(if-not minimized
(:base spacing/padding-horizontal)
{:opacity animation})))
(defn header-bottom-separator
[]
{:margin-bottom (:tiny spacing/spacing)
:height (:small spacing/spacing)
:border-bottom-width 1
:border-bottom-color (:ui-01 @colors/theme)})
(defn header-text
[]
{:padding-left (:base spacing/spacing)
:flex 1
:justify-content :center})
(defn header-subtitle
[{:keys [minimized]}]
(merge {:padding-right (:large spacing/spacing)
:flex-direction :row
:align-items :center}
(when-not minimized
{:padding-top subtitle-margin})))
(defn extended-header
[{:keys [title photo color membership subtitle subtitle-icon on-edit on-press monospace
bottom-separator emoji public-key community?]
:or {bottom-separator true}}]
(fn [{:keys [animation minimized]}]
(let [wrapper (if on-press
[rn/touchable-opacity {:on-press on-press}]
[:<>])
editable (if (and (not minimized) on-edit)
[rn/touchable-opacity {:on-press on-edit}]
[:<>])]
(into
wrapper
[[animated/view {:pointer-events :box-none}
[animated/view
{:style (container-style {:animation animation
:minimized minimized})
:pointer-events :box-none}
(into editable
[[animated/view {:pointer-events :box-none}
[chat-icon.screen/profile-icon-view
photo title color emoji (and (not minimized) on-edit)
(if minimized avatar-minimized-size avatar-extended-size)
nil public-key community?]]])
[animated/view
{:style (header-text)
:pointer-events :box-none}
[quo/text
{:animated? true
:number-of-lines (if minimized 1 2)
:size (if minimized :base :x-large)
:weight :bold
:elipsize-mode :tail
:accessibility-role :text
:accessibility-label :default-username}
title]
(when membership
[quo/text
{:number-of-lines 1
:ellipsize-mode :middle
:monospace monospace
:size (if minimized :small :base)
:color :secondary}
membership])
(when subtitle
[animated/view
{:style (header-subtitle {:minimized minimized})
:pointer-events :box-none}
(when subtitle-icon
[icons/icon subtitle-icon
{:color (:icon-02 @colors/theme)
:width 16
:height 16
:container-style {:margin-right 4}}])
[quo/text
{:number-of-lines 1
:ellipsize-mode :middle
:monospace monospace
:size (if minimized :small :base)
:color :secondary}
subtitle]])]]
(when-not minimized
[animated/view
{:pointer-events :none
:style (when bottom-separator (header-bottom-separator))}])]]))))
+3 -122
View File
@@ -2,26 +2,19 @@
(:require
["@react-native-clipboard/clipboard" :default Clipboard]
["@react-native-community/blur" :as blur]
["@react-native-community/masked-view" :default MaskedView]
["react" :as reactjs]
["react-native" :as react-native :refer (Keyboard BackHandler)]
["react-native" :as react-native :refer (Keyboard)]
["react-native-fast-image" :as FastImage]
["react-native-image-crop-picker" :default image-picker]
["react-native-linear-gradient" :default LinearGradient]
["react-native-navigation" :refer (Navigation)]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.text-style :as typography]
[legacy.status-im.utils.utils :as utils]
[react-native.platform :as platform]
[reagent.core :as reagent]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
[utils.i18n :as i18n]))
(def native-modules (.-NativeModules react-native))
;; React Components
(def app-state (.-AppState react-native))
(def view (reagent/adapt-react-class (.-View react-native)))
(def scroll-view-class (reagent/adapt-react-class (.-ScrollView react-native)))
@@ -34,15 +27,6 @@
(def fast-image-class (reagent/adapt-react-class FastImage))
(defn image-get-size [uri callback] (.getSize (.-Image react-native) uri callback))
(defn resolve-asset-source
[uri]
(js->clj (.resolveAssetSource (.-Image react-native) uri) :keywordize-keys true))
(def linear-gradient (reagent/adapt-react-class LinearGradient))
(def masked-view (reagent/adapt-react-class MaskedView))
(def blur-view (reagent/adapt-react-class (.-BlurView blur)))
(defn valid-source?
@@ -64,23 +48,12 @@
[switch-class props])
(def touchable-highlight-class (reagent/adapt-react-class (.-TouchableHighlight react-native)))
(def pressable-class (reagent/adapt-react-class (.-Pressable react-native)))
(def touchable-without-feedback-class
(reagent/adapt-react-class (.-TouchableWithoutFeedback react-native)))
(def touchable-opacity-class (reagent/adapt-react-class (.-TouchableOpacity react-native)))
(def activity-indicator-class (reagent/adapt-react-class (.-ActivityIndicator react-native)))
(defn activity-indicator
[props]
[activity-indicator-class (update props :color #(or % colors/gray))])
(defn small-loading-indicator
[color]
[activity-indicator
{:color color
:ios {:size :small}
:android {:size :16}}])
(def animated (.-Animated react-native))
(def animated-view-class
@@ -89,19 +62,11 @@
(def animated-flat-list-class
(reagent/adapt-react-class (.-FlatList ^js animated)))
(def animated-scroll-view-class
(reagent/adapt-react-class (.-ScrollView ^js animated)))
(defn animated-view
[props & content]
(vec (conj content props animated-view-class)))
(defn animated-scroll-view
[props & children]
(vec (conj children props animated-scroll-view-class)))
(def dimensions (.-Dimensions react-native))
(def keyboard (.-Keyboard react-native))
(def dismiss-keyboard! #(.dismiss ^js Keyboard))
(def linking (.-Linking react-native))
@@ -190,61 +155,16 @@
[{style :style k :key}]
[text {:style style} (i18n/label k)])
(defn touchable-opacity
[props content]
[touchable-opacity-class props content])
(defn touchable-highlight
[props content]
[touchable-highlight-class
(merge {:underlay-color :transparent} props)
content])
(defn pressable
[props content]
[pressable-class props content])
(defn touchable-without-feedback
[props content]
[touchable-without-feedback-class
props
content])
(defn get-dimensions
[name]
(js->clj (.get ^js dimensions name) :keywordize-keys true))
;; Image picker
(defn show-access-error
[o]
(when (= "E_PERMISSION_MISSING" (.-code ^js o))
(utils/show-popup (i18n/label :t/error)
(i18n/label :t/photos-access-error))))
(defn show-image-picker
([images-fn]
(show-image-picker images-fn nil nil))
([images-fn
{:keys [media-type]
:or {media-type "any"}
:as props}
finally-callback]
(-> ^js image-picker
(.openPicker (clj->js (merge {:mediaType media-type}
props)))
(.then images-fn)
(.catch show-access-error)
(.finally finally-callback))))
(defn show-image-picker-camera
([images-fn]
(show-image-picker-camera images-fn nil nil))
([images-fn props finally-callback]
(-> ^js image-picker
(.openCamera (clj->js props))
(.then images-fn)
(.catch show-access-error)
(.finally finally-callback))))
;; Clipboard
@@ -255,10 +175,7 @@
[s]
(.setString ^js Clipboard s))
(defn get-from-clipboard
[clbk]
(let [clipboard-contents (.getString ^js Clipboard)]
(.then clipboard-contents #(clbk %))))
;; KeyboardAvoidingView
(def navigation-const (atom nil))
@@ -278,42 +195,6 @@
(update props :keyboard-vertical-offset + 44 (:status-bar-height @navigation-const))))]
children))
(defn keyboard-avoiding-view-new
[props & children]
(into [keyboard-avoiding-view-class
(merge (when platform/ios? {:behavior :padding})
(if (:ignore-offset props)
props
(update props :keyboard-vertical-offset + 44)))]
children))
(defn scroll-view
[props & children]
(vec (conj children props scroll-view-class)))
(views/defview with-activity-indicator
[{:keys [timeout style enabled? preview]} component]
(views/letsubs
[loading (reagent/atom true)]
{:component-did-mount (fn []
(if (or (nil? timeout)
(> 100 timeout))
(reset! loading false)
(utils/set-timeout #(reset! loading false)
timeout)))}
(if (and (not enabled?) @loading)
(or preview
[view
{:style (or style
{:justify-content :center
:align-items :center})}
[activity-indicator {:animating true}]])
component)))
(defn hw-back-add-listener
[callback]
(.addEventListener BackHandler "hardwareBackPress" callback))
(defn hw-back-remove-listener
[callback]
(.removeEventListener BackHandler "hardwareBackPress" callback))
@@ -1,45 +0,0 @@
(ns legacy.status-im.ui.components.search-input.view
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.core :as quo]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn search-input-old
[{:keys [search-active?]}]
(let [input-ref (atom nil)
search-active? (or search-active? (reagent/atom nil))]
(fn [{:keys [on-focus on-change on-blur on-cancel search-filter auto-focus]}]
[quo/text-input
{:placeholder (i18n/label :t/search)
:accessibility-label :search-input
:blur-on-submit true
:multiline false
:get-ref #(reset! input-ref %)
:default-value search-filter
:auto-focus auto-focus
:on-cancel on-cancel
:show-cancel true
:auto-correct false
:auto-capitalize :none
:input-style {:height 36
:padding-top 2
:padding-bottom 2}
:before {:icon :main-icons/search
:style {:padding-horizontal 8}
:on-press #(some-> ^js @input-ref
(.focus))
:icon-opts {:color (:icon-02 @colors/theme)}}
:on-focus #(do
(when on-focus
(on-focus search-filter))
(reset! search-active? true))
:on-blur #(do
(when on-blur
(on-blur))
(reset! search-active? false))
:on-change (fn [e]
(let [^js native-event (.-nativeEvent ^js e)
text (.-text native-event)]
(when on-change
(on-change text))))}])))
@@ -1,10 +0,0 @@
(ns legacy.status-im.ui.components.slider
(:require
["@react-native-community/slider" :default Slider]
["react-native" :refer (Animated)]
[reagent.core :as reagent]))
(def slider (reagent/adapt-react-class Slider))
(def animated-slider
(reagent/adapt-react-class (.createAnimatedComponent Animated Slider)))
@@ -1,36 +0,0 @@
(ns legacy.status-im.ui.components.tabs
(:require
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.react :as react]))
(defn tab-title
[state k label active?]
[react/view {:align-items :center}
[react/touchable-highlight
{:on-press #(swap! state assoc :tab k)
:underlay-color colors/gray-lighter
:accessibility-label (str label "-item-button")
:style {:border-radius 8}}
[react/view {:padding-horizontal 12 :padding-vertical 8}
[react/text
{:style {:font-weight "500" :color (if active? colors/blue colors/gray) :line-height 22}}
label]]]
(when active?
[react/view {:width 24 :height 3 :border-radius 4 :background-color colors/blue}])])
(defn tab-button
[state k label active?]
[react/view
{:flex 1
:align-items :center
:border-radius 8
:background-color (if active? colors/blue colors/blue-light)}
[react/touchable-highlight
{:on-press #(swap! state assoc :tab k)
:accessibility-label (str label "-item-button")
:style {:border-radius 8}
:flex 1}
[react/view {:padding-horizontal 12 :padding-vertical 8}
[react/text
{:style {:font-weight "500" :color (if active? colors/white colors/blue) :line-height 22}}
label]]]])
@@ -26,8 +26,6 @@
(s/nilable (s/or :string string?
:component vector?)))
(s/def ::cancel-label (s/nilable string?))
(s/def ::default-value (s/nilable string?))
(s/def ::placeholder (s/nilable string?))
(s/def ::keyboard-type
(s/nilable (s/or :string string?
:keyword keyword?))) ; TODO: make set
@@ -1,121 +0,0 @@
(ns legacy.status-im.ui.components.toastable-highlight
"A wrapped touchable highlight that presents a toast when clicked"
(:require
[legacy.status-im.ui.components.animation :as animation]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.react :as react]
[reagent.core :as reagent]))
(defn hide-cue-atom
[anim-opacity anim-y cue-atom]
(animation/start
(animation/parallel
[(animation/timing
anim-opacity
{:toValue 0
:duration 140
:delay 1000
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-y
{:toValue 0
:duration 140
:delay 1000
:easing (.-ease ^js animation/easing)
:useNativeDriver true})])
#(reset! cue-atom false)))
(defn show-cue-atom
[anim-opacity anim-y cue-atom y]
(when @cue-atom
(animation/start
(animation/parallel
[(animation/timing
anim-opacity
{:toValue 1
:duration 140
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-y
{:toValue y
:duration 140
:easing (.-ease ^js animation/easing)
:useNativeDriver true})])
#(hide-cue-atom anim-opacity anim-y cue-atom))))
(defn toast
[anim-opacity anim-y width cue-atom label]
[react/animated-view
{:style
{:opacity anim-opacity
:transform [{:translateY anim-y}]
:max-width @width
:z-index (if @cue-atom 1 -1)
:height 34
:position :absolute
:border-radius 8
:align-self :center
:align-items :center
:justify-content :center
:shadow-offset {:width 0 :height 4}
:shadow-radius 12
:elevation 8
:shadow-opacity 1
:shadow-color "rgba(0, 34, 51, 0.08)"
:background-color colors/white}}
[react/view
{:padding-horizontal 16
:padding-vertical 7
:border-radius 8
:background-color colors/white
:shadow-offset {:width 0 :height 2}
:shadow-radius 4
:shadow-opacity 1
:shadow-color "rgba(0, 34, 51, 0.16)"}
[react/text
{:style
{:typography :main-medium
;; line height specified here because of figma spec
:line-height 20
:font-size 14}}
label]]])
(defn toastable-highlight-view
[{:keys [toast-label on-press
container-style]}
content]
(let [cue-atom (reagent/atom false)
width (reagent/atom 0)
height (reagent/atom 0)
anim-y (animation/create-value 0)
anim-opacity (animation/create-value 0)]
(reagent/create-class
{:reagent-render
(fn [{:keys []} _]
(let [press-fn #(when (not @cue-atom)
(reset! cue-atom true)
(show-cue-atom
anim-opacity
anim-y
cue-atom
(if (> @height 34)
(- (/ @height 2))
(- (+ 17 @height))))
(when on-press
(on-press)))]
[react/view
{:style (if container-style container-style {})
:on-layout
#(do
(reset! width (-> ^js % .-nativeEvent .-layout .-width))
(reset! height (-> ^js % .-nativeEvent .-layout .-height)))}
[toast anim-opacity anim-y width cue-atom toast-label]
[react/touchable-highlight
{:active-opacity (if @cue-atom 1 0.85)
:underlay-color colors/black
:on-press press-fn
:on-long-press press-fn}
[react/view {:background-color colors/white}
content]]]))})))
@@ -4,17 +4,6 @@
[legacy.status-im.utils.styles :as styles]
[status-im.config :as config]))
(def tooltip-container
(merge
{:position :absolute
:align-items :center
:left 0
:right 0
:top 0}
;;we need this for e2e tests
(when-not config/tooltip-events?
{:pointer-events :none})))
(styles/def bottom-tooltip-container
{:position :absolute
:align-items :center
@@ -38,20 +27,6 @@
(assoc :margin-top -20
:position :relative)))
(defn tooltip-text-container
[color]
{:padding-horizontal 16
:padding-vertical 6
:elevation 3
:background-color color
:border-radius 8
:shadow-radius 12
:shadow-offset {:width 0 :height 4}
:shadow-opacity 0.16
:shadow-color (if (colors/dark?)
"rgba(0, 0, 0, 0.75)"
"rgba(0, 34, 51)")})
(def bottom-tooltip-text-container
{:flex-direction :row
:align-items :center
@@ -61,12 +36,6 @@
:background-color colors/gray
:border-radius 8})
(defn tooltip-text
[font-size]
{:color colors/red
:line-height 15
:font-size font-size})
(def bottom-tooltip-text
{:color colors/white})
@@ -8,28 +8,6 @@
[legacy.status-im.ui.components.tooltip.styles :as styles])
(:require-macros [legacy.status-im.utils.views :as views]))
(views/defview tooltip
[label &
[{:keys [bottom-value color font-size container-style]
:or {bottom-value 30 color colors/white font-size 15}}
accessibility-label]]
(views/letsubs [bottom-anim-value (animation/create-value bottom-value)
opacity-value (animation/create-value 0)]
{:component-did-mount (animations/animate-tooltip bottom-value bottom-anim-value opacity-value -10)}
[react/view (merge styles/tooltip-container container-style)
[react/animated-view {:style (styles/tooltip-animated bottom-anim-value opacity-value)}
(when label
[react/view (styles/tooltip-text-container color)
[react/text
{:style (styles/tooltip-text font-size)
:accessibility-label accessibility-label}
label]])
#_[icons/icon :icons/tooltip-tip
(assoc
styles/tooltip-triangle
:color
color)]]]))
(views/defview bottom-tooltip-info
[label on-close]
(views/letsubs [bottom-anim-value (animation/create-value 75)
@@ -9,11 +9,6 @@
[utils.re-frame :as rf])
(:require-macros [legacy.status-im.utils.views :as views]))
(defn hide-sheet-and-dispatch
[event]
(re-frame/dispatch [:bottom-sheet/hide-old])
(re-frame/dispatch event))
(defn- normal-mode-settings-data
[{:keys [current-log-level
light-client-enabled?
@@ -1,55 +0,0 @@
(ns legacy.status-im.ui.screens.browser.bookmarks.views
(:require
[clojure.string :as string]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.components.toolbar :as toolbar]
[legacy.status-im.ui.components.topbar :as topbar]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn screen
[{url :url name :name new-arg :new}]
(let [input-name (reagent/atom name)]
(fn []
(let [edit? (not new-arg)]
[kb-presentation/keyboard-avoiding-view
{:style {:flex 1}
:ignore-offset true}
[topbar/topbar
{:modal? true
:border-bottom true
:title (if edit? (i18n/label :t/edit-favourite) (i18n/label :t/new-favourite))}]
[react/view {:flex 1}
[quo/text-input
{:container-style {:margin 16 :margin-top 10}
:accessibility-label :bookmark-input
:max-length 100
:auto-focus true
:show-cancel false
:label (i18n/label :t/name)
:default-value @input-name
:on-change-text #(reset! input-name %)}]
[react/text {:style {:margin 16 :color colors/gray}}
url]]
[toolbar/toolbar
{:show-border? true
:center
[quo/button
{:accessibility-label :save-bookmark
:type :secondary
:disabled (string/blank? @input-name)
:on-press #(do (if edit?
(re-frame/dispatch [:browser/update-bookmark
{:url url :name @input-name}])
(re-frame/dispatch [:browser/store-bookmark
{:url url :name @input-name}]))
(re-frame/dispatch [:navigate-back]))}
(if edit? (i18n/label :t/save) (i18n/label :t/add-favourite))]}]]))))
(defn new-bookmark
[]
[screen @(re-frame/subscribe [:get-screen-params])])
@@ -1,57 +0,0 @@
(ns legacy.status-im.ui.screens.browser.eip3085.sheet
(:require
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.browser.styles :as styles]
[re-frame.core :as re-frame]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
(views/defview permissions-panel
[_dapp-name message-id params]
(views/letsubs [{:keys [dapp? dapp]} [:get-current-browser]]
[react/view {}
[react/view styles/permissions-panel-icons-container
(if dapp?
[chat-icon.screen/dapp-icon-permission dapp 40]
[react/view styles/permissions-panel-dapp-icon-container
[icons/icon :main-icons/dapp {:color colors/gray}]])
[react/view {:margin-left 8 :margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 8}
[react/view styles/dot]]
[react/view styles/permissions-panel-ok-icon-container
[icons/icon :tiny-icons/tiny-check styles/permissions-panel-ok-ico]]
[react/view {:margin-left 8 :margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 8}
[react/view styles/dot]]
[react/view styles/permissions-panel-wallet-icon-container
[icons/icon :main-icons/wallet {:color colors/white}]]]
[react/view
{:style {:flex-direction :row
:justify-content :center
:margin-horizontal 8
:margin-top 24}}
[react/view
{:flex 1
:margin-horizontal 8}
[quo/button
{:theme :negative
:on-press #(re-frame/dispatch [:eip3085.ui/dapp-permission-denied message-id params])}
(i18n/label :t/deny)]]
[react/view
{:flex 1
:margin-horizontal 8}
[quo/button
{:theme :positive
:style {:margin-horizontal 8}
:on-press #(re-frame/dispatch [:eip3085.ui/dapp-permission-allowed message-id params])}
(i18n/label :t/allow)]]]]))
@@ -1,68 +0,0 @@
(ns legacy.status-im.ui.screens.browser.eip3326.sheet
(:require
[legacy.status-im.network.core :as network]
[legacy.status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[legacy.status-im.ui.components.colors :as colors]
[legacy.status-im.ui.components.core :as quo]
[legacy.status-im.ui.components.icons.icons :as icons]
[legacy.status-im.ui.components.react :as react]
[legacy.status-im.ui.screens.browser.styles :as styles]
[re-frame.core :as re-frame]
[utils.debounce :as debounce]
[utils.i18n :as i18n])
(:require-macros [legacy.status-im.utils.views :as views]))
(views/defview permissions-panel
[dapp-name message-id {:keys [network-from network-to target-network-id] :as params}]
(views/letsubs [{:keys [dapp? dapp]} [:get-current-browser]]
[react/view {}
[react/view styles/permissions-panel-icons-container
(if dapp?
[chat-icon.screen/dapp-icon-permission dapp 40]
[react/view styles/permissions-panel-dapp-icon-container
[icons/icon :main-icons/dapp {:color colors/gray}]])
[react/view {:margin-left 8 :margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 8}
[react/view styles/dot]]
[react/view styles/permissions-panel-ok-icon-container
[icons/icon :tiny-icons/tiny-check styles/permissions-panel-ok-ico]]
[react/view {:margin-left 8 :margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 4}
[react/view styles/dot]]
[react/view {:margin-right 8}
[react/view styles/dot]]
[react/view styles/permissions-panel-wallet-icon-container
[icons/icon :main-icons/wallet {:color colors/white}]]]
[react/text {:style styles/permissions-panel-title-label :number-of-lines 2}
(str "\"" dapp-name "\" Allow this site to switch the network?")]
[react/text {:style styles/permissions-panel-description-label :number-of-lines 5}
(str "This will switch the selected network within Status to a previously added network:\n"
network-from
" -> "
network-to
"\nit will require login/logout")]
[react/view
{:style {:flex-direction :row
:justify-content :center
:margin-horizontal 8
:margin-top 24}}
[react/view
{:flex 1
:margin-horizontal 8}
[quo/button
{:theme :negative
:on-press #(re-frame/dispatch [:eip3326.ui/dapp-permission-denied message-id params])}
(i18n/label :t/deny)]]
[react/view
{:flex 1
:margin-horizontal 8}
[quo/button
{:theme :positive
:style {:margin-horizontal 8}
:on-press #(debounce/throttle-and-dispatch [::network/connect-network-pressed target-network-id]
1000)}
(i18n/label :t/allow)]]]]))
@@ -10,15 +10,6 @@
{:margin-horizontal 16
:margin-vertical 10})
(defn browser-icon-container
[]
{:width 40
:height 40
:border-radius 20
:background-color colors/gray-lighter
:align-items :center
:justify-content :center})
(defn dapp-store-container
[]
{:margin 16
@@ -35,20 +26,3 @@
:font-size 15
:font-weight "500"
:line-height 22})
(defn dapps-account
[color]
{:flex-direction :row
:background-color color
:border-radius 36
:padding-horizontal 8
:padding-vertical 6
:align-items :center
:justify-content :center
:shadow-offset {:width 0 :height 1}
:shadow-radius 6
:shadow-opacity 1
:shadow-color (if (colors/dark?)
"rgba(0, 0, 0, 0.75)"
"rgba(0, 12, 63, 0.2)")
:elevation 2})

Some files were not shown because too many files have changed in this diff Show More