Compare commits

...
Author SHA1 Message Date
jo-mut e39c249868 Fix: logo and community image banner not showing
https://github.com/status-im/status-go/compare/4d705ce1...6b1cac69
2023-03-28 13:51:35 +03:00
J.M.N c6addce62a refactored scroll-page component to use flatlist instead of scroll view 2023-03-28 13:51:34 +03:00
J.M.N 18e2652ec3 mute community 2023-03-28 13:51:34 +03:00
J.M.N e938c2393a Fix: scroll-page component sticky header height 2023-03-28 13:51:34 +03:00
yqrashawn ac2d10bc5d fix: disable edit image message until it's implemented (#15496) 2023-03-28 17:04:01 +08:00
Ibrahem Khalil e825f930fa Add accessibility label for community options button (#15484) 2023-03-28 07:50:07 +02:00
frank 394dfde87b fixed #15446 (App crashes on syncing QR...) (#15464)
https://github.com/status-im/status-go/compare/4cc53630...458f2817
2023-03-28 12:27:22 +08:00
Alexander e4db23b0a9 Center input value within the field (#15472) 2023-03-27 23:00:33 +02:00
Ulises Manuel CárdenasandJamie Caprani 94ddbbcd2e Add checked? property, dark blur variant & tests to disclaimer component
Co-authored-by: Jamie Caprani <jamiecaprani@gmail.com>
2023-03-27 14:06:09 -06:00
Parvesh Monu ac27314547 fix login/forget password button overlaps password input (#15488) 2023-03-27 22:08:05 +05:30
Icaro Motta 7a4b12acf4 Make component test helpers usable from the REPL (#15468)
This commit makes the test-helpers.component namespace loadable in the REPL,
plus other changes that allow for a reasonably enjoyable RDD (REPL-Driven
Development) workflow.

Why? I want to be able to get instant feedback when I render a component with
the RN Testing Library (RNTL), and only once I'm satisfied with my findings is
when I proceed to write/update the tests. This nearly instant feedback loop is
only feasible using the ClojureScript REPL, and I'd rather not endure long
recompilation cycles.

Note that by REPL I mean connecting to the CLJS REPL of the Shadow-CLJS :mobile
target.

Essentially, this is what this commit does:

- [x] Allow the test-helpers.component namespace to be evaluated in the REPL.
      This is now possible because I changed all functions that assumed js/jest
      existed with a guard clause using the CLJS macro exists?. Without the
      guard clauses, evaluating the namespace explodes due to stuff like
      js/jest.useFakeTimers that fail in compile time (it's a syntax sugar
      macro).
- [x] Change the family of functions to get the translation by text to either
      translate using i18n/label or translate with the dummy prefix tx:,
      depending if the code is running inside the Jest runtime or not.
- [x] Wrap remaining RNTL query functions, except for the find-* ones, since
      they don't work at all outside the Jest runtime.
- [x] All wrapped functions support the original arguments supported by RNTL.
      Arguments are always converted with clj->js.
- [x] All wrapped functions can optionally take a node (ReactTestInstance) as
      their first argument, otherwise the global screen object will be used.
      This is very important! See the explanation on section Doesn't RNTL
      recommend using the screen object?
- [x] Update Shadow-CLJS preloads, so that (in development) you can fire off the
      REPL and always be ready to call component test helpers. This is critical!

What else would be possible? Just an idea, but now that we can easily render
components using the same machinery provided by RNTL in the tests, we can
roughly implement Storybook's Play function
https://storybook.js.org/docs/react/writing-stories/play-function

Lesson learned: In the REPL, you may need to call
(re-frame.core/clear-subscription-cache!), otherwise you will experience
subscriptions returning the same value if their arguments are the same. For
example, I faced this while playing with the namespace
status-im2.contexts.communities.menus.community-options.component-spec. There
are better ways to solve this particular problem in the context of tests if we
use the tooling provided by day8.re-frame.test.

Doesn't RNTL recommend using the screen object? Indeed, it is recommended to use
the screen object instead of destructuring the results of RNTL render. It's just
easier and less error prone, but this only works reliably within the Jest
runtime, since it automatically cleans up rendered state after each test. When
using the REPL this is no longer the case, and I faced some errors, like Unable
to find node on an unmounted component, where RNTL would refuse to re-render
components, even if I explicitly unmounted them or called cleanup.

The only reliable solution I found was to store the result of render (a node)
and pass it to every subsequent call. This is not a workaround, it's officially
supported, but it's a tad less convenient. You can also not pass the node
reference and it should work most of the time.

Practical examples

Workflow suggestion: write your local experiments in the same namespace as the
component spec and within the comment macro. This way, you can have the Jest
watcher running and a REPL connected to :mobile, and they won't step on each
other. For the test watcher, I usually change quo2-core-spec or
status-im2.core-spec to only require what I'm interested, otherwise Jest
consumes way too many resources.

```clojure
;; Namespace quo2.components.colors.color-picker.component-spec
(h/test "color picker color changed"
  (let [selected (reagent/atom nil)]
    (h/render [color-picker/view {:on-change #(reset! selected %)}])
    (h/fire-event :press (get (h/get-all-by-label-text :color-picker-item) 0))
    (-> (h/expect @selected)
        (.toStrictEqual :blue))))

(comment
  (def selected (atom nil))
  (def c (h/render [color-picker/view {:on-change #(reset! selected %)}]))

  (h/fire-event :press (get (h/get-all-by-label-text c :color-picker-item) 0))

  ;; Options are passed down converted to JS types.
  (h/debug c {:message "Rendering header"})

  @selected ; => :blue
)
```

```clojure
;; Namespace quo2.components.tags.--tests--.status-tags-component-spec
(h/test "renders status tag with pending type"
  (render-status-tag {:status {:type :pending}
                      :label  "Pending"
                      :size   :small})
  (-> (h/expect (h/get-all-by-label-text :status-tag-pending))
      (.toBeTruthy))
  (-> (h/expect (h/get-by-text "Pending"))
      (.toBeTruthy)))

(comment
  (def c (render-status-tag {:status {:type :pending}
                             :label  "Pending"
                             :size   :small}))

  (h/get-all-by-label-text c :status-tag-pending))
```

```clojure
;; Namespace status-im2.contexts.communities.menus.community-options.component-spec
(h/test "joined and muted community"
  (setup-subs {:communities/my-pending-request-to-join nil
               :communities/community                  {:joined       true
                                                        :muted        true
                                                        :token-gated? true}})
  (h/render [options/community-options-bottom-sheet {:id "test"}])
  (-> (h/expect (h/get-by-translation-text :unmute-community))
      (.toBeTruthy)))

(comment
  (setup-subs {:communities/my-pending-request-to-join nil
               :communities/community                  {:joined       true
                                                        :muted        true
                                                        :token-gated? true}})
  (def c (h/render [options/community-options-bottom-sheet {:id "test"}]))
  (some? (h/get-by-translation-text c :invite-people-from-contacts)) ; => true
)
```
2023-03-27 11:54:56 -03:00
Brian Sztamfater 4e6dea6b36 feat: enable biometrics screen 2023-03-27 14:23:23 +01:00
Ulises M 899f89c800 Implement create password screens 2023-03-27 14:21:39 +01:00
Volodymyr Kozieiev b121678281 Fix mentions component remounting that forced user to click 2 times (#15474) 2023-03-27 12:29:09 +01:00
Roman Volosovskyi 7f87c007c1 [#15471] Add PreviewPrivacy to CreateAccountRequest 2023-03-27 09:57:07 +02:00
Churikova Tetiana 11726df060 e2e: add to run_in_parallel args handling 2023-03-26 20:57:06 +02:00
John Ngei 2f84cfd354 Onboarding notifications flow
* enable notifications onboarding

* added blurred background

* support navigate to previous screen
2023-03-25 01:40:36 +03:00
Parvesh Monu 7b60a5f867 Refactor app theme management (#15455) 2023-03-24 22:04:55 +05:30
Mohamed Javid 7d4be37111 [Feature] Sign in by scanning sync QR code (#15416) 2023-03-24 20:36:25 +05:30
Jamie Caprani 2f19badc6c Add seed phrase flow & customization color 2023-03-24 14:24:18 +00:00
Roman Volosovskyi 3b034265c0 [#15443] Show community name/description in message on Android 2023-03-24 12:53:13 +01:00
Churikova Tetiana 5fffc230c9 e2e: new community screens 2023-03-24 11:22:02 +01:00
Parvesh Monu 5c92b7eb1e remove navigate-to-nav2 event (#15454) 2023-03-24 11:14:17 +01:00
frank 45da51bea6 changes corresponding to refactor of local pair of status-go (PR #3248) (#15412) 2023-03-23 20:23:26 +08:00
Alexander 8d166a3a52 Fix for sender's name, profile icon, chat key, timestamp being lost for messages that contain images (#15426)
* Fix for sender's name, profile icon, chat key, timestamp being lost for messages that contain images

Lint fix

Lint fix

* Lint fix

* Text fix
2023-03-23 10:10:07 +01:00
Churikova Tetiana 401f7d7383 e2e: activity centre and more checks 2023-03-22 18:20:48 +01:00
erikseppanen c238ebe36e Add validation for when adding a contact (#15192) 2023-03-22 13:13:56 -04:00
Jamie Caprani a502da6ea4 https://github.com/status-im/status-go/compare/9c1c01c6...48eb7052 (#15401)
feat: add create profile to onboarding
2023-03-22 06:51:38 -07:00
Omar Basem f9255100a1 feat: bottom sheet screen (#15399)
* feat: bottom sheet screen
2023-03-22 17:31:20 +04:00
yqrashawn daa78b4171 fix: still need this fix to run-ios on m1 mac (#15439) 2023-03-22 21:13:51 +08:00
yqrashawn f2c8f21336 fix: reply with album (#15424) 2023-03-22 21:04:39 +08:00
Icaro Motta 554476ede9 Remove support for cancelling outgoing contact requests (#15415)
Removes the feature that allows users to cancel outgoing contact requests (possible spam vector). From now on, the user who sent the contact request will only be able to see the notification in the pending state. It seems this feature will be revisited in the future, but for now the agreement is to do the simplest thing and remove it.

Fixes https://github.com/status-im/status-mobile/issues/15357

Steps to test:

- Send CR from A to B.
- A should see a new notification in the pending state.
- B should receive a notification. If B accepts the CR, then A's pending CR disappears. If B declines the CR, then A's notification stays pending forever.

Note: As expected, A can swipe left->right to mark the outgoing pending notification as read or swipe right->left to delete it.
2023-03-22 08:59:32 -03:00
pavloburykh 8c358d4ae4 e2e: fix login
Signed-off-by: Parvesh Monu <parvesh.dhullmonu@gmail.com>
2023-03-22 13:52:16 +05:30
Parvesh Monu 937c128c08 Onboarding app locked flow 2023-03-22 13:41:05 +05:30
Parvesh Monu 03cf4cec0e fix intro navigation (#15430) 2023-03-22 01:27:09 +05:30
Ulises Manuel Cárdenas 554f8aff09 Add input tests
Also fixes text align on non-multiline inputs
2023-03-21 12:28:02 -06:00
jakub d71cfd12c1 nix: unpatched Node modules for Gradle deps update
Otherwise we can end up with Gradle failing to find the dependencies
because we've patched away all entries referencing external repos.

Also made the regex in AWS parser a but more strict.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-21 15:21:58 +01:00
Omar Basem a5d767515d Remove 100ms delay when opening image (#15422)
* remove image delay
2023-03-21 17:57:26 +04:00
jakub c38fdec5b7 ci: upgrade Node.js from 16.17.1 to 18.9.1
The End-of-Life for Node.js 16 is set to 11th of September 2023.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-03-21 13:12:56 +01:00
Jamie Caprani ada7a02c21 chore: add skeleton for sign in with syncing flow 2023-03-21 09:42:31 +00:00
Siddarth Kumar 3f3cbe98a4 set signing to auto for debug builds (#15420) 2023-03-21 13:10:55 +05:30
146 changed files with 3803 additions and 1370 deletions
+29
View File
@@ -61,6 +61,35 @@ the source file. For a real example, see
[rn/view (do-something)]]) [rn/view (do-something)]])
``` ```
### Always add styles inside the `:style` key
Although when compiling ReactNative for mobile some components are able work with
their styles in the top-level of the properties map, prefer to add them inside the
`:style` key in order to separate styles from properties:
```clojure
;; bad
[rn/button {:flex 1
:padding-vertical 10
:padding-horizontal 20
:on-press #(js/alert "Hi!")
:title "Button"}]
;; good
[rn/button {:style {:flex 1
:padding-vertical 10
:padding-horizontal 20}
:on-press #(js/alert "Hi!")
:title "Button"}]
;; better
;; (define them in a style ns & place them inside `:style` key)
[rn/button {:style (style/button)
:on-press #(js/alert "Hi!")
:title "Button"}
]
```
### Don't use percents to define width/height ### Don't use percents to define width/height
In ReactNative, all layouts use the [flexbox In ReactNative, all layouts use the [flexbox
+10
View File
@@ -35,6 +35,16 @@ abstract_target 'Status' do
target 'StatusImPR' do target 'StatusImPR' do
end end
post_install do |installer|
# some of libs wouldn't be build for x86_64 otherwise and that is
# necessary for ios simulators
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
end
end
end
use_native_modules! use_native_modules!
end end
+6 -12
View File
@@ -417,9 +417,7 @@
TestTargetID = 13B07F861A680F5B00A75B9A; TestTargetID = 13B07F861A680F5B00A75B9A;
}; };
13B07F861A680F5B00A75B9A = { 13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 8B5X2M6H2Y;
LastSwiftMigration = 1140; LastSwiftMigration = 1140;
ProvisioningStyle = Manual;
SystemCapabilities = { SystemCapabilities = {
com.apple.BackgroundModes = { com.apple.BackgroundModes = {
enabled = 1; enabled = 1;
@@ -843,9 +841,8 @@
BUNDLE_ID_SUFFIX = .debug; BUNDLE_ID_SUFFIX = .debug;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements; CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = "Status Debug"; CUSTOM_PRODUCT_NAME = "Status Debug";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8B5X2M6H2Y; DEVELOPMENT_TEAM = 8B5X2M6H2Y;
@@ -898,8 +895,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum; PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum;
PRODUCT_NAME = StatusIm; PRODUCT_NAME = StatusIm;
PROVISIONING_PROFILE = "9da75626-9594-43d9-a827-0f6d43c28f54"; PROVISIONING_PROFILE_SPECIFIER = "";
PROVISIONING_PROFILE_SPECIFIER = "match Development im.status.ethereum";
SWIFT_OBJC_BRIDGING_HEADER = "StatusIm-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "StatusIm-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -917,9 +913,8 @@
BUNDLE_ID_SUFFIX = ""; BUNDLE_ID_SUFFIX = "";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements; CODE_SIGN_ENTITLEMENTS = StatusIm/StatusIm.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CUSTOM_PRODUCT_NAME = Status; CUSTOM_PRODUCT_NAME = Status;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8B5X2M6H2Y; DEVELOPMENT_TEAM = 8B5X2M6H2Y;
@@ -964,8 +959,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum; PRODUCT_BUNDLE_IDENTIFIER = im.status.ethereum;
PRODUCT_NAME = StatusIm; PRODUCT_NAME = StatusIm;
PROVISIONING_PROFILE = "e2202b12-7a66-4ff7-af3c-a52e35f32dc1"; PROVISIONING_PROFILE_SPECIFIER = "";
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc im.status.ethereum";
SWIFT_OBJC_BRIDGING_HEADER = "StatusIm-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "StatusIm-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = 1;
@@ -323,6 +323,30 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
callback.invoke(finalConfig); callback.invoke(finalConfig);
} }
@ReactMethod
public void createAccountAndLogin(final String createAccountRequest) {
Log.d(TAG, "createAccountAndLogin");
String result = Statusgo.createAccountAndLogin(createAccountRequest);
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "createAccountAndLogin success: " + result);
Log.d(TAG, "Geth node started");
} else {
Log.e(TAG, "createAccountAndLogin failed: " + result);
}
}
@ReactMethod
public void restoreAccountAndLogin(final String restoreAccountRequest) {
Log.d(TAG, "restoreAccountAndLogin");
String result = Statusgo.restoreAccountAndLogin(restoreAccountRequest);
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "restoreAccountAndLogin success: " + result);
Log.d(TAG, "Geth node started");
} else {
Log.e(TAG, "restoreAccountAndLogin failed: " + result);
}
}
@ReactMethod @ReactMethod
public void saveAccountAndLogin(final String multiaccountData, final String password, final String settings, final String config, final String accountsData) { public void saveAccountAndLogin(final String multiaccountData, final String password, final String settings, final String config, final String accountsData) {
try { try {
@@ -520,6 +544,8 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
return; return;
} }
Log.d(TAG, "[Opening accounts" + rootDir);
Runnable r = new Runnable() { Runnable r = new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -785,9 +811,10 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
@ReactMethod @ReactMethod
public void getConnectionStringForBootstrappingAnotherDevice(final String configJSON, final Callback callback) throws JSONException { public void getConnectionStringForBootstrappingAnotherDevice(final String configJSON, final Callback callback) throws JSONException {
final JSONObject jsonConfig = new JSONObject(configJSON); final JSONObject jsonConfig = new JSONObject(configJSON);
final String keyUID = jsonConfig.getString("keyUID"); final JSONObject senderConfig = jsonConfig.getJSONObject("senderConfig");
final String keyUID = senderConfig.getString("keyUID");
final String keyStorePath = this.getKeyStorePath(keyUID); final String keyStorePath = this.getKeyStorePath(keyUID);
jsonConfig.put("keystorePath", keyStorePath); senderConfig.put("keystorePath", keyStorePath);
executeRunnableStatusGoMethod(() -> Statusgo.getConnectionStringForBootstrappingAnotherDevice(jsonConfig.toString()), callback); executeRunnableStatusGoMethod(() -> Statusgo.getConnectionStringForBootstrappingAnotherDevice(jsonConfig.toString()), callback);
} }
@@ -795,9 +822,10 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
@ReactMethod @ReactMethod
public void inputConnectionStringForBootstrapping(final String connectionString, final String configJSON, final Callback callback) throws JSONException { public void inputConnectionStringForBootstrapping(final String connectionString, final String configJSON, final Callback callback) throws JSONException {
final JSONObject jsonConfig = new JSONObject(configJSON); final JSONObject jsonConfig = new JSONObject(configJSON);
final JSONObject receiverConfig = jsonConfig.getJSONObject("receiverConfig");
final String keyStorePath = pathCombine(this.getNoBackupDirectory(), "/keystore"); final String keyStorePath = pathCombine(this.getNoBackupDirectory(), "/keystore");
jsonConfig.put("keystorePath", keyStorePath); receiverConfig.put("keystorePath", keyStorePath);
jsonConfig.put("rootDataDir", this.getNoBackupDirectory()); receiverConfig.getJSONObject("nodeConfig").put("rootDataDir", this.getNoBackupDirectory());
executeRunnableStatusGoMethod(() -> Statusgo.inputConnectionStringForBootstrapping(connectionString, jsonConfig.toString()), callback); executeRunnableStatusGoMethod(() -> Statusgo.inputConnectionStringForBootstrapping(connectionString, jsonConfig.toString()), callback);
} }
@@ -1129,6 +1157,23 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
executeRunnableStatusGoMethod(() -> Statusgo.deleteImportedKey(address, password, keyStoreDir), callback); executeRunnableStatusGoMethod(() -> Statusgo.deleteImportedKey(address, password, keyStoreDir), callback);
} }
@ReactMethod(isBlockingSynchronousMethod = true)
public String keystoreDir() {
final String absRootDirPath = this.getNoBackupDirectory();
return pathCombine(absRootDirPath, "keystore");
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String backupDisabledDataDir() {
return this.getNoBackupDirectory();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String logFilePath() {
return getLogsFile().getAbsolutePath();
}
@ReactMethod(isBlockingSynchronousMethod = true) @ReactMethod(isBlockingSynchronousMethod = true)
public String generateAlias(final String seed) { public String generateAlias(final String seed) {
return Statusgo.generateAlias(seed); return Statusgo.generateAlias(seed);
@@ -309,12 +309,14 @@ RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)c
callback:(RCTResponseSenderBlock)callback) { callback:(RCTResponseSenderBlock)callback) {
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding]; NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil]; NSError *error;
NSString *keyUID = [configDict objectForKey:@"keyUID"]; NSMutableDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:&error];
NSMutableDictionary *senderConfig = configDict[@"senderConfig"];
NSString *keyUID = senderConfig[@"keyUID"];
NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID]; NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID];
NSString *keystoreDir = multiaccountKeystoreDir.path; NSString *keystoreDir = multiaccountKeystoreDir.path;
[configDict setValue:keystoreDir forKey:@"keystorePath"]; [senderConfig setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO]; NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO];
NSString *result = StatusgoGetConnectionStringForBootstrappingAnotherDevice(modifiedConfigJSON); NSString *result = StatusgoGetConnectionStringForBootstrappingAnotherDevice(modifiedConfigJSON);
@@ -326,17 +328,20 @@ RCT_EXPORT_METHOD(inputConnectionStringForBootstrapping:(NSString *)cs
callback:(RCTResponseSenderBlock)callback) { callback:(RCTResponseSenderBlock)callback) {
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding]; NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil]; NSError *error;
NSMutableDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:&error];
NSMutableDictionary *receiverConfig = configDict[@"receiverConfig"];
NSMutableDictionary *nodeConfig = receiverConfig[@"nodeConfig"];
NSFileManager *fileManager = [NSFileManager defaultManager]; NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject]; NSURL *rootUrl =[[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *rootDataDir = rootUrl.path;
NSURL *multiaccountKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"]; NSURL *multiaccountKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSString *keystoreDir = multiaccountKeystoreDir.path; NSString *keystoreDir = multiaccountKeystoreDir.path;
NSString *rootDataDir = rootUrl.path;
[configDict setValue:keystoreDir forKey:@"keystorePath"]; [receiverConfig setValue:keystoreDir forKey:@"keystorePath"];
[configDict setValue:rootDataDir forKey:@"rootDataDir"]; [nodeConfig setValue:rootDataDir forKey:@"rootDataDir"];
NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO]; NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO];
NSString *result = StatusgoInputConnectionStringForBootstrapping(cs,modifiedConfigJSON); NSString *result = StatusgoInputConnectionStringForBootstrapping(cs, modifiedConfigJSON);
callback(@[result]); callback(@[result]);
} }
@@ -857,6 +862,34 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(generateAlias:(NSString *)publicKey) {
return StatusgoGenerateAlias(publicKey); return StatusgoGenerateAlias(publicKey);
} }
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(keystoreDir) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *commonKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
return commonKeystoreDir.path;
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(backupDisabledDataDir) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
return rootUrl.path;
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(logFilePath) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
return rootUrl.path;
}
RCT_EXPORT_METHOD(generateAliasAsync:(NSString *)publicKey RCT_EXPORT_METHOD(generateAliasAsync:(NSString *)publicKey
callback:(RCTResponseSenderBlock)callback) { callback:(RCTResponseSenderBlock)callback) {
#if DEBUG #if DEBUG
@@ -934,6 +967,20 @@ RCT_EXPORT_METHOD(identiconAsync:(NSString *)publicKey
callback(@[result]); callback(@[result]);
} }
RCT_EXPORT_METHOD(createAccountAndLogin:(NSString *)request) {
#if DEBUG
NSLog(@"createAccountAndLogin() method called");
#endif
StatusgoCreateAccountAndLogin(request);
}
RCT_EXPORT_METHOD(restoreAccountAndLogin:(NSString *)request) {
#if DEBUG
NSLog(@"restoreAccountAndLogin() method called");
#endif
StatusgoRestoreAccountAndLogin(request);
}
RCT_EXPORT_METHOD(generateAliasAndIdenticonAsync:(NSString *)publicKey RCT_EXPORT_METHOD(generateAliasAndIdenticonAsync:(NSString *)publicKey
callback:(RCTResponseSenderBlock)callback) { callback:(RCTResponseSenderBlock)callback) {
#if DEBUG #if DEBUG
+25 -25
View File
@@ -11572,57 +11572,57 @@
}, },
{ {
"path": "org/slf4j/jcl-over-slf4j/2.0.6", "path": "org/slf4j/jcl-over-slf4j/2.0.7",
"repo": "https://repo.maven.apache.org/maven2", "repo": "https://repo.maven.apache.org/maven2",
"files": { "files": {
"jcl-over-slf4j-2.0.6.pom": { "jcl-over-slf4j-2.0.7.pom": {
"sha1": "c9d9caedcca2a1564e47b55614960958c5f78773", "sha1": "15536e4a74a7aa317322a3d7814db8251de60d6f",
"sha256": "1swgy5hwv54b1i1117shn3wmwba3v78qvy6cnv8v243j7ac6vzqq" "sha256": "1rzjwbmzf2hb85j6c41mch803vqwqdq1rfrrirfhp1lfpfnzyd23"
}, },
"jcl-over-slf4j-2.0.6.jar": { "jcl-over-slf4j-2.0.7.jar": {
"sha1": "839ff57e112f2e28ef372e96d135696a6896b9ad", "sha1": "f127fe5ee53404a8b3697cdd032dd1dd6a29dd77",
"sha256": "0s9scdwkxwj3al87ihanj10rscrjh44kligr5asb7qpl28d1xvks" "sha256": "0hhpc2qdl4aa9mb8dk89wzbd5vkna557vjmjdmfswvfjw5bng021"
} }
} }
}, },
{ {
"path": "org/slf4j/slf4j-api/2.0.6", "path": "org/slf4j/slf4j-api/2.0.7",
"repo": "https://repo.maven.apache.org/maven2", "repo": "https://repo.maven.apache.org/maven2",
"files": { "files": {
"slf4j-api-2.0.6.pom": { "slf4j-api-2.0.7.pom": {
"sha1": "2b93d5f66ad2ba259bf4b2c94da39f0d6c544400", "sha1": "facf002401dbff2065d4257690651e3fa775e3f4",
"sha256": "0dipzawn8rxikciij2z06c25rb2vdj83s8ga3a7n10r77p2qcklb" "sha256": "10br4q2w50fn3mkvk1xji81wdpy86cm0wyv6m30xa0ha1v7kqh1d"
}, },
"slf4j-api-2.0.6.jar": { "slf4j-api-2.0.7.jar": {
"sha1": "88c40d8b4f33326f19a7d3c0aaf2c7e8721d4953", "sha1": "41eb7184ea9d556f23e18b5cb99cad1f8581fc00",
"sha256": "1nkv0z4dpkvp6pr9ph8087z5r691bv95xdv3gnfi6s5j23a94aig" "sha256": "1x26v62ypzpp84yfad8mx53llbacq6580y34v8nc618r7awrhqjx"
} }
} }
}, },
{ {
"path": "org/slf4j/slf4j-jdk14/2.0.6", "path": "org/slf4j/slf4j-jdk14/2.0.7",
"repo": "https://repo.maven.apache.org/maven2", "repo": "https://repo.maven.apache.org/maven2",
"files": { "files": {
"slf4j-jdk14-2.0.6.pom": { "slf4j-jdk14-2.0.7.pom": {
"sha1": "96ac9d6ab608e787d54892b35205bc6a560aa007", "sha1": "31039eac9f263c48bda14efd90aef0fb7a5a0e6a",
"sha256": "0gnsyy1n5v8xqdy6a6p1752m5c4afihpw0n36n6aqw01y9l86q1h" "sha256": "1vx1ziyx36zf2lrpkvvm4c75hp8pw8vjpiyr5120ss8nwpd33qal"
}, },
"slf4j-jdk14-2.0.6.jar": { "slf4j-jdk14-2.0.7.jar": {
"sha1": "13056cb341f2d8795120f8027766a058da874f85", "sha1": "d91cd16b55ffbd5f46c60d0173fd4eeccacfee2d",
"sha256": "0jncm8a2ppliqpzkbqm26sn98mwif8c1nligc1zh4jbzr8mxyzhy" "sha256": "1xvphj12jpnvr1sw8zx42p49in4xi61mjvpxrkk3q3qpyad7017r"
} }
} }
}, },
{ {
"path": "org/slf4j/slf4j-parent/2.0.6", "path": "org/slf4j/slf4j-parent/2.0.7",
"repo": "https://repo.maven.apache.org/maven2", "repo": "https://repo.maven.apache.org/maven2",
"files": { "files": {
"slf4j-parent-2.0.6.pom": { "slf4j-parent-2.0.7.pom": {
"sha1": "0f99f8426c64fc5e0c4b6749245e50484a16c372", "sha1": "3f97e066227cc2353d212b8c43440b0bf4c033f3",
"sha256": "091il49sidk0lcmzdy0vl3a4l16kw6x1q0l9vk0hir1ipq66b0hl" "sha256": "1rs55v9gqda5lks89dd81frps5c9g3zgxk832qyckw9srlvbp0n1"
} }
} }
}, },
+4 -4
View File
@@ -802,9 +802,9 @@ https://repo.maven.apache.org/maven2/org/ow2/asm/asm/6.0/asm-6.0.pom
https://repo.maven.apache.org/maven2/org/ow2/asm/asm/9.4/asm-9.4.pom https://repo.maven.apache.org/maven2/org/ow2/asm/asm/9.4/asm-9.4.pom
https://repo.maven.apache.org/maven2/org/ow2/ow2/1.3/ow2-1.3.pom https://repo.maven.apache.org/maven2/org/ow2/ow2/1.3/ow2-1.3.pom
https://repo.maven.apache.org/maven2/org/ow2/ow2/1.5.1/ow2-1.5.1.pom https://repo.maven.apache.org/maven2/org/ow2/ow2/1.5.1/ow2-1.5.1.pom
https://repo.maven.apache.org/maven2/org/slf4j/jcl-over-slf4j/2.0.6/jcl-over-slf4j-2.0.6.pom https://repo.maven.apache.org/maven2/org/slf4j/jcl-over-slf4j/2.0.7/jcl-over-slf4j-2.0.7.pom
https://repo.maven.apache.org/maven2/org/slf4j/slf4j-api/2.0.6/slf4j-api-2.0.6.pom https://repo.maven.apache.org/maven2/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.pom
https://repo.maven.apache.org/maven2/org/slf4j/slf4j-jdk14/2.0.6/slf4j-jdk14-2.0.6.pom https://repo.maven.apache.org/maven2/org/slf4j/slf4j-jdk14/2.0.7/slf4j-jdk14-2.0.7.pom
https://repo.maven.apache.org/maven2/org/slf4j/slf4j-parent/2.0.6/slf4j-parent-2.0.6.pom https://repo.maven.apache.org/maven2/org/slf4j/slf4j-parent/2.0.7/slf4j-parent-2.0.7.pom
https://repo.maven.apache.org/maven2/org/sonatype/oss/oss-parent/7/oss-parent-7.pom https://repo.maven.apache.org/maven2/org/sonatype/oss/oss-parent/7/oss-parent-7.pom
https://repo.maven.apache.org/maven2/org/sonatype/oss/oss-parent/9/oss-parent-9.pom https://repo.maven.apache.org/maven2/org/sonatype/oss/oss-parent/9/oss-parent-9.pom
+3 -3
View File
@@ -29,17 +29,17 @@ function findPackage(line, regex) {
if (line ~ "com.facebook.react:react-native") { continue } if (line ~ "com.facebook.react:react-native") { continue }
# Example: +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.50 # Example: +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.50
if (findPackage(line, "--- ([^:]+):([^:]+):([^ ]+)$")) { if (findPackage(line, "--- ([^ :]+):([^ :]+):([^ :]+)$")) {
continue continue
} }
# Example: +--- androidx.lifecycle:lifecycle-common:{strictly 2.0.0} -> 2.0.0 (c) # Example: +--- androidx.lifecycle:lifecycle-common:{strictly 2.0.0} -> 2.0.0 (c)
if (findPackage(line, "--- ([^:]+):([^:]+):[^ ]+ -> ([^: ]+) ?(\\([*c]\\))?$")) { if (findPackage(line, "--- ([^ :]+):([^ :]+):[^:]+ -> ([^ :]+) ?(\\([*c]\\))?$")) {
continue continue
} }
# Example: +--- com.android.support:appcompat-v7:28.0.0 -> androidx.appcompat:appcompat:1.0.2 # Example: +--- com.android.support:appcompat-v7:28.0.0 -> androidx.appcompat:appcompat:1.0.2
if (findPackage(line, "--- [^:]+:[^:]+:[^ ]+ -> ([^:]+):([^:]+):([^ ]+)$")) { if (findPackage(line, "--- [^ :]+:[^ :]+:[^ ]+ -> ([^ :]+):([^ :]+):([^ :]+)$")) {
continue continue
} }
} }
+2 -2
View File
@@ -46,8 +46,8 @@ in {
# Package version adjustments # Package version adjustments
gradle = super.gradle_5; gradle = super.gradle_5;
nodejs = super.nodejs-16_x; nodejs = super.nodejs-18_x;
yarn = super.yarn.override { nodejs = super.nodejs-16_x; }; yarn = super.yarn.override { nodejs = super.nodejs-18_x; };
openjdk = super.openjdk8_headless; openjdk = super.openjdk8_headless;
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } { xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
version = "13.3"; version = "13.3";
+8 -1
View File
@@ -42,12 +42,19 @@ let
# for running gradle by hand # for running gradle by hand
gradle = mkShell { gradle = mkShell {
buildInputs = with pkgs; [ gradle maven goMavenResolver ]; buildInputs = with pkgs; [ gradle maven goMavenResolver ];
inputsFrom = [ nodejs-sh ];
shellHook = '' shellHook = ''
export STATUS_GO_ANDROID_LIBDIR="DUMMY" export STATUS_GO_ANDROID_LIBDIR="DUMMY"
export STATUS_NIX_MAVEN_REPO="${pkgs.deps.gradle}" export STATUS_NIX_MAVEN_REPO="${pkgs.deps.gradle}"
export ANDROID_SDK_ROOT="${pkgs.androidPkgs.sdk}" export ANDROID_SDK_ROOT="${pkgs.androidPkgs.sdk}"
export ANDROID_NDK_ROOT="${pkgs.androidPkgs.ndk}" export ANDROID_NDK_ROOT="${pkgs.androidPkgs.ndk}"
export STATUS_MOBILE_HOME=$(git rev-parse --show-toplevel)
# WARNING: Unpatched Node.js deps allow Gradle to use remote repos.
"$STATUS_MOBILE_HOME/nix/scripts/node_modules.sh" ${pkgs.deps.nodejs}
function restore_patched_modules() {
"$STATUS_MOBILE_HOME/nix/scripts/node_modules.sh" ${pkgs.deps.nodejs-patched}
}
trap restore_patched_modules EXIT
''; '';
}; };
+1 -1
View File
@@ -19,7 +19,7 @@
"@babel/preset-typescript": "^7.17.12", "@babel/preset-typescript": "^7.17.12",
"@react-native-async-storage/async-storage": "^1.17.9", "@react-native-async-storage/async-storage": "^1.17.9",
"@react-native-community/audio-toolkit": "git+https://github.com/tbenr/react-native-audio-toolkit.git#refs/tags/v2.0.3-status-v6", "@react-native-community/audio-toolkit": "git+https://github.com/tbenr/react-native-audio-toolkit.git#refs/tags/v2.0.3-status-v6",
"@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.1-status", "@react-native-community/blur": "git+https://github.com/status-im/react-native-blur#refs/tags/v4.3.2-status",
"@react-native-community/cameraroll": "git+https://github.com/status-im/react-native-cameraroll.git#refs/tags/v4.0.4-status.0", "@react-native-community/cameraroll": "git+https://github.com/status-im/react-native-cameraroll.git#refs/tags/v4.0.4-status.0",
"@react-native-community/clipboard": "^1.2.2", "@react-native-community/clipboard": "^1.2.2",
"@react-native-community/hooks": "^2.5.1", "@react-native-community/hooks": "^2.5.1",
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+7 -1
View File
@@ -45,7 +45,13 @@
:devtools {:autobuild #shadow/env ["SHADOW_AUTOBUILD_ENABLED" :default true :as :bool]} :devtools {:autobuild #shadow/env ["SHADOW_AUTOBUILD_ENABLED" :default true :as :bool]}
:dev {:devtools {:after-load status-im2.setup.hot-reload/reload :dev {:devtools {:after-load status-im2.setup.hot-reload/reload
:build-notify status-im2.setup.hot-reload/build-notify :build-notify status-im2.setup.hot-reload/build-notify
:preloads [re-frisk-remote.preload]} :preloads [re-frisk-remote.preload
;; In order to use component test helpers in
;; the REPL we need to preload namespaces
;; that are not normally required by
;; production code, such as
;; @testing-library/react-native.
test-helpers.component]}
:closure-defines :closure-defines
{status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN" {status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
status-im2.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"} status-im2.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"}
@@ -58,6 +58,18 @@
(h/test "Size :small" (h/test "Size :small"
(h/render (user-avatar-component :small)) (h/render (user-avatar-component :small))
(h/is-truthy (h/get-by-text "NU")))
(h/test "Two letters with excess whitespace"
(h/render [user-avatar/user-avatar
{:full-name "New User"
:size :big}])
(h/is-truthy (h/get-by-text "NU")))
(h/test "Two letters with leading whitespace"
(h/render [user-avatar/user-avatar
{:full-name " New User"
:size :big}])
(h/is-truthy (h/get-by-text "NU")))) (h/is-truthy (h/get-by-text "NU"))))
(h/describe "One letter" (h/describe "One letter"
@@ -5,14 +5,18 @@
[react-native.core :as rn] [react-native.core :as rn]
[react-native.fast-image :as fast-image])) [react-native.fast-image :as fast-image]))
(defn trim-whitespace [s] (string/join " " (string/split (string/trim s) #"\s+")))
(defn- extract-initials (defn- extract-initials
[full-name amount-initials] [full-name amount-initials]
(let [upper-case-first-letter (comp string/upper-case first) (let [upper-case-first-letter (comp string/upper-case first)
names-list (string/split full-name " ")] names-list (string/split (trim-whitespace full-name) " ")]
(->> names-list (if (= (first names-list) "")
(map upper-case-first-letter) ""
(take amount-initials) (->> names-list
(string/join)))) (map upper-case-first-letter)
(take amount-initials)
(string/join)))))
(defn initials-avatar (defn initials-avatar
[{:keys [full-name size draw-ring? customization-color]}] [{:keys [full-name size draw-ring? customization-color]}]
+14 -16
View File
@@ -6,12 +6,13 @@
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent])) [reagent.core :as reagent]))
(def themes (defn themes
[customization-color]
{:light {:primary {:icon-color colors/white {:light {:primary {:icon-color colors/white
:label-color colors/white :label-color colors/white
:background-color {:default colors/primary-50 :background-color {:default (colors/custom-color customization-color 50)
:pressed colors/primary-60 :pressed (colors/custom-color customization-color 60)
:disabled colors/primary-50}} :disabled (colors/custom-color customization-color 50)}}
:secondary {:icon-color colors/primary-50 :secondary {:icon-color colors/primary-50
:label-color colors/primary-50 :label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20 :background-color {:default colors/primary-50-opa-20
@@ -74,9 +75,9 @@
:disabled colors/neutral-95}}} :disabled colors/neutral-95}}}
:dark {:primary {:icon-color colors/white :dark {:primary {:icon-color colors/white
:label-color colors/white :label-color colors/white
:background-color {:default colors/primary-60 :background-color {:default (colors/custom-color customization-color 60)
:pressed colors/primary-50 :pressed (colors/custom-color customization-color 50)
:disabled colors/primary-60}} :disabled (colors/custom-color customization-color 60)}}
:secondary {:icon-color colors/primary-50 :secondary {:icon-color colors/primary-50
:label-color colors/primary-50 :label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20 :background-color {:default colors/primary-50-opa-20
@@ -218,14 +219,15 @@
(let [pressed (reagent/atom false)] (let [pressed (reagent/atom false)]
(fn (fn
[{:keys [on-press disabled type size community-color community-text-color before after above [{:keys [on-press disabled type size community-color community-text-color before after above
width width customization-color
override-theme override-background-color override-theme override-background-color
on-long-press accessibility-label icon icon-no-color style inner-style test-ID] on-long-press accessibility-label icon icon-no-color style inner-style test-ID]
:or {type :primary :or {type :primary
size 40}} size 40
customization-color :primary}}
children] children]
(let [{:keys [icon-color icon-secondary-color background-color label-color border-color]} (let [{:keys [icon-color icon-secondary-color background-color label-color border-color]}
(get-in themes (get-in (themes customization-color)
[(or [(or
override-theme override-theme
(theme/get-theme)) type]) (theme/get-theme)) type])
@@ -252,11 +254,7 @@
[rn/view [rn/view
{:style (merge {:style (merge
(shape-style-container type icon size) (shape-style-container type icon size)
{:background-color {:width width}
(if (= state :pressed)
(colors/theme-colors colors/neutral-100 colors/white)
:transparent)
:width width}
style)} style)}
[rn/view [rn/view
{:style (merge {:style (merge
@@ -16,15 +16,13 @@
:height 230 :height 230
:border-radius 20} :border-radius 20}
:on-press on-press} :on-press on-press}
[rn/view [rn/view {:flex 1}
{:flex 1}
[rn/view (style/community-cover-container 60) [rn/view (style/community-cover-container 60)
[rn/image [rn/image
{:source cover {:source cover
:style :style {:flex 1
{:flex 1 :border-top-right-radius 20
:border-top-right-radius 20 :border-top-left-radius 20}}]]
:border-top-left-radius 20}}]]
[rn/view (style/card-view-content-container 12) [rn/view (style/card-view-content-container 12)
[rn/view (style/card-view-chat-icon 48) [rn/view (style/card-view-chat-icon 48)
[icon/community-icon {:images images} 48]] [icon/community-icon {:images images} 48]]
@@ -38,7 +36,8 @@
{:title name {:title name
:description description}] :description description}]
[rn/view {:style (style/card-stats-position)} [rn/view {:style (style/card-stats-position)}
[community-view/community-stats-column :card-view]] [community-view/community-stats-column
{:type :card-view}]]
[rn/view {:style (style/community-tags-position)} [rn/view {:style (style/community-tags-position)}
[community-view/community-tags tags]]]]]]]) [community-view/community-tags tags]]]]]]])
@@ -64,7 +64,8 @@
colors/neutral-40 colors/neutral-40
colors/neutral-60))}} colors/neutral-60))}}
name] name]
[community-view/community-stats-column :list-view]] [community-view/community-stats-column
{:type :list-view}]]
(if (= status :gated) (if (= status :gated)
[community-view/permission-tag-container [community-view/permission-tag-container
{:locked? locked? {:locked? locked?
@@ -23,7 +23,7 @@
members-count]]) members-count]])
(defn community-stats-column (defn community-stats-column
[type] [{:keys [type]}]
(let [icon-color (colors/theme-colors colors/neutral-50 colors/neutral-40)] (let [icon-color (colors/theme-colors colors/neutral-50 colors/neutral-40)]
[rn/view [rn/view
(if (= type :card-view) (if (= type :card-view)
@@ -46,11 +46,12 @@
^{:key name} ^{:key name}
[rn/view {:margin-right 8} [rn/view {:margin-right 8}
[tag/tag [tag/tag
{:size 24 {:size 24
:label name :label name
:type :emoji :type :emoji
:labelled? true :labelled? true
:resource emoji}]])]) :scrollable? true
:resource emoji}]])])
(defn community-title (defn community-title
[{:keys [title description size] :or {size :small}}] [{:keys [title description size] :or {size :small}}]
+1 -1
View File
@@ -62,7 +62,7 @@
:bottom 0 :bottom 0
:left 0 :left 0
:right 0 :right 0
:height 20 :border-radius 20
:padding-horizontal padding-horizontal :padding-horizontal padding-horizontal
:border-top-right-radius 16 :border-top-right-radius 16
:border-top-left-radius 16 :border-top-left-radius 16
+4 -4
View File
@@ -22,18 +22,18 @@
opts opts
{:type :default/:success/:error {:type :default/:success/:error
:size :default/:tiny :size :default/:tiny
:icon :main-icons/info ;; info message icon :icon :i/info ;; info message icon
:text-color colors/white ;; text color override :text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override :icon-color colors/white ;; icon color override
:no-icon-color? false ;; disable tint color for icon" :no-icon-color? false ;; disable tint color for icon"
[{:keys [type size icon text-color icon-color no-icon-color?]} message] [{:keys [type size icon text-color icon-color no-icon-color? style]} message]
(let [weight (if (= size :default) :regular :medium) (let [weight (if (= size :default) :regular :medium)
size (if (= size :default) :paragraph-2 :label) size (if (= size :default) :paragraph-2 :label)
text-color (or text-color (get-color type)) text-color (or text-color (get-color type))
icon-color (or icon-color text-color)] icon-color (or icon-color text-color)]
[rn/view [rn/view
{:style {:flex-direction :row {:style (merge {:flex-direction :row}
:flex 1}} style)}
[quo2.icons/icon icon [quo2.icons/icon icon
{:color icon-color {:color icon-color
:no-color no-icon-color? :no-color no-icon-color?
@@ -0,0 +1,70 @@
(ns quo2.components.inputs.input.component-spec
(:require [quo2.components.inputs.input.view :as input]
[test-helpers.component :as h]))
(h/describe "Input"
(h/test "default render"
(h/render [input/input])
(h/is-truthy (h/query-by-label-text :input))
(h/is-null (h/query-by-label-text :password-input)))
(h/test "Password type"
(h/render [input/input {:type :password}])
(h/is-truthy (h/query-by-label-text :password-input))
(h/is-null (h/query-by-label-text :input)))
(h/describe "Icon"
(h/test "Doesn't exist in base input"
(h/render [input/input])
(h/is-null (h/query-by-label-text :input-icon)))
(h/test "Renders"
(h/render [input/input {:icon-name :i/placeholder}])
(h/is-truthy (h/get-by-label-text :input-icon))))
(h/describe "Right accessory"
(h/test "Doesn't exist in base input"
(h/render [input/input])
(h/is-null (h/query-by-label-text :input-right-icon)))
(h/test "Clear icon"
(h/render [input/input {:clearable? true}])
(h/is-truthy (h/query-by-label-text :input-right-icon)))
(h/test "Password icon"
(h/render [input/input {:type :password}])
(h/is-truthy (h/query-by-label-text :input-right-icon))))
(h/describe "Button"
(h/test "Doesn't exist in base input"
(h/render [input/input])
(h/is-null (h/query-by-label-text :input-button)))
(h/test "Renders with given text and it's pressable"
(let [button-text "This is a button"
button-callback (h/mock-fn)]
(h/render [input/input
{:button {:on-press button-callback
:text button-text}}])
(h/is-truthy (h/query-by-label-text :input-button))
(h/is-truthy (h/get-by-text button-text))
(h/fire-event :press (h/query-by-label-text :input-button))
(h/was-called button-callback))))
(h/describe "Label"
(h/test "Doesn't exist in base input"
(h/render [input/input])
(h/is-null (h/query-by-label-text :input-labels)))
(h/test "Renders with specified text"
(let [input-label "My label"]
(h/render [input/input {:label input-label}])
(h/is-truthy (h/query-by-label-text :input-labels))
(h/is-truthy (h/get-by-text input-label)))))
(h/test "Char limit counter"
(let [char-limit 100
char-limit-str (str "0/" char-limit)]
(h/render [input/input {:char-limit char-limit}])
(h/is-truthy (h/query-by-label-text :input-labels))
(h/is-truthy (h/get-by-text char-limit-str)))))
+11 -12
View File
@@ -66,7 +66,7 @@
:padding-horizontal 8 :padding-horizontal 8
:border-width 1 :border-width 1
:border-color (:border-color colors-by-status) :border-color (:border-color colors-by-status)
:border-radius (if small? 10 14) :border-radius (if small? 10 12)
:opacity (if disabled? 0.3 1)}) :opacity (if disabled? 0.3 1)})
(defn left-icon-container (defn left-icon-container
@@ -83,15 +83,15 @@
(defn input (defn input
[colors-by-status small? multiple-lines?] [colors-by-status small? multiple-lines?]
(merge (text/text-style {:size :paragraph-1 :weight :regular}) (let [base-props (assoc (text/text-style {:size :paragraph-1 :weight :regular})
{:flex 1 :flex 1
:text-align-vertical :top :padding-right 0
:padding-right 0 :padding-left (if small? 4 8)
:padding-left (if small? 4 8) :padding-vertical (if small? 4 8)
:padding-vertical (if small? 4 8) :color (:text colors-by-status))]
:color (:text colors-by-status)} (if multiple-lines?
(when-not multiple-lines? (assoc base-props :text-align-vertical :top)
{:height (if small? 30 38)}))) (assoc base-props :height (if small? 30 38) :line-height nil))))
(defn right-icon-touchable-area (defn right-icon-touchable-area
[small?] [small?]
@@ -110,8 +110,7 @@
:color (:clear-icon variant-colors)}) :color (:clear-icon variant-colors)})
(def texts-container (def texts-container
{:flex 1 {:flex-direction :row
:flex-direction :row
:height 18 :height 18
:margin-bottom 8}) :margin-bottom 8})
+34 -22
View File
@@ -9,7 +9,9 @@
(defn- label-&-counter (defn- label-&-counter
[{:keys [label current-chars char-limit variant-colors]}] [{:keys [label current-chars char-limit variant-colors]}]
(let [count-text (when char-limit (str current-chars "/" char-limit))] (let [count-text (when char-limit (str current-chars "/" char-limit))]
[rn/view {:style style/texts-container} [rn/view
{:accessibility-label :input-labels
:style style/texts-container}
[rn/view {:style style/label-container} [rn/view {:style style/label-container}
[text/text [text/text
{:style (style/label-color variant-colors) {:style (style/label-color variant-colors)
@@ -25,36 +27,40 @@
(defn- left-accessory (defn- left-accessory
[{:keys [variant-colors small? icon-name]}] [{:keys [variant-colors small? icon-name]}]
[rn/view {:style (style/left-icon-container small?)} [rn/view
{:accessibility-label :input-icon
:style (style/left-icon-container small?)}
[icon/icon icon-name (style/icon variant-colors)]]) [icon/icon icon-name (style/icon variant-colors)]])
(defn- right-accessory (defn- right-accessory
[{:keys [variant-colors small? disabled? on-press icon-style-fn icon-name]}] [{:keys [variant-colors small? disabled? on-press icon-style-fn icon-name]}]
[rn/touchable-opacity [rn/touchable-opacity
{:style (style/right-icon-touchable-area small?) {:accessibility-label :input-right-icon
:disabled disabled? :style (style/right-icon-touchable-area small?)
:on-press on-press} :disabled disabled?
:on-press on-press}
[icon/icon icon-name (icon-style-fn variant-colors)]]) [icon/icon icon-name (icon-style-fn variant-colors)]])
(defn- right-button (defn- right-button
[{:keys [variant-colors colors-by-status small? disabled? on-press text]}] [{:keys [variant-colors colors-by-status small? disabled? on-press text]}]
[rn/touchable-opacity [rn/touchable-opacity
{:style (style/button variant-colors small?) {:accessibility-label :input-button
:disabled disabled? :style (style/button variant-colors small?)
:on-press on-press} :disabled disabled?
:on-press on-press}
[rn/text {:style (style/button-text colors-by-status)} [rn/text {:style (style/button-text colors-by-status)}
text]]) text]])
(def ^:private custom-props (def ^:private custom-props
"Custom properties that must be removed from properties map passed to InputText." "Custom properties that must be removed from properties map passed to InputText."
[:type :blur? :override-theme :error? :right-icon :left-icon :disabled? :small? :button [:type :blur? :override-theme :error? :right-icon :left-icon :disabled? :small? :button
:label :char-limit :on-char-limit-reach :icon-name :multiline?]) :label :char-limit :on-char-limit-reach :icon-name :multiline? :on-focus :on-blur])
(defn- base-input (defn- base-input
[{:keys [on-change-text on-char-limit-reach]}] [{:keys [on-change-text on-char-limit-reach]}]
(let [status (reagent/atom :default) (let [status (reagent/atom :default)
on-focus #(reset! status :focus) internal-on-focus #(reset! status :focus)
on-blur #(reset! status :default) internal-on-blur #(reset! status :default)
multiple-lines? (reagent/atom false) multiple-lines? (reagent/atom false)
set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height")] set-multiple-lines! #(let [height (oops/oget % "nativeEvent.contentSize.height")]
(if (> height 57) (if (> height 57)
@@ -68,7 +74,7 @@
(when (>= amount-chars char-limit) (when (>= amount-chars char-limit)
(on-char-limit-reach amount-chars))))] (on-char-limit-reach amount-chars))))]
(fn [{:keys [blur? override-theme error? right-icon left-icon disabled? small? button (fn [{:keys [blur? override-theme error? right-icon left-icon disabled? small? button
label char-limit multiline? clearable?] label char-limit multiline? clearable? on-focus on-blur]
:as props}] :as props}]
(let [status-kw (cond (let [status-kw (cond
disabled? :disabled disabled? :disabled
@@ -77,7 +83,7 @@
colors-by-status (style/status-colors status-kw blur? override-theme) colors-by-status (style/status-colors status-kw blur? override-theme)
variant-colors (style/variants-colors blur? override-theme) variant-colors (style/variants-colors blur? override-theme)
clean-props (apply dissoc props custom-props)] clean-props (apply dissoc props custom-props)]
[rn/view [:<>
(when (or label char-limit) (when (or label char-limit)
[label-&-counter [label-&-counter
{:variant-colors variant-colors {:variant-colors variant-colors
@@ -92,11 +98,16 @@
:icon-name icon-name}]) :icon-name icon-name}])
[rn/text-input [rn/text-input
(cond-> {:style (style/input colors-by-status small? @multiple-lines?) (cond-> {:style (style/input colors-by-status small? @multiple-lines?)
:accessibility-label :input
:placeholder-text-color (:placeholder colors-by-status) :placeholder-text-color (:placeholder colors-by-status)
:cursor-color (:cursor variant-colors) :cursor-color (:cursor variant-colors)
:editable (not disabled?) :editable (not disabled?)
:on-focus on-focus :on-focus (fn []
:on-blur on-blur} (when on-focus (on-focus))
(internal-on-focus))
:on-blur (fn []
(when on-blur (on-blur))
(internal-on-blur))}
:always (merge clean-props) :always (merge clean-props)
multiline? (assoc :multiline true multiline? (assoc :multiline true
:on-content-size-change set-multiple-lines!) :on-content-size-change set-multiple-lines!)
@@ -126,12 +137,13 @@
(fn [props] (fn [props]
[base-input [base-input
(assoc props (assoc props
:auto-capitalize :none :accessibility-label :password-input
:auto-complete :new-password :auto-capitalize :none
:secure-text-entry (not @password-shown?) :auto-complete :new-password
:right-icon {:style-fn style/password-icon :secure-text-entry (not @password-shown?)
:icon-name (if @password-shown? :i/hide :i/reveal) :right-icon {:style-fn style/password-icon
:on-press #(swap! password-shown? not)})]))) :icon-name (if @password-shown? :i/hide :i/reveal)
:on-press #(swap! password-shown? not)})])))
(defn input (defn input
"This input supports the following properties: "This input supports the following properties:
@@ -146,7 +158,7 @@
- :clearable? - Booolean to specify if this input has a clear button at the end. - :clearable? - Booolean to specify if this input has a clear button at the end.
- :on-clear - Function executed when the clear button is pressed. - :on-clear - Function executed when the clear button is pressed.
- :button - Map containing `:on-press` & `:text` keys, if provided renders a button - :button - Map containing `:on-press` & `:text` keys, if provided renders a button
- :label - A label for this input. - :label - A string to set as label for this input.
- :char-limit - A number to set a maximum char limit for this input. - :char-limit - A number to set a maximum char limit for this input.
- :on-char-limit-reach - Function executed each time char limit is reached or exceeded. - :on-char-limit-reach - Function executed each time char limit is reached or exceeded.
and supports the usual React Native's TextInput properties to control its behaviour: and supports the usual React Native's TextInput properties to control its behaviour:
@@ -25,13 +25,13 @@
:height 24 :height 24
:borderRadius 12}]} :borderRadius 12}]}
[user-avatar/user-avatar [user-avatar/user-avatar
(merge image-picker-props (assoc image-picker-props
{:customization-color customization-color :customization-color customization-color
:full-name (if (seq full-name) :full-name (if (seq full-name)
full-name full-name
placeholder) placeholder)
:status-indicator? false :status-indicator? false
:size :medium})]] :size :medium)]]
[buttons/button [buttons/button
{:accessibility-label :select-profile-picture-button {:accessibility-label :select-profile-picture-button
:type :grey :type :grey
@@ -47,5 +47,6 @@
[rn/view {:style style/input-container} [rn/view {:style style/input-container}
[title-input/title-input [title-input/title-input
(merge title-input-props (merge title-input-props
{:placeholder placeholder {:override-theme :dark
:placeholder placeholder
:customization-color customization-color})]]])) :customization-color customization-color})]]]))
@@ -34,9 +34,11 @@
(def text-input-container {:flex 1}) (def text-input-container {:flex 1})
(defn title-text (defn title-text
[disabled? blur?] [disabled? blur? override-theme]
{:text-align-vertical :bottom {:text-align-vertical :bottom
:color (when disabled? (get-disabled-color blur?))}) :color (if disabled?
(get-disabled-color blur?)
(colors/theme-colors colors/neutral-100 colors/white override-theme))})
(defn char-count (defn char-count
[blur?] [blur?]
@@ -16,7 +16,8 @@
on-change-text on-change-text
placeholder placeholder
max-length max-length
default-value] default-value
override-theme]
:or {max-length 0 :or {max-length 0
default-value ""}}] default-value ""}}]
(let [focused? (reagent/atom false) (let [focused? (reagent/atom false)
@@ -34,7 +35,7 @@
(text/text-style (text/text-style
{:size :heading-2 {:size :heading-2
:weight :semi-bold :weight :semi-bold
:style (style/title-text disabled? blur?)}) :style (style/title-text disabled? blur? override-theme)})
:default-value default-value :default-value default-value
:accessibility-label :profile-title-input :accessibility-label :profile-title-input
:on-focus #(swap! focused? (fn [] true)) :on-focus #(swap! focused? (fn [] true))
+27 -22
View File
@@ -30,16 +30,16 @@
{:no-color true}))) {:no-color true})))
(defn left-section-view (defn left-section-view
[{:keys [on-press icon accessibility-label type icon-override-theme] :or {type :grey}} [{:keys [on-press icon accessibility-label type icon-background-color] :or {type :grey}}
put-middle-section-on-left?] put-middle-section-on-left?]
[rn/view {:style (when put-middle-section-on-left? {:margin-right 5})} [rn/view {:style (when put-middle-section-on-left? {:margin-right 5})}
[button/button [button/button
{:on-press on-press {:on-press on-press
:icon true :icon true
:type type :type type
:size 32 :size 32
:accessibility-label accessibility-label :accessibility-label accessibility-label
:override-theme icon-override-theme} :override-background-color icon-background-color}
icon]]) icon]])
(defn- mid-section-comp (defn- mid-section-comp
@@ -150,13 +150,15 @@
:justify-content :flex-end)} :justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)] (let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index (map-indexed (fn [index
{:keys [icon on-press type style icon-override-theme] {:keys [icon on-press type style icon-override-theme accessibility-label]
:or {type :grey}}] :or {type :grey}}]
^{:key index} ^{:key index}
[rn/view [rn/view
{:style (assoc style (cond-> {:style (assoc style
:margin-right :margin-right
(if (= index last-icon-index) 0 8))} (if (= index last-icon-index) 0 8))}
accessibility-label (assoc :accessibility-label accessibility-label
:accessible true))
[button/button [button/button
{:on-press on-press {:on-press on-press
:icon true :icon true
@@ -235,15 +237,18 @@
:align-items :center}} :align-items :center}}
(when left-section (when left-section
[left-section-view left-section put-middle-section-on-left?]) [left-section-view left-section put-middle-section-on-left?])
(when put-middle-section-on-left? (when mid-section
[mid-section-view (cond
(assoc mid-section-props put-middle-section-on-left?
:left-align? true [mid-section-view
:description (:description mid-section) (assoc mid-section-props
:description-color (:description-color mid-section) :left-align? true
:description-icon (:description-icon mid-section) :description (:description mid-section)
:align-mid? align-mid? :description-color (:description-color mid-section)
:description-user-icon (:description-user-icon mid-section))])] :description-icon (:description-icon mid-section)
(when-not put-middle-section-on-left? :align-mid? align-mid?
[mid-section-view mid-section-props]) :description-user-icon (:description-user-icon mid-section))]
(not put-middle-section-on-left?)
[mid-section-view mid-section-props]))]
[right-section-view right-section-buttons]])) [right-section-view right-section-buttons]]))
+2 -1
View File
@@ -16,7 +16,8 @@
[text/text [text/text
{:style (style/tip-text completed?) {:style (style/tip-text completed?)
:weight :regular :weight :regular
:size :paragraph-2} text] :size :paragraph-2}
text]
(when completed? (when completed?
[rn/view [rn/view
{:style style/strike-through {:style style/strike-through
@@ -2,14 +2,16 @@
(:require [quo2.foundations.colors :as colors])) (:require [quo2.foundations.colors :as colors]))
(defn card-container (defn card-container
[customization-color padding-bottom] [{:keys [customization-color padding-bottom border-bottom-radius]}]
{:flex-direction :column {:padding-horizontal 12
:padding-horizontal 12 :padding-top 12
:padding-top 12 :padding-bottom padding-bottom
:padding-bottom padding-bottom :flex 1
:flex 1 :border-top-left-radius 16
:border-radius 16 :border-top-right-radius 16
:background-color (colors/custom-color customization-color 50 40)}) :border-bottom-left-radius border-bottom-radius
:border-bottom-right-radius border-bottom-radius
:background-color (colors/custom-color customization-color 50 40)})
(def card-header (def card-header
{:flex-direction :row {:flex-direction :row
@@ -4,77 +4,104 @@
[quo2.components.icon :as icon] [quo2.components.icon :as icon]
[quo2.components.tags.tag :as tag] [quo2.components.tags.tag :as tag]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.hole-view :as hole-view]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button] [quo2.components.buttons.button :as button]
[quo2.components.avatars.user-avatar.view :as user-avatar] [quo2.components.profile.profile-card.style :as style]
[quo2.components.profile.profile-card.style :as style])) [quo2.components.avatars.user-avatar.view :as user-avatar]))
(defn profile-card (defn- profile-card-component
[{:keys [key-card? profile-picture name hash customization-color [{:keys [keycard-account? profile-picture name hash
emoji-hash on-options-press show-emoji-hash? padding-bottom customization-color emoji-hash on-options-press
show-options-button? show-user-hash? show-logged-in? on-card-press] show-emoji-hash? show-options-button? show-user-hash?
show-logged-in? on-card-press login-card? last-item? card-style]
:or {show-emoji-hash? false :or {show-emoji-hash? false
show-user-hash? false show-user-hash? false
customization-color :turquoise customization-color :turquoise
show-options-button? false show-options-button? false
show-logged-in? false show-logged-in? false
key-card? false}}] keycard-account? false
[rn/touchable-without-feedback login-card? false
{:on-press on-card-press last-item? false
:flex 1 card-style {:padding-horizontal 20
:accessibility-label :profile-card} :flex 1}}}]
[rn/view (let [{:keys [width]} (rn/use-window-dimensions)
(style/card-container padding-bottom (cond
customization-color login-card? 38
(or padding-bottom (if show-emoji-hash? 12 10))) show-emoji-hash? 12
[rn/view :else 10)
{:style style/card-header} border-bottom-radius (if (or (not login-card?) last-item?) 16 0)]
[user-avatar/user-avatar [rn/touchable-without-feedback
{:full-name name {:on-press on-card-press
:profile-picture profile-picture :accessibility-label :profile-card}
:override-theme :dark [hole-view/hole-view
:size :medium {:key (str name last-item?) ;; Key is required to force removal of holes
:status-indicator? false}] :style (merge {:flex-direction :row} card-style)
[rn/view {:flex-direction :row} :holes (if (or (not login-card?) last-item?)
(when show-logged-in? []
[tag/tag [{:x 20
{:type :icon :y 108
:size 32 :width (- width 40)
:blurred? true :height 50
:labelled? true :borderRadius 16}])}
:resource :main-icons2/check [rn/view
:accessibility-label :logged-in-tag {:style (style/card-container
:icon-color colors/success-50 {:customization-color customization-color
:padding-bottom padding-bottom
:border-bottom-radius border-bottom-radius})}
[rn/view
{:style style/card-header}
[user-avatar/user-avatar
{:full-name name
:profile-picture profile-picture
:override-theme :dark :override-theme :dark
:label (i18n/label :t/logged-in)}]) :size :medium
(when show-options-button? :status-indicator? false
[button/button :customization-color customization-color}]
{:size 32 [rn/view {:flex-direction :row}
:type :blur-bg (when show-logged-in?
:icon true [tag/tag
:override-theme :dark {:type :icon
:style style/option-button :size 32
:on-press on-options-press :blurred? true
:accessibility-label :profile-card-options} :labelled? true
:i/options])]] :resource :main-icons2/check
[rn/view :accessibility-label :logged-in-tag
{:style style/name-container} :icon-color colors/success-50
[text/text :override-theme :dark
{:size :heading-2 :label (i18n/label :t/logged-in)}])
:weight :semi-bold (when show-options-button?
:number-of-lines 1 [button/button
:style style/user-name} name] {:size 32
(when key-card? :type :blur-bg
(icon/icon :icon true
:i/keycard :override-theme :dark
style/keycard-icon))] :style style/option-button
(when show-user-hash? :on-press on-options-press
[text/text :accessibility-label :profile-card-options}
{:weight :monospace :i/options])]]
:number-of-lines 1 [rn/view
:style style/user-hash} hash]) {:style style/name-container}
(when (and show-emoji-hash? emoji-hash) [text/text
[text/text {:size :heading-2
{:weight :monospace :weight :semi-bold
:number-of-lines 1 :number-of-lines 1
:style style/emoji-hash} emoji-hash])]]) :style style/user-name} name]
(when keycard-account?
(icon/icon
:i/keycard
style/keycard-icon))]
(when show-user-hash?
[text/text
{:weight :monospace
:number-of-lines 1
:style style/user-hash} hash])
(when (and show-emoji-hash? emoji-hash)
[text/text
{:weight :monospace
:number-of-lines 1
:style style/emoji-hash} emoji-hash])]]]))
(defn profile-card
[props]
[:f> profile-card-component props])
@@ -0,0 +1,29 @@
(ns quo2.components.selectors.disclaimer.component-spec
(:require [quo2.components.selectors.disclaimer.view :as disclaimer]
[test-helpers.component :as h]))
(h/describe "Disclaimer tests"
(h/test "Default render of toggle component"
(h/render [disclaimer/view {:on-change (h/mock-fn)} "test"])
(h/is-truthy (h/get-by-label-text :checkbox-off)))
(h/test "Renders its text"
(let [text "I accept this disclaimer"]
(h/render [disclaimer/view {} text])
(h/is-truthy (h/get-by-text text))))
(h/test "On change event gets fire after press"
(let [mock-fn (h/mock-fn)]
(h/render [disclaimer/view {:on-change mock-fn} "test"])
(h/fire-event :press (h/get-by-label-text :checkbox-off))
(h/was-called mock-fn)))
(h/describe "It's rendered according to its `checked?` property"
(h/test "checked? true"
(h/render [disclaimer/view {:checked? true} "test"])
(h/is-null (h/query-by-label-text :checkbox-off))
(h/is-truthy (h/query-by-label-text :checkbox-on)))
(h/test "checked? false"
(h/render [disclaimer/view {:checked? false} "test"])
(h/is-null (h/query-by-label-text :checkbox-on))
(h/is-truthy (h/query-by-label-text :checkbox-off)))))
@@ -2,14 +2,16 @@
(:require [quo2.foundations.colors :as colors])) (:require [quo2.foundations.colors :as colors]))
(defn container (defn container
[] [blur?]
{:flex-direction :row (let [dark-background (if blur? colors/white-opa-5 colors/neutral-80-opa-40)
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-80-opa-40) dark-border (if blur? colors/white-opa-10 colors/neutral-70)]
:padding 11 {:flex-direction :row
:align-self :stretch :background-color (colors/theme-colors colors/neutral-5 dark-background)
:border-radius 12 :padding 11
:border-width 1 :align-self :stretch
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-70)}) :border-radius 12
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 dark-border)}))
(def text (def text
{:margin-left 8}) {:margin-left 8})
@@ -5,12 +5,13 @@
[react-native.core :as rn])) [react-native.core :as rn]))
(defn view (defn view
[{:keys [on-change accessibility-label container-style]} label] [{:keys [checked? blur? on-change accessibility-label container-style]} label]
[rn/view [rn/view
{:style (merge container-style (style/container))} {:style (merge container-style (style/container blur?))}
[selectors/checkbox [selectors/checkbox
{:accessibility-label accessibility-label {:accessibility-label accessibility-label
:on-change on-change}] :on-change on-change
:checked? checked?}]
[text/text [text/text
{:size :paragraph-2 {:size :paragraph-2
:style style/text} :style style/text}
+17 -10
View File
@@ -5,6 +5,10 @@
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent])) [reagent.core :as reagent]))
(def themes-for-blur
{:light {:background-color colors/neutral-80-opa-5}
:dark {:background-color colors/white-opa-5}})
(def themes (def themes
{:light {:background-color colors/neutral-20} {:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}}) :dark {:background-color colors/neutral-80}})
@@ -12,11 +16,12 @@
(defn segmented-control (defn segmented-control
[{:keys [default-active on-change]}] [{:keys [default-active on-change]}]
(let [active-tab-id (reagent/atom default-active)] (let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size]}] (fn [{:keys [data size override-theme blur?]}]
(let [active-id @active-tab-id] (let [active-id @active-tab-id]
[rn/view [rn/view
{:flex-direction :row {:flex-direction :row
:background-color (get-in themes [(theme/get-theme) :background-color]) :background-color (get-in (if blur? themes-for-blur themes)
[(or override-theme (theme/get-theme)) :background-color])
:border-radius (case size :border-radius (case size
32 10 32 10
28 8 28 8
@@ -29,12 +34,14 @@
{:margin-left (if (= 0 indx) 0 2) {:margin-left (if (= 0 indx) 0 2)
:flex 1} :flex 1}
[tab/view [tab/view
{:id id {:id id
:segmented? true :segmented? true
:size size :size size
:active (= id active-id) :override-theme override-theme
:on-press (fn [tab-id] :blur? blur?
(reset! active-tab-id tab-id) :active (= id active-id)
(when on-change :on-press (fn [tab-id]
(on-change tab-id)))} (reset! active-tab-id tab-id)
(when on-change
(on-change tab-id)))}
label]])])))) label]])]))))
+6 -5
View File
@@ -79,11 +79,12 @@
[notification-dot/notification-dot [notification-dot/notification-dot
{:style style/notification-dot}]) {:style style/notification-dot}])
[rn/view [rn/view
{:style (style/tab {:size size {:style (style/tab
:disabled disabled {:size size
:segmented? segmented? :disabled disabled
:background-color background-color :segmented? segmented?
:show-notification-dot? show-notification-dot?})} :background-color (if (and segmented? (not active)) :transparent background-color)
:show-notification-dot? show-notification-dot?})}
(when before (when before
[rn/view [rn/view
[icons/icon before {:color icon-color}]]) [icons/icon before {:color icon-color}]])
+2
View File
@@ -60,6 +60,7 @@
quo2.components.settings.accounts.view quo2.components.settings.accounts.view
quo2.components.settings.privacy-option quo2.components.settings.privacy-option
quo2.components.onboarding.small-option-card.view quo2.components.onboarding.small-option-card.view
quo2.components.tabs.segmented-tab
quo2.components.tabs.account-selector quo2.components.tabs.account-selector
quo2.components.tabs.tabs quo2.components.tabs.tabs
quo2.components.tags.context-tags quo2.components.tags.context-tags
@@ -91,6 +92,7 @@
(def audio-tag quo2.components.tags.context-tags/audio-tag) (def audio-tag quo2.components.tags.context-tags/audio-tag)
(def community-tag quo2.components.tags.context-tags/community-tag) (def community-tag quo2.components.tags.context-tags/community-tag)
(def tabs quo2.components.tabs.tabs/tabs) (def tabs quo2.components.tabs.tabs/tabs)
(def segmented-control quo2.components.tabs.segmented-tab/segmented-control)
(def account-selector quo2.components.tabs.account-selector/account-selector) (def account-selector quo2.components.tabs.account-selector/account-selector)
(def floating-shell-button quo2.components.navigation.floating-shell-button/floating-shell-button) (def floating-shell-button quo2.components.navigation.floating-shell-button/floating-shell-button)
(def page-nav quo2.components.navigation.page-nav/page-nav) (def page-nav quo2.components.navigation.page-nav/page-nav)
+4 -2
View File
@@ -2,21 +2,23 @@
(:require [quo2.components.avatars.user-avatar.component-spec] (:require [quo2.components.avatars.user-avatar.component-spec]
[quo2.components.banners.banner.component-spec] [quo2.components.banners.banner.component-spec]
[quo2.components.buttons.--tests--.buttons-component-spec] [quo2.components.buttons.--tests--.buttons-component-spec]
[quo2.components.colors.color-picker.component-spec]
[quo2.components.counter.--tests--.counter-component-spec] [quo2.components.counter.--tests--.counter-component-spec]
[quo2.components.dividers.--tests--.divider-label-component-spec] [quo2.components.dividers.--tests--.divider-label-component-spec]
[quo2.components.dividers.strength-divider.component-spec] [quo2.components.dividers.strength-divider.component-spec]
[quo2.components.drawers.action-drawers.component-spec] [quo2.components.drawers.action-drawers.component-spec]
[quo2.components.drawers.drawer-buttons.component-spec] [quo2.components.drawers.drawer-buttons.component-spec]
[quo2.components.drawers.permission-context.component-spec] [quo2.components.drawers.permission-context.component-spec]
[quo2.components.colors.color-picker.component-spec] [quo2.components.inputs.input.component-spec]
[quo2.components.inputs.profile-input.component-spec] [quo2.components.inputs.profile-input.component-spec]
[quo2.components.inputs.title-input.component-spec] [quo2.components.inputs.title-input.component-spec]
[quo2.components.markdown.--tests--.text-component-spec] [quo2.components.markdown.--tests--.text-component-spec]
[quo2.components.onboarding.small-option-card.component-spec] [quo2.components.onboarding.small-option-card.component-spec]
[quo2.components.password.tips.component-spec] [quo2.components.password.tips.component-spec]
[quo2.components.profile.select-profile.component-spec]
[quo2.components.record-audio.record-audio.--tests--.record-audio-component-spec] [quo2.components.record-audio.record-audio.--tests--.record-audio-component-spec]
[quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec] [quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec]
[quo2.components.profile.select-profile.component-spec]
[quo2.components.selectors.--tests--.selectors-component-spec] [quo2.components.selectors.--tests--.selectors-component-spec]
[quo2.components.selectors.disclaimer.component-spec]
[quo2.components.selectors.filter.component-spec] [quo2.components.selectors.filter.component-spec]
[quo2.components.tags.--tests--.status-tags-component-spec])) [quo2.components.tags.--tests--.status-tags-component-spec]))
+6 -1
View File
@@ -85,6 +85,7 @@
;;100 with transparency ;;100 with transparency
(def neutral-100-opa-0 (alpha neutral-100 0)) (def neutral-100-opa-0 (alpha neutral-100 0))
(def neutral-100-opa-10 (alpha neutral-100 0.1)) (def neutral-100-opa-10 (alpha neutral-100 0.1))
(def neutral-100-opa-30 (alpha neutral-100 0.3))
(def neutral-100-opa-60 (alpha neutral-100 0.6)) (def neutral-100-opa-60 (alpha neutral-100 0.6))
(def neutral-100-opa-70 (alpha neutral-100 0.7)) (def neutral-100-opa-70 (alpha neutral-100 0.7))
(def neutral-100-opa-80 (alpha neutral-100 0.8)) (def neutral-100-opa-80 (alpha neutral-100 0.8))
@@ -236,7 +237,11 @@
([color suffix] ([color suffix]
(custom-color color suffix nil)) (custom-color color suffix nil))
([color suffix opacity] ([color suffix opacity]
(let [base-color (get-in colors-map [(keyword color) suffix])] (let [color-keyword (keyword color)
base-color (get-in colors-map
[(if (= color-keyword :yinyang)
(if (theme/dark?) :yang :yin)
(keyword color)) suffix])]
(if opacity (alpha base-color (/ opacity 100)) base-color)))))) (if opacity (alpha base-color (/ opacity 100)) base-color))))))
(defn custom-color-by-theme (defn custom-color-by-theme
+5
View File
@@ -0,0 +1,5 @@
(ns react-native.camera-kit
(:require ["react-native-camera-kit" :refer (CameraKitCamera)]
[reagent.core :as reagent]))
(def camera (reagent/adapt-react-class CameraKitCamera))
+4
View File
@@ -6,3 +6,7 @@
(let [kb (.useKeyboard hooks)] (let [kb (.useKeyboard hooks)]
{:keyboard-shown (.-keyboardShown ^js kb) {:keyboard-shown (.-keyboardShown ^js kb)
:keyboard-height (.-keyboardHeight ^js kb)})) :keyboard-height (.-keyboardHeight ^js kb)}))
(defn use-back-handler
[handler]
(.useBackHandler hooks handler))
+12 -7
View File
@@ -21,6 +21,8 @@
[utils.collection] [utils.collection]
[utils.worklets.core :as worklets.core])) [utils.worklets.core :as worklets.core]))
(def ^:const default-duration 300)
;; Animations ;; Animations
(def slide-in-up-animation SlideInUp) (def slide-in-up-animation SlideInUp)
(def slide-out-up-animation SlideOutUp) (def slide-out-up-animation SlideOutUp)
@@ -65,6 +67,7 @@
(def in-out (def in-out
(.-inOut ^js Easing)) (.-inOut ^js Easing))
;; trying to put default-easing inside easings map causes test to fail
(defn default-easing [] (in-out (.-quad ^js Easing))) (defn default-easing [] (in-out (.-quad ^js Easing)))
(def easings (def easings
@@ -115,13 +118,15 @@
(js-obj "duration" duration (js-obj "duration" duration
"easing" (get easings easing)))))) "easing" (get easings easing))))))
(defn animate-shared-value-with-delay-default-easing (defn animate-delay
[anim val duration delay] ([animation val delay]
(set-shared-value anim (animate-delay animation val delay default-duration))
(with-delay delay ([animation val delay duration]
(with-timing val (set-shared-value animation
(js-obj "duration" duration (with-delay delay
"easing" (in-out (.-quad ^js Easing))))))) (with-timing val
(clj->js {:duration duration
:easing (default-easing)}))))))
(defn animate-shared-value-with-repeat (defn animate-shared-value-with-repeat
[anim val duration easing number-of-repetitions reverse?] [anim val duration easing number-of-repetitions reverse?]
+5 -3
View File
@@ -147,8 +147,9 @@
(defn build-image-messages (defn build-image-messages
[{db :db} chat-id input-text] [{db :db} chat-id input-text]
(let [images (get-in db [:chat/inputs chat-id :metadata :sending-image]) (let [images (get-in db [:chat/inputs chat-id :metadata :sending-image])
album-id (str (random-uuid))] {:keys [message-id]} (get-in db [:chat/inputs chat-id :metadata :responding-to-message])
album-id (str (random-uuid))]
(mapv (fn [[_ {:keys [resized-uri width height]}]] (mapv (fn [[_ {:keys [resized-uri width height]}]]
{:chat-id chat-id {:chat-id chat-id
:album-id album-id :album-id album-id
@@ -159,7 +160,8 @@
;; TODO: message not received if text field is ;; TODO: message not received if text field is
;; nil or empty, issue: ;; nil or empty, issue:
;; https://github.com/status-im/status-mobile/issues/14754 ;; https://github.com/status-im/status-mobile/issues/14754
:text (or input-text "placeholder")}) :text (or input-text "placeholder")
:response-to message-id})
images))) images)))
(rf/defn clean-input (rf/defn clean-input
+2 -2
View File
@@ -276,7 +276,7 @@
(vals (get-in db [:communities community-id :chats])))] (vals (get-in db [:communities community-id :chats])))]
(when (and id (when (and id
(not= (:current-chat-id db) (str community-id id))) (not= (:current-chat-id db) (str community-id id)))
(chat.events/navigate-to-chat cofx (str community-id id) nil)))) (chat.events/navigate-to-chat cofx (str community-id id)))))
(rf/defn fetch (rf/defn fetch
[_] [_]
@@ -817,7 +817,7 @@
[cofx community-id] [cofx community-id]
(rf/merge cofx (rf/merge cofx
(navigation/pop-to-root :shell-stack) (navigation/pop-to-root :shell-stack)
(navigation/navigate-to-nav2 :community community-id true))) (navigation/navigate-to-cofx :community-overview community-id)))
(rf/defn member-role-updated (rf/defn member-role-updated
{:events [:community.member/role-updated]} {:events [:community.member/role-updated]}
+1 -1
View File
@@ -202,7 +202,7 @@
(defn words-count (defn words-count
[s] [s]
(if (empty? s) (if (empty? s)
nil 0
(-> s (-> s
passphrase->words passphrase->words
count))) count)))
+2 -1
View File
@@ -57,6 +57,7 @@
status-im2.contexts.activity-center.events status-im2.contexts.activity-center.events
status-im2.contexts.activity-center.notification.contact-requests.events status-im2.contexts.activity-center.notification.contact-requests.events
status-im2.contexts.shell.events status-im2.contexts.shell.events
status-im2.contexts.onboarding.events
status-im.chat.models.gaps status-im.chat.models.gaps
[status-im2.navigation.events :as navigation])) [status-im2.navigation.events :as navigation]))
@@ -116,7 +117,7 @@
(let [current-theme-type (get-in cofx [:db :multiaccount :appearance])] (let [current-theme-type (get-in cofx [:db :multiaccount :appearance])]
(when (and (multiaccounts.model/logged-in? cofx) (when (and (multiaccounts.model/logged-in? cofx)
(= current-theme-type status-im2.constants/theme-type-system)) (= current-theme-type status-im2.constants/theme-type-system))
{:multiaccounts.ui/switch-theme {:multiaccounts.ui/switch-theme-fx
[(get-in db [:multiaccount :appearance]) [(get-in db [:multiaccount :appearance])
(:view-id db) true]}))) (:view-id db) true]})))
+1 -1
View File
@@ -15,7 +15,7 @@
{:events [:navigate-chat-updated]} {:events [:navigate-chat-updated]}
[cofx chat-id] [cofx chat-id]
(when (get-in cofx [:db :chats chat-id]) (when (get-in cofx [:db :chats chat-id])
(chat.events/navigate-to-chat cofx chat-id nil))) (chat.events/navigate-to-chat cofx chat-id)))
(rf/defn handle-chat-removed (rf/defn handle-chat-removed
{:events [:chat-removed]} {:events [:chat-removed]}
+1 -1
View File
@@ -17,7 +17,7 @@
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(rf/merge cofx (rf/merge cofx
{:db db} {:db db}
(navigation/pop-to-root :multiaccounts-stack))) (navigation/pop-to-root :profiles)))
(rf/defn login-pin-more-icon-pressed (rf/defn login-pin-more-icon-pressed
{:events [:keycard.login.pin.ui/more-icon-pressed]} {:events [:keycard.login.pin.ui/more-icon-pressed]}
+3 -1
View File
@@ -1,5 +1,6 @@
(ns status-im.mobile-sync-settings.core (ns status-im.mobile-sync-settings.core
(:require [status-im2.common.bottom-sheet.events :as bottom-sheet] (:require [status-im2.common.bottom-sheet.events :as bottom-sheet]
[status-im2.contexts.add-new-contact.events :as add-new-contact]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.model :as multiaccounts.model] [status-im.multiaccounts.model :as multiaccounts.model]
[status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.multiaccounts.update.core :as multiaccounts.update]
@@ -42,7 +43,8 @@
(and logged-in? initialized?) (and logged-in? initialized?)
[(mailserver/process-next-messages-request) [(mailserver/process-next-messages-request)
(bottom-sheet/hide-bottom-sheet) (bottom-sheet/hide-bottom-sheet)
(wallet/restart-wallet-service nil)] (wallet/restart-wallet-service nil)
(add-new-contact/set-new-identity-reconnected)]
logged-in? logged-in?
[(mailserver/process-next-messages-request) [(mailserver/process-next-messages-request)
+13 -6
View File
@@ -5,14 +5,13 @@
[status-im2.common.bottom-sheet.events :as bottom-sheet] [status-im2.common.bottom-sheet.events :as bottom-sheet]
[status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.native-module.core :as native-module] [status-im.native-module.core :as native-module]
[status-im.theme.core :as theme]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[status-im.utils.gfycat.core :as gfycat] [status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon] [status-im.utils.identicon :as identicon]
[status-im2.setup.hot-reload :as hot-reload] [status-im2.setup.hot-reload :as hot-reload]
[status-im2.common.theme.core :as utils.theme] [status-im2.common.theme.core :as theme]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im2.contexts.shell.animation :as shell.animation] [status-im2.contexts.shell.animation :as shell.animation]
[status-im.contact.db :as contact.db])) [status-im.contact.db :as contact.db]))
@@ -137,16 +136,16 @@
{::blank-preview-flag-changed private?})) {::blank-preview-flag-changed private?}))
(re-frame/reg-fx (re-frame/reg-fx
:multiaccounts.ui/switch-theme :multiaccounts.ui/switch-theme-fx
(fn [[theme-type view-id reload-ui?]] (fn [[theme-type view-id reload-ui?]]
(let [[theme status-bar-theme nav-bar-color] (let [[theme status-bar-theme nav-bar-color]
;; Status bar theme represents status bar icons colors, so opposite to app theme ;; Status bar theme represents status bar icons colors, so opposite to app theme
(if (or (= theme-type constants/theme-type-dark) (if (or (= theme-type constants/theme-type-dark)
(and (= theme-type constants/theme-type-system) (and (= theme-type constants/theme-type-system)
(utils.theme/dark-mode?))) (theme/device-theme-dark?)))
[:dark :light colors/neutral-100] [:dark :light colors/neutral-100]
[:light :dark colors/white])] [:light :dark colors/white])]
(theme/change-theme theme) (theme/set-theme theme)
(re-frame/dispatch [:change-root-status-bar-style (re-frame/dispatch [:change-root-status-bar-style
(if (shell.animation/home-stack-open?) status-bar-theme :light)]) (if (shell.animation/home-stack-open?) status-bar-theme :light)])
(when reload-ui? (when reload-ui?
@@ -158,9 +157,17 @@
{:events [:multiaccounts.ui/appearance-switched]} {:events [:multiaccounts.ui/appearance-switched]}
[cofx theme] [cofx theme]
(rf/merge cofx (rf/merge cofx
{:multiaccounts.ui/switch-theme [theme :appearance true]} {:multiaccounts.ui/switch-theme-fx [theme :appearance true]}
(multiaccounts.update/multiaccount-update :appearance theme {}))) (multiaccounts.update/multiaccount-update :appearance theme {})))
(rf/defn switch-theme
{:events [:multiaccounts.ui/switch-theme]}
[cofx theme view-id]
(let [theme (or theme
(get-in cofx [:db :multiaccount :appearance])
constants/theme-type-dark)]
{:multiaccounts.ui/switch-theme-fx [theme view-id false]}))
(rf/defn switch-profile-picture-show-to (rf/defn switch-profile-picture-show-to
{:events [:multiaccounts.ui/profile-picture-show-to-switched]} {:events [:multiaccounts.ui/profile-picture-show-to-switched]}
[cofx id] [cofx id]
+18 -11
View File
@@ -44,6 +44,7 @@
[status-im2.navigation.events :as navigation] [status-im2.navigation.events :as navigation]
[status-im2.common.log :as logging] [status-im2.common.log :as logging]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im2.contexts.shell.animation :as shell.animation]
[utils.security.core :as security])) [utils.security.core :as security]))
(re-frame/reg-fx (re-frame/reg-fx
@@ -338,10 +339,6 @@
{:method "permissions_getDappPermissions" {:method "permissions_getDappPermissions"
:on-success #(re-frame/dispatch [::initialize-dapp-permissions %])}]}) :on-success #(re-frame/dispatch [::initialize-dapp-permissions %])}]})
(rf/defn initialize-appearance
[cofx]
{:multiaccounts.ui/switch-theme [(get-in cofx [:db :multiaccount :appearance]) nil false]})
(rf/defn get-group-chat-invitations (rf/defn get-group-chat-invitations
[_] [_]
{:json-rpc/call {:json-rpc/call
@@ -393,7 +390,6 @@
#(do (re-frame/dispatch [:chats-list/load-success %]) #(do (re-frame/dispatch [:chats-list/load-success %])
(rf/dispatch [:communities/get-user-requests-to-join]) (rf/dispatch [:communities/get-user-requests-to-join])
(re-frame/dispatch [::get-chats-callback]))}) (re-frame/dispatch [::get-chats-callback]))})
(initialize-appearance)
(initialize-wallet-connect) (initialize-wallet-connect)
(get-node-config) (get-node-config)
(communities/fetch) (communities/fetch)
@@ -479,8 +475,17 @@
(defn redirect-to-root (defn redirect-to-root
"Decides which root should be initialised depending on user and app state" "Decides which root should be initialised depending on user and app state"
[db] [db]
(if (get db :tos/accepted?) (cond
(get db :local-pairing/completed-pairing?)
(re-frame/dispatch [:syncing/pairing-completed])
(get db :onboarding-2/new-account?)
(re-frame/dispatch [:navigate-to :enable-notifications])
(get db :tos/accepted?)
(re-frame/dispatch [:init-root :shell-stack]) (re-frame/dispatch [:init-root :shell-stack])
:else
(re-frame/dispatch [:init-root :tos]))) (re-frame/dispatch [:init-root :tos])))
(rf/defn login-only-events (rf/defn login-only-events
@@ -514,10 +519,11 @@
tos-accepted? (get db :tos/accepted?) tos-accepted? (get db :tos/accepted?)
{:networks/keys [current-network networks]} db {:networks/keys [current-network networks]} db
network-id (str (get-in networks [current-network :config :NetworkId]))] network-id (str (get-in networks [current-network :config :NetworkId]))]
(shell.animation/change-selected-stack-id :communities-stack true)
(rf/merge cofx (rf/merge cofx
{:db (-> db {:db (-> db
(dissoc :multiaccounts/login) (dissoc :multiaccounts/login)
(assoc :tos/next-root :onboarding-notification :chats/loading? false) (assoc :tos/next-root :enable-notifications :chats/loading? false)
(assoc-in [:multiaccount :multiaccounts/first-account] first-account?)) (assoc-in [:multiaccount :multiaccounts/first-account] first-account?))
::get-tokens [network-id accounts recovered-account?]} ::get-tokens [network-id accounts recovered-account?]}
(finish-keycard-setup) (finish-keycard-setup)
@@ -528,7 +534,7 @@
(multiaccounts/switch-preview-privacy-mode-flag) (multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist) (link-preview/request-link-preview-whitelist)
(logging/set-log-level (:log-level multiaccount)) (logging/set-log-level (:log-level multiaccount))
(navigation/init-root :shell-stack)))) (navigation/init-root :enable-notifications))))
(defn- keycard-setup? (defn- keycard-setup?
[cofx] [cofx]
@@ -631,7 +637,7 @@
(assoc-in [:keycard :pin :login] []))}) (assoc-in [:keycard :pin :login] []))})
#(if keycard-account? #(if keycard-account?
{:init-root-fx :multiaccounts-keycard} {:init-root-fx :multiaccounts-keycard}
{:init-root-fx :multiaccounts}) {:init-root-fx :profiles})
#(when goto-key-storage? #(when goto-key-storage?
(navigation/navigate-to-cofx % :actions-not-logged-in nil)))))) (navigation/navigate-to-cofx % :actions-not-logged-in nil))))))
@@ -730,8 +736,9 @@
keycard-multiaccount? (boolean (:keycard-pairing multiaccount))] keycard-multiaccount? (boolean (:keycard-pairing multiaccount))]
(rf/merge (rf/merge
cofx cofx
{:db (update db :keycard dissoc :application-info) (merge
:navigate-to-fx (if keycard-multiaccount? :keycard-login-pin :login)} {:db (update db :keycard dissoc :application-info)}
(when keycard-multiaccount? {:navigate-to-fx :keycard-login-pin}))
(open-login (select-keys multiaccount [:key-uid :name :public-key :identicon :images]))))) (open-login (select-keys multiaccount [:key-uid :name :public-key :identicon :images])))))
(rf/defn hide-keycard-banner (rf/defn hide-keycard-banner
+20
View File
@@ -91,6 +91,14 @@
key-uid key-uid
#(.loginWithConfig ^js (status) account-data hashed-password config)))) #(.loginWithConfig ^js (status) account-data hashed-password config))))
(defn create-account-and-login
[request]
(.createAccountAndLogin ^js (status) (types/clj->json request)))
(defn restore-account-and-login
[request]
(.restoreAccountAndLogin ^js (status) (types/clj->json request)))
(defn export-db (defn export-db
"NOTE: beware, the password has to be sha3 hashed" "NOTE: beware, the password has to be sha3 hashed"
[key-uid account-data hashed-password callback] [key-uid account-data hashed-password callback]
@@ -611,3 +619,15 @@
current-password# current-password#
new-password new-password
callback)) callback))
(defn backup-disabled-data-dir
[]
(.backupDisabledDataDir ^js (status)))
(defn keystore-dir
[]
(.keystoreDir ^js (status)))
(defn log-file-path
[]
(.logFilePath ^js (status)))
+40 -6
View File
@@ -9,7 +9,10 @@
[status-im.visibility-status-updates.core :as visibility-status-updates] [status-im.visibility-status-updates.core :as visibility-status-updates]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.contexts.chat.messages.link-preview.events :as link-preview] [status-im2.contexts.chat.messages.link-preview.events :as link-preview]
[taoensso.timbre :as log])) [taoensso.timbre :as log]
[status-im2.constants :as constants]
[quo2.foundations.colors :as colors]
[status-im.multiaccounts.model :as multiaccounts.model]))
(rf/defn status-node-started (rf/defn status-node-started
[{db :db :as cofx} {:keys [error]}] [{db :db :as cofx} {:keys [error]}]
@@ -53,10 +56,40 @@
:peer-stats peer-stats :peer-stats peer-stats
:peers-count (count (:peers peer-stats)))})) :peers-count (count (:peers peer-stats)))}))
(defn handle-local-pairing-signals (rf/defn handle-local-pairing-signals
[event] [{:keys [db] :as cofx} event]
(log/debug "local pairing signal received" (log/info "local pairing signal received"
{:event event})) {:event event})
(let [connection-success? (= (:type event)
constants/local-pairing-event-connection-success)
error-on-pairing? (contains? constants/local-pairing-event-errors (:type event))
completed-pairing? (and (= (:type event)
constants/local-pairing-event-process-success)
(= (:action event)
constants/local-pairing-action-pairing-account))
logged-in? (multiaccounts.model/logged-in? cofx)
;; since `connection-success` event is received on both sender and receiver devices
;; we check the `logged-in?` status to identify the receiver and take the user to next screen
navigate-to-syncing-devices? (and connection-success? (not logged-in?))
user-in-syncing-devices-screen? (= (:view-id db) :syncing-devices)]
(merge {:db (cond-> db
connection-success?
(assoc :local-pairing/completed-pairing? false)
error-on-pairing?
(dissoc :local-pairing/completed-pairing?)
completed-pairing?
(assoc :local-pairing/completed-pairing? true))}
(when navigate-to-syncing-devices?
{:dispatch [:navigate-to :syncing-devices]})
(when (and error-on-pairing? user-in-syncing-devices-screen?)
{:dispatch-n [[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/error-syncing-connection-failed)}]
[:navigate-back]]}))))
(rf/defn process (rf/defn process
{:events [:signals/signal-received]} {:events [:signals/signal-received]}
@@ -110,5 +143,6 @@
"status.updates.timedout" (visibility-status-updates/handle-visibility-status-updates "status.updates.timedout" (visibility-status-updates/handle-visibility-status-updates
cofx cofx
(js->clj event-js :keywordize-keys true)) (js->clj event-js :keywordize-keys true))
"localPairing" (handle-local-pairing-signals (js->clj event-js :keywordize-keys true)) "localPairing" (handle-local-pairing-signals cofx
(js->clj event-js :keywordize-keys true))
(log/debug "Event " type " not handled")))) (log/debug "Event " type " not handled"))))
-8
View File
@@ -1,8 +0,0 @@
(ns status-im.theme.core
(:require [quo.theme :as quo.theme]
[quo2.theme :as quo2.theme]))
(defn change-theme
[theme]
(quo.theme/set-theme theme)
(quo2.theme/set-theme theme))
@@ -48,7 +48,7 @@
:on-press (fn [] :on-press (fn []
(rf/dispatch [:communities/load-category-states id]) (rf/dispatch [:communities/load-category-states id])
(rf/dispatch [:dismiss-keyboard]) (rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:navigate-to-nav2 :community {:community-id id}])) (rf/dispatch [:navigate-to :community-overview id]))
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet :on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] {:content (fn []
[community/community-actions community])}])} [community/community-actions community])}])}
@@ -111,7 +111,7 @@
(i18n/label :t/open-membership))]] (i18n/label :t/open-membership))]]
:on-press #(do :on-press #(do
(rf/dispatch [:dismiss-keyboard]) (rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:navigate-to-nav2 :community {:community-id id}]))}])) (rf/dispatch [:navigate-to :community-overview id]))}]))
(defn communities-actions (defn communities-actions
[] []
@@ -12,7 +12,7 @@
[] []
[quo/list-item [quo/list-item
{:theme :accent {:theme :accent
:on-press #(hide-sheet-and-dispatch [:generate-and-derive-addresses]) :on-press #(hide-sheet-and-dispatch [:navigate-to :intro])
:icon :main-icons/add :icon :main-icons/add
:accessibility-label :generate-a-new-key :accessibility-label :generate-a-new-key
:title (i18n/label :t/generate-a-new-key)}]) :title (i18n/label :t/generate-a-new-key)}])
@@ -377,14 +377,14 @@
:height 40}}] :height 40}}]
[communities.icon/community-icon community])] [communities.icon/community-icon community])]
[rn/view {:padding-right 14 :flex 1} [rn/view {:padding-right 14 :flex 1}
[rn/text {:style {:font-weight "700" :font-size 17}} [rn/text {:style {:font-weight "700" :font-size 17 :color quo.colors/black}}
name] name]
[rn/text description]]] [rn/text {:style {:color quo.colors/black}} description]]]
[rn/view (style/community-view-button) [rn/view (style/community-view-button)
[rn/touchable-opacity [rn/touchable-opacity
{:on-press #(re-frame/dispatch {:on-press #(re-frame/dispatch
[:communities/navigate-to-community [:communities/navigate-to-community
{:community-id (:id community)}])} (:id community)])}
[rn/text [rn/text
{:style {:text-align :center {:style {:text-align :center
:color quo.colors/blue}} (i18n/label :t/view)]]]]))) :color quo.colors/blue}} (i18n/label :t/view)]]]])))
+8 -4
View File
@@ -225,8 +225,12 @@
(defn format-members (defn format-members
[count] [count]
(if (> count 1000000) (cond
(> count 1000000)
(str (with-precision (/ count 1000000) 1) (i18n/label :t/M)) (str (with-precision (/ count 1000000) 1) (i18n/label :t/M))
(if (and (> count 999) (< count 1000000))
(str (with-precision (/ count 1000) 1) (i18n/label :t/K)) (< 999 count 1000000)
count))) (str (with-precision (/ count 1000) 1) (i18n/label :t/K))
:else
count))
@@ -0,0 +1,42 @@
(ns status-im2.common.bottom-sheet-screen.style
(:require
[quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(defn background
[opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:background-color colors/neutral-100-opa-70
:position :absolute
:top 0
:bottom 0
:left 0
:right 0}))
(defn main-view
[translate-y]
(reanimated/apply-animations-to-style
{:transform [{:translate-y translate-y}]}
{:background-color (colors/theme-colors colors/white colors/neutral-95)
:border-top-left-radius 20
:border-top-right-radius 20
:flex 1
:overflow :hidden}))
(def handle-container
{:left 0
:right 0
:top 0
:height 20
:z-index 1
:position :absolute
:justify-content :center
:align-items :center})
(defn handle
[]
{:width 32
:height 4
:border-radius 100
:background-color (colors/theme-colors colors/neutral-100-opa-30 colors/white-opa-30)})
@@ -0,0 +1,81 @@
(ns status-im2.common.bottom-sheet-screen.view
(:require
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.navigation :as navigation]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[oops.core :as oops]
[react-native.safe-area :as safe-area]
[status-im2.common.bottom-sheet-screen.style :as style]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.re-frame :as rf]))
(def ^:const drag-threshold 100)
(defn drag-gesture
[translate-y opacity scroll-enabled curr-scroll]
(->
(gesture/gesture-pan)
(gesture/on-start (fn [e]
(when (< (oops/oget e "velocityY") 0)
(reset! scroll-enabled true))))
(gesture/on-update (fn [e]
(let [translation (oops/oget e "translationY")
progress (Math/abs (/ translation drag-threshold))]
(when (pos? translation)
(reanimated/set-shared-value translate-y translation)
(reanimated/set-shared-value opacity (- 1 (/ progress 5)))))))
(gesture/on-end (fn [e]
(if (> (oops/oget e "translationY") drag-threshold)
(do
(reanimated/set-shared-value opacity (reanimated/with-timing-duration 0 100))
(rf/dispatch [:navigate-back]))
(do
(reanimated/set-shared-value opacity (reanimated/with-timing 1))
(reanimated/set-shared-value translate-y (reanimated/with-timing 0))
(reset! scroll-enabled true)))))
(gesture/on-finalize (fn [e]
(when (and (>= (oops/oget e "velocityY") 0)
(<= @curr-scroll (if platform/ios? -1 0)))
(reset! scroll-enabled false))))))
(defn on-scroll
[e curr-scroll]
(let [y (oops/oget e "nativeEvent.contentOffset.y")]
(reset! curr-scroll y)))
(defn view
[content skip-background?]
[:f>
(let [scroll-enabled (reagent/atom true)
curr-scroll (atom 0)]
(fn []
(let [sb-height (navigation/status-bar-height)
insets (safe-area/use-safe-area)
padding-top (Math/max sb-height (:top insets))
padding-top (if platform/ios? padding-top (+ padding-top 10))
opacity (reanimated/use-shared-value 0)
translate-y (reanimated/use-shared-value 0)
close (fn []
(reanimated/set-shared-value opacity (reanimated/with-timing-duration 0 100))
(rf/dispatch [:navigate-back]))]
(rn/use-effect
(fn []
(reanimated/animate-delay opacity 1 (if platform/ios? 300 100))))
(hooks/use-back-handler close)
[rn/view
{:style {:flex 1
:padding-top padding-top}}
(when-not skip-background?
[reanimated/view {:style (style/background opacity)}])
[gesture/gesture-detector
{:gesture (drag-gesture translate-y opacity scroll-enabled curr-scroll)}
[reanimated/view {:style (style/main-view translate-y)}
[rn/view {:style style/handle-container}
[rn/view {:style (style/handle)}]]
[content
{:close close
:scroll-enabled @scroll-enabled
:on-scroll #(on-scroll % curr-scroll)}]]]])))])
@@ -26,18 +26,23 @@
[quo/text {:style {:margin-left 10}} extra-text]])) [quo/text {:style {:margin-left 10}} extra-text]]))
(defn confirmation-drawer (defn confirmation-drawer
[{:keys [title description context button-text on-press extra-action extra-text accessibility-label]}] [{:keys [title description context button-text on-press extra-action extra-text accessibility-label
close-button-text]}]
(let [extra-action-selected? (reagent/atom false)] (let [extra-action-selected? (reagent/atom false)]
(fn [] (fn []
(let [{:keys [group-chat chat-id public-key color name]} context (let [{:keys [group-chat chat-id public-key color profile-picture
id (or chat-id public-key) name]} context
display-name id (or chat-id public-key)
(if-not group-chat (first (rf/sub [:contacts/contact-two-names-by-identity id])) name) display-name (or
contact (when-not group-chat name
(rf/sub [:contacts/contact-by-address (when-not group-chat
id])) (rf/sub [:contacts/contact-name-by-identity id])))
photo-path (when-not (empty? (:images contact)) contact (when-not group-chat
(rf/sub [:chats/photo-path id]))] (rf/sub [:contacts/contact-by-address
id]))
photo-path (or profile-picture
(when-not (empty? (:images contact))
(rf/sub [:chats/photo-path id])))]
[rn/view [rn/view
{:style {:margin-horizontal 20} {:style {:margin-horizontal 20}
:accessibility-label accessibility-label} :accessibility-label accessibility-label}
@@ -57,7 +62,7 @@
{:type :grey {:type :grey
:style {:flex 0.48} ;;WUT? 0.48 , whats that ? :style {:flex 0.48} ;;WUT? 0.48 , whats that ?
:on-press #(rf/dispatch [:bottom-sheet/hide])} :on-press #(rf/dispatch [:bottom-sheet/hide])}
(i18n/label :t/close)] (or close-button-text (i18n/label :t/close))]
[quo/button [quo/button
{:type :danger {:type :danger
:style {:flex 0.48} :style {:flex 0.48}
+25 -21
View File
@@ -89,7 +89,7 @@
(defn display-picture (defn display-picture
[scroll-height cover] [scroll-height logo]
(let [input-range (if platform/ios? [-67 10] [0 150]) (let [input-range (if platform/ios? [-67 10] [0 150])
y (reanimated/use-shared-value scroll-height) y (reanimated/use-shared-value scroll-height)
animation (reanimated/interpolate y animation (reanimated/interpolate y
@@ -104,7 +104,7 @@
[reanimated/view [reanimated/view
{:style (style/display-picture-container animation)} {:style (style/display-picture-container animation)}
[rn/image [rn/image
{:source cover {:source logo
:style style/display-picture}]])) :style style/display-picture}]]))
(defn scroll-page (defn scroll-page
@@ -117,7 +117,7 @@
[:<> [:<>
[:f> scroll-page-header @scroll-height height name page-nav-right-section-buttons [:f> scroll-page-header @scroll-height height name page-nav-right-section-buttons
logo sticky-header top-nav title-colum navigate-back?] logo sticky-header top-nav title-colum navigate-back?]
[rn/scroll-view [rn/flat-list
{:content-container-style (style/scroll-view-container {:content-container-style (style/scroll-view-container
(diff-with-max-min @scroll-height 16 0)) (diff-with-max-min @scroll-height 16 0))
:shows-vertical-scroll-indicator false :shows-vertical-scroll-indicator false
@@ -128,21 +128,25 @@
event event
"nativeEvent.contentOffset.y"))) "nativeEvent.contentOffset.y")))
(when on-scroll (when on-scroll
(on-scroll @scroll-height)))} (on-scroll @scroll-height)))
(when cover-image :header [rn/view
[rn/view {:style {:height 151}} (when cover-image
[rn/image [rn/view {:style {:height 151}}
{:source cover-image [rn/image
;; Using negative margin-bottom as a workaround because on Android, {:source cover-image
;; ScrollView clips its children despite setting overflow: 'visible'. ;; Using negative margin-bottom as a workaround because
;; Related issue: https://github.com/facebook/react-native/issues/31218 ;; on Android,
:style {:margin-bottom -16 ;; ScrollView clips its children despite setting
:flex 1}}]]) ;; overflow: 'visible'.
(when children ;; Related issue:
[rn/view ;; https://github.com/facebook/react-native/issues/31218
{:flex 1 :style {:margin-bottom -16
:border-radius (diff-with-max-min @scroll-height 16 0) :flex 1}}]])
:background-color background-color} (when children
(when cover-image [rn/view
[:f> display-picture @scroll-height logo]) {:flex 1
children])]]))) :border-radius (diff-with-max-min @scroll-height 16 0)
:background-color background-color}
(when cover-image
[:f> display-picture @scroll-height logo])
children])]}]])))
+16 -9
View File
@@ -1,17 +1,24 @@
(ns status-im2.common.theme.core (ns status-im2.common.theme.core
(:require [react-native.core :as rn])) (:require [quo.theme :as quo]
[quo2.theme :as quo2]
[react-native.core :as rn]))
(def initial-mode (atom (rn/get-color-scheme))) (def device-theme (atom (rn/get-color-scheme)))
;; Note - don't use value returned by change listener ;; Note - don't use value returned by change listener
;; https://github.com/facebook/react-native/issues/28525 ;; https://github.com/facebook/react-native/issues/28525
(defn add-mode-change-listener (defn add-device-theme-change-listener
[callback] [callback]
(rn/appearance-add-change-listener #(let [mode (rn/get-color-scheme)] (rn/appearance-add-change-listener #(let [theme (rn/get-color-scheme)]
(when-not (= mode @initial-mode) (when-not (= theme @device-theme)
(reset! initial-mode mode) (reset! device-theme theme)
(callback (keyword mode)))))) (callback (keyword theme))))))
(defn dark-mode? (defn device-theme-dark?
[] []
(= @initial-mode "dark")) (= @device-theme "dark"))
(defn set-theme
[value]
(quo/set-theme value)
(quo2/set-theme value))
+31
View File
@@ -90,6 +90,9 @@
(def ^:const command-state-transaction-pending 6) (def ^:const command-state-transaction-pending 6)
(def ^:const command-state-transaction-sent 7) (def ^:const command-state-transaction-sent 7)
(def ^:const profile-default-color :blue)
(def ^:const profile-name-max-length 24)
(def ^:const profile-pictures-show-to-contacts-only 1) (def ^:const profile-pictures-show-to-contacts-only 1)
(def ^:const profile-pictures-show-to-everyone 2) (def ^:const profile-pictures-show-to-everyone 2)
(def ^:const profile-pictures-show-to-none 3) (def ^:const profile-pictures-show-to-none 3)
@@ -260,6 +263,27 @@
An example of a connection string is -> cs2:5vd6J6:Jfc:27xMmHKEYwzRGXcvTtuiLZFfXscMx4Mz8d9wEHUxDj4p7:EG7Z13QScfWBJNJ5cprszzDQ5fBVsYMirXo8MaQFJvpF:3 " An example of a connection string is -> cs2:5vd6J6:Jfc:27xMmHKEYwzRGXcvTtuiLZFfXscMx4Mz8d9wEHUxDj4p7:EG7Z13QScfWBJNJ5cprszzDQ5fBVsYMirXo8MaQFJvpF:3 "
"cs") "cs")
;; sender and receiver events
(def ^:const local-pairing-event-connection-success "connection-success")
(def ^:const local-pairing-event-connection-error "connection-error")
(def ^:const local-pairing-event-transfer-success "transfer-success")
(def ^:const local-pairing-event-transfer-error "transfer-error")
;; receiver events
(def ^:const local-pairing-event-received-amount "received-account")
(def ^:const local-pairing-event-process-success "process-success")
(def ^:const local-pairing-event-process-error "process-error")
(def ^:const local-pairing-event-errors
#{local-pairing-event-connection-error
local-pairing-event-transfer-error
local-pairing-event-process-error})
(def ^:const local-pairing-action-connect 1)
(def ^:const local-pairing-action-pairing-account 2)
(def ^:const local-pairing-action-sync-device 3)
(def ^:const local-pairing-action-pairing-installation 4)
(def ^:const serialization-key (def ^:const serialization-key
"We pass this serialization key as a parameter to MultiformatSerializePublicKey "We pass this serialization key as a parameter to MultiformatSerializePublicKey
function at status-go, This key determines the output base of the serialization. function at status-go, This key determines the output base of the serialization.
@@ -292,3 +316,10 @@
(def ^:const everyone-mention-id "0x00001") (def ^:const everyone-mention-id "0x00001")
(def ^:const empty-category-id :communities/not-categorized) (def ^:const empty-category-id :communities/not-categorized)
(def ^:const seed-phrase-valid-length #{12 18 24})
(def ^:const auth-method-password "password")
(def ^:const auth-method-biometric "biometric")
(def ^:const auth-method-biometric-prepare "biometric-prepare")
(def ^:const auth-method-none "none")
@@ -1,6 +1,5 @@
(ns status-im2.contexts.activity-center.notification.contact-requests.events (ns status-im2.contexts.activity-center.notification.contact-requests.events
(:require [status-im2.contexts.activity-center.events :as ac-events] (:require [taoensso.timbre :as log]
[taoensso.timbre :as log]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(rf/defn accept-contact-request (rf/defn accept-contact-request
@@ -19,8 +18,7 @@
(log/error "Failed to accept contact-request" (log/error "Failed to accept contact-request"
{:error error {:error error
:event :activity-center.contact-requests/accept :event :activity-center.contact-requests/accept
:contact-id contact-id}) :contact-id contact-id}))
nil)
(rf/defn decline-contact-request (rf/defn decline-contact-request
{:events [:activity-center.contact-requests/decline]} {:events [:activity-center.contact-requests/decline]}
@@ -38,32 +36,4 @@
(log/error "Failed to decline contact-request" (log/error "Failed to decline contact-request"
{:error error {:error error
:event :activity-center.contact-requests/decline :event :activity-center.contact-requests/decline
:contact-id contact-id}) :contact-id contact-id}))
nil)
(rf/defn cancel-outgoing-contact-request
{:events [:activity-center.contact-requests/cancel-outgoing]}
[{:keys [db]} {:keys [contact-id notification-id]}]
(when-let [notification (ac-events/get-notification db notification-id)]
{:json-rpc/call
[{:method "wakuext_cancelOutgoingContactRequest"
:params [{:id contact-id}]
:on-success #(rf/dispatch [:activity-center.contact-requests/cancel-outgoing-success
notification])
:on-error #(rf/dispatch [:activity-center.contact-requests/cancel-outgoing-error contact-id
%])}]}))
(rf/defn cancel-outgoing-contact-request-success
{:events [:activity-center.contact-requests/cancel-outgoing-success]}
[_ notification]
{:dispatch [:activity-center.notifications/reconcile
[(assoc notification :deleted true)]]})
(rf/defn cancel-outgoing-contact-request-error
{:events [:activity-center.contact-requests/cancel-outgoing-error]}
[_ contact-id error]
(log/error "Failed to cancel outgoing contact-request"
{:error error
:event :activity-center.contact-requests/cancel-outgoing
:contact-id contact-id})
nil)
@@ -1,12 +1,13 @@
(ns status-im2.contexts.activity-center.notification.contact-requests.view (ns status-im2.contexts.activity-center.notification.contact-requests.view
(:require [quo2.core :as quo] (:require
[react-native.gesture :as gesture] [quo2.core :as quo]
[status-im2.constants :as constants] [react-native.gesture :as gesture]
[status-im2.contexts.activity-center.notification.common.style :as common-style] [status-im2.constants :as constants]
[status-im2.contexts.activity-center.notification.common.view :as common] [status-im2.contexts.activity-center.notification.common.style :as common-style]
[utils.datetime :as datetime] [status-im2.contexts.activity-center.notification.common.view :as common]
[utils.i18n :as i18n] [utils.datetime :as datetime]
[utils.re-frame :as rf])) [utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- swipe-button-accept (defn- swipe-button-accept
[{:keys [style]} _] [{:keys [style]} _]
@@ -22,24 +23,17 @@
:icon :i/placeholder :icon :i/placeholder
:text (i18n/label :t/decline)}]) :text (i18n/label :t/decline)}])
(defn- swipe-button-cancel-pending
[{:keys [style]} _]
[common/swipe-button-container
{:style (common-style/swipe-danger-container style)
:icon :i/placeholder
:text (i18n/label :t/cancel)}])
(defn- swipeable (defn- swipeable
[{:keys [active-swipeable extra-fn notification]} child] [{:keys [active-swipeable extra-fn notification]} child]
(let [{:keys [id author message last-message]} notification (let [{:keys [id author message]} notification
{:keys [contact-request-state]} (or (:message notification) {:keys [contact-request-state]} message
(:last-message notification)) {:keys [public-key]} (rf/sub [:multiaccount/contact])
{:keys [public-key]} (rf/sub [:multiaccount/contact]) outgoing? (= public-key author)]
message (or message last-message)]
(cond (cond
(#{constants/contact-request-message-state-accepted (or (#{constants/contact-request-message-state-accepted
constants/contact-request-message-state-declined} constants/contact-request-message-state-declined}
contact-request-state) contact-request-state)
(and outgoing? (= contact-request-state constants/contact-request-message-state-pending)))
[common/swipeable [common/swipeable
{:left-button common/swipe-button-read-or-unread {:left-button common/swipe-button-read-or-unread
:left-on-press common/swipe-on-press-toggle-read :left-on-press common/swipe-on-press-toggle-read
@@ -49,33 +43,23 @@
:extra-fn extra-fn} :extra-fn extra-fn}
child] child]
(= contact-request-state constants/contact-request-message-state-pending) (and (= contact-request-state constants/contact-request-message-state-pending)
(if (= public-key author) (not outgoing?))
[common/swipeable [common/swipeable
{:right-button swipe-button-cancel-pending {:left-button swipe-button-accept
:right-on-press (fn [] :left-on-press #(rf/dispatch [:activity-center.contact-requests/accept id])
(rf/dispatch :right-button swipe-button-decline
[:activity-center.contact-requests/cancel-outgoing :right-on-press #(rf/dispatch [:activity-center.contact-requests/decline id])
{:contact-id (:from message) :active-swipeable active-swipeable
:notification-id id}])) :extra-fn extra-fn}
:active-swipeable active-swipeable child]
:extra-fn extra-fn}
child]
[common/swipeable
{:left-button swipe-button-accept
:left-on-press #(rf/dispatch [:activity-center.contact-requests/accept id])
:right-button swipe-button-decline
:right-on-press #(rf/dispatch [:activity-center.contact-requests/decline id])
:active-swipeable active-swipeable
:extra-fn extra-fn}
child])
:else :else
child))) child)))
(defn- outgoing-contact-request-view (defn- outgoing-contact-request-view
[{:keys [notification set-swipeable-height]}] [{:keys [notification set-swipeable-height]}]
(let [{:keys [id chat-id message last-message]} notification (let [{:keys [chat-id message last-message]} notification
{:keys [contact-request-state] :as message} (or message last-message)] {:keys [contact-request-state] :as message} (or message last-message)]
(if (= contact-request-state constants/contact-request-message-state-accepted) (if (= contact-request-state constants/contact-request-message-state-accepted)
[quo/activity-log [quo/activity-log
@@ -99,17 +83,7 @@
:message {:body (get-in message [:content :text])} :message {:body (get-in message [:content :text])}
:items (case contact-request-state :items (case contact-request-state
constants/contact-request-message-state-pending constants/contact-request-message-state-pending
[{:type :button [{:type :status
:subtype :danger
:key :button-cancel
:label (i18n/label :t/cancel)
:accessibility-label :cancel-contact-request
:on-press (fn []
(rf/dispatch
[:activity-center.contact-requests/cancel-outgoing
{:contact-id (:from message)
:notification-id id}]))}
{:type :status
:subtype :pending :subtype :pending
:key :status-pending :key :status-pending
:blur? true :blur? true
@@ -1,5 +1,6 @@
(ns status-im2.contexts.add-new-contact.events (ns status-im2.contexts.add-new-contact.events
(:require [utils.re-frame :as rf] (:require [clojure.string :as string]
[utils.re-frame :as rf]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
@@ -11,11 +12,120 @@
[status-im2.contexts.contacts.events :as data-store.contacts] [status-im2.contexts.contacts.events :as data-store.contacts]
[status-im.utils.utils :as utils])) [status-im.utils.utils :as utils]))
(defn init-contact
"Create a new contact (persisted to app-db as [:contacts/new-identity]).
The following options are available:
| key | description |
| -------------------|-------------|
| `:user-public-key` | user's public key (not the contact)
| `:input` | raw user input (untrimmed)
| `:scanned` | scanned user input (untrimmed)
| `:id` | public-key|compressed-key|ens
| `:type` | :empty|:public-key|:compressed-key|:ens
| `:ens` | id.eth|id.ens-stateofus
| `:public-key` | public-key (from decompression or ens resolution)
| `:state` | :empty|:invalid|:decompress-key|:resolve-ens|:valid
| `:msg` | keyword i18n msg"
([]
(-> [:user-public-key :input :scanned :id :type :ens :public-key :state :msg]
(zipmap (repeat nil))))
([kv] (-> (init-contact) (merge kv))))
(def url-regex #"^https?://join.status.im/u/(.+)")
(defn ->id
[{:keys [input] :as contact}]
(let [trimmed-input (utils/safe-trim input)]
(->> {:id (if (empty? trimmed-input)
nil
(if-some [[_ id] (re-matches url-regex trimmed-input)]
id
trimmed-input))}
(merge contact))))
(defn ->type
[{:keys [id] :as contact}]
(->> (cond
(empty? id)
{:type :empty}
(validators/valid-public-key? id)
{:type :public-key
:public-key id}
(validators/valid-compressed-key? id)
{:type :compressed-key}
:else
{:type :ens
:ens (stateofus/ens-name-parse id)})
(merge contact)))
(defn ->state
[{:keys [id type public-key user-public-key] :as contact}]
(->> (cond
(empty? id)
{:state :empty}
(= type :public-key)
{:state :invalid
:msg :t/not-a-chatkey}
(= public-key user-public-key)
{:state :invalid
:msg :t/can-not-add-yourself}
(and (= type :compressed-key) (empty? public-key))
{:state :decompress-key}
(and (= type :ens) (empty? public-key))
{:state :resolve-ens}
(and (or (= type :compressed-key) (= type :ens))
(validators/valid-public-key? public-key))
{:state :valid})
(merge contact)))
(def validate-contact (comp ->state ->type ->id))
(defn dispatcher [event input] (fn [arg] (rf/dispatch [event input arg])))
(rf/defn set-new-identity
{:events [:contacts/set-new-identity]}
[{:keys [db]} input scanned]
(let [user-public-key (get-in db [:multiaccount :public-key])
{:keys [input id ens state]
:as contact} (-> {:user-public-key user-public-key
:input input
:scanned scanned}
init-contact
validate-contact)]
(case state
:empty {:db (dissoc db :contacts/new-identity)}
(:valid :invalid) {:db (assoc db :contacts/new-identity contact)}
:decompress-key {:db (assoc db :contacts/new-identity contact)
:contacts/decompress-public-key
{:compressed-key id
:on-success
(dispatcher :contacts/set-new-identity-success input)
:on-error
(dispatcher :contacts/set-new-identity-error input)}}
:resolve-ens {:db (assoc db :contacts/new-identity contact)
:contacts/resolve-public-key-from-ens
{:chain-id (ethereum/chain-id db)
:ens ens
:on-success
(dispatcher :contacts/set-new-identity-success input)
:on-error
(dispatcher :contacts/set-new-identity-error input)}})))
(re-frame/reg-fx (re-frame/reg-fx
:contacts/decompress-public-key :contacts/decompress-public-key
(fn [{:keys [public-key on-success on-error]}] (fn [{:keys [compressed-key on-success on-error]}]
(status/compressed-key->public-key (status/compressed-key->public-key
public-key compressed-key
(fn [resp] (fn [resp]
(let [{:keys [error]} (types/json->clj resp)] (let [{:keys [error]} (types/json->clj resp)]
(if error (if error
@@ -23,74 +133,16 @@
(on-success (str "0x" (subs resp 5))))))))) (on-success (str "0x" (subs resp 5)))))))))
(re-frame/reg-fx (re-frame/reg-fx
:contacts/resolve-public-key-from-ens-name :contacts/resolve-public-key-from-ens
(fn [{:keys [chain-id ens-name on-success on-error]}] (fn [{:keys [chain-id ens on-success on-error]}]
(ens/pubkey chain-id ens-name on-success on-error))) (ens/pubkey chain-id ens on-success on-error)))
(defn fx-callbacks
[input ens-name]
{:on-success (fn [pubkey]
(rf/dispatch [:contacts/set-new-identity-success input ens-name pubkey]))
:on-error (fn [err]
(rf/dispatch [:contacts/set-new-identity-error err input]))})
(defn identify-type
[input]
(let [regex #"^https?://join.status.im/u/(.+)"
id (as-> (utils/safe-trim input) $
(if-some [[_ match] (re-matches regex $)]
match
$)
(if (empty? $) nil $))
public-key? (validators/valid-public-key? id)
compressed-key? (validators/valid-compressed-key? id)
type (cond (empty? id) :empty
public-key? :public-key
compressed-key? :compressed-key
:else :ens-name)
ens-name (when (= type :ens-name)
(stateofus/ens-name-parse id))]
{:input input
:id id
:type type
:ens-name ens-name}))
(rf/defn set-new-identity
{:events [:contacts/set-new-identity]}
[{:keys [db]} input]
(let [{:keys [input id type ens-name]} (identify-type input)]
(case type
:empty {:db (dissoc db :contacts/new-identity)}
:public-key {:db (assoc db
:contacts/new-identity
{:input input
:public-key id
:state :error
:error :uncompressed-key})}
:compressed-key {:db
(assoc db
:contacts/new-identity
{:input input
:state :searching})
:contacts/decompress-public-key
(merge {:public-key id}
(fx-callbacks id ens-name))}
:ens-name {:db
(assoc db
:contacts/new-identity
{:input input
:state :searching})
:contacts/resolve-public-key-from-ens-name
(merge {:chain-id (ethereum/chain-id db)
:ens-name ens-name}
(fx-callbacks id ens-name))})))
(rf/defn build-contact (rf/defn build-contact
{:events [:contacts/build-contact]} {:events [:contacts/build-contact]}
[_ pubkey ens-name open-profile-modal?] [_ pubkey ens open-profile-modal?]
{:json-rpc/call [{:method "wakuext_buildContact" {:json-rpc/call [{:method "wakuext_buildContact"
:params [{:publicKey pubkey :params [{:publicKey pubkey
:ENSName ens-name}] :ENSName ens}]
:js-response true :js-response true
:on-success #(rf/dispatch [:contacts/contact-built :on-success #(rf/dispatch [:contacts/contact-built
pubkey pubkey
@@ -106,24 +158,25 @@
(rf/defn set-new-identity-success (rf/defn set-new-identity-success
{:events [:contacts/set-new-identity-success]} {:events [:contacts/set-new-identity-success]}
[{:keys [db] :as cofx} input ens-name pubkey] [{:keys [db]} input pubkey]
(rf/merge cofx (let [contact (get-in db [:contacts/new-identity])]
{:db (assoc db (when (= (:input contact) input)
:contacts/new-identity (rf/merge {:db (assoc db
{:input input :contacts/new-identity
:public-key pubkey (->state (assoc contact :public-key pubkey)))}
:ens-name ens-name (build-contact pubkey (:ens contact) false)))))
:state :valid})}
(build-contact pubkey ens-name false)))
(rf/defn set-new-identity-error (rf/defn set-new-identity-error
{:events [:contacts/set-new-identity-error]} {:events [:contacts/set-new-identity-error]}
[{:keys [db]} error input] [{:keys [db]} input err]
{:db (assoc db (let [contact (get-in db [:contacts/new-identity])]
:contacts/new-identity (when (= (:input contact) input)
{:input input (let [state (cond
:state :error (or (string/includes? (:message err) "fallback failed")
:error :invalid})}) (string/includes? (:message err) "no such host"))
{:state :invalid :msg :t/lost-connection}
:else {:state :invalid})]
{:db (assoc db :contacts/new-identity (merge contact state))}))))
(rf/defn clear-new-identity (rf/defn clear-new-identity
{:events [:contacts/clear-new-identity :contacts/new-chat-focus]} {:events [:contacts/clear-new-identity :contacts/new-chat-focus]}
@@ -132,7 +185,13 @@
(rf/defn qr-code-scanned (rf/defn qr-code-scanned
{:events [:contacts/qr-code-scanned]} {:events [:contacts/qr-code-scanned]}
[{:keys [db] :as cofx} input] [{:keys [db] :as cofx} scanned]
(rf/merge cofx (rf/merge cofx
(set-new-identity input) (set-new-identity scanned scanned)
(navigation/navigate-back))) (navigation/navigate-back)))
(rf/defn set-new-identity-reconnected
[{:keys [db]}]
(let [input (get-in db [:contacts/new-identity :input])
resubmit? (and input (= :new-contact (get-in db [:view-id])))]
(rf/dispatch [:contacts/set-new-identity input])))
@@ -1,7 +1,10 @@
(ns status-im2.contexts.add-new-contact.events-test (ns status-im2.contexts.add-new-contact.events-test
(:require [cljs.test :refer-macros [deftest is are]] (:require [cljs.test :refer-macros [deftest are]]
[status-im2.contexts.add-new-contact.events :as core])) [status-im2.contexts.add-new-contact.events :as events]))
(def user-ukey
"0x04ca27ed9c7c4099d230c6d8853ad0cfaf084a019c543e9e433d3c04fac6de9147cf572b10e247cfe52f396b5aa10456b56dd1cf1d8a681e2b93993d44594b2e85")
(def user-ckey "zQ3shtFEo4PxpQiYGcNZZ8xhJmhD6WBXwnHPBueu5SRnvPXjk")
(def ukey (def ukey
"0x045596a7ff87da36860a84b0908191ce60a504afc94aac93c1abd774f182967ce694f1bf2d8773cd59f4dd0863e951f9b7f7351c5516291a0fceb73f8c392a0e88") "0x045596a7ff87da36860a84b0908191ce60a504afc94aac93c1abd774f182967ce694f1bf2d8773cd59f4dd0863e951f9b7f7351c5516291a0fceb73f8c392a0e88")
(def ckey "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA") (def ckey "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA")
@@ -10,47 +13,118 @@
(def link-ckey (str "https://join.status.im/u/" ckey)) (def link-ckey (str "https://join.status.im/u/" ckey))
(def link-ens (str "https://join.status.im/u/" ens)) (def link-ens (str "https://join.status.im/u/" ens))
(deftest identify-type-test ;;; unit tests (no app-db involved)
(are [input expected] (= (core/identify-type input) expected)
"" {:input ""
:id nil
:type :empty
:ens-name nil}
ukey {:input ukey (deftest validate-contact-test
:id ukey (are [i e] (= (events/validate-contact (events/init-contact
:type :public-key {:user-public-key user-ukey
:ens-name nil} :input i}))
(events/init-contact e))
ens {:input ens "" {:user-public-key user-ukey
:id ens :input ""
:type :ens-name :type :empty
:ens-name ens-stateofus-eth} :state :empty}
ckey {:input ckey " " {:user-public-key user-ukey
:id ckey :input " "
:type :compressed-key :type :empty
:ens-name nil} :state :empty}
link-ckey {:input link-ckey ukey {:user-public-key user-ukey
:id ckey :input ukey
:type :compressed-key :id ukey
:ens-name nil} :type :public-key
:public-key ukey
:state :invalid
:msg :t/not-a-chatkey}
link-ens {:input link-ens ens {:user-public-key user-ukey
:id ens :input ens
:type :ens-name :id ens
:ens-name ens-stateofus-eth})) :type :ens
:ens ens-stateofus-eth
:state :resolve-ens}
(deftest search-empty-string-test (str " " ens) {:user-public-key user-ukey
(is (= (core/set-new-identity {:db {:contacts/new-identity :foo}} "") :input (str " " ens)
{:db {}}))) :id ens
:type :ens
:ens ens-stateofus-eth
:state :resolve-ens}
(deftest search-uncompressed-key-test ckey {:user-public-key user-ukey
(is (= (core/set-new-identity {:db {}} ukey) :input ckey
{:db {:contacts/new-identity :id ckey
{:input ukey :type :compressed-key
:public-key ukey :state :decompress-key}
:state :error
:error :uncompressed-key}}})))
link-ckey {:user-public-key user-ukey
:input link-ckey
:id ckey
:type :compressed-key
:state :decompress-key}
link-ens {:user-public-key user-ukey
:input link-ens
:id ens
:type :ens
:ens ens-stateofus-eth
:state :resolve-ens}))
;;; event handler tests (no callbacks)
(def db
{:multiaccount {:public-key user-ukey}
:networks/current-network "mainnet_rpc"
:networks/networks {"mainnet_rpc"
{:id "mainnet_rpc"
:config {:NetworkId 1}}}})
(deftest set-new-identity-test
(with-redefs [events/dispatcher (fn [& args] args)]
(are [i edb] (= (events/set-new-identity {:db db} i nil) edb)
"" {:db db}
ukey {:db (assoc db
:contacts/new-identity
(events/init-contact
{:user-public-key user-ukey
:input ukey
:id ukey
:type :public-key
:public-key ukey
:state :invalid
:msg :t/not-a-chatkey}))}
ens {:db (assoc db
:contacts/new-identity
(events/init-contact
{:user-public-key user-ukey
:input ens
:id ens
:type :ens
:ens ens-stateofus-eth
:public-key nil ; not yet...
:state :resolve-ens}))
:contacts/resolve-public-key-from-ens
{:chain-id 1
:ens ens-stateofus-eth
:on-success [:contacts/set-new-identity-success ens]
:on-error [:contacts/set-new-identity-error ens]}}
;; compressed-key & add-self-as-contact
user-ckey {:db (assoc db
:contacts/new-identity
(events/init-contact
{:user-public-key user-ukey
:input user-ckey
:id user-ckey
:type :compressed-key
:public-key nil ; not yet...
:state :decompress-key}))
:contacts/decompress-public-key
{:compressed-key user-ckey
:on-success [:contacts/set-new-identity-success user-ckey]
:on-error [:contacts/set-new-identity-error user-ckey]}})))
@@ -32,7 +32,7 @@
{:style {:flex-direction :row {:style {:flex-direction :row
:justify-content :space-between}}) :justify-content :space-between}})
(def container-error (def container-invalid
{:style {:flex-direction :row {:style {:flex-direction :row
:align-items :center :align-items :center
:margin-top 8}}) :margin-top 8}})
@@ -64,18 +64,18 @@
colors/neutral-50 colors/neutral-50
colors/neutral-40)}}) colors/neutral-40)}})
(def icon-error (def icon-invalid
{:size 16 {:size 16
:color colors/danger-50}) :color colors/danger-50})
(def text-error (def text-invalid
{:size :paragraph-2 {:size :paragraph-2
:align :left :align :left
:style {:margin-left 4 :style {:margin-left 4
:color colors/danger-50}}) :color colors/danger-50}})
(defn text-input-container (defn text-input-container
[error?] [invalid?]
{:style {:padding-top 1 {:style {:padding-top 1
:padding-left 12 :padding-left 12
:padding-right 7 :padding-right 7
@@ -88,7 +88,7 @@
colors/neutral-95) colors/neutral-95)
:border-width 1 :border-width 1
:border-radius 12 :border-radius 12
:border-color (if error? :border-color (if invalid?
colors/danger-50-opa-40 colors/danger-50-opa-40
(colors/theme-colors (colors/theme-colors
colors/neutral-20 colors/neutral-20
@@ -4,6 +4,7 @@
[quo2.core :as quo] [quo2.core :as quo]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.clipboard :as clipboard] [react-native.clipboard :as clipboard]
[reagent.core :as reagent]
[status-im2.common.resources :as resources] [status-im2.common.resources :as resources]
[status-im.qr-scanner.core :as qr-scanner] [status-im.qr-scanner.core :as qr-scanner]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
@@ -44,60 +45,78 @@
(defn new-contact (defn new-contact
[] []
(let [{:keys [input public-key state error ens-name]} (rf/sub [:contacts/new-identity]) (let [clipboard (reagent/atom nil)
error? (and (= state :error) default-value (reagent/atom nil)]
(= error :uncompressed-key))] (fn []
[rn/keyboard-avoiding-view (style/container-kbd) (clipboard/get-string #(reset! clipboard %))
[rn/view style/container-image (let [{:keys [input scanned public-key ens state msg]}
[rn/image (rf/sub [:contacts/new-identity])
{:source (resources/get-image :add-new-contact) invalid? (= state :invalid)
:style style/image}] show-paste-button? (and (not (string/blank? @clipboard))
[quo/button (string/blank? @default-value)
(merge (style/button-close) (string/blank? input))]
{:on-press [rn/keyboard-avoiding-view (style/container-kbd)
(fn [] [rn/view style/container-image
(rf/dispatch [:contacts/clear-new-identity]) [rn/image
(rf/dispatch [:navigate-back]))}) :i/close]] {:source (resources/get-image :add-new-contact)
[rn/view (style/container-outer) :style style/image}]
[rn/view style/container-inner [quo/button
[quo/text (style/text-title) (merge (style/button-close)
(i18n/label :t/add-a-contact)] {:on-press
[quo/text (style/text-subtitle) (fn []
(i18n/label :t/find-your-friends)] (reset! clipboard nil)
[quo/text (style/text-description) (reset! default-value nil)
(i18n/label :t/ens-or-chat-key)] (rf/dispatch [:contacts/clear-new-identity])
[rn/view style/container-text-input (rf/dispatch [:navigate-back]))}) :i/close]]
[rn/view (style/text-input-container error?) [rn/view (style/container-outer)
[rn/text-input [rn/view style/container-inner
(merge (style/text-input) [quo/text (style/text-title)
{:default-value input (i18n/label :t/add-a-contact)]
:placeholder (i18n/label :t/type-some-chat-key) [quo/text (style/text-subtitle)
:on-change-text #(debounce/debounce-and-dispatch (i18n/label :t/find-your-friends)]
[:contacts/set-new-identity %] [quo/text (style/text-description)
600)})] (i18n/label :t/ens-or-chat-key)]
(when (string/blank? input) [rn/view style/container-text-input
[rn/view (style/text-input-container invalid?)
[rn/text-input
(merge (style/text-input)
{:default-value (or scanned @default-value input)
:placeholder (i18n/label :t/type-some-chat-key)
:on-change-text (fn [v]
(reset! default-value v)
(debounce/debounce-and-dispatch
[:contacts/set-new-identity v nil]
600))})]
(when show-paste-button?
[quo/button
(merge style/button-paste
{:on-press
(fn []
(reset! default-value @clipboard)
(rf/dispatch
[:contacts/set-new-identity @clipboard nil]))})
(i18n/label :t/paste)])]
[quo/button
(merge style/button-qr
{:on-press #(rf/dispatch
[::qr-scanner/scan-code
{:handler :contacts/qr-code-scanned}])})
:i/scan]]
(when invalid?
[rn/view style/container-invalid
[quo/icon :i/alert style/icon-invalid]
[quo/text style/text-invalid
(i18n/label (or msg :t/invalid-ens-or-key))]])
(when (= state :valid)
[found-contact public-key])]
[rn/view
[quo/button [quo/button
(merge style/button-paste (merge (style/button-view-profile state)
{:on-press (fn [] {:on-press
(clipboard/get-string #(rf/dispatch [:contacts/set-new-identity %])))}) (fn []
(i18n/label :t/paste)])] (reset! clipboard nil)
[quo/button (reset! default-value nil)
(merge style/button-qr (rf/dispatch [:contacts/clear-new-identity])
{:on-press #(rf/dispatch [::qr-scanner/scan-code (rf/dispatch [:navigate-back])
{:handler :contacts/qr-code-scanned}])}) (rf/dispatch [:chat.ui/show-profile public-key ens]))})
:i/scan]] (i18n/label :t/view-profile)]]]]))))
(when error?
[rn/view style/container-error
[quo/icon :i/alert style/icon-error]
[quo/text style/text-error (i18n/label :t/not-a-chatkey)]])
(when (= state :valid)
[found-contact public-key])]
[rn/view
[quo/button
(merge (style/button-view-profile state)
{:on-press
(fn []
(rf/dispatch [:contacts/clear-new-identity])
(rf/dispatch [:navigate-back])
(rf/dispatch [:chat.ui/show-profile public-key ens-name]))})
(i18n/label :t/view-profile)]]]]))
+10 -4
View File
@@ -13,7 +13,8 @@
[status-im2.contexts.contacts.events :as contacts-store] [status-im2.contexts.contacts.events :as contacts-store]
[status-im.multiaccounts.model :as multiaccounts.model] [status-im.multiaccounts.model :as multiaccounts.model]
[status-im.utils.clocks :as utils.clocks] [status-im.utils.clocks :as utils.clocks]
[status-im.utils.types :as types])) [status-im.utils.types :as types]
[reagent.core :as reagent]))
(defn- get-chat (defn- get-chat
[cofx chat-id] [cofx chat-id]
@@ -201,9 +202,9 @@
(rf/defn navigate-to-chat (rf/defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat/navigate-to-chat]} {:events [:chat/navigate-to-chat]}
[{db :db :as cofx} chat-id from-shell?] [{db :db :as cofx} chat-id]
(rf/merge cofx (rf/merge cofx
{:dispatch [:navigate-to-nav2 :chat chat-id from-shell?]} {:dispatch [:navigate-to :chat chat-id]}
(when-not (or (= (:view-id db) :community) (= (:view-id db) :community-overview)) (when-not (or (= (:view-id db) :community) (= (:view-id db) :community-overview))
(navigation/pop-to-root :shell-stack)) (navigation/pop-to-root :shell-stack))
(close-chat false) (close-chat false)
@@ -338,6 +339,12 @@
[{:keys [db]} shared-element-id] [{:keys [db]} shared-element-id]
{:db (assoc db :shared-element-id shared-element-id)}) {:db (assoc db :shared-element-id shared-element-id)})
(rf/defn navigate-to-lightbox
{:events [:chat.ui/navigate-to-lightbox]}
[{:keys [db]} shared-element-id screen-params]
(reagent/next-tick #(rf/dispatch [:navigate-to :lightbox screen-params]))
{:db (assoc db :shared-element-id shared-element-id)})
(rf/defn exit-lightbox-signal (rf/defn exit-lightbox-signal
{:events [:chat.ui/exit-lightbox-signal]} {:events [:chat.ui/exit-lightbox-signal]}
[{:keys [db]} value] [{:keys [db]} value]
@@ -357,4 +364,3 @@
{:events [:chat.ui/lightbox-scale]} {:events [:chat.ui/lightbox-scale]}
[{:keys [db]} value] [{:keys [db]} value]
{:db (assoc db :lightbox/scale value)}) {:db (assoc db :lightbox/scale value)})
@@ -88,7 +88,7 @@
(let [chat-id "test_chat" (let [chat-id "test_chat"
db {:pagination-info {chat-id {:all-loaded? true}}}] db {:pagination-info {chat-id {:all-loaded? true}}}]
(testing "Pagination info should be reset on navigation" (testing "Pagination info should be reset on navigation"
(let [res (chat/navigate-to-chat {:db db} chat-id false)] (let [res (chat/navigate-to-chat {:db db} chat-id)]
(is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?]))))))) (is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?])))))))
(deftest camera-roll-loading-more-test (deftest camera-roll-loading-more-test
@@ -41,13 +41,13 @@
:size 32} :i/reaction]]) :size 32} :i/reaction]])
(defn image-button (defn image-button
[chat-id] [insets]
[quo/button [quo/button
{:on-press (fn [] {:on-press (fn []
(permissions/request-permissions (permissions/request-permissions
{:permissions [:read-external-storage :write-external-storage] {:permissions [:read-external-storage :write-external-storage]
:on-allowed #(rf/dispatch :on-allowed #(rf/dispatch
[:open-modal :photo-selector {:chat-id chat-id}]) [:open-modal :photo-selector {:insets insets}])
:on-denied (fn [] :on-denied (fn []
(background-timer/set-timeout (background-timer/set-timeout
#(utils-old/show-popup (i18n/label :t/error) #(utils-old/show-popup (i18n/label :t/error)
@@ -122,7 +122,7 @@
(when (and (not @input/recording-audio?) (when (and (not @input/recording-audio?)
(nil? (get @input/reviewing-audio-filepath chat-id))) (nil? (get @input/reviewing-audio-filepath chat-id)))
[:<> [:<>
[image-button chat-id] [image-button insets]
[rn/view {:width 12}] [rn/view {:width 12}]
[reactions-button] [reactions-button]
[rn/view {:flex 1}] [rn/view {:flex 1}]
@@ -231,7 +231,7 @@
:sending-image (seq images) :sending-image (seq images)
:refs refs}]]]] :refs refs}]]]]
(if suggestions? (if suggestions?
[mentions/mentions params insets] [mentions/mentions (select-keys params [:refs :suggestions :max-y]) insets]
[controls/view send-ref record-ref params insets chat-id images [controls/view send-ref record-ref params insets chat-id images
edit #(clean-and-minimize params)]) edit #(clean-and-minimize params)])
;;;;black background ;;;;black background
@@ -52,13 +52,11 @@
{:key (:message-id item) {:key (:message-id item)
:active-opacity 1 :active-opacity 1
:on-long-press #(on-long-press message context) :on-long-press #(on-long-press message context)
:on-press (fn [] :on-press #(rf/dispatch [:chat.ui/navigate-to-lightbox
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)]) (:message-id item)
(js/setTimeout #(rf/dispatch [:navigate-to :lightbox {:messages (:album message)
{:messages (:album message) :index index
:index index :insets insets}])}
:insets insets}])
100))}
[fast-image/fast-image [fast-image/fast-image
{:style (style/image dimensions index portrait? images-count) {:style (style/image dimensions index portrait? images-count)
:source {:uri (:image (:content item))} :source {:uri (:image (:content item))}
@@ -27,13 +27,11 @@
:key message-id :key message-id
:style {:margin-top (when (pos? index) 10)} :style {:margin-top (when (pos? index) 10)}
:on-long-press on-long-press :on-long-press on-long-press
:on-press (fn [] :on-press #(rf/dispatch [:chat.ui/navigate-to-lightbox
(rf/dispatch [:chat.ui/update-shared-element-id message-id]) message-id
(js/setTimeout #(rf/dispatch [:navigate-to :lightbox {:messages [message]
{:messages [message] :index 0
:index 0 :insets insets}])}
:insets insets}])
100))}
(when (and (not= text "placeholder") (= index 0)) (when (and (not= text "placeholder") (= index 0))
[rn/view {:style {:margin-bottom 10}} [text/text-content message context]]) [rn/view {:style {:margin-bottom 10}} [text/text-content message context]])
[fast-image/fast-image [fast-image/fast-image
@@ -27,6 +27,10 @@
(concat (concat
(when (and outgoing (when (and outgoing
(not (or deleted? deleted-for-me?)) (not (or deleted? deleted-for-me?))
;; temporarily disable edit image message until
;; https://github.com/status-im/status-mobile/issues/15298
;; is implemented
(not= content-type constants/content-type-image)
(not= content-type constants/content-type-audio)) (not= content-type constants/content-type-audio))
[{:type :main [{:type :main
:on-press #(rf/dispatch [:chat.ui/edit-message message-data]) :on-press #(rf/dispatch [:chat.ui/edit-message message-data])
@@ -15,8 +15,7 @@
:flex-direction :row :flex-direction :row
:left 0 :left 0
:right 0 :right 0
:margin-top 20 :top 20
:margin-bottom 12
:justify-content :center :justify-content :center
:z-index 1}) :z-index 1})
@@ -66,8 +65,8 @@
:height (/ window-width 3) :height (/ window-width 3)
:margin-left (when (not= (mod index 3) 0) 1) :margin-left (when (not= (mod index 3) 0) 1)
:margin-bottom 1 :margin-bottom 1
:border-top-left-radius (when (= index 0) 10) :border-top-left-radius (when (= index 0) 20)
:border-top-right-radius (when (= index 2) 10)}) :border-top-right-radius (when (= index 2) 20)})
(defn overlay (defn overlay
[window-width] [window-width]
@@ -1,9 +1,9 @@
(ns status-im2.contexts.chat.photo-selector.view (ns status-im2.contexts.chat.photo-selector.view
(:require (:require
[react-native.gesture :as gesture]
[react-native.platform :as platform] [react-native.platform :as platform]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[react-native.safe-area :as safe-area]
[quo2.components.notifications.info-count :as info-count] [quo2.components.notifications.info-count :as info-count]
[quo2.core :as quo] [quo2.core :as quo]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
@@ -13,6 +13,7 @@
[status-im2.contexts.chat.photo-selector.style :as style] [status-im2.contexts.chat.photo-selector.style :as style]
[status-im.utils.core :as utils] [status-im.utils.core :as utils]
[quo.react] [quo.react]
[status-im2.common.bottom-sheet-screen.view :as bottom-sheet-screen]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn on-press-confirm-selection (defn on-press-confirm-selection
@@ -80,31 +81,38 @@
(inc (utils/first-index #(= (:uri item) (:uri %)) @selected))])]) (inc (utils/first-index #(= (:uri item) (:uri %)) @selected))])])
(defn album-title (defn album-title
[photos? selected-album selected temporary-selected] [photos? selected-album]
[rn/touchable-opacity (fn []
{:style (style/title-container) [rn/touchable-opacity
:active-opacity 1 {:style (style/title-container)
:accessibility-label :album-title :active-opacity 1
:on-press (fn [] :accessibility-label :album-title
(if photos? :on-press (fn []
(do ;; TODO: album-selector issue:
(reset! temporary-selected @selected) ;; https://github.com/status-im/status-mobile/issues/15398
(rf/dispatch [:open-modal :album-selector])) (js/alert "currently disabled")
(rf/dispatch [:navigate-back])))} ;(if photos?
[quo/text ; (do
{:weight :medium ; (reset! temporary-selected @selected)
:ellipsize-mode :tail ; (rf/dispatch [:open-modal :album-selector {:insets insets}]))
:number-of-lines 1 ; (rf/dispatch [:navigate-back]))
:style {:max-width 150}} )}
selected-album] [quo/text
[rn/view {:style (style/chevron-container)} {:weight :medium
[quo/icon (if photos? :i/chevron-down :i/chevron-up) :ellipsize-mode :tail
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]]) :number-of-lines 1
:style {:max-width 150}}
selected-album]
[rn/view {:style (style/chevron-container)}
[quo/icon (if photos? :i/chevron-down :i/chevron-up)
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))
(defn photo-selector (defn photo-selector
[] []
[:f> [:f>
(let [temporary-selected (reagent/atom [])] ; used when switching albums (let [{:keys [insets]} (rf/sub [:get-screen-params])
temporary-selected (reagent/atom [])] ; used when switching albums
(fn [] (fn []
(let [selected (reagent/atom []) ; currently selected (let [selected (reagent/atom []) ; currently selected
selected-images (rf/sub [:chats/sending-image]) ; already selected and dispatched selected-images (rf/sub [:chats/sending-image]) ; already selected and dispatched
@@ -116,26 +124,19 @@
(reset! selected (vec (vals selected-images))) (reset! selected (vec (vals selected-images)))
(reset! selected @temporary-selected))) (reset! selected @temporary-selected)))
[selected-album]) [selected-album])
[safe-area/consumer [bottom-sheet-screen/view
(fn [insets] (fn [{:keys [scroll-enabled on-scroll]}]
(let [window-width (:width (rn/get-window)) (let [window-width (:width (rn/get-window))
camera-roll-photos (rf/sub [:camera-roll/photos]) camera-roll-photos (rf/sub [:camera-roll/photos])
end-cursor (rf/sub [:camera-roll/end-cursor]) end-cursor (rf/sub [:camera-roll/end-cursor])
loading? (rf/sub [:camera-roll/loading-more]) loading? (rf/sub [:camera-roll/loading-more])
has-next-page? (rf/sub [:camera-roll/has-next-page])] has-next-page? (rf/sub [:camera-roll/has-next-page])]
[rn/view {:style {:flex 1}} [:<>
[rn/view [rn/view
{:style style/buttons-container} {:style style/buttons-container}
(when platform/android? [album-title true selected-album selected temporary-selected insets]
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:navigate-back])
:style (style/close-button-container)}
[quo/icon :i/close
{:size 20 :color (colors/theme-colors colors/black colors/white)}]])
[album-title true selected-album selected temporary-selected]
[clear-button selected]] [clear-button selected]]
[rn/flat-list [gesture/flat-list
{:key-fn identity {:key-fn identity
:render-fn image :render-fn image
:render-data {:window-width window-width :selected selected} :render-data {:window-width window-width :selected selected}
@@ -143,7 +144,9 @@
:num-columns 3 :num-columns 3
:content-container-style {:width "100%" :content-container-style {:width "100%"
:padding-bottom (+ (:bottom insets) 100) :padding-bottom (+ (:bottom insets) 100)
:padding-top 80} :padding-top 64}
:on-scroll on-scroll
:scroll-enabled scroll-enabled
:on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor :on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor
selected-album loading? selected-album loading?
has-next-page?])}] has-next-page?])}]
@@ -22,10 +22,11 @@
(def featured-list-container (def featured-list-container
{:flex-direction :row {:flex-direction :row
:overflow :hidden :overflow :hidden})
:margin-bottom 24
:margin-left 20 (def flat-list-container
:padding-right 20}) {:padding-bottom 24
:padding-horizontal 20})
(def other-communities-container (def other-communities-container
{:flex 1 {:flex 1
@@ -7,7 +7,6 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.common.resources :as resources] [status-im2.common.resources :as resources]
[status-im2.contexts.communities.menus.community-options.view :as options] [status-im2.contexts.communities.menus.community-options.view :as options]
[status-im.ui.screens.communities.community :as community]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[react-native.platform :as platform] [react-native.platform :as platform]
[status-im2.common.scroll-page.view :as scroll-page] [status-im2.common.scroll-page.view :as scroll-page]
@@ -22,23 +21,28 @@
:group [{:id 1 :group [{:id 1
:token-icon (resources/get-mock-image :status-logo)}]}]}}) :token-icon (resources/get-mock-image :status-logo)}]}]}})
(defn render-fn (defn community-list-item
[community-item _ _ {:keys [width view-type]}] [{:keys [id] :as community} _ _ {:keys [width view-type]}]
(let [item (merge community-item (let [community-item (merge
(get mock-community-item-data :data)) community
cover {:uri (get-in (:images item) [:banner :uri])}] (get mock-community-item-data :data))
cover {:uri (get-in (:images community) [:banner :uri])}]
(if (= view-type :card-view) (if (= view-type :card-view)
[quo/community-card-view-item (assoc item :width width :cover cover) [quo/community-card-view-item (assoc community-item :width width :cover cover)
#(rf/dispatch [:navigate-to :community-overview (:id item)])] #(rf/dispatch [:navigate-to :community-overview (:id community)])]
[quo/communities-list-view-item [quo/communities-list-view-item
{:on-press (fn [] {:on-press (fn []
(rf/dispatch [:communities/load-category-states (:id item)]) (rf/dispatch [:communities/load-category-states id])
(rf/dispatch [:dismiss-keyboard]) (rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:navigate-to :community {:community-id (:id item)}])) <<<<<<< HEAD
(rf/dispatch [:navigate-to :community-overview (:id item)]))
=======
(rf/dispatch [:navigate-to :community {:community-id id}]))
>>>>>>> 1675fef63 (Fix: community data not displayed)
:on-long-press #(rf/dispatch :on-long-press #(rf/dispatch
[:bottom-sheet/show-sheet [:bottom-sheet/show-sheet
{:content (fn [] {:content (fn []
[options/community-options-bottom-sheet (:id item)])}])}]))) [options/community-options-bottom-sheet id])}])}])))
(defn screen-title (defn screen-title
[] []
@@ -89,7 +93,6 @@
:label (i18n/label :t/gated) :label (i18n/label :t/gated)
:accessibility-label :gated-communities-tab}]}]]) :accessibility-label :gated-communities-tab}]}]])
(defn featured-list (defn featured-list
[communities view-type] [communities view-type]
(let [view-size (reagent/atom 0)] (let [view-size (reagent/atom 0)]
@@ -98,7 +101,7 @@
{:style style/featured-list-container {:style style/featured-list-container
:on-layout #(swap! view-size :on-layout #(swap! view-size
(fn [] (fn []
(- (oops/oget % "nativeEvent.layout.width") 20)))} (- (oops/oget % "nativeEvent.layout.width") 40)))}
(when-not (= @view-size 0) (when-not (= @view-size 0)
[rn/flat-list [rn/flat-list
{:key-fn :id {:key-fn :id
@@ -107,9 +110,22 @@
:shows-horizontal-scroll-indicator false :shows-horizontal-scroll-indicator false
:separator [rn/view {:width 12}] :separator [rn/view {:width 12}]
:data communities :data communities
:render-fn render-fn :render-fn community-list-item
:render-data {:width @view-size :render-data {:width @view-size
:view-type view-type}}])]))) :view-type view-type}
:contentContainerStyle style/flat-list-container}])])))
(defn other-communities-list
[{:keys [communities view-type]}]
[rn/view style/other-communities-container
[rn/flat-list
{:key-fn :id
:keyboard-should-persist-taps :always
:separator [rn/view {:height 16}]
:data communities
:render-fn community-list-item
:contentContainerStyle style/flat-list-container
:render-data {:view-type view-type}}]])
(defn discover-communities-header (defn discover-communities-header
[{:keys [featured-communities-count [{:keys [featured-communities-count
@@ -120,39 +136,10 @@
[screen-title] [screen-title]
[featured-communities-header featured-communities-count] [featured-communities-header featured-communities-count]
[featured-list featured-communities view-type] [featured-list featured-communities view-type]
[quo/separator] [rn/view {:style {:margin-horizontal 20}}
[quo/separator]]
[discover-communities-segments selected-tab false]]) [discover-communities-segments selected-tab false]])
(defn other-communities-list
[{:keys [communities communities-ids view-type]}]
[rn/view {:style style/other-communities-container}
(map-indexed
(fn [inner-index item]
(let [community-id (when communities-ids item)
community (if communities
item
[rf/sub [:communities/home-item community-id]])]
[rn/view
{:key (str inner-index (:id community))
:margin-bottom 16}
(if (= view-type :card-view)
[quo/community-card-view-item
(merge community
(get mock-community-item-data :data))
#(rf/dispatch [:navigate-to :community-overview (:id community)])]
[quo/communities-list-view-item
{:on-press (fn []
(rf/dispatch [:communities/load-category-states (:id community)])
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:navigate-to :community (:id community)]))
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn []
;; TODO implement with quo2
[community/community-actions community])}])}
(merge community
(get mock-community-item-data :data))])]))
(if communities communities communities-ids))])
(defn communities-lists (defn communities-lists
[selected-tab view-type] [selected-tab view-type]
[rn/view {:style {:flex 1}} [rn/view {:style {:flex 1}}
@@ -18,7 +18,7 @@
item (merge item unviewed-counts)] item (merge item unviewed-counts)]
[quo/communities-membership-list-item [quo/communities-membership-list-item
{:style {:padding-horizontal 18} {:style {:padding-horizontal 18}
:on-press #(rf/dispatch [:navigate-to-nav2 :community-overview id]) :on-press #(rf/dispatch [:navigate-to :community-overview id])
:on-long-press #(rf/dispatch :on-long-press #(rf/dispatch
[:bottom-sheet/show-sheet [:bottom-sheet/show-sheet
{:content (fn [] {:content (fn []
@@ -295,14 +295,14 @@
(defn page-nav-right-section-buttons (defn page-nav-right-section-buttons
[id] [id]
[{:icon :i/options [{:icon :i/options
:background-color (scroll-page/icon-color) :background-color (scroll-page/icon-color)
:on-press #(rf/dispatch :accessibility-label :community-options-for-community
[:bottom-sheet/show-sheet :on-press #(rf/dispatch
{:content [:bottom-sheet/show-sheet
(fn [] {:content (fn []
[options/community-options-bottom-sheet [options/community-options-bottom-sheet
id])}])}]) id])}])}])
(defn pick-first-category-by-height (defn pick-first-category-by-height
[scroll-height first-channel-height categories-heights] [scroll-height first-channel-height categories-heights]
@@ -3,7 +3,12 @@
(def background-container (def background-container
{:background-color colors/neutral-95 {:background-color colors/neutral-95
:flex-direction :row}) :flex-direction :row
:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(defn background-gradient-overlay (defn background-gradient-overlay
[dark-overlay?] [dark-overlay?]
@@ -11,7 +11,8 @@
{:style style/background-container} {:style style/background-container}
[rn/image [rn/image
{:blur-radius (if dark-overlay? 13 0) {:blur-radius (if dark-overlay? 13 0)
:style {:flex 1} :style {:height "100%"
:width "100%"}
;; Todo - get background image from sub using carousel index on landing page ;; Todo - get background image from sub using carousel index on landing page
:source (resources/get-image :onboarding-bg-1)}] :source (resources/get-image :onboarding-bg-1)}]
[linear-gradient/linear-gradient [linear-gradient/linear-gradient
@@ -22,4 +23,4 @@
:style (style/background-gradient-overlay dark-overlay?)}] :style (style/background-gradient-overlay dark-overlay?)}]
(when dark-overlay? (when dark-overlay?
[rn/view [rn/view
{:style style/background-blur-overlay}])]) {:style style/background-blur-overlay}])])
@@ -61,7 +61,7 @@
:source (get-in carousels [@carousel-index :image])}] :source (get-in carousels [@carousel-index :image])}]
[quo/drawer-buttons [quo/drawer-buttons
{:top-card {:on-press (fn [] {:top-card {:on-press (fn []
(rf/dispatch [:navigate-to :new-to-status]) (rf/dispatch [:navigate-to :sign-in])
(rf/dispatch [:hide-terms-of-services-opt-in-screen])) (rf/dispatch [:hide-terms-of-services-opt-in-screen]))
:heading (i18n/label :t/sign-in) :heading (i18n/label :t/sign-in)
:accessibility-label :already-use-status-button} :accessibility-label :already-use-status-button}
@@ -0,0 +1,19 @@
(ns status-im2.contexts.onboarding.common.navigation-bar.view
(:require [quo2.core :as quo]
[react-native.core :as rn]
[utils.re-frame :as rf]))
(defn navigation-bar
[{:keys [on-press-info]}]
[rn/view {:style {:height 56}}
[quo/page-nav
{:align-mid? true
:mid-section {:type :text-only}
:left-section {:type :blur-bg
:icon :i/arrow-left
:icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:right-section-buttons [{:type :blur-bg
:icon :i/info
:icon-override-theme :dark
:on-press on-press-info}]}]])
@@ -1,14 +1,69 @@
(ns status-im2.contexts.onboarding.create-password.style (ns status-im2.contexts.onboarding.create-password.style
(:require [quo2.foundations.colors :as colors] (:require [quo2.foundations.colors :as colors]))
[react-native.platform :as platform]))
(def navigation-bar {:height 56}) (def image-background
{:height "100%"
:width "100%"})
(def page-container (def overlay
{:padding-top (if platform/ios? 44 0) {:position :absolute
:position :absolute
:top 0 :top 0
:bottom 0 :bottom 0
:left 0 :left 0
:right 0 :right 0
:background-color colors/neutral-80-opa-80-blur}) :background-color colors/neutral-80-opa-80-blur})
(def content-style {:flex-grow 1})
(def heading {:margin-bottom 20})
(def heading-subtitle {:color colors/white})
(def heading-title (assoc heading-subtitle :margin-bottom 8))
(def label-container
{:margin-top 8
:flex-direction :row
:align-items :center
:height 16})
(def label-icon
{:width 16
:height 18
:margin-right 4})
(defn label-icon-color
[status]
(get {:neutral colors/neutral-40
:success colors/success-60
:danger colors/danger-60}
status))
(defn label-color
[status]
(let [colors {:neutral colors/white-opa-70
:success colors/success-60
:danger colors/danger-60}]
{:color (get colors status)}))
(def space-between-inputs {:height 16})
(def password-tips
{:flex-direction :row
:justify-content :space-between
:margin-horizontal 20})
(def top-part
{:margin-horizontal 20
:margin-top 12})
(def bottom-part
{:flex 1
:margin-top 12
:justify-content :flex-end})
(def disclaimer-container
{:margin-horizontal 20
:margin-vertical 4})
(def button-container
{:margin-horizontal 20
:margin-vertical 12})
@@ -1,42 +1,198 @@
(ns status-im2.contexts.onboarding.create-password.view (ns status-im2.contexts.onboarding.create-password.view
(:require [quo2.core :as quo] (:require
[quo2.foundations.colors :as colors] [quo2.core :as quo]
[react-native.core :as rn] [quo2.foundations.colors :as colors]
[status-im2.contexts.onboarding.create-password.style :as style] [react-native.core :as rn]
[utils.i18n :as i18n] [reagent.core :as reagent]
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
[utils.re-frame :as rf])) [status-im2.contexts.onboarding.common.navigation-bar.view :as navigation-bar]
[status-im2.contexts.onboarding.create-password.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]
[utils.string :as utils.string]))
(defn navigation-bar (defn header
[] []
[rn/view {:style style/navigation-bar} [rn/view {:style style/heading}
[quo/page-nav [quo/text
{:align-mid? true {:style style/heading-title
:mid-section {:type :text-only :main-text ""} :weight :semi-bold
:left-section {:type :blur-bg :size :heading-1}
:icon :i/arrow-left (i18n/label :t/password-creation-title)]
:icon-override-theme :dark [quo/text
:on-press #(rf/dispatch [:navigate-back])} {:style style/heading-subtitle
:right-section-buttons [{:type :blur-bg :weight :regular
:icon :i/info :size :paragraph-1}
:icon-override-theme :dark (i18n/label :t/password-creation-subtitle)]])
:on-press #(js/alert "Pending")}]}]])
(defn page (defn password-with-hint
[] [{{:keys [text status shown]} :hint :as input-props}]
[rn/view {:style style/page-container} [rn/view
[navigation-bar] [quo/input
[rn/view {:style {:padding-horizontal 20}} (-> input-props
[quo/text (dissoc :hint)
{:size :heading-1 (assoc :type :password
:weight :semi-bold :blur? true))]
:style {:color colors/white}} "Create profile password"] [rn/view {:style style/label-container}
[quo/button (when shown
{:on-press #(rf/dispatch [:navigate-to :enable-biometrics]) [:<>
:style {}} (i18n/label :t/continue)]]]) [quo/icon (if (= status :success) :i/check-circle :i/info)
{:container-style style/label-icon
:color (style/label-icon-color status)
:size 16}]
[quo/text
{:style (style/label-color status)
:size :paragraph-2}
text]])]])
(defn password-inputs
[{:keys [passwords-match? on-change-password on-change-repeat-password on-input-focus
password-long-enough? empty-password? show-password-validation?
on-blur-repeat-password]}]
(let [hint-1-status (if password-long-enough? :success :neutral)
hint-2-status (if passwords-match? :success :danger)
hint-2-text (if passwords-match?
(i18n/label :t/password-creation-match)
(i18n/label :t/password-creation-dont-match))
error? (and show-password-validation?
(not passwords-match?)
(not empty-password?))]
[:<>
[password-with-hint
{:hint {:text (i18n/label :t/password-creation-hint)
:status hint-1-status
:shown true}
:placeholder (i18n/label :t/password-creation-placeholder-1)
:on-change-text on-change-password
:on-focus #(on-input-focus :password)}]
[rn/view {:style style/space-between-inputs}]
[password-with-hint
{:hint {:text hint-2-text
:status hint-2-status
:shown (and (not empty-password?)
show-password-validation?)}
:error? error?
:placeholder (i18n/label :t/password-creation-placeholder-2)
:on-change-text on-change-repeat-password
:on-focus #(on-input-focus :repeat-password)
:on-blur on-blur-repeat-password}]]))
(def strength-status
{1 :very-weak
2 :weak
3 :okay
4 :strong
5 :very-strong})
(defn help
[{{:keys [lower-case? upper-case? numbers? symbols?]} :validations
password-strength :password-strength}]
[rn/view
[quo/strength-divider {:type (strength-status password-strength :info)}
(i18n/label :t/password-creation-tips-title)]
[rn/view {:style style/password-tips}
[quo/tips {:completed? lower-case?}
(i18n/label :t/password-creation-tips-1)]
[quo/tips {:completed? upper-case?}
(i18n/label :t/password-creation-tips-2)]
[quo/tips {:completed? numbers?}
(i18n/label :t/password-creation-tips-3)]
[quo/tips {:completed? symbols?}
(i18n/label :t/password-creation-tips-4)]]])
(defn password-validations
[password]
(let [validations (juxt utils.string/has-lower-case?
utils.string/has-upper-case?
utils.string/has-numbers?
utils.string/has-symbols?
#(utils.string/at-least-n-chars? % 10))]
(->> password
(validations)
(zipmap [:lower-case? :upper-case? :numbers? :symbols? :long-enough?]))))
(defn calc-password-strength
[validations]
(->> (vals validations)
(filter true?)
(count)))
(defn password-form
[{:keys [scroll-to-end-fn]}]
(let [password (reagent/atom "")
repeat-password (reagent/atom "")
accepts-disclaimer? (reagent/atom false)
focused-input (reagent/atom nil)
show-password-validation? (reagent/atom false)
same-password-length? #(and (seq @password)
(= (count @password) (count @repeat-password)))]
(fn []
(let [{user-color :color} (rf/sub [:onboarding-2/profile])
{:keys [long-enough?]
:as validations} (password-validations @password)
password-strength (calc-password-strength validations)
empty-password? (empty? @password)
same-passwords? (= @password @repeat-password)
meet-requirements? (and (not empty-password?)
(utils.string/at-least-n-chars? @password 10)
same-passwords?
@accepts-disclaimer?)]
[:<>
[rn/view {:style style/top-part}
[header]
[password-inputs
{:password-long-enough? long-enough?
:passwords-match? same-passwords?
:empty-password? empty-password?
:show-password-validation? @show-password-validation?
:on-input-focus (fn [input-id]
(scroll-to-end-fn)
(reset! focused-input input-id))
:on-change-password (fn [new-value]
(reset! password new-value)
(when (same-password-length?)
(reset! show-password-validation? true)))
:on-change-repeat-password (fn [new-value]
(reset! repeat-password new-value)
(when (same-password-length?)
(reset! show-password-validation? true)))
:on-blur-repeat-password #(if empty-password?
(reset! show-password-validation? false)
(reset! show-password-validation? true))}]]
[rn/view {:style style/bottom-part}
(when (= @focused-input :password)
[help
{:validations validations
:password-strength password-strength}])
(when (= @focused-input :repeat-password)
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:on-change #(reset! accepts-disclaimer? %)
:checked? @accepts-disclaimer?}
(i18n/label :t/password-creation-disclaimer)]])
[rn/view {:style style/button-container}
[quo/button
{:disabled (not meet-requirements?)
:override-background-color (colors/custom-color user-color 60)
:on-press #(rf/dispatch
[:onboarding-2/password-set
(security/mask-data @password)])}
(i18n/label :t/password-creation-confirm)]]]]))))
(defn create-password (defn create-password
[] []
[rn/view {:style {:flex 1}} (let [scroll-view-ref (atom nil)
[background/view true] scroll-to-end-fn #(js/setTimeout ^js/Function (.-scrollToEnd @scroll-view-ref) 250)]
[page]]) (fn []
[:<>
[background/view true]
[rn/scroll-view
{:ref #(reset! scroll-view-ref %)
:style style/overlay
:content-container-style style/content-style}
[navigation-bar/navigation-bar {:on-press-info #(js/alert "Info pressed")}]
[password-form {:scroll-to-end-fn scroll-to-end-fn}]]])))
@@ -2,6 +2,14 @@
(:require [quo2.foundations.colors :as colors] (:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform])) [react-native.platform :as platform]))
(def continue-button
{:width "100%"
:margin-top :auto
:margin-bottom 72
:margin-left :auto
:margin-right :auto
:align-self :flex-end})
(def page-container (def page-container
{:padding-top (if platform/ios? 44 0) {:padding-top (if platform/ios? 44 0)
:position :absolute :position :absolute
@@ -13,3 +21,5 @@
(def navigation-bar {:height 56}) (def navigation-bar {:height 56})
(def info-message
{:margin-top 8})
@@ -1,11 +1,15 @@
(ns status-im2.contexts.onboarding.create-profile.view (ns status-im2.contexts.onboarding.create-profile.view
(:require [quo2.core :as quo] (:require [quo2.core :as quo]
[clojure.string :as string]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.core :as rn]
[status-im2.contexts.onboarding.create-profile.style :as style] [status-im2.contexts.onboarding.create-profile.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
[utils.re-frame :as rf])) [status-im2.contexts.onboarding.select-photo.method-menu.view :as method-menu]
[utils.re-frame :as rf]
[status-im2.constants :as c]))
(defn navigation-bar (defn navigation-bar
[] []
@@ -18,21 +22,104 @@
:icon-override-theme :dark :icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}}]]) :on-press #(rf/dispatch [:navigate-back])}}]])
(def emoji-regex
(new
js/RegExp
#"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])"
"i"))
(defn has-emojis [s] (re-find emoji-regex s))
(def common-names ["Ethereum" "Bitcoin"])
(defn has-common-names [s] (pos? (count (filter #(string/includes? s %) common-names))))
(def special-characters-regex (new js/RegExp #"[^a-zA-Z\d\s-]" "i"))
(defn has-special-characters [s] (re-find special-characters-regex s))
(defn validation-message
[s]
(cond
(or (= s nil) (= s "")) nil
(has-emojis s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)})
(has-special-characters s) (i18n/label :t/are-not-allowed
{:check (i18n/label :t/special-characters)})
(string/ends-with? s "-eth") (i18n/label :t/ending-not-allowed {:ending "-eth"})
(has-common-names s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/common-names)})
:else nil))
(defn page (defn page
[] [{:keys [image-path display-name color]}]
[rn/view {:style style/page-container} (let [full-name (reagent/atom display-name)
[navigation-bar] validation-msg (reagent/atom (validation-message @full-name))
[rn/view {:style {:padding-horizontal 20}} on-change-text (fn [s]
[quo/text (reset! validation-msg (validation-message s))
{:size :heading-1
:weight :semi-bold (reset! full-name s))
:style {:color colors/white}} "Create Profile"] custom-color (reagent/atom (or color c/profile-default-color))
[quo/button profile-pic (reagent/atom image-path)
{:on-press #(rf/dispatch [:navigate-to :create-profile-password]) on-change-profile-pic #(reset! profile-pic %)
:style {}} (i18n/label :t/continue)]]]) on-change #(reset! custom-color %)]
(fn []
[rn/view {:style style/page-container}
[navigation-bar]
[rn/view
{:style {:flex 1
:padding-horizontal 20}}
[quo/text
{:size :heading-1
:weight :semi-bold
:style {:color colors/white
:margin-top 12
:margin-bottom 20}} (i18n/label :t/create-profile)]
[rn/view
{:flex 1
:align-items :flex-start}
[rn/view
{:flex-direction :row
:justify-content :center}
[quo/profile-input
{:customization-color @custom-color
:placeholder (i18n/label :t/your-name)
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:override-theme :dark
:content
(fn []
[method-menu/view on-change-profile-pic])}])
:image-picker-props {:profile-picture @profile-pic
:full-name @full-name}
:title-input-props {:max-length c/profile-name-max-length
:on-change-text on-change-text}}]]
(when @validation-msg
[quo/info-message
{:type :error
:size :default
:icon :i/info
:style style/info-message}
@validation-msg])
[quo/text
{:size :paragraph-2
:style {:color colors/white-70-blur
:margin-top 20
:margin-bottom 16}} (i18n/label :t/accent-colour)]
[quo/color-picker
{:blur? true
:default-selected? :blue
:selected @custom-color
:on-change on-change}]]
[quo/button
{:accessibility-label :submit-create-profile-button
:type :primary
:override-background-color (colors/custom-color @custom-color 60)
:on-press #(rf/dispatch [:onboarding-2/profile-data-set
{:image-path @profile-pic
:display-name @full-name
:color @custom-color}])
:style style/continue-button
:disabled (and (not (seq @full-name)) (not @validation-msg))}
(i18n/label :t/continue)]]])))
(defn create-profile (defn create-profile
[] []
[rn/view {:style {:flex 1}} [rn/view {:style {:flex 1}}
[background/view true] (let [onboarding-profile-data (rf/sub [:onboarding-2/profile])]
[page]]) [background/view true]
[page onboarding-profile-data])])
@@ -12,3 +12,12 @@
:background-color colors/neutral-80-opa-80-blur}) :background-color colors/neutral-80-opa-80-blur})
(def navigation-bar {:height 56}) (def navigation-bar {:height 56})
(def image-container
{:margin-top 20
:margin-bottom 24
:background-color colors/danger-50
:border-radius 20
:flex 1
:align-items :center
:justify-content :center})
@@ -5,7 +5,8 @@
[status-im2.contexts.onboarding.enable-biometrics.style :as style] [status-im2.contexts.onboarding.enable-biometrics.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im.multiaccounts.biometric.core :as biometric]))
(defn navigation-bar (defn navigation-bar
[] []
@@ -17,18 +18,39 @@
(defn page (defn page
[] []
[rn/view {:style style/page-container} (let [supported-biometric (rf/sub [:supported-biometric-auth])
[navigation-bar] bio-type-label (biometric/get-label supported-biometric)
[rn/view {:style {:padding-horizontal 20}} profile-color (:color (rf/sub [:onboarding-2/profile]))]
[quo/text [rn/view {:style style/page-container}
{:size :heading-1 [navigation-bar]
:weight :semi-bold [rn/view
:style {:color colors/white}} "Enable-biometrics"] {:style {:padding-horizontal 20
[quo/button :flex 1}}
{:on-press #(rf/dispatch [:navigate-to :enable-notifications]) [quo/text
:type :grey {:size :heading-1
:override-theme :dark :weight :semi-bold
:style {}} (i18n/label :t/continue)]]]) :style {:color colors/white}} (i18n/label :t/enable-biometrics)]
[quo/text
{:size :paragraph-1
:style {:color colors/white
:margin-top 8}}
(i18n/label :t/use-biometrics)]
;; TODO(@briansztamfater): Replace view with image view with the real illustration,
;; https://github.com/status-im/status-mobile/issues/15445
[rn/view {:style style/image-container}
[quo/text {:size :paragraph-1}
"Illustration here"]]
[rn/view {:style {:margin-bottom 55}}
[quo/button
{:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])
:before :i/face-id
:override-background-color (colors/custom-color profile-color 50)}
(i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
[quo/button
{:on-press #(rf/dispatch [:onboarding-2/create-account-and-login])
:override-background-color colors/white-opa-5
:style {:margin-top 12}}
(i18n/label :t/maybe-later)]]]]))
(defn enable-biometrics (defn enable-biometrics
[] []
@@ -1,14 +1,26 @@
(ns status-im2.contexts.onboarding.enable-notifications.style (ns status-im2.contexts.onboarding.enable-notifications.style
(:require [quo2.foundations.colors :as colors] (:require
[react-native.platform :as platform])) [react-native.platform :as platform]
[quo2.foundations.colors :as colors]))
(def page-container (def title-container
{:padding-top (if platform/ios? 44 0) {:justify-content :center
:position :absolute :margin-top 12
:top 0 :padding-horizontal 20})
:bottom 0
:left 0 (def enable-notifications-buttons
:right 0 {:margin 20})
(def enable-notifications
{:flex 1
:padding-top (if platform/ios? 44 0)
:background-color colors/neutral-80-opa-80-blur}) :background-color colors/neutral-80-opa-80-blur})
(def navigation-bar {:height 56}) (def page-illustration
{:flex 1
:background-color colors/danger-50
:align-items :center
:margin-horizontal 20
:border-radius 20
:margin-top 20
:justify-content :center})
@@ -1,37 +1,71 @@
(ns status-im2.contexts.onboarding.enable-notifications.view (ns status-im2.contexts.onboarding.enable-notifications.view
(:require [quo2.core :as quo] (:require
[quo2.foundations.colors :as colors] [quo2.core :as quo]
[react-native.core :as rn] [quo2.foundations.colors :as colors]
[status-im2.contexts.onboarding.enable-notifications.style :as style] [utils.i18n :as i18n]
[utils.i18n :as i18n] [utils.re-frame :as rf]
[status-im2.contexts.onboarding.common.background.view :as background] [react-native.core :as rn]
[utils.re-frame :as rf])) [react-native.platform :as platform]
[status-im.notifications.core :as notifications]
[status-im2.contexts.onboarding.common.background.view :as background]
[status-im2.contexts.onboarding.enable-notifications.style :as style]))
(defn navigation-bar (defn navigation-bar
[] []
[rn/view {:style style/navigation-bar} [quo/page-nav
[quo/page-nav (merge {:horizontal-description? false
{:align-mid? true :one-icon-align-left? true
:mid-section {:type :text-only :main-text ""} :align-mid? false
}]]) :page-nav-color :transparent
:left-section {:icon :i/arrow-left
:icon-background-color colors/white-opa-5
:icon-override-theme :dark
:type :shell
:on-press #()}})])
(defn page (defn page-title
[] []
[rn/view {:style style/page-container} [rn/view {:style style/title-container}
[navigation-bar] [quo/text
[rn/view {:style {:padding-horizontal 20}} {:accessibility-label :notifications-screen-title
[quo/text :weight :semi-bold
{:size :heading-1 :size :heading-1
:weight :semi-bold :style {:color colors/white}}
:style {:color colors/white}} "Enable-notifications"] (i18n/label :t/intro-wizard-title6)]
[quo/button [quo/text
{:on-press #(rf/dispatch [:navigate-to :shell-stack]) {:accessibility-label :notifications-screen-sub-title
:type :grey :weight :regular
:override-theme :dark :size :paragraph-1
:style {}} (i18n/label :t/continue)]]]) :style {:color colors/white}}
(i18n/label :t/enable-notifications-sub-title)]])
(defn enable-notification-buttons
[]
[rn/view {:style style/enable-notifications-buttons}
[quo/button
{:on-press (fn []
(rf/dispatch [::notifications/switch true platform/ios?])
(rf/dispatch [:init-root :welcome]))
:type :primary
:before :i/notifications
:accessibility-label :enable-notifications-button
:override-background-color (colors/custom-color :magenta 60)}
(i18n/label :t/intro-wizard-title6)]
[quo/button
{:on-press #(rf/dispatch [:init-root :welcome])
:accessibility-label :enable-notifications-later-button
:override-background-color colors/white-opa-5
:style {:margin-top 12}}
(i18n/label :t/maybe-later)]])
(defn enable-notifications (defn enable-notifications
[] []
[rn/view {:style {:flex 1}} [rn/view {:style style/enable-notifications}
[background/view true] [background/view true]
[page]]) [navigation-bar]
[page-title]
[rn/view {:style style/page-illustration}
[quo/text
"[Illustration here]"]]
[enable-notification-buttons]])
@@ -0,0 +1,14 @@
(ns status-im2.contexts.onboarding.enter-seed-phrase.style
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
(def page-container
{:padding-top (if platform/ios? 44 0)
:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:background-color colors/neutral-80-opa-80-blur})
(def navigation-bar {:height 56})
@@ -0,0 +1,79 @@
(ns status-im2.contexts.onboarding.enter-seed-phrase.view
(:require [quo2.core :as quo]
[quo.core :as quo1]
[clojure.string :as string]
[status-im.ethereum.mnemonic :as mnemonic]
[status-im2.constants :as constants]
[utils.security.core :as security]
[utils.re-frame :as rf]
[reagent.core :as reagent]
[react-native.core :as rn]
[status-im2.contexts.onboarding.enter-seed-phrase.style :as style]
[status-im2.contexts.onboarding.common.background.view :as background]
[utils.i18n :as i18n]))
(defn navigation-bar
[]
[rn/view {:style style/navigation-bar}
[quo/page-nav
{:align-mid? true
:left-section {:type :blur-bg
:icon :i/arrow-left
:icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:mid-section {:type :text-only :main-text ""}}]])
(def button-disabled?
(comp not constants/seed-phrase-valid-length mnemonic/words-count))
(defn clean-seed-phrase
[s]
(as-> s $
(string/lower-case $)
(string/split $ #"\s")
(filter #(not (string/blank? %)) $)
(string/join " " $)))
(defn page
[]
(let [seed-phrase (reagent/atom "")
error-message (reagent/atom "")
on-invalid-seed-phrase #(reset! error-message (i18n/label :t/custom-seed-phrase))]
(fn []
[rn/view {:style style/page-container}
[navigation-bar]
[rn/view {:style {:padding-horizontal 20}}
[quo/text
{:weight :bold
:align :center}
(i18n/label :t/use-recovery-phrase)]
[quo/text
(i18n/label-pluralize (mnemonic/words-count @seed-phrase) :t/words-n)]
[:<>
[quo1/text-input
{:on-change-text (fn [t]
(reset! seed-phrase (clean-seed-phrase t))
(reset! error-message ""))
:auto-focus true
:accessibility-label :passphrase-input
:placeholder (i18n/label :t/seed-phrase-placeholder)
:show-cancel false
:bottom-value 40
:multiline true
:auto-correct false
:keyboard-type :visible-password
:monospace true}]]
[quo/button
{:disabled (button-disabled? @seed-phrase)
:on-press #(rf/dispatch [:onboarding-2/seed-phrase-entered
(security/mask-data @seed-phrase)
on-invalid-seed-phrase])}
(i18n/label :t/continue)]
(when (seq @error-message)
[quo/text @error-message])]])))
(defn enter-seed-phrase
[]
[rn/view {:style {:flex 1}}
[background/view true]
[page]])
@@ -0,0 +1,139 @@
(ns status-im2.contexts.onboarding.events
(:require
[utils.re-frame :as rf]
[re-frame.core :as re-frame]
[status-im.utils.types :as types]
[status-im2.config :as config]
[clojure.string :as string]
[utils.security.core :as security]
[status-im.native-module.core :as status]
[status-im.ethereum.core :as ethereum]
[status-im2.constants :as constants]
[utils.i18n :as i18n]))
(re-frame/reg-fx
:multiaccount/create-account-and-login
(fn [request]
(status/create-account-and-login request)))
(re-frame/reg-fx
:multiaccount/validate-mnemonic
(fn [[mnemonic on-success on-error]]
(status/validate-mnemonic
(security/safe-unmask-data mnemonic)
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(if (seq error)
(when on-error (on-error error))
(on-success mnemonic)))))))
(re-frame/reg-fx
:multiaccount/restore-account-and-login
(fn [request]
(status/restore-account-and-login request)))
(rf/defn profile-data-set
{:events [:onboarding-2/profile-data-set]}
[{:keys [db]} onboarding-data]
{:db (update db :onboarding-2/profile merge onboarding-data)
:dispatch [:navigate-to :create-profile-password]})
(rf/defn enable-biometrics
{:events [:onboarding-2/enable-biometrics]}
[_]
{:biometric-auth/authenticate [#(rf/dispatch [:onboarding-2/biometrics-done %]) {}]})
(rf/defn show-biometrics-message
[cofx bioauth-message bioauth-code]
(let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code)
(i18n/label :t/grant-face-id-permissions))
bioauth-message)]
(when content
{:utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}})))
(rf/defn biometrics-done
{:events [:onboarding-2/biometrics-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(if bioauth-success
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch [:onboarding-2/create-account-and-login]}
(show-biometrics-message cofx bioauth-message bioauth-code)))
(defn strip-file-prefix
[path]
(when path
(string/replace-first path "file://" "")))
(rf/defn create-account-and-login
{:events [:onboarding-2/create-account-and-login]}
[{:keys [db]}]
(let [{:keys [display-name
seed-phrase
password
image-path
color]} (:onboarding-2/profile db)
log-enabled? (boolean (not-empty config/log-level))
effect (if seed-phrase
:multiaccount/restore-account-and-login
:multiaccount/create-account-and-login)
request {:displayName display-name
:password (ethereum/sha3 (security/safe-unmask-data password))
:mnemonic (when seed-phrase
(security/safe-unmask-data seed-phrase))
:imagePath (strip-file-prefix image-path)
:customizationColor color
:backupDisabledDataDir (status/backup-disabled-data-dir)
:rootKeystoreDir (status/keystore-dir)
;; Temporary fix until https://github.com/status-im/status-go/issues/3024 is
;; resolved
:wakuV2Nameserver "1.1.1.1"
:logLevel (when log-enabled? config/log-level)
:logEnabled log-enabled?
:logFilePath (status/log-file-path)
:openseaAPIKey config/opensea-api-key
:verifyTransactionURL config/verify-transaction-url
:verifyENSURL config/verify-ens-url
:verifyENSContractAddress config/verify-ens-contract-address
:verifyTransactionChainID config/verify-transaction-chain-id
:previewPrivacy config/blank-preview?}]
{effect request
:dispatch [:navigate-to :generating-keys]
:db (-> db
(dissoc :onboarding-2/profile)
(assoc :onboarding-2/new-account? true))}))
(rf/defn on-delete-profile-success
{:events [:onboarding-2/on-delete-profile-success]}
[{:keys [db]} key-uid]
{:db (update-in db [:multiaccounts/multiaccounts] dissoc key-uid)})
(rf/defn password-set
{:events [:onboarding-2/password-set]}
[{:keys [db]} password]
{:db (-> db
(assoc-in [:onboarding-2/profile :password] password)
(assoc-in [:onboarding-2/profile :auth-method] constants/auth-method-password))
:dispatch [:navigate-to :enable-biometrics]})
(rf/defn seed-phrase-entered
{:events [:onboarding-2/seed-phrase-entered]}
[_ seed-phrase on-error]
{:multiaccount/validate-mnemonic [seed-phrase
#(re-frame/dispatch [:onboarding-2/seed-phrase-validated
seed-phrase])
on-error]})
(rf/defn seed-phrase-validated
{:events [:onboarding-2/seed-phrase-validated]}
[{:keys [db]} seed-phrase]
{:db (assoc-in db [:onboarding-2/profile :seed-phrase] seed-phrase)
:dispatch [:navigate-to :create-profile]})
(rf/defn navigate-to-create-profile
{:events [:onboarding-2/navigate-to-create-profile]}
[{:keys [db]}]
;; Restart the flow
{:db (dissoc db :onboarding-2/profile)
:dispatch [:navigate-to :create-profile]})
@@ -0,0 +1,14 @@
(ns status-im2.contexts.onboarding.generating-keys.style
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
(def page-container
{:padding-top (if platform/ios? 44 0)
:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:background-color colors/neutral-80-opa-80-blur})
(def navigation-bar {:height 56})

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