Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eae887e6b4 | ||
|
|
eff9003b68 | ||
|
|
6a2c13928e | ||
|
|
3191c70048 | ||
|
|
8e3c42796f | ||
|
|
141fbba9a4 | ||
|
|
aaca9c9388 | ||
|
|
1b9c3b577c | ||
|
|
c1daea3e99 | ||
|
|
9673eb40df | ||
|
|
b7cd92ce67 | ||
|
|
8878bf096c | ||
|
|
6774271d9a | ||
|
|
744ba2917e | ||
|
|
a5b001f237 | ||
|
|
6104ec05e3 | ||
|
|
b933d386b2 | ||
|
|
e2ec2e6171 | ||
|
|
a3bb6305b9 | ||
|
|
06be16d081 | ||
|
|
6a9e79f450 | ||
|
|
84f5a4378c | ||
|
|
2329b79f3d | ||
|
|
5b1f25d545 | ||
|
|
d35c2d2616 | ||
|
|
4226aa4d5e |
+185
@@ -0,0 +1,185 @@
|
||||
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
|
||||
+3
-28
@@ -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 clojure.core/defn
|
||||
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
|
||||
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,29 +13,7 @@
|
||||
test-helpers.unit/deftest-event clojure.core/defn
|
||||
|
||||
taoensso.tufte/defnp clojure.core/defn}
|
||||
: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}
|
||||
:linters {:case-duplicate-test {:level :error}
|
||||
:case-quoted-test {:level :error}
|
||||
:case-symbol-test {:level :error}
|
||||
:clj-kondo-config {:level :error}
|
||||
@@ -62,8 +40,7 @@
|
||||
: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/merge]}
|
||||
utils.re-frame/defn]}
|
||||
:loop-without-recur {:level :error}
|
||||
:minus-one {:level :error}
|
||||
:misplaced-docstring {:level :error}
|
||||
@@ -76,8 +53,6 @@
|
||||
: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
@@ -2,7 +2,7 @@ DEBUG_WEBVIEW=1
|
||||
ETHEREUM_DEV_CLUSTER=1
|
||||
EXTENSIONS=0
|
||||
GROUP_CHATS_ENABLED=1
|
||||
LOG_LEVEL=debug
|
||||
LOG_LEVEL=info
|
||||
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=1
|
||||
API_LOGGING_ENABLED=0
|
||||
|
||||
@@ -1,26 +1,39 @@
|
||||
[comment]: # (Please replace ... with your information. Remove < and >)
|
||||
[comment]: # (To auto-close issue on merge, please insert the related issue number after # i.e fixes #566)
|
||||
|
||||
If you submit PR for issue with bounty then write here Fixes #NN where NN is issue number
|
||||
|
||||
*otherwise*
|
||||
|
||||
fixes #...
|
||||
|
||||
## Summary
|
||||
### Summary
|
||||
|
||||
[comment]: # (Summarise the problem and how the pull request solves it)
|
||||
...
|
||||
|
||||
## Review notes
|
||||
[comment]: # (Optional. Specify if something in particular should be looked at, or ignored, during review)
|
||||
<!-- (Optional, remove if no changes to documentation) -->
|
||||
Documentation change PR (review please): https://github.com/status-im/status.im/pull/xxx
|
||||
|
||||
## Testing notes
|
||||
[comment]: # (Optional)
|
||||
### Review notes
|
||||
<!-- (Optional. Specify if something in particular should be looked at, or ignored, during review) -->
|
||||
|
||||
### Platforms
|
||||
[comment]: # (Optional. Specify which platforms should be tested)
|
||||
### Testing notes
|
||||
<!-- (Optional) -->
|
||||
|
||||
#### Platforms
|
||||
<!-- (Optional. Specify which platforms should be tested) -->
|
||||
|
||||
- Android
|
||||
- iOS
|
||||
- macOS
|
||||
- Linux
|
||||
- Windows
|
||||
|
||||
### Areas that may be impacted
|
||||
[comment]: # (Optional. Specify if some specific areas need to be tested, such as 1-1 chats)
|
||||
#### Areas that maybe impacted
|
||||
<!-- (Optional. Specify if some specific areas has to be tested, for example 1-1 chats) -->
|
||||
|
||||
#### Functional
|
||||
##### Functional
|
||||
|
||||
- 1-1 chats
|
||||
- public chats
|
||||
@@ -35,27 +48,32 @@ fixes #...
|
||||
- fleet
|
||||
- bootnodes
|
||||
|
||||
#### Non-functional
|
||||
##### Non-functional
|
||||
|
||||
- battery performance
|
||||
- CPU performance / speed of the app
|
||||
- network consumption
|
||||
|
||||
## Steps to test
|
||||
[comment]: # (Specify exact steps to test if there are such)
|
||||
### Steps to test
|
||||
<!-- (Specify exact steps to test if there are such) -->
|
||||
|
||||
- Open Status
|
||||
- ...
|
||||
- Step 3, etc.
|
||||
|
||||
<!-- (PRs will only be accepted if squashed into single commit.) -->
|
||||
|
||||
[comment]: # (Can be ready or wip)
|
||||
status: ready
|
||||
### Before and after screenshots comparison
|
||||
|
||||
| Figma (if available) | iOS (if available) | Android (if available)
|
||||
| --- | --- | --- |
|
||||
| Please embed Image/Video here of the before and after. | Please embed Image/Video here of the before and after. | Please embed Image/Video here of the before and after. |
|
||||
|
||||
status: ready <!-- Can be ready or wip -->
|
||||
|
||||
<!-- Uncomment this section for status-go upgrade/dogfooding pull requests
|
||||
|
||||
- Specify potentially impacted user flows in _Areas that may be impacted*.
|
||||
- Specify potentially impacted user flows in _Areas that maybe impacted*.
|
||||
- Ensure that _Steps to test_ is filled in.
|
||||
|
||||
### Risk
|
||||
|
||||
@@ -43,6 +43,8 @@ export NODE_OPTIONS += --openssl-legacy-provider
|
||||
# The path can be anything, but home is usually safest.
|
||||
export KEYSTORE_PATH ?= $(HOME)/.gradle/status-im.keystore
|
||||
|
||||
# Our custom config is located in nix/nix.conf
|
||||
export NIX_USER_CONF_FILES = $(PWD)/nix/nix.conf
|
||||
# Location of symlinks to derivations that should not be garbage collected
|
||||
export _NIX_GCROOTS = ./.nix-gcroots
|
||||
# Defines which variables will be kept for Nix pure shell, use semicolon as divider
|
||||
@@ -267,11 +269,9 @@ run-metro: export TARGET := clojure
|
||||
run-metro: ##@run Start Metro to build React Native changes
|
||||
@scripts/run-metro.sh
|
||||
|
||||
export RE_FRISK_PORT ?= 4567
|
||||
|
||||
run-re-frisk: export TARGET := clojure
|
||||
run-re-frisk: ##@run Start re-frisk server
|
||||
yarn shadow-cljs run re-frisk-remote.core/start ${RE_FRISK_PORT}
|
||||
yarn shadow-cljs run re-frisk-remote.core/start
|
||||
|
||||
# TODO: Migrate this to a Nix recipe, much the same way as nix/mobile/android/targets/release-android.nix
|
||||
run-android: export TARGET := android
|
||||
@@ -445,7 +445,7 @@ android-ports: export RCT_METRO_PORT ?= 8081
|
||||
android-ports: ##@other Add proxies to Android Device/Simulator
|
||||
adb reverse tcp:$(RCT_METRO_PORT) tcp:$(RCT_METRO_PORT) && \
|
||||
adb reverse tcp:3449 tcp:3449 && \
|
||||
adb reverse tcp:$(RE_FRISK_PORT) tcp:$(RE_FRISK_PORT) && \
|
||||
adb reverse tcp:4567 tcp:4567 && \
|
||||
adb reverse tcp:$(FLOWSTORM_PORT) tcp:$(FLOWSTORM_PORT) && \
|
||||
adb forward tcp:5561 tcp:5561
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ library 'status-jenkins-lib@v1.9.13'
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux && x86_64 && nix-2.24' }
|
||||
agent { label 'linux && x86_64 && nix-2.19' }
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ library 'status-jenkins-lib@v1.9.13'
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
pipeline {
|
||||
agent { label 'macos && arm64 && nix-2.24 && xcode-16.2' }
|
||||
agent { label 'macos && arm64 && nix-2.19 && xcode-15.1' }
|
||||
|
||||
parameters {
|
||||
string(
|
||||
|
||||
@@ -5,7 +5,7 @@ library 'status-jenkins-lib@v1.9.13'
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux && x86_64 && nix-2.24' }
|
||||
agent { label 'linux && x86_64 && nix-2.19' }
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
|
||||
@@ -73,9 +73,9 @@ pipeline {
|
||||
passwordVariable: 'TESTRAIL_PASS'
|
||||
),
|
||||
usernamePassword(
|
||||
credentialsId: 'lambda-test-api',
|
||||
usernameVariable: 'LAMBDA_TEST_USERNAME',
|
||||
passwordVariable: 'LAMBDA_TEST_ACCESS_KEY'
|
||||
credentialsId: 'sauce-labs-api',
|
||||
usernameVariable: 'SAUCE_USERNAME',
|
||||
passwordVariable: 'SAUCE_ACCESS_KEY'
|
||||
),
|
||||
string(
|
||||
credentialsId: 'etherscan-api-key',
|
||||
@@ -95,7 +95,8 @@ pipeline {
|
||||
sh 'cp -f $TEST_ETH_ACCOUNTS_FILE users.py'
|
||||
sh """
|
||||
python3 -m pytest \
|
||||
--numprocesses 9 \
|
||||
--numprocesses 8 \
|
||||
--rerun_count=2 \
|
||||
--testrail_report=True \
|
||||
-m testrail_id \
|
||||
-m \"nightly\" \
|
||||
@@ -109,6 +110,21 @@ 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'
|
||||
}
|
||||
|
||||
@@ -111,9 +111,9 @@ pipeline {
|
||||
passwordVariable: 'TESTRAIL_PASS'
|
||||
),
|
||||
usernamePassword(
|
||||
credentialsId: 'lambda-test-api',
|
||||
usernameVariable: 'LAMBDA_TEST_USERNAME',
|
||||
passwordVariable: 'LAMBDA_TEST_ACCESS_KEY'
|
||||
credentialsId: 'sauce-labs-api',
|
||||
usernameVariable: 'SAUCE_USERNAME',
|
||||
passwordVariable: 'SAUCE_ACCESS_KEY'
|
||||
),
|
||||
string(
|
||||
credentialsId: 'etherscan-api-key',
|
||||
@@ -133,7 +133,8 @@ pipeline {
|
||||
sh 'cp -f $TEST_ETH_ACCOUNTS_FILE users.py'
|
||||
sh """
|
||||
python3 -m pytest \
|
||||
--numprocesses 9 \
|
||||
--numprocesses 8 \
|
||||
--rerun_count=2 \
|
||||
--testrail_report=True \
|
||||
-k \"${params.KEYWORD_EXPRESSION}\" \
|
||||
--apk=${params.APK_URL ?: apk_path} \
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
- [Patching](patching.md)
|
||||
- [Creating a pixel perfect UI](pixel-perfection.md)
|
||||
- [Contributing to status-go](status-go-changes.md)
|
||||
- [Status Backend Server](use-status-backend-server.md)
|
||||
- [Malli schemas (recorded demo)](https://www.youtube.com/watch?v=SlRio70aYVI) ([slides](files/forging-code-with-schemas-sep-2023-slides.pdf))
|
||||
|
||||
## Testing
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ This one is straightforward, just evaluate a navigation dispatch form from anywh
|
||||
|
||||
#### REPL-ing a component
|
||||
|
||||
This is also straightforward, but there are some small differences. Just like above, we only have to evaluate a dispatch form, as follows:
|
||||
This is also straighforward, but there are some small differences. Just like above, we only have to evaluate a dispatch form, as follows:
|
||||
|
||||
```cljs
|
||||
;; example debugging a quo component, although it could be any other component e.g. from the `status-im2` ns.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: محفظة إيثريوم كريبتو
|
||||
Status: محفظة تشفير اجتماعية
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: Ethereum Crypto Wallet
|
||||
Status: Social Crypto Wallet
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: Billetera Ethereum
|
||||
Status: Billetera social cripto
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: Portefeuille Ethereum
|
||||
Status : Portef. crypto social
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: イーサリアム クリプト ウォレット
|
||||
Status: ソーシャル クリプト ウォレット
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: 이더리움 크립토 지갑
|
||||
Status: 소셜 크립토 지갑
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: Carteira Ethereum
|
||||
Status: Carteira social cripto
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: Ethereum Криптокошелек
|
||||
Status: Криптокошелек
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status Ethereum Kripto Cüzdanı
|
||||
Status: Sosyal Kripto Cüzdanı
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status Ethereum Криптогаманець
|
||||
Status: Криптогаманець
|
||||
|
||||
@@ -1 +1 @@
|
||||
Status: 以太坊加密钱包
|
||||
Status: 可以社交的加密货币钱包
|
||||
|
||||
+18
-21
@@ -23,18 +23,15 @@ PODS:
|
||||
- secp256k1
|
||||
- SSZipArchive
|
||||
- libevent (2.1.12)
|
||||
- libwebp (1.3.2):
|
||||
- libwebp/demux (= 1.3.2)
|
||||
- libwebp/mux (= 1.3.2)
|
||||
- libwebp/sharpyuv (= 1.3.2)
|
||||
- libwebp/webp (= 1.3.2)
|
||||
- libwebp/demux (1.3.2):
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
- libwebp/webp (= 1.2.4)
|
||||
- libwebp/demux (1.2.4):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.3.2):
|
||||
- libwebp/mux (1.2.4):
|
||||
- libwebp/demux
|
||||
- libwebp/sharpyuv (1.3.2)
|
||||
- libwebp/webp (1.3.2):
|
||||
- libwebp/sharpyuv
|
||||
- libwebp/webp (1.2.4)
|
||||
- RCT-Folly (2022.05.16.00):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -1155,15 +1152,15 @@ PODS:
|
||||
- glog
|
||||
- RCT-Folly (= 2022.05.16.00)
|
||||
- React-Core
|
||||
- RNImageCropPicker (0.41.2):
|
||||
- RNImageCropPicker (0.40.0):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNImageCropPicker/QBImagePickerController (= 0.41.2)
|
||||
- TOCropViewController (~> 2.7.4)
|
||||
- RNImageCropPicker/QBImagePickerController (0.41.2):
|
||||
- RNImageCropPicker/QBImagePickerController (= 0.40.0)
|
||||
- TOCropViewController
|
||||
- RNImageCropPicker/QBImagePickerController (0.40.0):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- TOCropViewController (~> 2.7.4)
|
||||
- TOCropViewController
|
||||
- RNKeychain (8.1.2):
|
||||
- React-Core
|
||||
- RNLinearGradient (3.0.0-alpha.1):
|
||||
@@ -1192,7 +1189,7 @@ PODS:
|
||||
- secp256k1 (0.1.6)
|
||||
- SocketRocket (0.6.1)
|
||||
- SSZipArchive (2.4.3)
|
||||
- TOCropViewController (2.7.4)
|
||||
- TOCropViewController (2.6.1)
|
||||
- Yoga (1.14.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -1492,7 +1489,7 @@ CHECKOUT OPTIONS:
|
||||
:git: https://github.com/status-im/Keycard.swift.git
|
||||
:tag: 3.1.1
|
||||
secp256k1:
|
||||
:commit: 4ab977cc2b2d7319be858bcb30a5d189bb149884
|
||||
:commit: 46a1fa30d9b8babeae85ff519050f42394ab5fcc
|
||||
:git: https://github.com/status-im/secp256k1.swift.git
|
||||
:submodules: true
|
||||
|
||||
@@ -1509,7 +1506,7 @@ SPEC CHECKSUMS:
|
||||
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
||||
Keycard: 3bd24405b9a875b3b3cf1ab1be69b5c30c5f28bc
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
|
||||
RCTRequired: 2544c0f1081a5fa12e108bb8cb40e5f4581ccd87
|
||||
RCTTypeSafety: 50efabe2b115c11ed03fbf3fd79e2f163ddb5d7c
|
||||
@@ -1580,7 +1577,7 @@ SPEC CHECKSUMS:
|
||||
RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7
|
||||
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
|
||||
RNGestureHandler: 15c6ef51acba34c49ff03003806cf5dd6098f383
|
||||
RNImageCropPicker: 771e2ca319d2cf92e04ebf334ece892ee9a6728f
|
||||
RNImageCropPicker: 486e2f7e2b0461ce24321f751410dce1b3b49e6d
|
||||
RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c
|
||||
RNLinearGradient: ccaaebce083b54180da61d992e7fa56abfe000d6
|
||||
RNPermissions: 08e619529cced22695f4b6d0efcc0a233e278903
|
||||
@@ -1593,9 +1590,9 @@ SPEC CHECKSUMS:
|
||||
secp256k1: f61d67e6fdcb85fd727acf1bf35ace6036db540c
|
||||
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
|
||||
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
||||
Yoga: a716eea57d0d3430219c0a5a233e1e93ee931eb7
|
||||
|
||||
PODFILE CHECKSUM: 56d9f695324306d6db368d1172d211dfab55b7cb
|
||||
PODFILE CHECKSUM: 81a66b45adddb083171ade882c343deb4faf2a8b
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
@@ -572,7 +572,6 @@
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Status-StatusIm-StatusImTests/Pods-Status-StatusIm-StatusImTests-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNPermissions/RNPermissionsPrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
|
||||
@@ -580,7 +579,6 @@
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNPermissionsPrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
|
||||
@@ -616,7 +614,6 @@
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Status-StatusIm/Pods-Status-StatusIm-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNPermissions/RNPermissionsPrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
|
||||
@@ -624,7 +621,6 @@
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNPermissionsPrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
|
||||
@@ -732,7 +728,6 @@
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Status-StatusImPR/Pods-Status-StatusImPR-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNPermissions/RNPermissionsPrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
|
||||
@@ -740,7 +735,6 @@
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNPermissionsPrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
|
||||
|
||||
+1
-1
@@ -26,4 +26,4 @@ These log files are uploaded to Jenkins as CI job artifacts on failure using `ar
|
||||
* `set_xcode_version.log` - Output from `node_modules/react-native/scripts/react-native-xcode.sh`.
|
||||
- Created by redirecting output of `shellScript` in `ios/StatusIm.xcodeproj/project.pbxproj` for "Run Script" build Phase.
|
||||
* `Status PR-StatusImPR.log - Created by [Fastlane Gym](https://docs.fastlane.tools/actions/gym/).
|
||||
- Configured via [`Fastfile`](../Fastfile) using `buildlog_path` argument for `build_ios_app`.
|
||||
- Configred via [`Fastfile`](../Fastfile) using `buildlog_path` argument for `build_ios_app`.
|
||||
|
||||
+18
@@ -203,6 +203,24 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
|
||||
)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun verify(address: String, password: String, callback: Callback) {
|
||||
val absRootDirPath = utils.getNoBackupDirectory()
|
||||
val newKeystoreDir = utils.pathCombine(absRootDirPath, "keystore")
|
||||
|
||||
val jsonParams = JSONObject()
|
||||
jsonParams.put("keyStoreDir", newKeystoreDir)
|
||||
jsonParams.put("address", address)
|
||||
jsonParams.put("password", password)
|
||||
|
||||
StatusBackendClient.executeStatusGoRequestWithCallback(
|
||||
endpoint = "VerifyAccountPasswordV2",
|
||||
requestBody = jsonParams.toString(),
|
||||
statusgoFunction = { Statusgo.verifyAccountPasswordV2(jsonParams.toString()) },
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun verifyDatabasePassword(keyUID: String, password: String, callback: Callback) {
|
||||
val jsonParams = JSONObject()
|
||||
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +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
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
-118
@@ -1,118 +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 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -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.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +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 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+890
@@ -0,0 +1,890 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
-750
@@ -1,750 +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 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-93
@@ -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.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
-24
@@ -1,24 +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
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +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.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -195,6 +195,36 @@ RCT_EXPORT_METHOD(loginAccount:(NSString *)request) {
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(verify:(NSString *)address
|
||||
password:(NSString *)password
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
#if DEBUG
|
||||
NSLog(@"VerifyAccountPasswordV2() method called");
|
||||
#endif
|
||||
NSURL *rootUrl = [Utils getRootUrl];
|
||||
NSString *keystorePath = [rootUrl.path stringByAppendingPathComponent:@"keystore"];
|
||||
|
||||
NSDictionary *params = @{
|
||||
@"keyStoreDir": keystorePath,
|
||||
@"address": address,
|
||||
@"password": password
|
||||
};
|
||||
NSError *error;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error creating JSON: %@", error);
|
||||
return;
|
||||
}
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
|
||||
[StatusBackendClient executeStatusGoRequestWithCallback:@"VerifyAccountPasswordV2"
|
||||
body:jsonString
|
||||
statusgoFunction:^NSString *{
|
||||
return StatusgoVerifyAccountPasswordV2(jsonString);
|
||||
}
|
||||
callback:callback];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID
|
||||
password:(NSString *)password
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
|
||||
@@ -105,6 +105,48 @@ RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
|
||||
callback:callback];
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeTransfer:(NSString *)to
|
||||
value:(NSString *)value) {
|
||||
NSDictionary *params = @{
|
||||
@"to": to,
|
||||
@"value": value
|
||||
};
|
||||
NSError *error;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
|
||||
return nil;
|
||||
}
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
|
||||
return [StatusBackendClient executeStatusGoRequestWithResult:@"EncodeTransferV2"
|
||||
body:jsonString
|
||||
statusgoFunction:^NSString *{
|
||||
return StatusgoEncodeTransferV2(jsonString);
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeFunctionCall:(NSString *)method
|
||||
paramsJSON:(NSString *)paramsJSON) {
|
||||
NSDictionary *params = @{
|
||||
@"method": method,
|
||||
@"paramsJSON": paramsJSON
|
||||
};
|
||||
NSError *error;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
|
||||
return nil;
|
||||
}
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
|
||||
return [StatusBackendClient executeStatusGoRequestWithResult:@"EncodeFunctionCallV2"
|
||||
body:jsonString
|
||||
statusgoFunction:^NSString *{
|
||||
return StatusgoEncodeFunctionCallV2(jsonString);
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(decodeParameters:(NSString *)decodeParamJSON) {
|
||||
return [StatusBackendClient executeStatusGoRequestWithResult:@"DecodeParameters"
|
||||
body:decodeParamJSON
|
||||
|
||||
@@ -389,6 +389,86 @@ void _StopCPUProfiling(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
}
|
||||
|
||||
void _EncodeTransfer(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() != 2) {
|
||||
// Throw an Error that is passed back to JavaScript
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong number of arguments for EncodeTransfer")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the argument types
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'to'")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args[1]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'value'")));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
|
||||
char *arg0 = *arg0Obj;
|
||||
String::Utf8Value arg1Obj(isolate, args[1]->ToString(context).ToLocalChecked());
|
||||
char *arg1 = *arg1Obj;
|
||||
|
||||
// Call exported Go function, which returns a C string
|
||||
char *c = EncodeTransfer(arg0, arg1);
|
||||
|
||||
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
|
||||
args.GetReturnValue().Set(ret);
|
||||
delete c;
|
||||
|
||||
}
|
||||
|
||||
void _EncodeFunctionCall(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() != 2) {
|
||||
// Throw an Error that is passed back to JavaScript
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong number of arguments for EncodeFunctionCall")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the argument types
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'method'")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args[1]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'paramsJSON'")));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
|
||||
char *arg0 = *arg0Obj;
|
||||
String::Utf8Value arg1Obj(isolate, args[1]->ToString(context).ToLocalChecked());
|
||||
char *arg1 = *arg1Obj;
|
||||
|
||||
// Call exported Go function, which returns a C string
|
||||
char *c = EncodeFunctionCall(arg0, arg1);
|
||||
|
||||
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
|
||||
args.GetReturnValue().Set(ret);
|
||||
delete c;
|
||||
|
||||
}
|
||||
|
||||
void _DecodeParameters(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
@@ -1300,6 +1380,53 @@ void _CallPrivateRPC(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
}
|
||||
|
||||
void _VerifyAccountPassword(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() != 3) {
|
||||
// Throw an Error that is passed back to JavaScript
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong number of arguments for VerifyAccountPassword")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the argument types
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'keyStoreDir'")));
|
||||
return;
|
||||
}
|
||||
if (!args[1]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'address'")));
|
||||
return;
|
||||
}
|
||||
if (!args[2]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'password'")));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
|
||||
char *arg0 = *arg0Obj;
|
||||
String::Utf8Value arg1Obj(isolate, args[1]->ToString(context).ToLocalChecked());
|
||||
char *arg1 = *arg1Obj;
|
||||
String::Utf8Value arg2Obj(isolate, args[2]->ToString(context).ToLocalChecked());
|
||||
char *arg2 = *arg2Obj;
|
||||
|
||||
// Call exported Go function, which returns a C string
|
||||
char *c = VerifyAccountPassword(arg0, arg1, arg2);
|
||||
|
||||
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
|
||||
args.GetReturnValue().Set(ret);
|
||||
delete c;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void _SendTransactionWithSignature(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
@@ -1865,6 +1992,8 @@ void init(Local<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "acceptTerms", _AcceptTerms);
|
||||
NODE_SET_METHOD(exports, "fleets", _Fleets);
|
||||
NODE_SET_METHOD(exports, "stopCPUProfiling", _StopCPUProfiling);
|
||||
NODE_SET_METHOD(exports, "encodeTransfer", _EncodeTransfer);
|
||||
NODE_SET_METHOD(exports, "encodeFunctionCall", _EncodeFunctionCall);
|
||||
NODE_SET_METHOD(exports, "decodeParameters", _DecodeParameters);
|
||||
NODE_SET_METHOD(exports, "hexToNumber", _HexToNumber);
|
||||
NODE_SET_METHOD(exports, "numberToHex", _NumberToHex);
|
||||
@@ -1888,6 +2017,7 @@ void init(Local<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "openAccounts", _OpenAccounts);
|
||||
NODE_SET_METHOD(exports, "extractGroupMembershipSignatures", _ExtractGroupMembershipSignatures);
|
||||
NODE_SET_METHOD(exports, "callPrivateRPC", _CallPrivateRPC);
|
||||
NODE_SET_METHOD(exports, "verifyAccountPassword", _VerifyAccountPassword);
|
||||
NODE_SET_METHOD(exports, "sendTransactionWithSignature", _SendTransactionWithSignature);
|
||||
NODE_SET_METHOD(exports, "writeHeapProfile", _WriteHeapProfile);
|
||||
NODE_SET_METHOD(exports, "addPeer", _AddPeer);
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ in {
|
||||
yarn = super.yarn.override { nodejs = super.nodejs_20; };
|
||||
openjdk = super.openjdk17_headless;
|
||||
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
|
||||
versions = ["16.0" "16.1" "16.2"];
|
||||
versions = ["15.1" "15.2" "15.3" "15.4"];
|
||||
};
|
||||
go = super.go_1_21;
|
||||
clang = super.clang_15;
|
||||
|
||||
+2
-2
@@ -38,8 +38,8 @@ nix_install_type() {
|
||||
"${UID}") echo "single";;
|
||||
"(${UID})") echo "single";;
|
||||
"") echo "none";
|
||||
echo "No Nix installation detected!" >&2;;
|
||||
*) echo "Unknown Nix installation type!" >&2; exit 1;;
|
||||
echo "No Nix installtion detected!" >&2;;
|
||||
*) echo "Unknown Nix installtion type!" >&2; exit 1;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
export NIX_VERSION="2.24.11"
|
||||
export NIX_PACKAGE="nixVersions.nix_2_24"
|
||||
export NIX_VERSION="2.19.3"
|
||||
export NIX_PACKAGE="nixVersions.nix_2_19"
|
||||
export NIX_INSTALL_URL="https://nixos.org/releases/nix/nix-${NIX_VERSION}/install"
|
||||
export NIX_INSTALL_SHA256="5c610a0e97c8a2dd48c1a630608502e43fdecd7d2572463dd4166126106d9188"
|
||||
export NIX_INSTALL_SHA256="73d47b0ab783fddca1b2d44a03d52a74c97c28a1fc8ff5a29419079302ef9c3d"
|
||||
export NIX_INSTALL_PATH="/tmp/nix-install-${NIX_VERSION}"
|
||||
|
||||
@@ -38,9 +38,6 @@ in mkShell {
|
||||
LANG="en_US.UTF-8";
|
||||
LANGUAGE="en_US.UTF-8";
|
||||
|
||||
# Important to load our own Nix binary cache.
|
||||
NIX_CONFIG = builtins.readFile ./nix.conf;
|
||||
|
||||
# just a nicety for easy access to node scripts
|
||||
shellHook = ''
|
||||
export STATUS_MOBILE_HOME=$(git rev-parse --show-toplevel)
|
||||
|
||||
+4
-4
@@ -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.17.3",
|
||||
"@reown/walletkit": "^1.0.0",
|
||||
"@walletconnect/react-native-compat": "2.11.2",
|
||||
"@walletconnect/web3wallet": "1.10.2",
|
||||
"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",
|
||||
@@ -51,7 +51,7 @@
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-gifted-charts": "^1.3.2",
|
||||
"react-native-hole-view": "^3.0.0-alpha4",
|
||||
"react-native-image-crop-picker": "0.41.2",
|
||||
"react-native-image-crop-picker": "0.40.0",
|
||||
"react-native-image-resizer": "^1.2.3",
|
||||
"react-native-keychain": "8.1.2",
|
||||
"react-native-linear-gradient": "3.0.0-alpha.1",
|
||||
@@ -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.5",
|
||||
"react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#refs/tags/v2.6.3",
|
||||
"react-native-svg": "13.10.0",
|
||||
"react-native-webview": "13.6.3",
|
||||
"react-syntax-highlighter": "^15.5.0"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
@@ -37,8 +37,6 @@ SECRETS_ENV_VARS=(
|
||||
'ALCHEMY_ARBITRUM_SEPOLIA_TOKEN'
|
||||
'ALCHEMY_OPTIMISM_MAINNET_TOKEN'
|
||||
'ALCHEMY_OPTIMISM_SEPOLIA_TOKEN'
|
||||
'ALCHEMY_BASE_MAINNET_TOKEN'
|
||||
'ALCHEMY_BASE_SEPOLIA_TOKEN'
|
||||
'RARIBLE_MAINNET_API_KEY'
|
||||
'RARIBLE_TESTNET_API_KEY'
|
||||
'INFURA_TOKEN'
|
||||
|
||||
+3
-9
@@ -58,7 +58,7 @@
|
||||
:preloads [;; The official recommendation is to
|
||||
;; load the debugger preload first.
|
||||
flow-storm.api
|
||||
dev.re-frisk-preload
|
||||
re-frisk-remote.preload
|
||||
status-im.setup.schema-preload
|
||||
;; In order to use component test helpers in the REPL we need to
|
||||
;; preload namespaces that are not normally required by
|
||||
@@ -87,9 +87,7 @@
|
||||
status-im.config/ALCHEMY_ARBITRUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ARBITRUM_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ARBITRUM_SEPOLIA_TOKEN"
|
||||
status-im.config/ALCHEMY_OPTIMISM_MAINNET_TOKEN #shadow/env "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"
|
||||
status-im.config/ALCHEMY_BASE_MAINNET_TOKEN #shadow/env "ALCHEMY_BASE_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_BASE_SEPOLIA_TOKEN #shadow/env "ALCHEMY_BASE_SEPOLIA_TOKEN"}
|
||||
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"}
|
||||
:compiler-options {:output-feature-set :es5
|
||||
;; We disable `:fn-deprecated` warnings because we
|
||||
;; are managing deprecation via clj-kondo and we
|
||||
@@ -125,9 +123,7 @@
|
||||
status-im.config/ALCHEMY_ARBITRUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ARBITRUM_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ARBITRUM_SEPOLIA_TOKEN"
|
||||
status-im.config/ALCHEMY_OPTIMISM_MAINNET_TOKEN #shadow/env "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"
|
||||
status-im.config/ALCHEMY_BASE_MAINNET_TOKEN #shadow/env "ALCHEMY_BASE_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_BASE_SEPOLIA_TOKEN #shadow/env "ALCHEMY_BASE_SEPOLIA_TOKEN"}
|
||||
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"}
|
||||
:compiler-options {:output-feature-set :es6
|
||||
;;disable for android build as there
|
||||
;;is an intermittent warning with deftype
|
||||
@@ -169,8 +165,6 @@
|
||||
status-im.config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ARBITRUM_SEPOLIA_TOKEN"
|
||||
status-im.config/ALCHEMY_OPTIMISM_MAINNET_TOKEN #shadow/env "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"
|
||||
status-im.config/ALCHEMY_BASE_MAINNET_TOKEN #shadow/env "ALCHEMY_BASE_MAINNET_TOKEN"
|
||||
status-im.config/ALCHEMY_BASE_SEPOLIA_TOKEN #shadow/env "ALCHEMY_BASE_SEPOLIA_TOKEN"
|
||||
status-im.config/WALLET_CONNECT_PROJECT_ID #shadow/env "WALLET_CONNECT_PROJECT_ID"}
|
||||
:compiler-options
|
||||
{;; needed because we override require and it
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
(ns dev.re-frisk-preload
|
||||
(:require [re-frisk-remote.core :as re-frisk-remote]
|
||||
[status-im.config]))
|
||||
|
||||
(re-frisk-remote/enable {:host (str status-im.config/re-frisk-host ":" status-im.config/re-frisk-port)})
|
||||
@@ -71,10 +71,6 @@
|
||||
[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))
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
[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]
|
||||
@@ -137,11 +139,13 @@
|
||||
{:events [:browser/delete-bookmark]}
|
||||
[{:keys [db] :as cofx}
|
||||
url]
|
||||
(rf/merge cofx
|
||||
{:db (update db :bookmarks/bookmarks dissoc url)
|
||||
:json-rpc/call [{:method "wakuext_removeBookmark"
|
||||
:params [url]
|
||||
:on-success #()}]}))
|
||||
(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 #()}]})))
|
||||
|
||||
(defn can-go-back?
|
||||
[{:keys [history-index]}]
|
||||
@@ -353,11 +357,43 @@
|
||||
: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")
|
||||
@@ -448,6 +484,18 @@
|
||||
(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]
|
||||
@@ -500,8 +548,8 @@
|
||||
|
||||
(rf/defn lock-pressed
|
||||
{:events [:browser.ui/lock-pressed]}
|
||||
[cofx is-secure?]
|
||||
(update-browser-option cofx :show-tooltip (if is-secure? :secure :not-secure)))
|
||||
[cofx secure?]
|
||||
(update-browser-option cofx :show-tooltip (if secure? :secure :not-secure)))
|
||||
|
||||
(rf/defn close-tooltip-pressed
|
||||
{:events [:browser.ui/close-tooltip-pressed]}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
(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
|
||||
[cofx dapp-name permission message-id]
|
||||
[{:keys [db] :as cofx} dapp-name permission message-id params]
|
||||
(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]} dapp-name permission message-id yield-control?]
|
||||
[{:keys [db] :as cofx} 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"
|
||||
[_cofx permission message-id allowed? data]
|
||||
[{:keys [db] :as 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?]} next-permission]
|
||||
(when-let [{:keys [yield-control? permission message-id allowed? params]} next-permission]
|
||||
(if (and yield-control? allowed?)
|
||||
(permission-yield-control new-cofx dapp-name permission message-id)
|
||||
(permission-yield-control new-cofx dapp-name permission message-id params)
|
||||
(permission-show-permission new-cofx dapp-name permission message-id yield-control?)))))))
|
||||
|
||||
(rf/defn send-response-and-process-next-permission
|
||||
[cofx dapp-name requested-permission message-id]
|
||||
[{:keys [db] :as 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?]}
|
||||
(let [{:keys [requested-permission message-id dapp-name yield-control? params]}
|
||||
(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)
|
||||
(permission-yield-control dapp-name requested-permission message-id params)
|
||||
(send-response-and-process-next-permission dapp-name requested-permission message-id)))))
|
||||
|
||||
(rf/defn deny-permission
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
(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"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
[legacy.status-im.data-store.messages :as data-store.messages]
|
||||
[legacy.status-im.utils.deprecated-types :as types]
|
||||
[react-native.platform :as platform]
|
||||
[status-im.contexts.chat.messenger.messages.delete-message.events :as delete-message]
|
||||
[status-im.contexts.chat.messenger.messages.list.events :as message-list]
|
||||
[status-im.contexts.chat.messenger.messages.list.state :as view.state]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -118,35 +119,15 @@
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
{:db (assoc-in db [:messages chat-id message-id :outgoing-status] status)}))
|
||||
|
||||
(defn- update-db-delete-locally-without-time-limit
|
||||
"Delete message in re-frame db, used to handle received removed-messages"
|
||||
[db chat-id message-id deleted-by]
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
(update-in db [:messages chat-id message-id] assoc :deleted? true :deleted-by deleted-by)))
|
||||
|
||||
(rf/defn delete-messages-locally
|
||||
"Mark messages :deleted? localy in client"
|
||||
{:events [:chat.ui/delete-messages-localy]}
|
||||
[{:keys [db]} messages chat-id]
|
||||
(let [new-db (->> messages
|
||||
(filter #(get-in db [:messages chat-id (:message-id %)]))
|
||||
(reduce #(update-db-delete-locally-without-time-limit %1
|
||||
chat-id
|
||||
(:message-id %2)
|
||||
(:deleted-by %2))
|
||||
db))]
|
||||
(when new-db
|
||||
(message-list/rebuild-message-list {:db new-db} chat-id))))
|
||||
|
||||
(rf/defn handle-removed-messages
|
||||
[cofx removed-messages]
|
||||
[{:keys [db] :as cofx} removed-messages]
|
||||
(let [mark-as-deleted-fx (->> removed-messages
|
||||
(map #(assoc %
|
||||
:message-id (:messageId %)
|
||||
:deleted-by (:deletedBy %)))
|
||||
(group-by :chatId)
|
||||
(mapv (fn [[chat-id messages]]
|
||||
(delete-messages-locally messages chat-id))))
|
||||
(delete-message/delete-messages-localy messages chat-id))))
|
||||
mark-as-seen-fx (mapv
|
||||
(fn [removed-message]
|
||||
(let [chat-id (:chatId removed-message)
|
||||
@@ -155,7 +136,7 @@
|
||||
[message-id]
|
||||
nil)))
|
||||
removed-messages)
|
||||
remove-messages-fx (fn [_cofx]
|
||||
remove-messages-fx (fn [{:keys [db]}]
|
||||
{:dispatch [:activity-center.notifications/fetch-unread-count]})]
|
||||
(apply rf/merge
|
||||
cofx
|
||||
|
||||
@@ -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,6 +4,12 @@
|
||||
[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
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
(rf/defn update-ens-tx-state-and-redirect
|
||||
{:events [:update-ens-tx-state-and-redirect]}
|
||||
[cofx new-state username custom-domain? tx-hash]
|
||||
[{:keys [db] :as 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,6 +178,13 @@
|
||||
: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]
|
||||
@@ -232,14 +239,14 @@
|
||||
(let [{:keys [custom-domain?]} (:ens/registration db)
|
||||
chain-id (chain/chain-id db)
|
||||
usernames (into #{} (keys (get-in db [:ens/names chain-id])))
|
||||
next-state (state custom-domain? username usernames)]
|
||||
state (state custom-domain? username usernames)]
|
||||
(reset! resolve-last-id (random/id))
|
||||
(merge
|
||||
{:db (update db
|
||||
:ens/registration assoc
|
||||
:username username
|
||||
:state next-state)}
|
||||
(when (= next-state :searching)
|
||||
:state state)}
|
||||
(when (= state :searching)
|
||||
(let [{:profile/keys [profile]} db
|
||||
{:keys [public-key]} profile
|
||||
addresses (addresses-without-watch db)
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
(ns legacy.status-im.ethereum.macros
|
||||
(:require
|
||||
[clojure.java.io :as io]))
|
||||
[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))))
|
||||
|
||||
(defn network->icon
|
||||
[network]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
(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]))
|
||||
(:require-macros [legacy.status-im.ethereum.macros :as ethereum.macros :refer [resolve-icons]]))
|
||||
|
||||
(def default-native-currency
|
||||
(memoize
|
||||
@@ -12,6 +13,8 @@
|
||||
: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"
|
||||
@@ -35,8 +38,54 @@
|
||||
: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,9 +7,37 @@
|
||||
[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
|
||||
@@ -93,16 +121,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} {tx-hash :hash :as transaction}]
|
||||
[{:keys [db] :as cofx} {:keys [hash] :as transaction}]
|
||||
(when-let [watch-params
|
||||
(get-in db [:ethereum/watched-transactions tx-hash])]
|
||||
(get-in db [:ethereum/watched-transactions hash])]
|
||||
(let [{:keys [trigger-fn on-trigger]} watch-params]
|
||||
(when (trigger-fn db transaction)
|
||||
(rf/merge cofx
|
||||
{:db (update db
|
||||
:ethereum/watched-transactions
|
||||
dissoc
|
||||
tx-hash)}
|
||||
hash)}
|
||||
(on-trigger transaction))))))
|
||||
|
||||
(rf/defn check-watched-transactions
|
||||
@@ -124,14 +152,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} {tx-hash :hash :keys [id address] :as transfer}]
|
||||
(let [transfer-by-hash (get-in db [:wallet-legacy :accounts address :transactions tx-hash])]
|
||||
[{:keys [db] :as cofx} {:keys [hash id address] :as transfer}]
|
||||
(let [transfer-by-hash (get-in db [:wallet-legacy :accounts address :transactions hash])]
|
||||
(when-let [unique-id (when-not (= transfer transfer-by-hash)
|
||||
(if (and transfer-by-hash
|
||||
(not (= :pending
|
||||
(:type transfer-by-hash))))
|
||||
id
|
||||
tx-hash))]
|
||||
hash))]
|
||||
(rf/merge cofx
|
||||
{:db (assoc-in db
|
||||
[:wallet-legacy :accounts address :transactions unique-id]
|
||||
@@ -142,7 +170,11 @@
|
||||
[db address]
|
||||
(get-in db [:wallet-legacy :accounts (eip55/address->checksum address) :min-block]))
|
||||
|
||||
(defn count-min-block-transfers
|
||||
(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
|
||||
[db address]
|
||||
(get-in db
|
||||
[:wallet-legacy :accounts
|
||||
@@ -155,14 +187,14 @@
|
||||
{:keys [min-block min-block-transfers-count]}
|
||||
(reduce
|
||||
(fn [{:keys [min-block] :as acc}
|
||||
{tx-hash :hash block :block}]
|
||||
{:keys [block hash]}]
|
||||
(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 tx-hash])))
|
||||
(nil? (get-in db [:wallet-legacy :accounts checksum :transactions hash])))
|
||||
(update acc :min-block-transfers-count inc)
|
||||
|
||||
:else acc))
|
||||
@@ -171,7 +203,7 @@
|
||||
(js/parseInt min-block-string))
|
||||
|
||||
:min-block-transfers-count
|
||||
(count-min-block-transfers db address)}
|
||||
(min-block-transfers-count db address)}
|
||||
transfers)]
|
||||
(log/debug "[transactions] set-lowest-fetched-block"
|
||||
"address" address
|
||||
@@ -206,7 +238,7 @@
|
||||
{:db (update-fetching-status db addresses :history? false)})
|
||||
|
||||
(rf/defn tx-history-end-reached
|
||||
[{:keys [db]} address]
|
||||
[{:keys [db] :as cofx} address]
|
||||
(let [syncing-allowed? (utils.mobile-sync/syncing-allowed? db)]
|
||||
{:db (assoc-in db
|
||||
[:wallet-legacy :fetching address :all-fetched?]
|
||||
@@ -215,22 +247,23 @@
|
||||
:all-preloaded))}))
|
||||
|
||||
(rf/defn handle-new-transfer
|
||||
[cofx transfers {:keys [address limit]}]
|
||||
[{:keys [db] :as cofx} transfers {:keys [address limit]}]
|
||||
(log/debug "[transfers] new-transfers"
|
||||
"address" address
|
||||
"count" (count transfers)
|
||||
"limit" limit)
|
||||
(let [checksum (eip55/address->checksum address)
|
||||
effects (cond-> [(when (seq transfers)
|
||||
(set-lowest-fetched-block checksum transfers))]
|
||||
(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))]
|
||||
|
||||
(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
|
||||
@@ -278,12 +311,16 @@
|
||||
: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-count (or (count-min-block-transfers db address) 0)]
|
||||
(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)]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:transactions/get-transfers
|
||||
@@ -296,7 +333,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-count)}}}
|
||||
min-block-transfers-count)}}}
|
||||
(tx-fetching-in-progress [address]))))
|
||||
|
||||
(rf/defn get-fetched-transfers
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
legacy.status-im.chat.models.loading
|
||||
legacy.status-im.data-store.chats
|
||||
legacy.status-im.data-store.visibility-status-updates
|
||||
legacy.status-im.ens.core
|
||||
legacy.status-im.fleet.core
|
||||
legacy.status-im.group-chats.core
|
||||
legacy.status-im.log-level.core
|
||||
@@ -60,11 +59,21 @@
|
||||
(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]
|
||||
@@ -74,6 +83,11 @@
|
||||
(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]}
|
||||
[_]
|
||||
@@ -219,4 +233,7 @@
|
||||
: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]]]}))
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
(rf/defn save
|
||||
{:events [:fleet.ui/save-fleet-confirmed]}
|
||||
[{:keys [db] :as cofx} fleet]
|
||||
[{:keys [db now] :as cofx} fleet]
|
||||
(let [old-fleet (get-in db [:profile/profile :fleet])]
|
||||
(when (not= fleet old-fleet)
|
||||
(multiaccounts.update/multiaccount-update
|
||||
|
||||
@@ -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]}
|
||||
[_cofx chat-id]
|
||||
[{:keys [db] :as 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,6 +176,15 @@
|
||||
%
|
||||
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]
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
(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))))
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(rf/defn save-log-level
|
||||
{:events [:log-level.ui/change-log-level-confirmed]}
|
||||
[{:keys [db] :as cofx} log-level]
|
||||
[{:keys [db now] :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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
(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,43 +0,0 @@
|
||||
(ns ^{:doc "Mailserver events and API"} legacy.status-im.mailserver.core
|
||||
(:require
|
||||
[legacy.status-im.multiaccounts.update.core :as multiaccounts.update]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/defn handle-mailserver-changed
|
||||
[{:keys [db]} ms]
|
||||
(when (seq ms)
|
||||
{:db (assoc db :mailserver/current-id (keyword ms))}))
|
||||
|
||||
(rf/defn handle-mailserver-available
|
||||
[{:keys [db]} ms]
|
||||
{:db (assoc db :mailserver/current-id (keyword ms))})
|
||||
|
||||
(rf/defn toggle-use-mailservers
|
||||
[_ value]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_toggleUseMailservers"
|
||||
:params [value]
|
||||
:on-success #(log/info "successfully toggled use-mailservers" value)
|
||||
:on-error #(log/error "failed to toggle use-mailserver" value %)}]})
|
||||
|
||||
(rf/defn update-use-mailservers
|
||||
{:events [:mailserver.ui/use-history-switch-pressed]}
|
||||
[cofx use-mailservers?]
|
||||
(rf/merge cofx
|
||||
(multiaccounts.update/optimistic :use-mailservers? use-mailservers?)
|
||||
(toggle-use-mailservers use-mailservers?)))
|
||||
|
||||
(defn add-mailservers
|
||||
[db mailservers]
|
||||
(reduce (fn [db {:keys [fleet id name] :as mailserver}]
|
||||
(let [updated-mailserver
|
||||
(-> mailserver
|
||||
(update :id keyword)
|
||||
(assoc :name (if (seq name) name id))
|
||||
(dissoc :fleet))]
|
||||
(assoc-in db
|
||||
[:mailserver/mailservers (keyword fleet) (keyword id)]
|
||||
updated-mailserver)))
|
||||
db
|
||||
mailservers))
|
||||
@@ -1,6 +1,7 @@
|
||||
(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]
|
||||
@@ -28,6 +29,11 @@
|
||||
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 []
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
(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])))))))))
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
(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]} (:profile/profile db)]
|
||||
(let [{:keys [name preferred-name display-name address]} (:profile/profile db)]
|
||||
{:json-rpc/call [{:method "wakuext_sendContactUpdates"
|
||||
:params [(or preferred-name display-name name) "" ""]
|
||||
:on-success #(log/debug "sent contact update")}]}))
|
||||
@@ -79,27 +79,30 @@
|
||||
synced-stickers))
|
||||
|
||||
(rf/defn optimistic
|
||||
[{: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)]
|
||||
[{: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)]
|
||||
{:db (if setting-value
|
||||
(assoc-in db [:profile/profile setting] setting-value)
|
||||
(update db :profile/profile dissoc setting))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns legacy.status-im.node.core
|
||||
(:require
|
||||
[legacy.status-im.utils.deprecated-types :as types]))
|
||||
[legacy.status-im.utils.deprecated-types :as types]
|
||||
[status-im.config :as config]))
|
||||
|
||||
(defn fleets
|
||||
[{:keys [custom-fleets]}]
|
||||
@@ -8,3 +9,9 @@
|
||||
(mapv #(:fleets (types/json->clj %)) $)
|
||||
(conj $ custom-fleets)
|
||||
(reduce merge $)))
|
||||
|
||||
(defn current-fleet-key
|
||||
[db]
|
||||
(keyword (get-in db
|
||||
[:profile/profile :fleet]
|
||||
config/fleet)))
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
(rf/defn finish-success
|
||||
{:events [:my-profile/finish-success]}
|
||||
[{:keys [db]}]
|
||||
[{:keys [db] :as cofx}]
|
||||
{:db (update db :my-profile/seed assoc :step :finish :error nil :word nil)})
|
||||
|
||||
(rf/defn finish
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
(rf/defn set-qr-code
|
||||
{:events [:qr-scanner.callback/scan-qr-code-success]}
|
||||
[_ opts data]
|
||||
[{:keys [db]} 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]} {:keys [chat-id]}]
|
||||
[{:keys [db] :as cofx} {: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,4 +1,6 @@
|
||||
(ns legacy.status-im.react-native.resources)
|
||||
(ns legacy.status-im.react-native.resources
|
||||
(:require
|
||||
[legacy.status-im.ui.components.colors :as colors]))
|
||||
|
||||
(def ui
|
||||
{:empty-chats-header (js/require "../resources/images/ui/empty-chats-header.png")
|
||||
@@ -50,6 +52,10 @@
|
||||
: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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
(: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]))
|
||||
@@ -22,6 +23,21 @@
|
||||
: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,6 +1,64 @@
|
||||
(ns legacy.status-im.subs.ens
|
||||
(:require
|
||||
[re-frame.core :as re-frame]))
|
||||
[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/reg-sub
|
||||
:ens/current-names
|
||||
@@ -8,3 +66,37 @@
|
||||
:<- [: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})))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns legacy.status-im.subs.mailservers
|
||||
(:require
|
||||
[legacy.status-im.fleet.core :as fleet]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
@@ -10,9 +11,58 @@
|
||||
(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]
|
||||
:<- [:mailserver/mailservers]
|
||||
(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]
|
||||
(fn [multiaccount]
|
||||
(get-in multiaccount
|
||||
[:pinned-mailservers (fleet/current-fleet-sub multiaccount)])))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user