Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64dba60029 | ||
|
|
a340e2a55c | ||
|
|
88c4521321 | ||
|
|
645a5b312b | ||
|
|
c55f12ecf8 | ||
|
|
6c28e9349f | ||
|
|
6170686e34 | ||
|
|
72e56dcf75 | ||
|
|
4801342a42 | ||
|
|
7282ba04bc | ||
|
|
2e397f0065 | ||
|
|
bcb55a1ab3 | ||
|
|
224d6c7cad | ||
|
|
126a8705cd | ||
|
|
3fce4816dc | ||
|
|
704b160964 | ||
|
|
7350c88920 | ||
|
|
7392b9bf50 | ||
|
|
f5948fa016 | ||
|
|
919fbf2091 | ||
|
|
728d9d01c5 | ||
|
|
9ac14b6fac | ||
|
|
4414f8ffd3 | ||
|
|
1e8ea3fd16 | ||
|
|
17f5ad79f4 | ||
|
|
d76e64b1bb | ||
|
|
ac675e28eb | ||
|
|
5755d2e21a | ||
|
|
cf2a3bfce7 | ||
|
|
dcda44fab6 | ||
|
|
362aa476ae | ||
|
|
25b088833d | ||
|
|
3395baa966 | ||
|
|
4327653b0b | ||
|
|
26dcc16ea6 | ||
|
|
9ed68ee7d1 | ||
|
|
4d7cf3e94e | ||
|
|
4decba8d00 | ||
|
|
229a806f12 | ||
|
|
abe653c086 | ||
|
|
f047665f75 | ||
|
|
ea3b090265 |
@@ -14,6 +14,13 @@
|
||||
clojure.set set
|
||||
clojure.walk walk
|
||||
taoensso.timbre log}}
|
||||
:shadowed-var {:level :error
|
||||
;; We temporarily use :include to define an
|
||||
;; allowlist of core Clojure vars. In the
|
||||
;; future, as we progressively fix shadowed
|
||||
;; vars, we should be able to delete this
|
||||
;; option and lint all vars.
|
||||
:include [count iter key time]}
|
||||
:invalid-arity {:skip-args [status-im.utils.fx/defn utils.re-frame/defn]}
|
||||
;; TODO remove number when this is fixed
|
||||
;; https://github.com/borkdude/clj-kondo/issues/867
|
||||
|
||||
@@ -74,7 +74,7 @@ pipeline {
|
||||
sh 'cp -f $TEST_ETH_ACCOUNTS_FILE users.py'
|
||||
sh """
|
||||
python3 -m pytest \
|
||||
--numprocesses 4 \
|
||||
--numprocesses 6 \
|
||||
--rerun_count=2 \
|
||||
--testrail_report=True \
|
||||
-m testrail_id \
|
||||
|
||||
@@ -123,7 +123,7 @@ pipeline {
|
||||
sh 'cp -f $TEST_ETH_ACCOUNTS_FILE users.py'
|
||||
sh """
|
||||
python3 -m pytest \
|
||||
--numprocesses 4 \
|
||||
--numprocesses 6 \
|
||||
--rerun_count=2 \
|
||||
--testrail_report=True \
|
||||
-k \"${params.KEYWORD_EXPRESSION}\" \
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
[Patching](patching.md)
|
||||
|
||||
[Creating a pixel perfect UI](pixel-perfection.md)
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Git Etiquette Guide
|
||||
|
||||
To ensure smooth collaboration, we've outlined the following Git etiquette guide. This guide will help in writing concise commit messages, managing git branches, pull request descriptions, and other best practices.
|
||||
|
||||
## 1. Commit Messages
|
||||
|
||||
Your commit message should provide clear context about the change. This allows others (and future you) to understand why a particular change was made.
|
||||
|
||||
Here are the guidelines to write concise and meaningful commit messages:
|
||||
|
||||
- **Use the Imperative Mood**: Commit messages should be written in the present tense and imperative mood ("Add feature" not "Added feature" or "Adds feature").
|
||||
- **First Line Should Be Summary**: The first line of your commit message should be a brief summary of the changes, kept under 50 characters whenever possible.
|
||||
- **Body**: Follow this summary line with a blank line, then a more detailed explanation is always helpful but not necessary. This is often useful when the commit has significant importance or complexity.
|
||||
- **Consider Including the 'Why'**: If the commit resolves an issue or bug, mention the issue number and provide a brief explanation of how the changes fix the issue.
|
||||
|
||||
Here's a good commit message example:
|
||||
|
||||
```
|
||||
Fix off-by-one error in loop (issue #123)
|
||||
|
||||
This commit fixes an off-by-one error in the list iteration function.
|
||||
This was causing an index out of bounds exception when processing the last item in the list.
|
||||
Tests have been updated to catch this in the future.
|
||||
```
|
||||
|
||||
## 2. Branch Management
|
||||
|
||||
Git branches should be concise and focused on a single feature, fix, or improvement. Here are a few practices:
|
||||
|
||||
- **Branch Name**: The branch name should be descriptive of the change and follow a specific format such as `type/description`. Example: `feature/login-system`.
|
||||
- **Please don't accept branch recommendation by Github UI**: The branch name recommendation by Github UI will in most cases be a very long branch name concatenated with the issue number, which often results in branch names like this one :
|
||||
`feature/Merge_yada_and_yada_yada_into_one_yada_yada_status-lol_#199021`
|
||||
- **Keep Branches Short-lived**: The longer a branch lives separate from the `main` or `master` branch, the higher the possibility of merge conflicts. Aim to merge branches as soon as the work is complete and has been tested.
|
||||
- **Rebase Frequently**: If your branch lives longer, rebase it frequently on top of the main branch.
|
||||
|
||||
## 3. Pull Requests
|
||||
|
||||
Pull requests (PRs) are how your changes get incorporated into the project. Here are a few key points:
|
||||
|
||||
- **Link PR to an Issue**: Your PR should link to the issue it resolves. This helps track the resolution of bugs or addition of features.
|
||||
- **PR Description**: The PR description should outline what changes have been made and why. This should be a summary of your commits.
|
||||
- **Small and Focused PRs**: Aim to keep PRs small and focused. It's easier to understand and review a PR with a handful of related changes than one with multiple unrelated changes.
|
||||
|
||||
Example PR description:
|
||||
|
||||
```
|
||||
## Description
|
||||
This PR implements a login system (issue #456). It adds a login and logout button, as well as an authentication system. This will allow users to create an account and sign in.
|
||||
|
||||
## Changes
|
||||
- Add login button component
|
||||
- Add logout button component
|
||||
- Add authentication API integration
|
||||
|
||||
## Issue Related
|
||||
This PR closes #456
|
||||
```
|
||||
|
||||
## 4. Other Best Practices
|
||||
|
||||
- **Stay Updated**: Keep your local copy up to date with the upstream regularly.
|
||||
- **Test Before Pushing**: Always test your code and make sure all tests pass before you push.
|
||||
|
||||
Following these guidelines ensures a smoother collaboration and maintains a clean, manageable project history. Thanks for your contributions!
|
||||
|
||||
|
After Width: | Height: | Size: 564 KiB |
@@ -80,6 +80,52 @@ This mistake mostly happens with functional components
|
||||
|
||||
it's important to name functional components with `f-` prefix
|
||||
|
||||
### Component props and API scheme to match Figma as closely as possible
|
||||
|
||||
Ideally, the prop names for components (particularly in quo2 Design System) should match the Figma properties as best as possible. This makes it easier for the developer using that component to configure it correctly for the screen it is being used on and avoids unnecessary overwrites and adjustments being made.
|
||||
|
||||
#### Avoid unnecessarily grouping categories to reduce the number of props
|
||||
For example in Figma if there is a component and it has the following variants:
|
||||
|
||||
|theme: "light" blur: "False"|theme: "dark" blur: "False"|theme: "light" blur: "True"|theme: "dark" blur: "True"|
|
||||
|----------------------------|---------------------------|---------------------------|---------------------------|
|
||||
| type :neutral label "ABC" | type :neutral label "ABC" | | |
|
||||
| type :active label "ABC" | type :active label "ABC" | | |
|
||||
| type :danger label "ABC" | type :danger label "ABC" | type :danger label "ABC" | type :danger label "ABC" |
|
||||
|
||||
```clojure
|
||||
;; bad
|
||||
"theme - :light or :dark
|
||||
type - can be :neutral :active :danger :danger-blur"
|
||||
(defn my-component [{:keys [theme type]} label])
|
||||
|
||||
;; good
|
||||
"theme - :light or :dark
|
||||
type - can be :neutral :active :danger
|
||||
blur? - boolean
|
||||
"
|
||||
(defn my-component [{:keys [theme blur? type]} label])
|
||||
```
|
||||
|
||||
Please note this is only for the external API of the component and there should be no restriction of how the component manages its internal API as that will not affect the developer using the component with the issues described above.
|
||||
|
||||
In some cases this is not always possible or does not make sense. However the thought process should be how easy will it be for another developer to use this component with the correct configuration given the screen designs for Figma.
|
||||
|
||||
#### Avoid unnecessarily renaming props
|
||||
In general it can be helpful to avoid renaming props from their counterpart in Figma.
|
||||
|
||||
For example if Figma has sizes `:small`, `:medium` and `:large`
|
||||
|
||||
```clojure
|
||||
;; bad
|
||||
":size - :little, :default or :big"
|
||||
(defn my-component [{:keys [size]}])
|
||||
|
||||
;; good
|
||||
":size - :small, :medium or :large"
|
||||
(defn my-component [{:keys [size]}])
|
||||
```
|
||||
|
||||
### Component styles
|
||||
|
||||
Prefer to define styles in a separate file named `style.cljs`, colocated with
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Pixel Perfection
|
||||
The Status Mobile team aims to align the design implementation perfectly according to a given screens respective Figma design spec.
|
||||
|
||||
Currently the Figma Designs match up with an "iPhone 11 Pro" screen size.
|
||||
|
||||
To test your implementation is correct you can take the following steps
|
||||
|
||||
- Open a new Figma file so that you have write privileges.
|
||||
- Copy in the component or screen from the Figma file.
|
||||
|
||||
- Using the "iPhone 11 Pro" simulator (it is the default now) you take a screenshot of your component and paste it into your Figma file.
|
||||
|
||||
- To get the right size, set your screenshot to width 375px
|
||||
|
||||

|
||||
|
||||
- Lower the opacity so you can see it sitting on top of the Figma mock ups.
|
||||
@@ -833,6 +833,37 @@ void _SaveAccountAndLogin(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
}
|
||||
|
||||
void _CreateAccountAndLogin(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() != 1) {
|
||||
// Throw an Error that is passed back to JavaScript
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong number of arguments for SaveAccountAndLogin")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the argument types
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'settingsJSON'")));
|
||||
return;
|
||||
}
|
||||
|
||||
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
|
||||
char *arg0 = *arg0Obj;
|
||||
|
||||
// Call exported Go function, which returns a C string
|
||||
char *c = CreateAccountAndLogin(arg0);
|
||||
|
||||
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
|
||||
args.GetReturnValue().Set(ret);
|
||||
delete c;
|
||||
|
||||
}
|
||||
|
||||
void _GenerateAlias(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
@@ -1929,6 +1960,7 @@ void init(Local<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "hashMessage", _HashMessage);
|
||||
NODE_SET_METHOD(exports, "resetChainData", _ResetChainData);
|
||||
NODE_SET_METHOD(exports, "saveAccountAndLogin", _SaveAccountAndLogin);
|
||||
NODE_SET_METHOD(exports, "createAccountAndLogin", _CreateAccountAndLogin);
|
||||
NODE_SET_METHOD(exports, "generateAlias", _GenerateAlias);
|
||||
NODE_SET_METHOD(exports, "validateMnemonic", _ValidateMnemonic);
|
||||
NODE_SET_METHOD(exports, "multiformatSerializePublicKey", _MultiformatSerializePublicKey);
|
||||
|
||||
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 414 KiB |
|
After Width: | Height: | Size: 295 KiB |
|
After Width: | Height: | Size: 540 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 566 KiB |
@@ -6,3 +6,10 @@ export function infoLayout(input, isTop) {
|
||||
return isTop ? input.value : -input.value;
|
||||
});
|
||||
}
|
||||
|
||||
export function textSheet(input, isHeight) {
|
||||
return useDerivedValue(function () {
|
||||
'worklet';
|
||||
return isHeight ? input.value : -input.value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Easing } from 'react-native-reanimated';
|
||||
|
||||
// Home Stack States
|
||||
export const CLOSE_WITH_ANIMATION = 0;
|
||||
export const OPEN_WITH_ANIMATION = 1;
|
||||
export const CLOSE_WITHOUT_ANIMATION = 2;
|
||||
export const OPEN_WITHOUT_ANIMATION = 3;
|
||||
export const CLOSE_WITHOUT_ANIMATION = 0;
|
||||
export const OPEN_WITHOUT_ANIMATION = 1;
|
||||
export const CLOSE_WITH_ANIMATION = 2;
|
||||
export const OPEN_WITH_ANIMATION = 3;
|
||||
|
||||
// Floating Screen States
|
||||
export const CLOSE_SCREEN_WITH_SLIDE_ANIMATION = 0;
|
||||
export const OPEN_SCREEN_WITH_SLIDE_ANIMATION = 1;
|
||||
export const CLOSE_SCREEN_WITH_SHELL_ANIMATION = 2;
|
||||
export const OPEN_SCREEN_WITH_SHELL_ANIMATION = 3;
|
||||
export const CLOSE_SCREEN_WITHOUT_ANIMATION = 4;
|
||||
export const OPEN_SCREEN_WITHOUT_ANIMATION = 5;
|
||||
export const CLOSE_SCREEN_WITHOUT_ANIMATION = 0;
|
||||
export const OPEN_SCREEN_WITHOUT_ANIMATION = 1;
|
||||
export const CLOSE_SCREEN_WITH_SLIDE_ANIMATION = 2;
|
||||
export const OPEN_SCREEN_WITH_SLIDE_ANIMATION = 3;
|
||||
export const CLOSE_SCREEN_WITH_SHELL_ANIMATION = 4;
|
||||
export const OPEN_SCREEN_WITH_SHELL_ANIMATION = 5;
|
||||
|
||||
export const SHELL_ANIMATION_TIME = 200;
|
||||
|
||||
|
||||
@@ -287,11 +287,11 @@
|
||||
and then compressed. Example input/output :
|
||||
input key = zQ3shTAten2v9CwyQD1Kc7VXAqNPDcHZAMsfbLHCZEx6nFqk9 and
|
||||
output key = 0x025596a7ff87da36860a84b0908191ce60a504afc94aac93c1abd774f182967ce6"
|
||||
[key callback]
|
||||
[input-key callback]
|
||||
(log/info "[native-module] Deserializing and then compressing public key"
|
||||
{:fn :deserialize-and-compress-key
|
||||
:key key})
|
||||
(.deserializeAndCompressKey ^js (status) key callback))
|
||||
:key input-key})
|
||||
(.deserializeAndCompressKey ^js (status) input-key callback))
|
||||
|
||||
(defn compressed-key->public-key
|
||||
"Provides compressed key to status-go and gets back the uncompressed public key via deserialization"
|
||||
|
||||
@@ -235,31 +235,6 @@
|
||||
[value velocity snap-points]
|
||||
(.snapPoint ^js redash value velocity (to-array snap-points)))
|
||||
|
||||
(defn cancelable-loop
|
||||
[{:keys [clock duration finished on-reach]}]
|
||||
(let [time (value 0)
|
||||
frame-time (value 0)
|
||||
position (value 0)
|
||||
to-value (value 1)
|
||||
state {:time time
|
||||
:frameTime frame-time
|
||||
:finished finished
|
||||
:position position}
|
||||
config {:toValue to-value
|
||||
:duration duration
|
||||
:easing (:linear easings)}]
|
||||
(block
|
||||
[(timing clock state config)
|
||||
(cond* (and* finished
|
||||
(eq position to-value))
|
||||
(call* [] on-reach))
|
||||
(cond* finished
|
||||
[(set finished 0)
|
||||
(set time 0)
|
||||
(set frame-time 0)
|
||||
(set position 0)])
|
||||
position])))
|
||||
|
||||
(defn with-easing
|
||||
[{val :value
|
||||
:keys [snap-points velocity offset state easing duration
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
(ns quo.previews.preview)
|
||||
|
||||
(defn descriptor->values
|
||||
[{:keys [key options type]}]
|
||||
{key (case type
|
||||
:boolean [false true]
|
||||
:text [nil "Just simple text"] ; NOTE(Ferossgp): add example with long text?
|
||||
:select (mapv :key options))})
|
||||
|
||||
(defmacro list-comp
|
||||
[[binding seq-expr & bindings] body-expr]
|
||||
(cond (not binding)
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
:background-color (:ui-background @colors/theme)})
|
||||
|
||||
(defn customizer-boolean
|
||||
[{:keys [label key state]}]
|
||||
(let [state* (reagent/cursor state [key])]
|
||||
[{:keys [label state] :as args}]
|
||||
(let [state* (reagent/cursor state [(:key args)])]
|
||||
[rn/view {:style container}
|
||||
[label-view state label]
|
||||
[rn/view
|
||||
@@ -95,8 +95,8 @@
|
||||
"False"]]]]))
|
||||
|
||||
(defn customizer-text
|
||||
[{:keys [label key state]}]
|
||||
(let [state* (reagent/cursor state [key])]
|
||||
[{:keys [label state] :as args}]
|
||||
(let [state* (reagent/cursor state [(:key args)])]
|
||||
[rn/view {:style container}
|
||||
[label-view state label]
|
||||
[rn/view {:style {:flex 0.6}}
|
||||
@@ -117,8 +117,8 @@
|
||||
(defn customizer-select
|
||||
[]
|
||||
(let [open (reagent/atom nil)]
|
||||
(fn [{:keys [label key state options]}]
|
||||
(let [state* (reagent/cursor state [key])
|
||||
(fn [{:keys [label state options] :as args}]
|
||||
(let [state* (reagent/cursor state [(:key args)])
|
||||
selected (value-for-key @state* options)]
|
||||
[rn/view {:style container}
|
||||
[label-view state label]
|
||||
@@ -133,15 +133,15 @@
|
||||
[rn/view {:style (modal-view)}
|
||||
[rn/scroll-view
|
||||
(doall
|
||||
(for [{:keys [key value]} options]
|
||||
^{:key key}
|
||||
(for [{k :key v :value} options]
|
||||
^{:key k}
|
||||
[rn/touchable-opacity
|
||||
{:style (select-option-style (= @state* key))
|
||||
{:style (select-option-style (= @state* k))
|
||||
:on-press #(do
|
||||
(reset! open false)
|
||||
(reset! state* key))}
|
||||
[quo/text {:color (if (= @state* key) :link :secondary)}
|
||||
value]]))
|
||||
(reset! state* k))}
|
||||
[quo/text {:color (if (= @state* k) :link :secondary)}
|
||||
v]]))
|
||||
[rn/view {:flex-direction :row}
|
||||
[rn/touchable-opacity
|
||||
{:style (select-option-style false)
|
||||
@@ -175,17 +175,15 @@
|
||||
{:style {:flex 1}
|
||||
:padding-horizontal 16}
|
||||
(doall
|
||||
(for [{:keys [key type]
|
||||
:as desc}
|
||||
descriptors
|
||||
:let [descriptor (merge desc
|
||||
{:state state})]]
|
||||
^{:key key}
|
||||
(for [desc descriptors
|
||||
:let [descriptor (merge desc {:state state})]]
|
||||
^{:key (:key desc)}
|
||||
[:<>
|
||||
(case type
|
||||
(case (:type desc)
|
||||
:boolean [customizer-boolean descriptor]
|
||||
:text [customizer-text descriptor]
|
||||
:select [customizer-select descriptor])]))])
|
||||
:select [customizer-select descriptor]
|
||||
nil)]))])
|
||||
|
||||
(comment
|
||||
[{:label "Show error:"
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
(ns quo2.components.avatars.channel-avatar
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn channel-avatar
|
||||
[{:keys [big? locked? emoji-background-color emoji]}]
|
||||
(let [lock-exists? locked?
|
||||
dark? (theme/dark?)]
|
||||
[rn/view
|
||||
{:style {:width (if big? 32 24)
|
||||
:height (if big? 32 24)
|
||||
:border-radius (if big? 32 24)
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color emoji-background-color}}
|
||||
[rn/view
|
||||
{:style {:display :flex
|
||||
:justify-content :center
|
||||
:align-items :center}}
|
||||
[text/text
|
||||
{:size (if big?
|
||||
:paragraph-1
|
||||
:label)} emoji]
|
||||
(when lock-exists?
|
||||
[rn/view
|
||||
{:style {:position :absolute
|
||||
:left (if big?
|
||||
14
|
||||
8)
|
||||
:top (if big?
|
||||
15
|
||||
8)
|
||||
:background-color (if dark?
|
||||
colors/neutral-90
|
||||
colors/white)
|
||||
:border-radius 15
|
||||
:padding 2}}
|
||||
[icons/icon
|
||||
(if locked?
|
||||
:main-icons/locked
|
||||
:main-icons/unlocked)
|
||||
{:color (if dark?
|
||||
colors/neutral-40
|
||||
colors/neutral-50)
|
||||
:container-style {:width 12
|
||||
:height 12}
|
||||
:size 12}]])]]))
|
||||
@@ -0,0 +1,35 @@
|
||||
(ns quo2.components.avatars.channel-avatar.component-spec
|
||||
(:require [quo2.components.avatars.channel-avatar.view :as component]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Channel Avatar"
|
||||
(h/test "default render"
|
||||
(h/render [component/view])
|
||||
(h/is-truthy (h/query-by-label-text :initials))
|
||||
(h/is-null (h/query-by-label-text :emoji))
|
||||
(h/is-null (h/query-by-label-text :lock)))
|
||||
|
||||
(h/test "with emoji, no lock set, large size"
|
||||
(let [emoji "🍓"]
|
||||
(h/render [component/view {:emoji emoji :size :size/l}])
|
||||
(h/is-null (h/query-by-label-text :initials))
|
||||
(h/is-truthy (h/query-by-text emoji))
|
||||
(h/is-null (h/query-by-label-text :lock))))
|
||||
|
||||
(h/test "locked"
|
||||
(h/render [component/view {:locked? true}])
|
||||
(h/is-truthy (h/query-by-label-text :lock)))
|
||||
|
||||
(h/test "unlocked"
|
||||
(h/render [component/view {:locked? false}])
|
||||
(h/is-truthy (h/query-by-label-text :lock)))
|
||||
|
||||
(h/test "no emoji, smaller size"
|
||||
(h/render [component/view {:full-name "Status Mobile"}])
|
||||
(h/is-truthy (h/query-by-text "S")))
|
||||
|
||||
(h/test "no emoji, large size"
|
||||
(h/render [component/view
|
||||
{:full-name "Status Mobile"
|
||||
:size :size/l}])
|
||||
(h/is-truthy (h/query-by-text "SM"))))
|
||||
@@ -0,0 +1,28 @@
|
||||
(ns quo2.components.avatars.channel-avatar.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def lock-icon-size 12)
|
||||
|
||||
(defn outer-container
|
||||
[{:keys [size color]}]
|
||||
(let [size (if (= size :size/l) 32 24)]
|
||||
{:width size
|
||||
:height size
|
||||
:border-radius size
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color (colors/theme-alpha color 0.1 0.1)}))
|
||||
|
||||
(defn lock-container
|
||||
[size]
|
||||
(let [distance (if (= size :size/l) 20 12)]
|
||||
{:position :absolute
|
||||
:left distance
|
||||
:top distance
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:border-radius (* 2 lock-icon-size)
|
||||
:padding 2}))
|
||||
|
||||
(def lock-icon
|
||||
{:width lock-icon-size
|
||||
:height lock-icon-size})
|
||||
@@ -0,0 +1,60 @@
|
||||
(ns quo2.components.avatars.channel-avatar.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.channel-avatar.style :as style]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
utils.string))
|
||||
|
||||
(defn- initials
|
||||
[full-name size color]
|
||||
(let [amount-initials (if (= size :size/l) 2 1)]
|
||||
[text/text
|
||||
{:accessibility-label :initials
|
||||
:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
:style {:color color}}
|
||||
(utils.string/get-initials full-name amount-initials)]))
|
||||
|
||||
(defn- lock
|
||||
[locked? size]
|
||||
;; When `locked?` is nil, we must not display the unlocked icon.
|
||||
(when (boolean? locked?)
|
||||
[rn/view
|
||||
{:accessibility-label :lock
|
||||
:style (style/lock-container size)}
|
||||
[icons/icon (if locked? :i/locked :i/unlocked)
|
||||
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
|
||||
:container-style style/lock-icon
|
||||
:size 12}]]))
|
||||
|
||||
(defn view
|
||||
"Options:
|
||||
|
||||
:size - keyword (default nil) - Container size, for the moment,
|
||||
only :size/l (meaning large) is supported.
|
||||
|
||||
:emoji - string (default nil)
|
||||
|
||||
:customization-color - color (default nil) If the component is used for a
|
||||
community channel, then the default color should be the community custom
|
||||
color.
|
||||
|
||||
:locked? - nil/bool (default nil) - When true/false display a locked/unlocked
|
||||
icon respectively. When nil does not show icon.
|
||||
|
||||
:full-name - string (default nil) - When :emoji is blank, this value will be
|
||||
used to extract the initials.
|
||||
"
|
||||
[{:keys [size emoji customization-color locked? full-name]}]
|
||||
[rn/view
|
||||
{:accessibility-label :channel-avatar
|
||||
:style (style/outer-container {:size size :color customization-color})}
|
||||
(if (string/blank? emoji)
|
||||
[initials full-name size customization-color]
|
||||
[text/text
|
||||
{:accessibility-label :emoji
|
||||
:size (if (= size :size/l) :paragraph-1 :label)}
|
||||
(string/trim emoji)])
|
||||
[lock locked? size]])
|
||||
@@ -1,22 +1,9 @@
|
||||
(ns quo2.components.avatars.user-avatar.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.user-avatar.style :as style]
|
||||
(:require [quo2.components.avatars.user-avatar.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
(defn trim-whitespace [s] (string/join " " (string/split (string/trim s) #"\s+")))
|
||||
|
||||
(defn- extract-initials
|
||||
[full-name amount-initials]
|
||||
(let [upper-case-first-letter (comp string/upper-case first)
|
||||
names-list (string/split (trim-whitespace full-name) " ")]
|
||||
(if (= (first names-list) "")
|
||||
""
|
||||
(->> names-list
|
||||
(map upper-case-first-letter)
|
||||
(take amount-initials)
|
||||
(string/join)))))
|
||||
[react-native.fast-image :as fast-image]
|
||||
utils.string))
|
||||
|
||||
(defn initials-avatar
|
||||
[{:keys [full-name size draw-ring? customization-color]}]
|
||||
@@ -29,7 +16,7 @@
|
||||
{:style style/initials-avatar-text
|
||||
:size font-size
|
||||
:weight :semi-bold}
|
||||
(extract-initials full-name amount-initials)]]))
|
||||
(utils.string/get-initials full-name amount-initials)]]))
|
||||
|
||||
(def valid-ring-sizes #{:big :medium :small})
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
(ns quo2.components.buttons.--tests--.buttons-component-spec
|
||||
(:require ["@testing-library/react-native" :as rtl]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn render-button
|
||||
([options label]
|
||||
(rtl/render (reagent/as-element [button/button options label]))))
|
||||
|
||||
(js/global.test "default render of button component"
|
||||
(fn []
|
||||
(render-button {:accessibility-label "test-button"} "")
|
||||
(-> (js/expect (rtl/screen.getByLabelText "test-button"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(js/global.test "button renders with a label"
|
||||
(fn []
|
||||
(render-button {} "test-label")
|
||||
(-> (js/expect (rtl/screen.getByText "test-label"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(js/global.test "button on-press works"
|
||||
(let [event (js/jest.fn)]
|
||||
(fn []
|
||||
(render-button {:on-press event} "test-label")
|
||||
(rtl/fireEvent.press (rtl/screen.getByText "test-label"))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1)))))
|
||||
@@ -0,0 +1,18 @@
|
||||
(ns quo2.components.buttons.button.component-spec
|
||||
(:require [quo2.components.buttons.button.view :as button]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "button tests"
|
||||
(h/test "default render of button component"
|
||||
(h/render [button/button {:accessibility-label "test-button"} ""])
|
||||
(h/is-truthy (h/get-by-label-text "test-button")))
|
||||
|
||||
(h/test "button renders with a label"
|
||||
(h/render [button/button {} "test-label"])
|
||||
(h/is-truthy (h/get-by-text "test-label")))
|
||||
|
||||
(h/test "button on-press works"
|
||||
(let [event (h/mock-fn)]
|
||||
(h/render [button/button {:on-press event} "test-label"])
|
||||
(h/fire-event :press (h/get-by-text "test-label"))
|
||||
(h/was-called event))))
|
||||
@@ -1,12 +1,46 @@
|
||||
(ns quo2.components.buttons.button
|
||||
(:require [quo2.components.icon :as quo2.icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.blur :as blur]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.components.buttons.style :as style]))
|
||||
(ns quo2.components.buttons.button.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def blur-view
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom 0})
|
||||
|
||||
(defn before-icon-style
|
||||
[{:keys [override-margins size icon-container-size icon-background-color icon-container-rounded?
|
||||
icon-size]}]
|
||||
(merge
|
||||
{:margin-left (or (get override-margins :left)
|
||||
(if (= size 40) 12 8))
|
||||
:margin-right (or (get override-margins :right) 4)
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(when icon-container-size
|
||||
{:width icon-container-size
|
||||
:height icon-container-size})
|
||||
(when icon-background-color
|
||||
{:background-color icon-background-color})
|
||||
(when icon-container-rounded?
|
||||
{:border-radius (/ (or icon-container-size icon-size) 2)})))
|
||||
|
||||
(defn after-icon-style
|
||||
[{:keys [override-margins size icon-container-size icon-background-color icon-container-rounded?
|
||||
icon-size]}]
|
||||
(merge
|
||||
{:margin-left (or (get override-margins :left) 4)
|
||||
:margin-right (or (get override-margins :right)
|
||||
(if (= size 40) 12 8))
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(when icon-container-size
|
||||
{:width icon-container-size
|
||||
:height icon-container-size})
|
||||
(when icon-background-color
|
||||
{:background-color icon-background-color})
|
||||
(when icon-container-rounded?
|
||||
{:border-radius (/ (or icon-container-size icon-size) 2)})))
|
||||
|
||||
(defn themes
|
||||
[customization-color]
|
||||
@@ -179,7 +213,7 @@
|
||||
24 8))})
|
||||
|
||||
(defn style-container
|
||||
[type size disabled background-color border-color icon above width before after blur-active?]
|
||||
[{:keys [type size disabled background-color border-color icon above width before after blur-active?]}]
|
||||
(merge {:height size
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
@@ -228,132 +262,3 @@
|
||||
:border-width 1})
|
||||
(when disabled
|
||||
{:opacity 0.3})))
|
||||
|
||||
(defn- button-internal
|
||||
"with label
|
||||
[button opts \"label\"]
|
||||
opts
|
||||
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
|
||||
:danger/:photo-bg/:blur-bg/:blur-bg-outline/:shell/:community
|
||||
:size 40 [default] /32/24
|
||||
:icon true/false
|
||||
:community-color '#FFFFFF'
|
||||
:community-text-color '#000000'
|
||||
:before :icon-keyword
|
||||
:after :icon-keyword}
|
||||
|
||||
only icon
|
||||
[button {:icon true} :i/close-circle]"
|
||||
[_ _]
|
||||
(let [pressed-in (reagent/atom false)]
|
||||
(fn
|
||||
[{:keys [on-press disabled type size before after above icon-secondary-no-color
|
||||
width customization-color theme override-background-color pressed
|
||||
on-long-press accessibility-label icon icon-no-color style inner-style test-ID
|
||||
blur-active? override-before-margins override-after-margins icon-size icon-container-size
|
||||
icon-container-rounded?]
|
||||
:or {type :primary
|
||||
size 40
|
||||
customization-color :primary
|
||||
blur-active? true}}
|
||||
children]
|
||||
(let [{:keys [icon-color icon-secondary-color background-color label-color border-color blur-type
|
||||
blur-overlay-color icon-background-color]}
|
||||
(get-in (themes customization-color)
|
||||
[theme type])
|
||||
state (cond disabled :disabled
|
||||
(or @pressed-in pressed) :pressed
|
||||
:else :default)
|
||||
blur-state (if blur-active? :blurred :default)
|
||||
icon-size (or icon-size (when (= 24 size) 12))
|
||||
icon-secondary-color (or icon-secondary-color icon-color)]
|
||||
[rn/touchable-without-feedback
|
||||
(merge {:test-ID test-ID
|
||||
:disabled disabled
|
||||
:accessibility-label accessibility-label
|
||||
:on-press-in #(reset! pressed-in true)
|
||||
:on-press-out #(reset! pressed-in nil)}
|
||||
(when on-press
|
||||
{:on-press on-press})
|
||||
(when on-long-press
|
||||
{:on-long-press on-long-press}))
|
||||
[rn/view
|
||||
{:style (merge
|
||||
(shape-style-container type icon size)
|
||||
{:width width}
|
||||
style)}
|
||||
[rn/view
|
||||
{:style (merge
|
||||
(style-container
|
||||
type
|
||||
size
|
||||
disabled
|
||||
(or override-background-color (get background-color state))
|
||||
(get border-color state)
|
||||
icon
|
||||
above
|
||||
width
|
||||
before
|
||||
after
|
||||
blur-active?)
|
||||
(when (= state :pressed) {:opacity 0.9})
|
||||
inner-style)}
|
||||
(when (and (= type :blurred)
|
||||
blur-active?)
|
||||
[blur/view
|
||||
{:blur-radius 20
|
||||
:blur-type blur-type
|
||||
:overlay-color blur-overlay-color
|
||||
:style style/blur-view}])
|
||||
(when above
|
||||
[rn/view
|
||||
[quo2.icons/icon above
|
||||
{:container-style {:margin-bottom 2}
|
||||
:color icon-secondary-color
|
||||
:size icon-size}]])
|
||||
(when before
|
||||
[rn/view
|
||||
{:style (style/before-icon-style
|
||||
{:override-margins override-before-margins
|
||||
:size size
|
||||
:icon-container-size icon-container-size
|
||||
:icon-background-color (get icon-background-color blur-state)
|
||||
:icon-container-rounded? icon-container-rounded?
|
||||
:icon-size icon-size})}
|
||||
[quo2.icons/icon before
|
||||
{:color icon-secondary-color
|
||||
:size icon-size}]])
|
||||
[rn/view
|
||||
(cond
|
||||
(or icon icon-no-color)
|
||||
[quo2.icons/icon children
|
||||
{:color icon-color
|
||||
:no-color icon-no-color
|
||||
:size icon-size}]
|
||||
|
||||
(string? children)
|
||||
[text/text
|
||||
{:size (when (#{56 24} size) :paragraph-2)
|
||||
:weight :medium
|
||||
:number-of-lines 1
|
||||
:style {:color label-color}}
|
||||
|
||||
children]
|
||||
|
||||
(vector? children)
|
||||
children)]
|
||||
(when after
|
||||
[rn/view
|
||||
{:style (style/after-icon-style
|
||||
{:override-margins override-after-margins
|
||||
:size size
|
||||
:icon-container-size icon-container-size
|
||||
:icon-background-color (get icon-background-color blur-state)
|
||||
:icon-container-rounded? icon-container-rounded?
|
||||
:icon-size icon-size})}
|
||||
[quo2.icons/icon after
|
||||
{:no-color icon-secondary-no-color
|
||||
:color icon-secondary-color
|
||||
:size icon-size}]])]]]))))
|
||||
|
||||
(def button (theme/with-theme button-internal))
|
||||
@@ -0,0 +1,138 @@
|
||||
(ns quo2.components.buttons.button.view
|
||||
(:require [quo2.components.icon :as quo2.icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.blur :as blur]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.components.buttons.button.style :as style]))
|
||||
|
||||
(defn- button-internal
|
||||
"with label
|
||||
[button opts \"label\"]
|
||||
opts
|
||||
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
|
||||
:danger/:photo-bg/:blur-bg/:blur-bg-outline/:shell/:community
|
||||
:size 40 [default] /32/24
|
||||
:icon true/false
|
||||
:community-color '#FFFFFF'
|
||||
:community-text-color '#000000'
|
||||
:before :icon-keyword
|
||||
:after :icon-keyword}
|
||||
|
||||
only icon
|
||||
[button {:icon true} :i/close-circle]"
|
||||
[_ _]
|
||||
(let [pressed-in (reagent/atom false)]
|
||||
(fn
|
||||
[{:keys [on-press disabled type size before after above icon-secondary-no-color
|
||||
width customization-color theme override-background-color pressed
|
||||
on-long-press accessibility-label icon icon-no-color style inner-style test-ID
|
||||
blur-active? override-before-margins override-after-margins icon-size icon-container-size
|
||||
icon-container-rounded?]
|
||||
:or {type :primary
|
||||
size 40
|
||||
customization-color :primary
|
||||
blur-active? true}}
|
||||
children]
|
||||
(let [{:keys [icon-color icon-secondary-color background-color label-color border-color blur-type
|
||||
blur-overlay-color icon-background-color]}
|
||||
(get-in (style/themes customization-color)
|
||||
[theme type])
|
||||
state (cond disabled :disabled
|
||||
(or @pressed-in pressed) :pressed
|
||||
:else :default)
|
||||
blur-state (if blur-active? :blurred :default)
|
||||
icon-size (or icon-size (when (= 24 size) 12))
|
||||
icon-secondary-color (or icon-secondary-color icon-color)]
|
||||
[rn/touchable-without-feedback
|
||||
(merge {:test-ID test-ID
|
||||
:disabled disabled
|
||||
:accessibility-label accessibility-label
|
||||
:on-press-in #(reset! pressed-in true)
|
||||
:on-press-out #(reset! pressed-in nil)}
|
||||
(when on-press
|
||||
{:on-press on-press})
|
||||
(when on-long-press
|
||||
{:on-long-press on-long-press}))
|
||||
[rn/view
|
||||
{:style (merge
|
||||
(style/shape-style-container type icon size)
|
||||
{:width width}
|
||||
style)}
|
||||
[rn/view
|
||||
{:style (merge
|
||||
(style/style-container {:type type
|
||||
:size size
|
||||
:disabled disabled
|
||||
:background-color
|
||||
(or override-background-color (get background-color state))
|
||||
:border-color
|
||||
(get border-color state)
|
||||
:icon icon
|
||||
:above above
|
||||
:width width
|
||||
:before before
|
||||
:after after
|
||||
:blur-active? blur-active?})
|
||||
(when (= state :pressed) {:opacity 0.9})
|
||||
inner-style)}
|
||||
(when (and (= type :blurred)
|
||||
blur-active?)
|
||||
[blur/view
|
||||
{:blur-radius 20
|
||||
:blur-type blur-type
|
||||
:overlay-color blur-overlay-color
|
||||
:style style/blur-view}])
|
||||
(when above
|
||||
[rn/view
|
||||
[quo2.icons/icon above
|
||||
{:container-style {:margin-bottom 2}
|
||||
:color icon-secondary-color
|
||||
:size icon-size}]])
|
||||
(when before
|
||||
[rn/view
|
||||
{:style (style/before-icon-style
|
||||
{:override-margins override-before-margins
|
||||
:size size
|
||||
:icon-container-size icon-container-size
|
||||
:icon-background-color (get icon-background-color blur-state)
|
||||
:icon-container-rounded? icon-container-rounded?
|
||||
:icon-size icon-size})}
|
||||
[quo2.icons/icon before
|
||||
{:color icon-secondary-color
|
||||
:size icon-size}]])
|
||||
[rn/view
|
||||
(cond
|
||||
(or icon icon-no-color)
|
||||
[quo2.icons/icon children
|
||||
{:color icon-color
|
||||
:no-color icon-no-color
|
||||
:size icon-size}]
|
||||
|
||||
(string? children)
|
||||
[text/text
|
||||
{:size (when (#{56 24} size) :paragraph-2)
|
||||
:weight :medium
|
||||
:number-of-lines 1
|
||||
:style {:color label-color}}
|
||||
|
||||
children]
|
||||
|
||||
(vector? children)
|
||||
children)]
|
||||
(when after
|
||||
[rn/view
|
||||
{:style (style/after-icon-style
|
||||
{:override-margins override-after-margins
|
||||
:size size
|
||||
:icon-container-size icon-container-size
|
||||
:icon-background-color (get icon-background-color blur-state)
|
||||
:icon-container-rounded? icon-container-rounded?
|
||||
:icon-size icon-size})}
|
||||
[quo2.icons/icon after
|
||||
{:no-color icon-secondary-no-color
|
||||
:color icon-secondary-color
|
||||
:size icon-size}]])]]]))))
|
||||
|
||||
(def button (theme/with-theme button-internal))
|
||||
@@ -62,7 +62,7 @@
|
||||
:customization-color customize jump-to and mention button color}"
|
||||
[_]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [type label on-press count customization-color style]}]
|
||||
(fn [{:keys [type label on-press customization-color style] :as args}]
|
||||
[rn/touchable-opacity
|
||||
{:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)
|
||||
@@ -102,6 +102,6 @@
|
||||
(case type
|
||||
:jump-to label
|
||||
:search-with-label label
|
||||
(:mention :notification-down :notification-up) (str count))])
|
||||
(:mention :notification-down :notification-up) (str (:count args)))])
|
||||
(when (#{:jump-to :notification-down :notification-up} type)
|
||||
[icon-view type])]])))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[quo2.theme :as theme]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.buttons.button :as button]))
|
||||
[quo2.components.buttons.button.view :as button]))
|
||||
|
||||
(def ^:private gradients
|
||||
{:light [(colors/alpha colors/neutral-40 0.05) (colors/alpha colors/neutral-40 0)]
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
(ns quo2.components.buttons.style)
|
||||
|
||||
(def blur-view
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom 0})
|
||||
|
||||
(defn before-icon-style
|
||||
[{:keys [override-margins size icon-container-size icon-background-color icon-container-rounded?
|
||||
icon-size]}]
|
||||
(merge
|
||||
{:margin-left (or (get override-margins :left)
|
||||
(if (= size 40) 12 8))
|
||||
:margin-right (or (get override-margins :right) 4)
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(when icon-container-size
|
||||
{:width icon-container-size
|
||||
:height icon-container-size})
|
||||
(when icon-background-color
|
||||
{:background-color icon-background-color})
|
||||
(when icon-container-rounded?
|
||||
{:border-radius (/ (or icon-container-size icon-size) 2)})))
|
||||
|
||||
(defn after-icon-style
|
||||
[{:keys [override-margins size icon-container-size icon-background-color icon-container-rounded?
|
||||
icon-size]}]
|
||||
(merge
|
||||
{:margin-left (or (get override-margins :left) 4)
|
||||
:margin-right (or (get override-margins :right)
|
||||
(if (= size 40) 12 8))
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(when icon-container-size
|
||||
{:width icon-container-size
|
||||
:height icon-container-size})
|
||||
(when icon-background-color
|
||||
{:background-color icon-background-color})
|
||||
(when icon-container-rounded?
|
||||
{:border-radius (/ (or icon-container-size icon-size) 2)})))
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo2.components.code.snippet
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.code.code.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns quo2.components.separator
|
||||
(ns quo2.components.common.separator.view
|
||||
(:require [quo2.foundations.colors :as quo2.colors]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
@@ -29,52 +29,6 @@
|
||||
unread-messages?
|
||||
[unread-grey-dot :unviewed-messages-public]))
|
||||
|
||||
(defn communities-list-view-item
|
||||
[props
|
||||
{:keys [name
|
||||
locked?
|
||||
status
|
||||
muted?
|
||||
unread-messages?
|
||||
unread-mentions-count
|
||||
community-icon
|
||||
tokens]}]
|
||||
[rn/view
|
||||
{:style (merge (style/community-card 16)
|
||||
{:margin-bottom 12})}
|
||||
[rn/touchable-highlight
|
||||
(merge {:style {:height 56
|
||||
:border-radius 16}}
|
||||
props)
|
||||
[rn/view {:style style/detail-container}
|
||||
[rn/view (style/list-info-container)
|
||||
[community-icon/community-icon
|
||||
{:images community-icon} 32]
|
||||
[rn/view
|
||||
{:flex 1
|
||||
:margin-horizontal 12}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:accessibility-label :community-name-text
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:style {:color (when muted?
|
||||
(colors/theme-colors
|
||||
colors/neutral-40
|
||||
colors/neutral-60))}}
|
||||
name]
|
||||
[community-view/community-stats-column
|
||||
{:type :list-view}]]
|
||||
(if (= status :gated)
|
||||
[community-view/permission-tag-container
|
||||
{:locked? locked?
|
||||
:tokens tokens}]
|
||||
[notification-view
|
||||
{:muted? muted?
|
||||
:unread-mentions-count unread-mentions-count
|
||||
:unread-messages? unread-messages?}])]]]])
|
||||
|
||||
(defn communities-membership-list-item
|
||||
[props
|
||||
{:keys [name
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
[quo2.components.tags.permission-tag :as permission]
|
||||
[quo2.components.tags.tag :as tag]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo.gesture-handler :as gesture-handler] ;;TODO move to quo2
|
||||
[react-native.core :as rn]))
|
||||
[quo2.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.gesture :as gesture]
|
||||
utils.money))
|
||||
|
||||
(defn community-stats
|
||||
[{:keys [icon members-count icon-color]}]
|
||||
[rn/view (style/stats-count-container)
|
||||
[rn/view {:margin-right 4}
|
||||
[{:keys [icon members-count icon-color accessibility-label]}]
|
||||
[rn/view
|
||||
{:accessibility-label accessibility-label
|
||||
:style (style/stats-count-container)}
|
||||
[rn/view {:margin-right 2}
|
||||
[icons/icon icon
|
||||
{:container-style {:align-items :center
|
||||
:justify-content :center}
|
||||
@@ -20,29 +24,32 @@
|
||||
:color icon-color}]]
|
||||
[text/text
|
||||
{:weight :regular
|
||||
:size :paragraph-1}
|
||||
:size :paragraph-2}
|
||||
members-count]])
|
||||
|
||||
(defn community-stats-column
|
||||
[{:keys [type]}]
|
||||
(let [icon-color (colors/theme-colors colors/neutral-50 colors/neutral-40)]
|
||||
[{:keys [type theme blur? members-count active-count]}]
|
||||
(let [icon-color (if (and (= :dark theme) blur?)
|
||||
colors/white-opa-40
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))]
|
||||
[rn/view
|
||||
(if (= type :card-view)
|
||||
(style/card-stats-container)
|
||||
(style/list-stats-container))
|
||||
[community-stats
|
||||
{:icon :i/group
|
||||
:members-count "629.2K" ;;TODO here should be formatted value, use money/format-members from
|
||||
;;outside this component
|
||||
:icon-color icon-color}]
|
||||
{:accessibility-label :stats-members-count
|
||||
:icon :i/group
|
||||
:members-count (utils.money/format-amount members-count)
|
||||
:icon-color icon-color}]
|
||||
[community-stats
|
||||
{:icon :i/lightning
|
||||
:members-count "112.1K"
|
||||
:icon-color icon-color}]]))
|
||||
{:accessibility-label :stats-active-count
|
||||
:icon :i/active-members
|
||||
:members-count (utils.money/format-amount active-count)
|
||||
:icon-color icon-color}]]))
|
||||
|
||||
(defn community-tags
|
||||
[{:keys [tags container-style last-item-style]}]
|
||||
[gesture-handler/scroll-view
|
||||
[gesture/scroll-view
|
||||
{:shows-horizontal-scroll-indicator false
|
||||
:horizontal true
|
||||
:style container-style}
|
||||
@@ -84,13 +91,16 @@
|
||||
:style {:margin-top (if (= size :large) 8 2)}}
|
||||
description])])
|
||||
|
||||
(defn permission-tag-container
|
||||
[{:keys [locked? tokens on-press]}]
|
||||
(defn- permission-tag-container-internal
|
||||
[{:keys [locked? blur? tokens on-press theme]}]
|
||||
[permission/tag
|
||||
{:background-color (colors/theme-colors
|
||||
colors/neutral-10
|
||||
colors/neutral-80)
|
||||
:locked? locked?
|
||||
:tokens tokens
|
||||
:size 24
|
||||
:on-press on-press}])
|
||||
{:accessibility-label :permission-tag
|
||||
:background-color (if (and (= :dark theme) blur?)
|
||||
colors/white-opa-10
|
||||
(colors/theme-colors colors/neutral-10 colors/neutral-80 theme))
|
||||
:locked? locked?
|
||||
:tokens tokens
|
||||
:size 24
|
||||
:on-press on-press}])
|
||||
|
||||
(def permission-tag-container (theme/with-theme permission-tag-container-internal))
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
:outline colors/neutral-70}})
|
||||
|
||||
(defn get-color
|
||||
[key]
|
||||
(get-in themes [(theme/get-theme) key]))
|
||||
[k]
|
||||
(get-in themes [(theme/get-theme) k]))
|
||||
|
||||
(defn counter
|
||||
[{:keys [type override-text-color override-bg-color style accessibility-label max-value]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.dividers.date
|
||||
(:require [quo2.components.markdown.text :as text]
|
||||
[quo2.components.separator :as separator]
|
||||
[quo2.components.common.separator.view :as separator]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns quo2.components.drawers.documentation-drawers.view
|
||||
(:require [quo2.components.buttons.button :as button]
|
||||
(:require [quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.drawers.documentation-drawers.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns quo2.components.dropdowns.dropdown
|
||||
(:require [quo2.components.buttons.button :as button]))
|
||||
(:require [quo2.components.buttons.button.view :as button]))
|
||||
|
||||
(defn dropdown
|
||||
[_ _]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns quo2.components.empty-state.empty-state.view
|
||||
(:require [quo2.components.buttons.button :as button]
|
||||
(:require [quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.empty-state.empty-state.styles :as styles]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.header
|
||||
(:require [oops.core :refer [oget]]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
:error colors/danger-60}})
|
||||
|
||||
(defn get-color
|
||||
[key]
|
||||
(get-in themes [(theme/get-theme) key]))
|
||||
[k]
|
||||
(get-in themes [(theme/get-theme) k]))
|
||||
|
||||
(defn info-message
|
||||
"[info-message opts \"message\"]
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
:close-button colors/white}})
|
||||
|
||||
(defn get-color
|
||||
[theme key]
|
||||
(get-in themes [theme key]))
|
||||
[theme k]
|
||||
(get-in themes [theme k]))
|
||||
|
||||
(defn get-color-by-type
|
||||
[theme type key]
|
||||
(get-in themes [theme type key]))
|
||||
[theme type k]
|
||||
(get-in themes [theme type k]))
|
||||
|
||||
(defn container
|
||||
[{:keys [theme type include-button?]}]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.info.information-box.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.info.information-box.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.inputs.profile-input.view
|
||||
(:require
|
||||
[quo2.components.buttons.button :as buttons]
|
||||
[quo2.components.buttons.button.view :as buttons]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.inputs.profile-input.style :as style]
|
||||
[quo2.components.inputs.title-input.view :as title-input]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns quo2.components.links.link-preview.view
|
||||
(:require [quo2.components.buttons.button :as button]
|
||||
(:require [quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.links.link-preview.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.list-items.channel
|
||||
(:require [quo2.components.avatars.channel-avatar :as channel-avatar]
|
||||
[quo2.components.common.unread-grey-dot.view :refer [unread-grey-dot]]
|
||||
(:require [quo2.components.avatars.channel-avatar.view :as channel-avatar]
|
||||
[quo2.components.common.unread-grey-dot.view :as unread-grey-dot]
|
||||
[quo2.components.counter.counter :as quo2.counter]
|
||||
[quo2.components.icon :as quo2.icons]
|
||||
[quo2.components.markdown.text :as quo2.text]
|
||||
@@ -13,10 +13,11 @@
|
||||
|
||||
(defn list-item
|
||||
[{:keys [locked? mentions-count unread-messages?
|
||||
muted? is-active-channel? emoji channel-color]
|
||||
:or {channel-color colors/primary-50}
|
||||
muted? is-active-channel? emoji channel-color
|
||||
default-color]
|
||||
:as props}]
|
||||
(let [standard-props (apply dissoc props custom-props)
|
||||
(let [channel-color (or channel-color default-color)
|
||||
standard-props (apply dissoc props custom-props)
|
||||
name-text (:name props)]
|
||||
[rn/touchable-opacity standard-props
|
||||
[rn/view
|
||||
@@ -36,11 +37,12 @@
|
||||
:align-items :center}
|
||||
:accessible true
|
||||
:accessibility-label :chat-name-text}
|
||||
[channel-avatar/channel-avatar
|
||||
{:big? true
|
||||
:locked? locked?
|
||||
:emoji-background-color (colors/theme-alpha channel-color 0.1 0.1)
|
||||
:emoji emoji}]
|
||||
[channel-avatar/view
|
||||
{:size :size/l
|
||||
:locked? locked?
|
||||
:full-name (:name props)
|
||||
:customization-color channel-color
|
||||
:emoji emoji}]
|
||||
[quo2.text/text
|
||||
{:style (cond-> {:margin-left 12}
|
||||
(and (not locked?) muted?)
|
||||
@@ -63,4 +65,4 @@
|
||||
mentions-count]]
|
||||
|
||||
unread-messages?
|
||||
[unread-grey-dot :unviewed-messages-public])])]]))
|
||||
[unread-grey-dot/unread-grey-dot :unviewed-messages-public])])]]))
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
(ns quo2.components.list-items.community.component-spec
|
||||
(:require [quo2.components.list-items.community.view :as component]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Community list"
|
||||
(h/test "default render"
|
||||
(h/render [component/view {}])
|
||||
(h/is-truthy (h/get-by-label-text :community-item-title))
|
||||
(h/is-null (h/query-by-label-text :community-item-info)))
|
||||
|
||||
(h/describe "type share"
|
||||
(h/test "renders"
|
||||
(h/render [component/view
|
||||
{:type :share
|
||||
:title "Title"
|
||||
:subtitle "Subtitle"}])
|
||||
(h/is-truthy (h/get-by-text "# Title"))
|
||||
(h/is-truthy (h/get-by-text "Subtitle"))
|
||||
(h/is-null (h/query-by-label-text :community-item-info))))
|
||||
|
||||
(h/describe "type engage"
|
||||
(h/test "default, no info"
|
||||
(h/render [component/view {:type :engage}])
|
||||
(h/is-null (h/query-by-label-text :community-item-info)))
|
||||
|
||||
(h/test "info is :navigation"
|
||||
(h/render [component/view {:type :engage :info :navigation}])
|
||||
(h/is-truthy (h/get-by-label-text :info-navigation)))
|
||||
|
||||
(h/test "info is :token-gated"
|
||||
(h/render [component/view {:type :engage :info :token-gated}])
|
||||
(h/is-truthy (h/get-by-label-text :info-token-gated)))
|
||||
|
||||
(h/test "info is :muted"
|
||||
(h/render [component/view {:type :engage :info :muted}])
|
||||
(h/is-truthy (h/get-by-label-text :info-muted)))
|
||||
|
||||
(h/test "info is :notification"
|
||||
(h/render [component/view {:type :engage :info :notification}])
|
||||
(h/is-truthy (h/get-by-label-text :info-notification-dot)))
|
||||
|
||||
(h/test "info is :mention"
|
||||
(h/render [component/view
|
||||
{:type :engage
|
||||
:info :mention
|
||||
:unread-count 9000}])
|
||||
(h/is-truthy (h/get-by-text "99+"))))
|
||||
|
||||
(h/describe "type discover"
|
||||
(h/test "default, no info, no member stats"
|
||||
(h/render [component/view {:type :discover}])
|
||||
(h/is-null (h/query-by-label-text :stats-members-count))
|
||||
(h/is-null (h/query-by-label-text :community-item-info)))
|
||||
|
||||
(h/test "info is :token-gated, show member stats"
|
||||
(let [on-press-info (h/mock-fn)]
|
||||
(h/render [component/view
|
||||
{:type :discover
|
||||
:info :token-gated
|
||||
:locked? true
|
||||
:blur? false
|
||||
:on-press-info on-press-info
|
||||
:members {:members-count 9876 :active-count 1350}
|
||||
:tokens [{:id 1 :group [{:id 1}]}]}])
|
||||
(h/is-truthy (h/get-by-label-text :community-item-info))
|
||||
(h/is-truthy (h/get-by-label-text :permission-tag))
|
||||
(h/is-truthy (h/get-by-label-text :stats-members-count))
|
||||
(h/is-truthy (h/get-by-label-text :stats-active-count))
|
||||
(h/fire-event :press (h/get-by-label-text :permission-tag))
|
||||
(h/was-called on-press-info)))))
|
||||
@@ -0,0 +1,65 @@
|
||||
(ns quo2.components.list-items.community.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[quo2.foundations.shadows :as shadows]))
|
||||
|
||||
(def logo
|
||||
{:border-radius 50
|
||||
:border-width 0
|
||||
:width 32
|
||||
:height 32
|
||||
:margin-right 10})
|
||||
|
||||
(defn notification-dot
|
||||
[blur? theme]
|
||||
{:width 8
|
||||
:height 8
|
||||
:border-radius 4
|
||||
:background-color (if (and (= :dark theme) blur?)
|
||||
colors/white-opa-40
|
||||
(colors/theme-colors colors/neutral-40 colors/neutral-60 theme))})
|
||||
|
||||
(defn title
|
||||
[{:keys [type info blur? theme]}]
|
||||
{:color (cond
|
||||
(and (= type :engage) (= info :muted))
|
||||
(colors/theme-colors colors/neutral-40 colors/neutral-60 theme)
|
||||
|
||||
(and (= type :engage) (= info :default) (not blur?))
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme)
|
||||
|
||||
(and (= type :engage) (= info :default) (= :dark theme) blur?)
|
||||
colors/white-70-blur
|
||||
|
||||
:else
|
||||
(colors/theme-colors colors/neutral-100 colors/white theme))})
|
||||
|
||||
(defn subtitle
|
||||
[blur? theme]
|
||||
{:color (if (and (= :dark theme) blur?)
|
||||
colors/white-opa-40
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))})
|
||||
|
||||
(defn container
|
||||
[{:keys [type pressed? blur? customization-color theme info]}]
|
||||
(merge {:padding-vertical 8
|
||||
:padding-horizontal 12
|
||||
:border-radius 12
|
||||
:flex-direction :row
|
||||
:align-items :center
|
||||
:background-color (cond
|
||||
(and pressed? (= type :engage) (= info :default) (= :dark theme) blur?)
|
||||
colors/white-opa-5
|
||||
|
||||
(and pressed? (#{:engage :share} type))
|
||||
(colors/theme-alpha customization-color 0.05 0.05)
|
||||
|
||||
(and (not pressed?) (= type :discover) (not blur?))
|
||||
(colors/theme-colors colors/white colors/neutral-90 theme)
|
||||
|
||||
(and (not pressed?) (= type :discover) (= :dark theme) blur?)
|
||||
colors/white-opa-5
|
||||
|
||||
(and pressed? (= type :discover))
|
||||
(colors/theme-colors colors/white :transparent theme))}
|
||||
(when (and (= type :discover) (not pressed?))
|
||||
(shadows/get 3))))
|
||||
@@ -0,0 +1,165 @@
|
||||
(ns quo2.components.list-items.community.view
|
||||
(:require [quo2.components.community.community-view :as community-view]
|
||||
[quo2.components.counter.counter :as counter]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.list-items.community.style :as style]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- logo-component
|
||||
[logo]
|
||||
[rn/image {:source logo :style style/logo}])
|
||||
|
||||
(defn- title-component
|
||||
[{:keys [title type] :as props}]
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:accessibility-label :community-item-title
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:style (style/title props)}
|
||||
(if (= type :share)
|
||||
(str "# " title)
|
||||
title)])
|
||||
|
||||
(defn- subtitle-component
|
||||
[subtitle blur? theme]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:number-of-lines 1
|
||||
:accessibility-label :community-item-subtitle
|
||||
:style (style/subtitle blur? theme)}
|
||||
subtitle])
|
||||
|
||||
(defn- notification-dot
|
||||
[blur? theme]
|
||||
[rn/view
|
||||
{:style (style/notification-dot blur? theme)
|
||||
:accessibility-label :info-notification-dot}])
|
||||
|
||||
(defn- info-component
|
||||
[{:keys [customization-color info type blur? locked? on-press-info theme tokens unread-count]}]
|
||||
(let [component
|
||||
(cond
|
||||
(and (= type :discover) (= info :token-gated) (seq tokens))
|
||||
[community-view/permission-tag-container
|
||||
{:locked? locked?
|
||||
:tokens tokens
|
||||
:on-press on-press-info
|
||||
:theme theme
|
||||
:blur? blur?}]
|
||||
|
||||
(and (= type :engage) (= info :mention) (pos? unread-count))
|
||||
[counter/counter
|
||||
{:type :default
|
||||
:override-bg-color customization-color}
|
||||
unread-count]
|
||||
|
||||
(and (= type :engage) (= info :notification))
|
||||
[notification-dot blur? theme]
|
||||
|
||||
(and (= type :engage) (= info :muted))
|
||||
[icons/icon :i/muted
|
||||
{:accessibility-label :info-muted
|
||||
:color (colors/theme-colors colors/neutral-40 colors/neutral-60 theme)}]
|
||||
|
||||
(and (= type :engage) (= info :token-gated))
|
||||
[icons/icon (if locked? :i/locked :i/unlocked)
|
||||
{:accessibility-label :info-token-gated
|
||||
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}]
|
||||
|
||||
(and (= type :engage) (= info :navigation))
|
||||
[icons/icon :i/chevron-right
|
||||
{:accessibility-label :info-navigation
|
||||
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}])]
|
||||
(when component
|
||||
[rn/view
|
||||
{:accessibility-label :community-item-info
|
||||
:style {:margin-left 10}}
|
||||
component])))
|
||||
|
||||
(defn- view-internal
|
||||
"Options:
|
||||
|
||||
:type - :discover/engage/share
|
||||
:info - keyword - Acceptable values vary based on the :type option.
|
||||
|
||||
:title - string
|
||||
:logo - image resource
|
||||
:theme - :light/dark
|
||||
|
||||
:container-style - style map - Override styles in top-level view component.
|
||||
|
||||
:members - {:members-count number, :active-count number} - When non-nil, the
|
||||
statistics component will be shown.
|
||||
|
||||
:tokens - A sequence of maps, e.g. [{:id 1 :group [{:id 1 :token-icon an-icon}]}].
|
||||
|
||||
:locked? - boolean - When true, the permission-tag icon or the token gated
|
||||
icon will appear as closed.
|
||||
|
||||
:blur? - boolean - It will be taken into account when true and dark mode is
|
||||
enabled.
|
||||
|
||||
:customization-color - color - It will be passed down to components that
|
||||
should vary based on a custom color.
|
||||
|
||||
:on-press/:on-long-press - fn - Used by the top-level pressable component.
|
||||
|
||||
:on-press-info - fn - Will be called when the info component is pressed.
|
||||
|
||||
:unread-count - number - When the info is :mention, it will be used to show
|
||||
the number of unread mentions.
|
||||
"
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [members type info tokens locked? title subtitle
|
||||
logo blur? customization-color
|
||||
on-press on-long-press on-press-info
|
||||
container-style unread-count theme]}]
|
||||
[rn/pressable
|
||||
{:accessibility-label :container
|
||||
:on-press-in (fn [] (reset! pressed? true))
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:on-press-out (fn [] (reset! pressed? false))
|
||||
:style (merge (style/container {:blur? blur?
|
||||
:customization-color customization-color
|
||||
:info info
|
||||
:type type
|
||||
:pressed? @pressed?
|
||||
:theme theme})
|
||||
container-style)}
|
||||
[logo-component logo]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[title-component
|
||||
{:blur? blur?
|
||||
:info info
|
||||
:theme theme
|
||||
:title title
|
||||
:type type}]
|
||||
(when (and (= type :share) subtitle)
|
||||
[subtitle-component subtitle blur? theme])
|
||||
(when (and members (= type :discover))
|
||||
[community-view/community-stats-column
|
||||
{:type :list-view
|
||||
:theme theme
|
||||
:blur? blur?
|
||||
:members-count (:members-count members)
|
||||
:active-count (:active-count members)}])]
|
||||
[info-component
|
||||
{:blur? blur?
|
||||
:customization-color customization-color
|
||||
:info info
|
||||
:type type
|
||||
:locked? locked?
|
||||
:on-press-info on-press-info
|
||||
:theme theme
|
||||
:tokens tokens
|
||||
:unread-count unread-count}]])))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
@@ -31,8 +31,8 @@
|
||||
|
||||
(defn- internal-view
|
||||
[{:keys [content theme blur? parent-height]}]
|
||||
(let [number-of-skeletons (int (Math/floor (/ parent-height
|
||||
(get-in constants/layout-dimensions [content :height]))))
|
||||
(let [skeleton-height (get-in constants/layout-dimensions [content :height])
|
||||
number-of-skeletons (int (Math/ceil (/ parent-height skeleton-height)))
|
||||
color (cond
|
||||
blur? colors/white-opa-5
|
||||
(= theme :dark) colors/neutral-90
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
:background colors/neutral-95}})
|
||||
|
||||
(defn get-color
|
||||
[key]
|
||||
(get-in themes [(theme/get-theme) key]))
|
||||
[k]
|
||||
(get-in themes [(theme/get-theme) k]))
|
||||
|
||||
(def ui-images
|
||||
{:light {:horizontal (js/require "../resources/images/ui/message-gap-hborder-light.png")
|
||||
@@ -28,8 +28,8 @@
|
||||
:circles (js/require "../resources/images/ui/message-gap-circle-bg-dark.png")}})
|
||||
|
||||
(defn get-image
|
||||
[key]
|
||||
(get-in ui-images [(theme/get-theme) key]))
|
||||
[k]
|
||||
(get-in ui-images [(theme/get-theme) k]))
|
||||
|
||||
;;; components
|
||||
;;;; borders
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
(defn dynamic-button-view
|
||||
[type dynamic-buttons style]
|
||||
(when-let [{:keys [count on-press customization-color label]} (get dynamic-buttons type)]
|
||||
(when-let [{:keys [on-press customization-color label] :as props} (get dynamic-buttons type)]
|
||||
[dynamic-button/dynamic-button
|
||||
{:type type
|
||||
:label label
|
||||
:on-press on-press
|
||||
:count count
|
||||
:count (:count props)
|
||||
:style style
|
||||
:customization-color customization-color}]))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo2.components.navigation.page-nav
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo2.components.notifications.activity-log.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.icon :as icon]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.notifications.activity-log.style :as style]
|
||||
@@ -150,8 +150,8 @@
|
||||
[activity-reply-text-input props reply-input])
|
||||
(when items
|
||||
[rn/view style/footer-container
|
||||
(for [{:keys [key] :as item} items]
|
||||
^{:key key}
|
||||
(for [item items]
|
||||
^{:key (:key item)}
|
||||
[footer-item-view item replying? reply-input])])])))
|
||||
|
||||
(defn view
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
:path-length path-length}))
|
||||
|
||||
(defn- linear-ease
|
||||
[time start goal duration]
|
||||
[input-time start goal duration]
|
||||
(if (zero? duration)
|
||||
start
|
||||
(-> time
|
||||
(-> input-time
|
||||
(/ duration)
|
||||
(* goal)
|
||||
(+ start))))
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn info-count
|
||||
([count]
|
||||
(info-count {} count))
|
||||
([props count]
|
||||
(when (> count 0)
|
||||
([amount]
|
||||
(info-count {} amount))
|
||||
([props amount]
|
||||
(when (> amount 0)
|
||||
[rn/view
|
||||
(merge props
|
||||
{:style (merge {:width 16
|
||||
@@ -23,4 +23,4 @@
|
||||
{:style (merge typography/font-medium
|
||||
typography/label
|
||||
{:color colors/white :text-align :center})}
|
||||
count]])))
|
||||
amount]])))
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
:background-color :transparent})
|
||||
|
||||
(defn content-container
|
||||
[override-theme]
|
||||
[theme]
|
||||
(merge
|
||||
(:shadow-1 shadows/normal-scale)
|
||||
{:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 override-theme)
|
||||
(shadows/get 1 theme)
|
||||
{:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 theme)
|
||||
:flex-direction :row
|
||||
:padding-vertical 8
|
||||
:padding-horizontal 12}))
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
:background-color :transparent})
|
||||
|
||||
(defn content-container
|
||||
[override-theme]
|
||||
[theme]
|
||||
(merge
|
||||
(:shadow-1 shadows/normal-scale)
|
||||
{:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 override-theme)
|
||||
(shadows/get 1 theme)
|
||||
{:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 theme)
|
||||
:flex-direction :row
|
||||
:justify-content :space-between
|
||||
:padding-vertical 8
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.hole-view :as hole-view]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.profile.profile-card.style :as style]
|
||||
[quo2.components.avatars.user-avatar.view :as user-avatar]))
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn :refer [use-effect]]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.record-audio.record-audio.helpers :as helpers]))
|
||||
|
||||
(defn f-record-button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.settings.accounts.view
|
||||
(:require [quo2.components.avatars.account-avatar :as account-avatar]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.settings.accounts.style :as style]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require [quo2.components.settings.settings-list.style :as style]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.selectors.selectors.view :as selectors]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.tags.status-tags :as status-tag]
|
||||
[quo2.foundations.colors :as colors]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[react-native.blur :as blur]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.buttons.button :as button]
|
||||
[quo2.components.buttons.button.view :as button]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn view
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
:margin-right 8})
|
||||
|
||||
(defn get-color-by-type
|
||||
[type key]
|
||||
(get-in themes [(theme/get-theme) type key]))
|
||||
[type k]
|
||||
(get-in themes [(theme/get-theme) type k]))
|
||||
|
||||
(defn account-selector
|
||||
"[account-selector opts]
|
||||
@@ -73,4 +73,3 @@
|
||||
:size :paragraph-1
|
||||
:style {:color account-text-color}}
|
||||
account-text]]]))
|
||||
|
||||
|
||||
@@ -132,13 +132,14 @@
|
||||
|
||||
(defn tag
|
||||
[_ _]
|
||||
(fn [{:keys [locked? tokens size background-color on-press]
|
||||
(fn [{:keys [locked? tokens size background-color on-press accessibility-label]
|
||||
:or {size 24}}]
|
||||
[base-tag/base-tag
|
||||
{:background-color background-color
|
||||
:size size
|
||||
:type :permission
|
||||
:on-press on-press}
|
||||
{:accessibility-label accessibility-label
|
||||
:background-color background-color
|
||||
:size size
|
||||
:type :permission
|
||||
:on-press on-press}
|
||||
[rn/view
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
|
||||
@@ -2,21 +2,24 @@
|
||||
(:refer-clojure :exclude [filter])
|
||||
(:require
|
||||
quo2.components.avatars.account-avatar
|
||||
quo2.components.avatars.channel-avatar
|
||||
quo2.components.avatars.channel-avatar.view
|
||||
quo2.components.avatars.group-avatar
|
||||
quo2.components.avatars.icon-avatar
|
||||
quo2.components.avatars.user-avatar.view
|
||||
quo2.components.avatars.wallet-user-avatar
|
||||
quo2.components.banners.banner.view
|
||||
quo2.components.buttons.button
|
||||
quo2.components.buttons.button.view
|
||||
quo2.components.buttons.dynamic-button
|
||||
quo2.components.buttons.predictive-keyboard.view
|
||||
quo2.components.buttons.slide-button.view
|
||||
quo2.components.code.snippet
|
||||
quo2.components.colors.color-picker.view
|
||||
quo2.components.common.separator.view
|
||||
quo2.components.community.banner.view
|
||||
quo2.components.community.channel-actions
|
||||
quo2.components.community.community-card-view
|
||||
quo2.components.community.community-list-view
|
||||
quo2.components.community.community-view
|
||||
quo2.components.community.banner.view
|
||||
quo2.components.community.icon
|
||||
quo2.components.community.token-gating
|
||||
quo2.components.counter.counter
|
||||
@@ -30,8 +33,8 @@
|
||||
quo2.components.drawers.drawer-buttons.view
|
||||
quo2.components.drawers.permission-context.view
|
||||
quo2.components.dropdowns.dropdown
|
||||
quo2.components.header
|
||||
quo2.components.empty-state.empty-state.view
|
||||
quo2.components.header
|
||||
quo2.components.icon
|
||||
quo2.components.info.info-message
|
||||
quo2.components.info.information-box.view
|
||||
@@ -41,10 +44,11 @@
|
||||
quo2.components.inputs.search-input.view
|
||||
quo2.components.inputs.title-input.view
|
||||
quo2.components.keycard.view
|
||||
quo2.components.links.link-preview.view
|
||||
quo2.components.links.url-preview-list.view
|
||||
quo2.components.links.url-preview.view
|
||||
quo2.components.links.link-preview.view
|
||||
quo2.components.list-items.channel
|
||||
quo2.components.list-items.community.view
|
||||
quo2.components.list-items.menu-item
|
||||
quo2.components.list-items.preview-list
|
||||
quo2.components.list-items.user-list
|
||||
@@ -68,15 +72,16 @@
|
||||
quo2.components.profile.profile-card.view
|
||||
quo2.components.profile.select-profile.view
|
||||
quo2.components.reactions.reaction
|
||||
quo2.components.selectors.reactions.view
|
||||
quo2.components.record-audio.record-audio.view
|
||||
quo2.components.record-audio.soundtrack.view
|
||||
quo2.components.selectors.disclaimer.view
|
||||
quo2.components.selectors.filter.view
|
||||
quo2.components.selectors.reactions.view
|
||||
quo2.components.selectors.selectors.view
|
||||
quo2.components.separator
|
||||
quo2.components.settings.accounts.view
|
||||
quo2.components.settings.privacy-option
|
||||
quo2.components.settings.reorder-item.view
|
||||
quo2.components.settings.settings-list.view
|
||||
quo2.components.share.qr-code.view
|
||||
quo2.components.share.share-qr-code.view
|
||||
quo2.components.tabs.account-selector
|
||||
@@ -88,13 +93,10 @@
|
||||
quo2.components.tags.tag
|
||||
quo2.components.tags.tags
|
||||
quo2.components.tags.token-tag
|
||||
quo2.components.text-combinations.title.view
|
||||
quo2.components.settings.settings-list.view
|
||||
quo2.components.settings.reorder-item.view
|
||||
quo2.components.community.channel-actions))
|
||||
quo2.components.text-combinations.title.view))
|
||||
|
||||
(def icon quo2.components.icon/icon)
|
||||
(def separator quo2.components.separator/separator)
|
||||
(def separator quo2.components.common.separator.view/separator)
|
||||
(def header quo2.components.header/header)
|
||||
(def dropdown quo2.components.dropdowns.dropdown/dropdown)
|
||||
(def info-message quo2.components.info.info-message/info-message)
|
||||
@@ -120,7 +122,7 @@
|
||||
|
||||
;;;; AVATAR
|
||||
(def account-avatar quo2.components.avatars.account-avatar/account-avatar)
|
||||
(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar)
|
||||
(def channel-avatar quo2.components.avatars.channel-avatar.view/view)
|
||||
(def group-avatar quo2.components.avatars.group-avatar/group-avatar)
|
||||
(def icon-avatar quo2.components.avatars.icon-avatar/icon-avatar)
|
||||
(def user-avatar quo2.components.avatars.user-avatar.view/user-avatar)
|
||||
@@ -130,11 +132,14 @@
|
||||
(def banner quo2.components.banners.banner.view/banner)
|
||||
|
||||
;;;; BUTTONS
|
||||
(def button quo2.components.buttons.button/button)
|
||||
(def button quo2.components.buttons.button.view/button)
|
||||
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
|
||||
(def predictive-keyboard quo2.components.buttons.predictive-keyboard.view/view)
|
||||
(def slide-button quo2.components.buttons.slide-button.view/view)
|
||||
|
||||
;;;; CODE
|
||||
(def snippet quo2.components.code.snippet/snippet)
|
||||
|
||||
;;;; CARDS
|
||||
(def small-option-card quo2.components.onboarding.small-option-card.view/small-option-card)
|
||||
(def keycard quo2.components.keycard.view/keycard)
|
||||
@@ -145,7 +150,6 @@
|
||||
|
||||
;;;; COMMUNITY
|
||||
(def community-card-view-item quo2.components.community.community-card-view/community-card-view-item)
|
||||
(def communities-list-view-item quo2.components.community.community-list-view/communities-list-view-item)
|
||||
(def communities-membership-list-item
|
||||
quo2.components.community.community-list-view/communities-membership-list-item)
|
||||
(def community-stats-column quo2.components.community.community-view/community-stats-column)
|
||||
@@ -189,6 +193,7 @@
|
||||
(def menu-item quo2.components.list-items.menu-item/menu-item)
|
||||
(def preview-list quo2.components.list-items.preview-list/preview-list)
|
||||
(def user-list quo2.components.list-items.user-list/user-list)
|
||||
(def community-list-item quo2.components.list-items.community.view/view)
|
||||
|
||||
;;;; LOADERS
|
||||
(def skeleton quo2.components.loaders.skeleton/skeleton)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require
|
||||
[quo2.components.avatars.user-avatar.component-spec]
|
||||
[quo2.components.banners.banner.component-spec]
|
||||
[quo2.components.buttons.--tests--.buttons-component-spec]
|
||||
[quo2.components.buttons.button.component-spec]
|
||||
[quo2.components.buttons.predictive-keyboard.component-spec]
|
||||
[quo2.components.buttons.slide-button.component-spec]
|
||||
[quo2.components.colors.color-picker.component-spec]
|
||||
@@ -22,6 +22,7 @@
|
||||
[quo2.components.links.link-preview.component-spec]
|
||||
[quo2.components.links.url-preview-list.component-spec]
|
||||
[quo2.components.links.url-preview.component-spec]
|
||||
[quo2.components.list-items.community.component-spec]
|
||||
[quo2.components.markdown.--tests--.text-component-spec]
|
||||
[quo2.components.markdown.list.component-spec]
|
||||
[quo2.components.notifications.notification.component-spec]
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
(def neutral-100-opa-5 (alpha neutral-100 0.05))
|
||||
(def neutral-100-opa-10 (alpha neutral-100 0.1))
|
||||
(def neutral-100-opa-30 (alpha neutral-100 0.3))
|
||||
(def neutral-100-opa-50 (alpha neutral-100 0.5))
|
||||
(def neutral-100-opa-60 (alpha neutral-100 0.6))
|
||||
(def neutral-100-opa-70 (alpha neutral-100 0.7))
|
||||
(def neutral-100-opa-80 (alpha neutral-100 0.8))
|
||||
@@ -245,7 +246,8 @@
|
||||
([color suffix opacity]
|
||||
(let [color-keyword (keyword color)
|
||||
base-color (get-in colors-map
|
||||
[color-keyword suffix])]
|
||||
[color-keyword suffix]
|
||||
color)]
|
||||
(if opacity (alpha base-color (/ opacity 100)) base-color))))))
|
||||
|
||||
(defn custom-color-by-theme
|
||||
|
||||
@@ -1,66 +1,77 @@
|
||||
(ns quo2.foundations.shadows
|
||||
(:refer-clojure :exclude [get])
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]))
|
||||
[quo2.theme :as quo.theme]))
|
||||
|
||||
(defn- get-inverted
|
||||
[inverted? number]
|
||||
(if inverted? (* -1 number) number))
|
||||
(def ^:private shadows
|
||||
(let [dark-normal {1 {:shadow-color (colors/alpha colors/neutral-100 0.5)
|
||||
:shadow-offset {:width 0 :height 4}
|
||||
:elevation 3
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20}
|
||||
2 {:shadow-color (colors/alpha colors/neutral-100 0.64)
|
||||
:shadow-offset {:width 0 :height 4}
|
||||
:elevation 4
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20}
|
||||
3 {:shadow-color (colors/alpha colors/neutral-100 0.64)
|
||||
:shadow-offset {:width 0 :height 12}
|
||||
:elevation 8
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20}
|
||||
4 {:shadow-color (colors/alpha colors/neutral-100 0.72)
|
||||
:shadow-offset {:width 0 :height 16}
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20
|
||||
:elevation 15}}
|
||||
dark-normal-inverted (-> dark-normal
|
||||
(update-in [:soft :shadow-offset :height] -)
|
||||
(update-in [:medium :shadow-offset :height] -)
|
||||
(update-in [:intense :shadow-offset :height] -)
|
||||
(update-in [:strong :shadow-offset :height] -))
|
||||
light-normal {1 {:shadow-color (colors/alpha colors/neutral-100 0.04)
|
||||
:shadow-offset {:width 0 :height 4}
|
||||
:elevation 1
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16}
|
||||
2 {:shadow-color (colors/alpha colors/neutral-100 0.08)
|
||||
:shadow-offset {:width 0 :height 4}
|
||||
:elevation 2
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16}
|
||||
3 {:shadow-color (colors/alpha colors/neutral-100 0.12)
|
||||
:shadow-offset {:width 0 :height 12}
|
||||
:elevation 5
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16}
|
||||
4 {:shadow-color (colors/alpha colors/neutral-100 0.16)
|
||||
:shadow-offset {:width 0 :height 16}
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16
|
||||
:elevation 13}}
|
||||
light-normal-inverted (-> light-normal
|
||||
(update-in [:soft :shadow-offset :height] -)
|
||||
(update-in [:medium :shadow-offset :height] -)
|
||||
(update-in [:intense :shadow-offset :height] -)
|
||||
(update-in [:strong :shadow-offset :height] -))]
|
||||
{:dark {:normal dark-normal :inverted dark-normal-inverted}
|
||||
:light {:normal light-normal :inverted light-normal-inverted}}))
|
||||
|
||||
(defn- get-scales
|
||||
[inverted?]
|
||||
(if (theme/dark?)
|
||||
{:shadow-1 {:shadow-color (colors/alpha colors/neutral-100 0.5)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 4)}
|
||||
:elevation 3
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20}
|
||||
:shadow-2 {:shadow-color (colors/alpha colors/neutral-100 0.64)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 4)}
|
||||
:elevation 4
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20}
|
||||
:shadow-3 {:shadow-color (colors/alpha colors/neutral-100 0.64)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 12)}
|
||||
:elevation 8
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20}
|
||||
:shadow-4 {:shadow-color (colors/alpha colors/neutral-100 0.72)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 16)}
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 20
|
||||
:elevation 15}}
|
||||
{:shadow-1 {:shadow-color (colors/alpha colors/neutral-100 0.04)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 4)}
|
||||
:elevation 1
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16}
|
||||
:shadow-2 {:shadow-color (colors/alpha colors/neutral-100 0.08)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 4)}
|
||||
:elevation 2
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16}
|
||||
:shadow-3 {:shadow-color (colors/alpha colors/neutral-100 0.12)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 12)}
|
||||
:elevation 5
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16}
|
||||
:shadow-4 {:shadow-color (colors/alpha colors/neutral-100 0.16)
|
||||
:shadow-offset {:width 0
|
||||
:height (get-inverted inverted? 16)}
|
||||
:shadow-opacity 1
|
||||
:shadow-radius 16
|
||||
:elevation 13}}))
|
||||
(defn get
|
||||
"Get the appropriate shadow map for a given shadow `weight`, `theme`, and `scale-type`.
|
||||
|
||||
(def normal-scale (get-scales false))
|
||||
Return nil if no shadow is found.
|
||||
|
||||
(def inverted-scale (get-scales true))
|
||||
`weight` - int (required) from 1 to 4.
|
||||
`theme` - :light/:dark (optional).
|
||||
`scale-type` - :normal/:inverted (optional).
|
||||
"
|
||||
([weight]
|
||||
(get weight (quo.theme/get-theme)))
|
||||
([weight theme]
|
||||
(get weight theme :normal))
|
||||
([weight theme scale-type]
|
||||
(get-in shadows [theme scale-type weight])))
|
||||
|
||||
(def inner-shadow
|
||||
{:shadow-color (colors/alpha colors/neutral-100 0.08)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
(def text (reagent/adapt-react-class (.-Text ^js react-native)))
|
||||
(def text-input (reagent/adapt-react-class (.-TextInput ^js react-native)))
|
||||
|
||||
(def pressable (reagent/adapt-react-class (.-Pressable ^js react-native)))
|
||||
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js react-native)))
|
||||
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js react-native)))
|
||||
(def touchable-without-feedback
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
(defn on-finalize [gesture handler] (.onFinalize ^js gesture handler))
|
||||
|
||||
(defn max-pointers [gesture count] (.maxPointers ^js gesture count))
|
||||
(defn max-pointers [gesture amount] (.maxPointers ^js gesture amount))
|
||||
|
||||
(defn min-distance [gesture dist] (.minDistance ^js gesture dist))
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
(defn hit-slop [gesture settings] (.hitSlop ^js gesture settings))
|
||||
|
||||
(defn number-of-taps [gesture count] (.numberOfTaps ^js gesture count))
|
||||
(defn number-of-taps [gesture amount] (.numberOfTaps ^js gesture amount))
|
||||
|
||||
(defn enabled [gesture enabled?] (.enabled ^js gesture enabled?))
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
(ns react-native.keychain
|
||||
(:require ["react-native-keychain" :as react-native-keychain]
|
||||
[clojure.string :as string]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
;; ********************************************************************************
|
||||
;; Storing / Retrieving a user password to/from Keychain
|
||||
;; ********************************************************************************
|
||||
;;
|
||||
;; We are using set/get/reset internet credentials there because they are bound
|
||||
;; to an address (`server`) property.
|
||||
|
||||
(defn enum-val
|
||||
[enum-name value-name]
|
||||
(get-in (js->clj ^js react-native-keychain) [enum-name value-name]))
|
||||
|
||||
;; We need a more strict access mode for keychain entries that save user password.
|
||||
;; iOS
|
||||
;; see this article for more details:
|
||||
;; https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility?language=objc
|
||||
(def keychain-restricted-availability
|
||||
;; From Apple's documentation:
|
||||
;; > The kSecAttrAccessible attribute enables you to control item availability
|
||||
;; > relative to the lock state of the device.
|
||||
;; > It also lets you specify eligibility for restoration to a new device.
|
||||
;; > If the attribute ends with the string ThisDeviceOnly,
|
||||
;; > the item can be restored to the same device that created a backup,
|
||||
;; > but it isn’t migrated when restoring another device’s backup data.
|
||||
;; > ...
|
||||
;; > For extremely sensitive data
|
||||
;; > THAT YOU NEVER WANT STORED IN iCloud,
|
||||
;; > you might choose kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly.
|
||||
;; That is exactly what we use there.
|
||||
;; Note that the password won't be stored if the device isn't locked by a passcode.
|
||||
#js {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")})
|
||||
|
||||
(def keychain-secure-hardware
|
||||
;; (Android) Requires storing the encryption key for the entry in secure hardware
|
||||
;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention)
|
||||
"SECURE_HARDWARE")
|
||||
|
||||
;; Android only
|
||||
(defn secure-hardware-available?
|
||||
[callback]
|
||||
(-> (.getSecurityLevel ^js react-native-keychain)
|
||||
(.then (fn [level] (callback (= level keychain-secure-hardware))))))
|
||||
|
||||
;; iOS only
|
||||
(defn device-encrypted?
|
||||
[callback]
|
||||
(-> (.canImplyAuthentication
|
||||
^js react-native-keychain
|
||||
(clj->js
|
||||
{:authenticationType
|
||||
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
|
||||
(.then callback)))
|
||||
|
||||
(defn save-credentials
|
||||
"Stores the credentials for the address to the Keychain"
|
||||
[server username password callback]
|
||||
(-> (.setInternetCredentials ^js react-native-keychain
|
||||
(string/lower-case server)
|
||||
username
|
||||
password
|
||||
keychain-secure-hardware
|
||||
keychain-restricted-availability)
|
||||
(.then callback)))
|
||||
|
||||
(defn get-credentials
|
||||
"Gets the credentials for a specified server from the Keychain"
|
||||
[server callback]
|
||||
(-> (.getInternetCredentials ^js react-native-keychain (string/lower-case server))
|
||||
(.then callback)))
|
||||
|
||||
(defn reset-credentials
|
||||
[server]
|
||||
(-> (.resetInternetCredentials ^js react-native-keychain (string/lower-case server))
|
||||
(.then #(when-not % (log/error (str "Error while clearing saved password."))))))
|
||||
@@ -0,0 +1,19 @@
|
||||
(ns react-native.touch-id
|
||||
(:require ["react-native-touch-id" :default touchid]))
|
||||
|
||||
;; currently, for android, react-native-touch-id
|
||||
;; is not returning supported biometric type
|
||||
;; defaulting to :fingerprint
|
||||
(def android-default-support :fingerprint)
|
||||
|
||||
(defn get-supported-type
|
||||
[callback]
|
||||
(-> (.isSupported ^js touchid)
|
||||
(.then #(callback (or (keyword %) android-default-support)))
|
||||
(.catch #(callback nil))))
|
||||
|
||||
(defn authenticate
|
||||
[{:keys [on-success on-fail reason options]}]
|
||||
(-> (.authenticate ^js touchid reason (clj->js options))
|
||||
(.then #(when on-success (on-success)))
|
||||
(.catch #(when on-fail (on-fail (aget % "code"))))))
|
||||
@@ -10,9 +10,9 @@
|
||||
(def key->string str)
|
||||
|
||||
(defn set-item!
|
||||
[key value]
|
||||
[k value]
|
||||
(-> ^js async-storage
|
||||
(.setItem (key->string key)
|
||||
(.setItem (key->string k)
|
||||
(clj->transit value))
|
||||
(.catch (fn [error]
|
||||
(log/error "[async-storage]" error)))))
|
||||
|
||||
@@ -619,3 +619,40 @@
|
||||
bookmarks)
|
||||
stored-bookmarks (get-in db [:bookmarks/bookmarks])]
|
||||
{:db (assoc-in db [:bookmarks/bookmarks] (merge stored-bookmarks changed-bookmarks))}))
|
||||
|
||||
(rf/defn initialize-dapp-permissions
|
||||
{:events [::initialize-dapp-permissions]}
|
||||
[{:keys [db]} all-dapp-permissions]
|
||||
(let [dapp-permissions (reduce (fn [acc {:keys [dapp] :as dapp-permissions}]
|
||||
(assoc acc dapp dapp-permissions))
|
||||
{}
|
||||
all-dapp-permissions)]
|
||||
{:db (assoc db :dapps/permissions dapp-permissions)}))
|
||||
|
||||
(rf/defn initialize-browsers
|
||||
{:events [::initialize-browsers]}
|
||||
[{:keys [db]} all-stored-browsers]
|
||||
(let [browsers (reduce (fn [acc {:keys [browser-id] :as browser}]
|
||||
(assoc acc browser-id browser))
|
||||
{}
|
||||
all-stored-browsers)]
|
||||
{:db (assoc db :browser/browsers browsers)}))
|
||||
|
||||
(rf/defn initialize-bookmarks
|
||||
{:events [::initialize-bookmarks]}
|
||||
[{:keys [db]} stored-bookmarks]
|
||||
(let [bookmarks (reduce (fn [acc {:keys [url] :as bookmark}]
|
||||
(assoc acc url bookmark))
|
||||
{}
|
||||
stored-bookmarks)]
|
||||
{:db (assoc db :bookmarks/bookmarks bookmarks)}))
|
||||
|
||||
(rf/defn initialize-browser
|
||||
[_]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_getBrowsers"
|
||||
:on-success #(re-frame/dispatch [::initialize-browsers %])}
|
||||
{:method "browsers_getBookmarks"
|
||||
:on-success #(re-frame/dispatch [::initialize-bookmarks %])}
|
||||
{:method "permissions_getDappPermissions"
|
||||
:on-success #(re-frame/dispatch [::initialize-dapp-permissions %])}]})
|
||||
|
||||
@@ -167,14 +167,16 @@
|
||||
{: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]}]]
|
||||
{:chat-id chat-id
|
||||
:album-id album-id
|
||||
:content-type constants/content-type-image
|
||||
:image-path (utils.string/safe-replace resized-uri #"file://" "")
|
||||
:image-width width
|
||||
:image-height height
|
||||
:text input-text
|
||||
:response-to message-id})
|
||||
{:chat-id chat-id
|
||||
:album-id album-id
|
||||
:content-type constants/content-type-image
|
||||
:image-path (utils.string/safe-replace resized-uri #"file://" "")
|
||||
:image-width width
|
||||
:image-height height
|
||||
:text input-text
|
||||
:link-previews (map #(select-keys % [:url :title :description :thumbnail])
|
||||
(get-in db [:chat/link-previews :unfurled]))
|
||||
:response-to message-id})
|
||||
images)))
|
||||
|
||||
(rf/defn clean-input
|
||||
|
||||
@@ -284,9 +284,10 @@
|
||||
:params [community-id]
|
||||
:js-response true
|
||||
:on-success #(re-frame/dispatch [::left %])
|
||||
:on-error (fn []
|
||||
:on-error (fn [response]
|
||||
(log/error "failed to leave community"
|
||||
community-id)
|
||||
community-id
|
||||
response)
|
||||
(re-frame/dispatch [::failed-to-leave]))}]}))
|
||||
|
||||
(rf/defn status-tag-pressed
|
||||
@@ -833,12 +834,17 @@
|
||||
:cb #(re-frame/dispatch
|
||||
[::category-states-loaded community-id hashes %])}}))
|
||||
|
||||
;; Note - dispatch is used to make sure we are opening community once `pop-to-root` is processed.
|
||||
;; Don't directly merge effects using `navigation/navigate-to`, because it will work in debug and
|
||||
;; release, but for e2e `pop-to-root` closes even currently opened community
|
||||
;; https://github.com/status-im/status-mobile/pull/16438#issuecomment-1623954774
|
||||
(rf/defn navigate-to-community
|
||||
{:events [:communities/navigate-to-community]}
|
||||
[cofx community-id]
|
||||
(rf/merge cofx
|
||||
(navigation/pop-to-root :shell-stack)
|
||||
(navigation/navigate-to :community-overview community-id)))
|
||||
(rf/merge
|
||||
cofx
|
||||
{:dispatch [:navigate-to :community-overview community-id]}
|
||||
(navigation/pop-to-root :shell-stack)))
|
||||
|
||||
(rf/defn member-role-updated
|
||||
{:events [:community.member/role-updated]}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
rpc->type
|
||||
unmarshal-members))
|
||||
|
||||
(rf/defn fetch-chats-rpc
|
||||
(rf/defn fetch-chats-preview
|
||||
[_ {:keys [on-success]}]
|
||||
{:json-rpc/call [{:method "wakuext_chatsPreview"
|
||||
:params []
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
(ns status-im.data-store.settings
|
||||
(:require [status-im.data-store.visibility-status-updates :as visibility-status-updates]
|
||||
[status-im.ethereum.eip55 :as eip55]
|
||||
[status-im2.config :as config]))
|
||||
[status-im2.config :as config]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn rpc->networks
|
||||
[networks]
|
||||
@@ -33,27 +34,19 @@
|
||||
{}
|
||||
custom-bootnodes))
|
||||
|
||||
(defn rpc->stickers-packs
|
||||
[stickers-packs]
|
||||
(reduce-kv (fn [acc pack-id stickers-pack]
|
||||
(assoc acc (js/parseInt (name pack-id)) stickers-pack))
|
||||
{}
|
||||
stickers-packs))
|
||||
|
||||
(defn rpc->settings
|
||||
[settings]
|
||||
(-> settings
|
||||
(update :dapps-address eip55/address->checksum)
|
||||
(update :address eip55/address->checksum)
|
||||
(update :networks/networks rpc->networks)
|
||||
(update :networks/current-network
|
||||
#(if (seq %)
|
||||
%
|
||||
config/default-network))
|
||||
(update :networks/current-network #(if (seq %) % config/default-network))
|
||||
(update :wallet/visible-tokens rpc->visible-tokens)
|
||||
(update :pinned-mailservers rpc->pinned-mailservers)
|
||||
(update :link-previews-enabled-sites set)
|
||||
(update :custom-bootnodes rpc->custom-bootnodes)
|
||||
(update :custom-bootnodes-enabled? rpc->custom-bootnodes)
|
||||
(update :currency keyword)
|
||||
(visibility-status-updates/<-rpc-settings)))
|
||||
(visibility-status-updates/<-rpc-settings)
|
||||
(set/rename-keys {:compressedKey :compressed-key
|
||||
:emojiHash :emoji-hash})))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns status-im.ethereum.decode
|
||||
(:require [status-im.utils.money :as money]))
|
||||
(:require [utils.money :as money]))
|
||||
|
||||
(defn uint
|
||||
[hex]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.ethereum.ens :as ens]
|
||||
[status-im.ethereum.tokens :as tokens]
|
||||
[status-im.utils.money :as money]))
|
||||
[utils.money :as money]))
|
||||
|
||||
(def scheme "ethereum")
|
||||
(def scheme-separator ":")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns status-im.ethereum.eip681-test
|
||||
(:require [cljs.test :refer-macros [deftest is] :as test]
|
||||
[status-im.ethereum.eip681 :as eip681]
|
||||
[status-im.utils.money :as money]))
|
||||
[utils.money :as money]))
|
||||
|
||||
(deftest parse-uri
|
||||
(is (= nil (eip681/parse-uri nil)))
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
status-im.log-level.core
|
||||
status-im.mailserver.constants
|
||||
[status-im.mailserver.core :as mailserver]
|
||||
[status-im.multiaccounts.biometric.core :as biometric]
|
||||
status-im.multiaccounts.login.core
|
||||
status-im.multiaccounts.logout.core
|
||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||
@@ -63,7 +62,8 @@
|
||||
[react-native.platform :as platform]
|
||||
status-im2.contexts.chat.home.events
|
||||
status-im2.contexts.communities.home.events
|
||||
status-im.ui.components.invite.events))
|
||||
status-im.ui.components.invite.events
|
||||
[status-im2.common.biometric.events :as biometric]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:dismiss-keyboard
|
||||
@@ -124,33 +124,26 @@
|
||||
[(get-in db [:profile/profile :appearance])
|
||||
(:view-id db) true]})))
|
||||
|
||||
(def authentication-options
|
||||
{:reason (i18n/label :t/biometric-auth-reason-login)})
|
||||
|
||||
(defn- on-biometric-auth-result
|
||||
[{:keys [bioauth-success bioauth-code bioauth-message]}]
|
||||
(when-not bioauth-success
|
||||
(if (= bioauth-code "USER_FALLBACK")
|
||||
(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
|
||||
(utils/show-confirmation
|
||||
{:title (i18n/label :t/biometric-auth-confirm-title)
|
||||
:content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message))
|
||||
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
|
||||
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout)
|
||||
:on-accept #(biometric/authenticate nil
|
||||
on-biometric-auth-result
|
||||
authentication-options)
|
||||
:on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])}))))
|
||||
(defn- on-biometric-auth-fail
|
||||
[{:keys [code]}]
|
||||
(if (= code "USER_FALLBACK")
|
||||
(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
|
||||
(utils/show-confirmation
|
||||
{:title (i18n/label :t/biometric-auth-confirm-title)
|
||||
:content (i18n/label :t/biometric-auth-confirm-message)
|
||||
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
|
||||
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout)
|
||||
:on-accept #(biometric/authenticate nil {:on-fail on-biometric-auth-fail})
|
||||
:on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])})))
|
||||
|
||||
(rf/defn on-return-from-background
|
||||
[{:keys [db now] :as cofx}]
|
||||
(let [new-account? (get db :onboarding-2/new-account?)
|
||||
app-in-background-since (get db :app-in-background-since)
|
||||
signed-up? (get-in db [:profile/profile :signed-up?])
|
||||
biometric-auth? (= (:auth-method db) "biometric")
|
||||
requires-bio-auth (and
|
||||
signed-up?
|
||||
biometric-auth?
|
||||
(= (:auth-method db) "biometric")
|
||||
(some? app-in-background-since)
|
||||
(>= (- now app-in-background-since)
|
||||
constants/ms-in-bg-for-require-bioauth))]
|
||||
@@ -163,7 +156,7 @@
|
||||
#(when-let [chat-id (:current-chat-id db)]
|
||||
{:dispatch [:chat/mark-all-as-read chat-id]})
|
||||
#(when requires-bio-auth
|
||||
(biometric/authenticate % on-biometric-auth-result authentication-options)))))
|
||||
(biometric/authenticate % {:on-fail on-biometric-auth-fail})))))
|
||||
|
||||
(rf/defn on-going-in-background
|
||||
[{:keys [db now]}]
|
||||
@@ -205,16 +198,8 @@
|
||||
:key-uid (get-in cofx [:db :profile/profile :key-uid])}
|
||||
:db (assoc db :screens/was-focused-once? true)}
|
||||
|
||||
(= :login view-id)
|
||||
{}
|
||||
|
||||
(not (get db :screens/was-focused-once?))
|
||||
{:db (assoc db :screens/was-focused-once? true)}
|
||||
|
||||
:else
|
||||
{::async-storage/set! {:chat-id nil
|
||||
:key-uid nil}
|
||||
:db (assoc db :screens/was-focused-once? true)})
|
||||
{:db (assoc db :screens/was-focused-once? true)})
|
||||
#(case view-id
|
||||
:keycard-settings (keycard/settings-screen-did-load %)
|
||||
:reset-card (keycard/reset-card-screen-did-load %)
|
||||
@@ -274,53 +259,3 @@
|
||||
cofx
|
||||
(navigation/open-modal :buy-crypto nil)
|
||||
(wallet/keep-watching-history)))
|
||||
|
||||
;; Information Box
|
||||
|
||||
(def closable-information-boxes
|
||||
"[{:id information box id
|
||||
:global? true/false (close information box across all profiles)}]"
|
||||
[])
|
||||
|
||||
(defn information-box-id-hash
|
||||
[id public-key global?]
|
||||
(if global?
|
||||
(hash id)
|
||||
(hash (str public-key id))))
|
||||
|
||||
(rf/defn close-information-box
|
||||
{:events [:close-information-box]}
|
||||
[{:keys [db]} id global?]
|
||||
(let [public-key (get-in db [:profile/profile :public-key])
|
||||
hash (information-box-id-hash id public-key global?)]
|
||||
{::async-storage/set! {hash true}
|
||||
:db (assoc-in db [:information-box-states id] true)}))
|
||||
|
||||
(rf/defn information-box-states-loaded
|
||||
{:events [:information-box-states-loaded]}
|
||||
[{:keys [db]} hashes states]
|
||||
{:db (assoc db
|
||||
:information-box-states
|
||||
(reduce
|
||||
(fn [acc [id hash]]
|
||||
(assoc acc id (get states hash)))
|
||||
{}
|
||||
hashes))})
|
||||
|
||||
(rf/defn load-information-box-states
|
||||
{:events [:load-information-box-states]}
|
||||
[{:keys [db]}]
|
||||
(let [public-key (get-in db [:profile/profile :public-key])
|
||||
{:keys [keys hashes]} (reduce (fn [acc {:keys [id global?]}]
|
||||
(let [hash (information-box-id-hash
|
||||
id
|
||||
public-key
|
||||
global?)]
|
||||
(-> acc
|
||||
(assoc-in [:hashes id] hash)
|
||||
(update :keys #(conj % hash)))))
|
||||
{}
|
||||
closable-information-boxes)]
|
||||
{::async-storage/get {:keys keys
|
||||
:cb #(re-frame/dispatch
|
||||
[:information-box-states-loaded hashes %])}}))
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.shell.activity-center.events :as activity-center]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.data-store.invitations :as data-store.invitations]))
|
||||
|
||||
(rf/defn navigate-chat-updated
|
||||
{:events [:navigate-chat-updated]}
|
||||
@@ -258,3 +259,19 @@
|
||||
:on-accept #(do
|
||||
(re-frame/dispatch [:bottom-sheet/hide-old])
|
||||
(re-frame/dispatch [:group-chats.ui/leave-chat-confirmed chat-id]))}}))
|
||||
|
||||
(rf/defn initialize-invitations
|
||||
{:events [::initialize-invitations]}
|
||||
[{:keys [db]} invitations]
|
||||
{:db (assoc db
|
||||
:group-chat/invitations
|
||||
(reduce (fn [acc {:keys [id] :as inv}]
|
||||
(assoc acc id (data-store.invitations/<-rpc inv)))
|
||||
{}
|
||||
invitations))})
|
||||
|
||||
(rf/defn get-group-chat-invitations
|
||||
[_]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_getGroupChatInvitations"
|
||||
:on-success #(re-frame/dispatch [::initialize-invitations %])}]})
|
||||
|
||||
@@ -26,13 +26,10 @@
|
||||
[]
|
||||
(rf/dispatch [:app-started]))
|
||||
|
||||
(defn generate-and-derive-addresses!
|
||||
[]
|
||||
(rf/dispatch [:generate-and-derive-addresses]))
|
||||
|
||||
(defn create-multiaccount!
|
||||
[]
|
||||
(rf/dispatch [:create-multiaccount password]))
|
||||
(rf/dispatch [:profile.create/create-and-login
|
||||
{:display-name account-name :password password :color "blue"}]))
|
||||
|
||||
(defn assert-app-initialized
|
||||
[]
|
||||
@@ -82,15 +79,12 @@
|
||||
(initialize-app!) ; initialize app
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!) ; generate 5 new keys
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))
|
||||
|
||||
(deftest create-community-test
|
||||
(log/info "====== create-community-test ==================")
|
||||
@@ -98,22 +92,19 @@
|
||||
(initialize-app!) ; initialize app
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!) ; generate 5 new keys
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success]
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:legacy-only-for-e2e/open-create-community])
|
||||
(doseq [[k v] (dissoc community :membership)]
|
||||
(rf/dispatch-sync [:status-im.communities.core/create-field k v]))
|
||||
(rf/dispatch [:status-im.communities.core/create-confirmation-pressed])
|
||||
(rf-test/wait-for
|
||||
[:status-im.communities.core/community-created]
|
||||
(assert-community-created)
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:legacy-only-for-e2e/open-create-community])
|
||||
(doseq [[k v] (dissoc community :membership)]
|
||||
(rf/dispatch-sync [:status-im.communities.core/create-field k v]))
|
||||
(rf/dispatch [:status-im.communities.core/create-confirmation-pressed])
|
||||
(rf-test/wait-for
|
||||
[:status-im.communities.core/community-created]
|
||||
(assert-community-created)
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest create-wallet-account-test
|
||||
(log/info "====== create-wallet-account-test ==================")
|
||||
@@ -121,19 +112,16 @@
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!) ; generate 5 new keys
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success]
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(create-new-account!) ; create a new account
|
||||
(rf-test/wait-for
|
||||
[:wallet.accounts/account-stored]
|
||||
(assert-new-account-created) ; assert account was created
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))
|
||||
(create-multiaccount!) ; create a multiaccount
|
||||
(rf-test/wait-for ; wait for login
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(create-new-account!) ; create a new account
|
||||
(rf-test/wait-for
|
||||
[:wallet.accounts/account-stored]
|
||||
(assert-new-account-created) ; assert account was created
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest back-up-seed-phrase-test
|
||||
(log/info "========= back-up-seed-phrase-test ==================")
|
||||
@@ -141,30 +129,27 @@
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!)
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success]
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :step] :12-words]) ; display seed phrase to user
|
||||
(rf/dispatch-sync [:my-profile/enter-two-random-words]) ; begin prompting user for seed words
|
||||
(let [{:keys [mnemonic]} @(rf/subscribe [:profile/profile])
|
||||
seed @(rf/subscribe [:my-profile/seed])
|
||||
word1 (second (:first-word seed))
|
||||
word2 (second (:second-word seed))]
|
||||
(is (= 12 (count (string/split mnemonic #" ")))) ; assert 12-word seed phrase
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :word] word1])
|
||||
(rf/dispatch-sync [:my-profile/set-step :second-word])
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :word] word2])
|
||||
;; TODO: refactor (defn next-handler) & (defn enter-word) to improve test coverage?
|
||||
(rf/dispatch [:my-profile/finish])
|
||||
(rf-test/wait-for
|
||||
[:my-profile/finish-success]
|
||||
(is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))))
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :step] :12-words]) ; display seed phrase to user
|
||||
(rf/dispatch-sync [:my-profile/enter-two-random-words]) ; begin prompting user for seed words
|
||||
(let [{:keys [mnemonic]} @(rf/subscribe [:profile/profile])
|
||||
seed @(rf/subscribe [:my-profile/seed])
|
||||
word1 (second (:first-word seed))
|
||||
word2 (second (:second-word seed))]
|
||||
(is (= 12 (count (string/split mnemonic #" ")))) ; assert 12-word seed phrase
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :word] word1])
|
||||
(rf/dispatch-sync [:my-profile/set-step :second-word])
|
||||
(rf/dispatch-sync [:set-in [:my-profile/seed :word] word2])
|
||||
;; TODO: refactor (defn next-handler) & (defn enter-word) to improve test coverage?
|
||||
(rf/dispatch [:my-profile/finish])
|
||||
(rf-test/wait-for
|
||||
[:my-profile/finish-success]
|
||||
(is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))
|
||||
|
||||
(def multiaccount-name "Narrow Frail Lemming")
|
||||
(def multiaccount-mnemonic
|
||||
@@ -180,20 +165,17 @@
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!)
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
|
||||
(create-multiaccount!)
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest delete-chat-test
|
||||
(log/info "========= delete-chat-test ==================")
|
||||
@@ -201,23 +183,20 @@
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!)
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
|
||||
(create-multiaccount!)
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id])
|
||||
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id])
|
||||
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))
|
||||
|
||||
(deftest mute-chat-test
|
||||
(log/info "========= mute-chat-test ==================")
|
||||
@@ -225,29 +204,26 @@
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!)
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
|
||||
(create-multiaccount!)
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id true constants/mute-till-unmuted])
|
||||
(rf-test/wait-for
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id true constants/mute-till-unmuted])
|
||||
[:chat/mute-successfully]
|
||||
(is @(rf/subscribe [:chats/muted chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id false])
|
||||
(rf-test/wait-for
|
||||
[:chat/mute-successfully]
|
||||
(is @(rf/subscribe [:chats/muted chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id false])
|
||||
(rf-test/wait-for
|
||||
[:chat/mute-successfully]
|
||||
(is (not @(rf/subscribe [:chats/muted chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))))
|
||||
(is (not @(rf/subscribe [:chats/muted chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))))
|
||||
|
||||
(deftest add-contact-test
|
||||
(log/info "========= add-contact-test ==================")
|
||||
@@ -260,27 +236,24 @@
|
||||
(initialize-app!)
|
||||
(rf-test/wait-for
|
||||
[:profile/get-profiles-overview-success]
|
||||
(generate-and-derive-addresses!)
|
||||
(create-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[:multiaccount-generate-and-derive-addresses-success]
|
||||
(create-multiaccount!)
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
;; search for contact using compressed key
|
||||
(rf/dispatch [:contacts/set-new-identity compressed-key])
|
||||
(rf-test/wait-for
|
||||
[::transport/messenger-started]
|
||||
(assert-messenger-started)
|
||||
;; search for contact using compressed key
|
||||
(rf/dispatch [:contacts/set-new-identity compressed-key])
|
||||
[:contacts/set-new-identity-success]
|
||||
(let [new-identity @(rf/subscribe [:contacts/new-identity])]
|
||||
(is (= public-key (:public-key new-identity)))
|
||||
(is (= :valid (:state new-identity))))
|
||||
;; click 'view profile' button
|
||||
(rf/dispatch [:chat.ui/show-profile public-key])
|
||||
(rf-test/wait-for
|
||||
[:contacts/set-new-identity-success]
|
||||
(let [new-identity @(rf/subscribe [:contacts/new-identity])]
|
||||
(is (= public-key (:public-key new-identity)))
|
||||
(is (= :valid (:state new-identity))))
|
||||
;; click 'view profile' button
|
||||
(rf/dispatch [:chat.ui/show-profile public-key])
|
||||
[:contacts/build-contact]
|
||||
(rf-test/wait-for
|
||||
[:contacts/build-contact]
|
||||
(rf-test/wait-for
|
||||
[:contacts/contact-built]
|
||||
(let [contact @(rf/subscribe [:contacts/current-contact])]
|
||||
(is (= three-words-name (:primary-name contact))))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method]))))))))))
|
||||
[:contacts/contact-built]
|
||||
(let [contact @(rf/subscribe [:contacts/current-contact])]
|
||||
(is (= three-words-name (:primary-name contact))))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method])))))))))
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
[status-im.keycard.sign :as sign]
|
||||
status-im.keycard.unpair
|
||||
[status-im.keycard.wallet :as wallet]
|
||||
[status-im.multiaccounts.recover.core :as multiaccounts.recover]
|
||||
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.datetime :as datetime]
|
||||
@@ -589,7 +588,8 @@
|
||||
(when (and (= card-state :profile/profile)
|
||||
(= flow :import))
|
||||
(if (common/find-multiaccount-by-key-uid db key-uid)
|
||||
(multiaccounts.recover/show-existing-multiaccount-alert key-uid)
|
||||
;; reimplement
|
||||
;;(multiaccounts.recover/show-existing-multiaccount-alert key-uid)
|
||||
(if paired?
|
||||
(load-recovery-pin-screen)
|
||||
(recovery/load-pair-screen))))
|
||||
@@ -681,3 +681,11 @@
|
||||
{:events [:keycard.callback/stop-nfc-failure]}
|
||||
[{:keys [db]} _]
|
||||
(log/debug "[keycard] nfc failed stopping")) ;; leave current value on :nfc-running
|
||||
|
||||
(rf/defn init
|
||||
{:events [:keycard/init]}
|
||||
[_]
|
||||
{:keycard/register-card-events nil
|
||||
:keycard/check-nfc-support nil
|
||||
:keycard/check-nfc-enabled nil
|
||||
:keycard/retrieve-pairings nil})
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
{:db (-> db
|
||||
(update :keycard dissoc :flow)
|
||||
(dissoc :restored-account?))}
|
||||
(multiaccounts.create/prepare-intro-wizard)
|
||||
(if (pos? (count accs))
|
||||
(navigation/navigate-to :get-your-keys nil)
|
||||
(navigation/set-stack-root :onboarding [:get-your-keys])))))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.keycard.common :as common]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.money :as money]
|
||||
[utils.money :as money]
|
||||
[status-im.utils.types :as types]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
{:db (assoc-in db [:signing/sign :keycard-step] :signing)}
|
||||
(common/set-on-card-connected :keycard/sign))
|
||||
|
||||
(pos? keycard-pin-retries) ; if 0, get-application-info will have already closed the connection
|
||||
; sheet and opened the frozen card popup
|
||||
(pos? keycard-pin-retries) ; if 0, get-application-info will have already closed the
|
||||
; connection sheet and opened the frozen card popup
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :card-read-in-progress?] true)
|
||||
(assoc-in [:keycard :pin :status] :verifying))
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
(ns status-im.multiaccounts.biometric.core
|
||||
(:require ["react-native-touch-id" :default touchid]
|
||||
[quo.design-system.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[utils.i18n :as i18n]
|
||||
[native-module.core :as native-module]
|
||||
[status-im.popover.core :as popover]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.keychain.core :as keychain]
|
||||
[status-im.utils.platform :as platform]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
;; currently, for android, react-native-touch-id
|
||||
;; is not returning supported biometric type
|
||||
;; defaulting to :fingerprint
|
||||
(def android-default-support :fingerprint)
|
||||
|
||||
;;; android blacklist based on device info:
|
||||
|
||||
(def deviceinfo (native-module/get-device-model-info))
|
||||
|
||||
;; {:model ?
|
||||
;; :brand "Xiaomi"
|
||||
;; :build-id "13D15"
|
||||
;; :device-id "goldfish"
|
||||
;; more info on https://github.com/react-native-community/react-native-device-info
|
||||
|
||||
(def android-device-blacklisted?
|
||||
(cond
|
||||
(= (:brand deviceinfo) "bannedbrand") true
|
||||
:else false))
|
||||
|
||||
;; biometric auth config
|
||||
;; https://github.com/naoufal/react-native-touch-id#authenticatereason-config
|
||||
(defn- authenticate-options
|
||||
[ios-fallback-label]
|
||||
(clj->js (merge
|
||||
{:unifiedErrors true}
|
||||
(when platform/ios?
|
||||
{:passcodeFallback false
|
||||
:fallbackLabel (or ios-fallback-label "")})
|
||||
(when platform/android?
|
||||
{:title (i18n/label :t/biometric-auth-android-title)
|
||||
:imageColor colors/blue
|
||||
:imageErrorColor colors/red
|
||||
:sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc)
|
||||
:sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc)
|
||||
:cancelText (i18n/label :cancel)}))))
|
||||
|
||||
(defn get-label
|
||||
[supported-biometric-auth]
|
||||
(case supported-biometric-auth
|
||||
:fingerprint (i18n/label :t/biometric-fingerprint)
|
||||
:FaceID (i18n/label :t/biometric-faceid)
|
||||
(i18n/label :t/biometric-touchid)))
|
||||
|
||||
(defn- get-error-message
|
||||
"must return an error message for the user"
|
||||
[touchid-error-code]
|
||||
(cond
|
||||
;; no message if user canceled or falled back to password
|
||||
(= touchid-error-code "USER_CANCELED") nil
|
||||
(= touchid-error-code "USER_FALLBACK") nil
|
||||
;; add here more specific errors if needed
|
||||
;; https://github.com/naoufal/react-native-touch-id#unified-errors
|
||||
:else (i18n/label :t/biometric-auth-error
|
||||
{:code touchid-error-code})))
|
||||
|
||||
(def success-result
|
||||
{:bioauth-success true})
|
||||
|
||||
(defn- generate-error-result
|
||||
[touchid-error-obj]
|
||||
(let [code (aget touchid-error-obj "code")]
|
||||
{:bioauth-success false
|
||||
:bioauth-code code
|
||||
:bioauth-message (get-error-message code)}))
|
||||
|
||||
(defn- do-get-supported
|
||||
[callback]
|
||||
(-> (.isSupported touchid)
|
||||
(.then #(callback (or (keyword %) android-default-support)))
|
||||
(.catch #(callback nil))))
|
||||
|
||||
(defn get-supported
|
||||
[callback]
|
||||
(log/debug "[biometric] get-supported")
|
||||
(cond platform/ios? (do-get-supported callback)
|
||||
platform/android? (if android-device-blacklisted?
|
||||
(callback nil)
|
||||
(do-get-supported callback))
|
||||
:else (callback nil)))
|
||||
|
||||
(defn authenticate-fx
|
||||
([cb]
|
||||
(authenticate-fx cb nil))
|
||||
([cb {:keys [reason ios-fallback-label]}]
|
||||
(log/debug "[biometric] authenticate-fx")
|
||||
(-> (.authenticate touchid reason (authenticate-options ios-fallback-label))
|
||||
(.then #(cb success-result))
|
||||
(.catch #(cb (generate-error-result %))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:biometric/get-supported-biometric-auth
|
||||
(fn []
|
||||
(let [callback #(re-frame/dispatch [:init.callback/get-supported-biometric-auth-success %])]
|
||||
;;NOTE: if we can't save user password, we can't support biometrics
|
||||
(keychain/can-save-user-password?
|
||||
(fn [can-save?]
|
||||
(if can-save?
|
||||
(get-supported callback)
|
||||
(callback nil)))))))
|
||||
|
||||
(rf/defn set-supported-biometric-auth
|
||||
{:events [:init.callback/get-supported-biometric-auth-success]}
|
||||
[{:keys [db]} supported-biometric-auth]
|
||||
{:db (assoc db :supported-biometric-auth supported-biometric-auth)})
|
||||
|
||||
(rf/defn authenticate
|
||||
[_ cb options]
|
||||
{:biometric-auth/authenticate [cb options]})
|
||||
|
||||
(re-frame/reg-fx
|
||||
:biometric-auth/authenticate
|
||||
(fn [[cb options]]
|
||||
(authenticate-fx #(cb %) options)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:biometric/enable-and-save-password
|
||||
(fn [{:keys [key-uid
|
||||
masked-password
|
||||
on-success
|
||||
on-error]}]
|
||||
(-> (keychain/save-user-password!
|
||||
key-uid
|
||||
masked-password)
|
||||
(.then
|
||||
(fn [_]
|
||||
(keychain/save-auth-method!
|
||||
key-uid
|
||||
keychain/auth-method-biometric)))
|
||||
(.then
|
||||
(fn [_]
|
||||
(when on-success
|
||||
(on-success))))
|
||||
(.catch (fn [error]
|
||||
(when on-error
|
||||
(on-error error)))))))
|
||||
|
||||
(rf/defn update-biometric
|
||||
[{db :db :as cofx} biometric-auth?]
|
||||
(let [key-uid (or (get-in db [:profile/profile :key-uid])
|
||||
(get-in db [:profile/login :key-uid]))]
|
||||
(rf/merge cofx
|
||||
(keychain/save-auth-method
|
||||
key-uid
|
||||
(if biometric-auth?
|
||||
keychain/auth-method-biometric
|
||||
keychain/auth-method-none))
|
||||
#(when-not biometric-auth?
|
||||
{:keychain/clear-user-password key-uid}))))
|
||||
|
||||
(rf/defn biometric-auth-switched
|
||||
{:events [:multiaccounts.ui/biometric-auth-switched]}
|
||||
[cofx biometric-auth?]
|
||||
(if biometric-auth?
|
||||
(authenticate
|
||||
cofx
|
||||
#(re-frame/dispatch [:biometric-init-done %])
|
||||
{})
|
||||
(update-biometric cofx false)))
|
||||
|
||||
(rf/defn show-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 biometric-init-done
|
||||
{:events [:biometric-init-done]}
|
||||
[cofx {:keys [bioauth-success bioauth-message bioauth-code]}]
|
||||
(if bioauth-success
|
||||
(if (= keychain/auth-method-password
|
||||
(get-in cofx [:db :auth-method]))
|
||||
(update-biometric cofx true)
|
||||
(popover/show-popover cofx {:view :enable-biometric}))
|
||||
(show-message cofx bioauth-message bioauth-code)))
|
||||
|
||||
(rf/defn biometric-auth
|
||||
{:events [:biometric-authenticate]}
|
||||
[cofx]
|
||||
(authenticate
|
||||
cofx
|
||||
#(re-frame/dispatch [:biometric-auth-done %])
|
||||
{:reason (i18n/label :t/biometric-auth-reason-login)
|
||||
:ios-fallback-label (i18n/label :t/biometric-auth-login-ios-fallback-label)}))
|
||||
|
||||
(rf/defn enable
|
||||
{:events [:biometric/enable]}
|
||||
[cofx]
|
||||
(rf/merge
|
||||
cofx
|
||||
(popover/hide-popover)
|
||||
(authenticate #(re-frame/dispatch [:biometric/setup-done %]) {})))
|
||||
|
||||
(rf/defn disable
|
||||
{:events [:biometric/disable]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> db
|
||||
(assoc :auth-method keychain/auth-method-none)
|
||||
(assoc-in [:profile/login :save-password?] false))}
|
||||
(popover/hide-popover)))
|
||||
|
||||
(rf/defn setup-done
|
||||
{:events [:biometric/setup-done]}
|
||||
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
|
||||
(log/debug "[biometric] setup-done"
|
||||
"bioauth-success" bioauth-success
|
||||
"bioauth-message" bioauth-message
|
||||
"bioauth-code" bioauth-code)
|
||||
(if bioauth-success
|
||||
{:db (assoc db :auth-method keychain/auth-method-biometric-prepare)}
|
||||
(show-message cofx bioauth-message bioauth-code)))
|
||||
@@ -41,43 +41,6 @@
|
||||
(fn [cofx _]
|
||||
(assoc cofx :signing-phrase (signing-phrase/generate))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::store-multiaccount
|
||||
(fn [[id key-uid hashed-password callback]]
|
||||
(native-module/multiaccount-store-derived
|
||||
id
|
||||
key-uid
|
||||
[constants/path-wallet-root
|
||||
constants/path-eip1581
|
||||
constants/path-whisper
|
||||
constants/path-default-wallet]
|
||||
hashed-password
|
||||
callback)))
|
||||
|
||||
(rf/defn create-multiaccount
|
||||
{:events [:create-multiaccount]}
|
||||
[{:keys [db]} key-code]
|
||||
(let [{:keys [selected-id]} (:intro-wizard db)]
|
||||
{::store-multiaccount
|
||||
[selected-id
|
||||
(some
|
||||
(fn [{:keys [id key-uid]}]
|
||||
(when (= id selected-id)
|
||||
key-uid))
|
||||
(get-in db [:intro-wizard :multiaccounts]))
|
||||
(ethereum/sha3 (security/safe-unmask-data key-code))
|
||||
(fn [result]
|
||||
(let [derived-data (normalize-derived-data-keys (types/json->clj result))
|
||||
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
|
||||
(native-module/gfycat-identicon-async
|
||||
public-key
|
||||
(fn [name _]
|
||||
(let [derived-whisper (derived-data constants/path-whisper-keyword)
|
||||
derived-data-extended (assoc-in derived-data
|
||||
[constants/path-whisper-keyword]
|
||||
(assoc derived-whisper :name name))]
|
||||
(re-frame/dispatch [::store-multiaccount-success key-code derived-data-extended]))))))]}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:multiaccount-generate-and-derive-addresses
|
||||
(fn []
|
||||
@@ -116,10 +79,6 @@
|
||||
(dissoc :recovered-account?))
|
||||
:multiaccount-generate-and-derive-addresses nil})
|
||||
|
||||
(rf/defn prepare-intro-wizard
|
||||
[{:keys [db]}]
|
||||
{:db (assoc db :intro-wizard {})})
|
||||
|
||||
(rf/defn save-multiaccount-and-login-with-keycard
|
||||
[_ args]
|
||||
{:keycard/save-multiaccount-and-login args})
|
||||
@@ -255,27 +214,3 @@
|
||||
settings
|
||||
(node/get-new-config db)
|
||||
accounts-data)))))
|
||||
|
||||
(rf/defn store-multiaccount-success
|
||||
{:events [::store-multiaccount-success]
|
||||
:interceptors [(re-frame/inject-cofx :random-guid-generator)
|
||||
(re-frame/inject-cofx ::get-signing-phrase)]}
|
||||
[{:keys [db] :as cofx} password derived]
|
||||
(rf/merge cofx
|
||||
{:db (dissoc db :intro-wizard)}
|
||||
(on-multiaccount-created (assoc (let [{:keys [selected-id multiaccounts]} (:intro-wizard db)]
|
||||
(some #(when (= selected-id (:id %)) %) multiaccounts))
|
||||
:derived derived
|
||||
:recovered (get-in db [:intro-wizard :recovering?]))
|
||||
password
|
||||
{:save-mnemonic? true})))
|
||||
|
||||
(rf/defn on-key-selected
|
||||
{:events [:intro-wizard/on-key-selected]}
|
||||
[{:keys [db]} id]
|
||||
{:db (assoc-in db [:intro-wizard :selected-id] id)})
|
||||
|
||||
(rf/defn on-key-storage-selected
|
||||
{:events [:intro-wizard/on-key-storage-selected]}
|
||||
[{:keys [db]} storage-type]
|
||||
{:db (assoc-in db [:intro-wizard :selected-storage-type] storage-type)})
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
(ns status-im.multiaccounts.key-storage.core
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[native-module.core :as native-module]
|
||||
[status-im.utils.types :as types]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:key-storage/delete-imported-key
|
||||
(fn [{:keys [key-uid address password on-success on-error]}]
|
||||
(let [hashed-pass (ethereum/sha3 (security/safe-unmask-data password))]
|
||||
(native-module/delete-imported-key
|
||||
key-uid
|
||||
(string/lower-case (subs address 2))
|
||||
hashed-pass
|
||||
(fn [result]
|
||||
(let [{:keys [error]} (types/json->clj result)]
|
||||
(if-not (string/blank? error)
|
||||
(on-error error)
|
||||
(on-success))))))))
|
||||
@@ -1,76 +1,13 @@
|
||||
(ns status-im.multiaccounts.login.core
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[clojure.set :as set]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.async-storage.core :as async-storage]
|
||||
[status-im.communities.core :as communities]
|
||||
[status-im.data-store.chats :as data-store.chats]
|
||||
[status-im.data-store.invitations :as data-store.invitations]
|
||||
[status-im.data-store.settings :as data-store.settings]
|
||||
[status-im.data-store.switcher-cards :as switcher-cards-store]
|
||||
[status-im.data-store.visibility-status-updates :as visibility-status-updates-store]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.ethereum.eip55 :as eip55]
|
||||
[status-im.ethereum.tokens :as tokens]
|
||||
[status-im.ethereum.transactions.core :as transactions]
|
||||
[status-im.fleet.core :as fleet]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im.mobile-sync-settings.core :as mobile-network]
|
||||
[status-im.multiaccounts.biometric.core :as biometric]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[native-module.core :as native-module]
|
||||
[status-im.notifications.core :as notifications]
|
||||
[status-im.popover.core :as popover]
|
||||
[status-im.signing.eip1559 :as eip1559]
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im2.config :as config]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.keychain.core :as keychain]
|
||||
[status-im.utils.mobile-sync :as utils.mobile-sync]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.utils.wallet-connect :as wallet-connect]
|
||||
[status-im.wallet.core :as wallet]
|
||||
[status-im.wallet.prices :as prices]
|
||||
[status-im2.common.json-rpc.events :as json-rpc]
|
||||
[status-im2.contexts.shell.activity-center.events :as activity-center]
|
||||
[status-im2.contexts.chat.messages.link-preview.events :as link-preview]
|
||||
[status-im2.contexts.contacts.events :as contacts]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[status-im2.contexts.shell.jump-to.constants :as shell.constants]
|
||||
[status-im2.contexts.communities.discover.events :as contract-communities]
|
||||
[status-im2.common.log :as logging]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im2.contexts.shell.jump-to.utils :as shell.utils]
|
||||
[utils.security.core :as security]
|
||||
[status-im.keycard.common :as keycard.common]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::initialize-transactions-management-enabled
|
||||
(fn []
|
||||
(let [callback #(re-frame/dispatch [:multiaccounts.ui/switch-transactions-management-enabled %])]
|
||||
(async-storage/get-item
|
||||
:transactions-management-enabled?
|
||||
callback))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::login
|
||||
(fn [[key-uid _ hashed-password]]
|
||||
(native-module/login-account {:keyUid key-uid
|
||||
:password hashed-password
|
||||
:wakuV2Nameserver "1.1.1.1"
|
||||
:openseaAPIKey config/opensea-api-key
|
||||
|
||||
:poktToken config/POKT_TOKEN
|
||||
:infuraToken config/INFURA_TOKEN
|
||||
|
||||
:alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN
|
||||
:alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN
|
||||
:alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN
|
||||
:alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN})))
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::export-db
|
||||
@@ -82,123 +19,6 @@
|
||||
(fn [[key-uid account-data hashed-password]]
|
||||
(native-module/import-db key-uid account-data hashed-password)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::enable-local-notifications
|
||||
(fn []
|
||||
(native-module/start-local-notifications)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::initialize-wallet-connect
|
||||
(fn []
|
||||
(wallet-connect/init
|
||||
#(re-frame/dispatch [:wallet-connect/client-init %])
|
||||
#(log/error "[wallet-connect]" %))))
|
||||
|
||||
(defn rpc->accounts
|
||||
[accounts]
|
||||
(reduce (fn [acc {:keys [chat type wallet] :as account}]
|
||||
(if chat
|
||||
acc
|
||||
(let [account (cond-> (update account
|
||||
:address
|
||||
eip55/address->checksum)
|
||||
type
|
||||
(update :type keyword))]
|
||||
;; if the account is the default wallet we
|
||||
;; put it first in the list
|
||||
(if wallet
|
||||
(into [account] acc)
|
||||
(conj acc account)))))
|
||||
[]
|
||||
accounts))
|
||||
|
||||
;;TODO remove this code after all invalid names will be fixed (ask chu or flexsurfer)
|
||||
(def invalid-addrr
|
||||
#{"0x9575cf381f71368a54e09b8138ebe046a1ef31ce93e6c37661513b21faaf741e"
|
||||
"0x56fa5de8cd4f2a3cbc122e7c51ac8690c6fc739b7c3724add97d0c55cc783d45"
|
||||
"0xf0e49d178fa34ac3ade4625e144f51e5f982434f0912bcbe23b6467343f48305"
|
||||
"0x60d1bf67e9d0d34368a6422c55f034230cc0997b186dd921fd18e89b7f0df5f2"
|
||||
"0x5fe69d562990616a02f4a5f934aa973b27bf02c4fc774f9ad82f105379f16789"
|
||||
"0xf1cabf2d74576ef76dfcb1182fd59a734a26c95ea6e68fc8f91ca4bfa1ea0594"
|
||||
"0x21d8ce6c0e32481506f98218920bee88f03d9c1b45dab3546948ccc34b1aadea"
|
||||
"0xbf7a74b39090fb5b1366f61fb4ac3ecc4b7f99f0fd3cb326dc5c18c58d58d7b6"
|
||||
"0xeeb570494d442795235589284100812e4176e9c29d151a81df43b6286ef66c49"
|
||||
"0x86a12d91c813f69a53349ff640e32af97d5f5c1f8d42d54cf4c8aa8dea231955"
|
||||
"0x0011a30f5b2023bc228f6dd5608b3e7149646fa83f33350926ceb1925af78f08"})
|
||||
|
||||
(rf/defn check-invalid-ens
|
||||
[{:keys [db]}]
|
||||
(async-storage/get-item
|
||||
:invalid-ens-name-seen
|
||||
(fn [already-seen]
|
||||
(when (and (not already-seen)
|
||||
(boolean (get invalid-addrr
|
||||
(ethereum/sha3 (string/lower-case (ethereum/default-address db))))))
|
||||
(utils/show-popup
|
||||
(i18n/label :t/warning)
|
||||
(i18n/label :t/ens-username-invalid-name-warning)
|
||||
#(async-storage/set-item! :invalid-ens-name-seen true)))))
|
||||
nil)
|
||||
|
||||
(rf/defn initialize-wallet
|
||||
{:events [::initialize-wallet]}
|
||||
[{:keys [db] :as cofx} accounts tokens custom-tokens
|
||||
favourites scan-all-tokens? new-account?]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (assoc db
|
||||
:profile/wallet-accounts
|
||||
(rpc->accounts accounts))
|
||||
;; NOTE: Local notifications should be enabled only after wallet was started
|
||||
::enable-local-notifications nil}
|
||||
(check-invalid-ens)
|
||||
(wallet/initialize-tokens tokens custom-tokens)
|
||||
(wallet/initialize-favourites favourites)
|
||||
(wallet/get-pending-transactions)
|
||||
(wallet/fetch-collectibles-collection)
|
||||
(cond
|
||||
(and new-account?
|
||||
(not scan-all-tokens?))
|
||||
(wallet/set-zero-balances (first accounts))
|
||||
|
||||
(and new-account?
|
||||
scan-all-tokens?
|
||||
(not (utils.mobile-sync/cellular? (:network/type db))))
|
||||
(wallet/set-max-block (get (first accounts) :address) 0)
|
||||
|
||||
:else
|
||||
(wallet/get-cached-balances scan-all-tokens?))
|
||||
(when-not (get db :wallet/new-account)
|
||||
(wallet/restart-wallet-service nil))
|
||||
(when (or (not (utils.mobile-sync/syncing-allowed? cofx))
|
||||
(ethereum/binance-chain? db))
|
||||
(transactions/get-fetched-transfers))
|
||||
(when (ethereum/binance-chain? db)
|
||||
(wallet/request-current-block-update))
|
||||
(prices/update-prices)))
|
||||
|
||||
(rf/defn login-local-paired-user
|
||||
{:events [:multiaccounts.login/local-paired-user]}
|
||||
[{:keys [db]}]
|
||||
(let [{:keys [key-uid name password]} (get-in db [:syncing :profile/profile])]
|
||||
{::login [key-uid
|
||||
(types/clj->json {:name name
|
||||
:key-uid key-uid})
|
||||
password]}))
|
||||
|
||||
(rf/defn login
|
||||
{:events [:multiaccounts.login.ui/password-input-submitted]}
|
||||
[{:keys [db]}]
|
||||
(let [{:keys [key-uid password name]} (:profile/login db)]
|
||||
{:db (-> db
|
||||
(assoc-in [:profile/login :processing] true)
|
||||
(dissoc :intro-wizard :recovered-account?)
|
||||
(update :keycard dissoc :flow))
|
||||
::login [key-uid
|
||||
(types/clj->json {:name name
|
||||
:key-uid key-uid})
|
||||
(ethereum/sha3 (security/safe-unmask-data password))]}))
|
||||
|
||||
(rf/defn export-db-submitted
|
||||
{:events [:multiaccounts.login.ui/export-db-submitted]}
|
||||
[{:keys [db]}]
|
||||
@@ -222,565 +42,3 @@
|
||||
(types/clj->json {:name name
|
||||
:key-uid key-uid})
|
||||
(ethereum/sha3 (security/safe-unmask-data password))]}))
|
||||
|
||||
(rf/defn finish-keycard-setup
|
||||
[{:keys [db] :as cofx}]
|
||||
{:db (update db :keycard dissoc :flow)})
|
||||
|
||||
(rf/defn initialize-dapp-permissions
|
||||
{:events [::initialize-dapp-permissions]}
|
||||
[{:keys [db]} all-dapp-permissions]
|
||||
(let [dapp-permissions (reduce (fn [acc {:keys [dapp] :as dapp-permissions}]
|
||||
(assoc acc dapp dapp-permissions))
|
||||
{}
|
||||
all-dapp-permissions)]
|
||||
{:db (assoc db :dapps/permissions dapp-permissions)}))
|
||||
|
||||
(rf/defn initialize-browsers
|
||||
{:events [::initialize-browsers]}
|
||||
[{:keys [db]} all-stored-browsers]
|
||||
(let [browsers (reduce (fn [acc {:keys [browser-id] :as browser}]
|
||||
(assoc acc browser-id browser))
|
||||
{}
|
||||
all-stored-browsers)]
|
||||
{:db (assoc db :browser/browsers browsers)}))
|
||||
|
||||
(rf/defn initialize-bookmarks
|
||||
{:events [::initialize-bookmarks]}
|
||||
[{:keys [db]} stored-bookmarks]
|
||||
(let [bookmarks (reduce (fn [acc {:keys [url] :as bookmark}]
|
||||
(assoc acc url bookmark))
|
||||
{}
|
||||
stored-bookmarks)]
|
||||
{:db (assoc db :bookmarks/bookmarks bookmarks)}))
|
||||
|
||||
(rf/defn initialize-invitations
|
||||
{:events [::initialize-invitations]}
|
||||
[{:keys [db]} invitations]
|
||||
{:db (assoc db
|
||||
:group-chat/invitations
|
||||
(reduce (fn [acc {:keys [id] :as inv}]
|
||||
(assoc acc id (data-store.invitations/<-rpc inv)))
|
||||
{}
|
||||
invitations))})
|
||||
|
||||
(rf/defn initialize-web3-client-version
|
||||
{:events [::initialize-web3-client-version]}
|
||||
[{:keys [db]} node-version]
|
||||
{:db (assoc db :web3-node-version node-version)})
|
||||
|
||||
(rf/defn handle-close-app-confirmed
|
||||
{:events [::close-app-confirmed]}
|
||||
[_]
|
||||
{:ui/close-application nil})
|
||||
|
||||
(rf/defn check-network-version
|
||||
[_ network-id]
|
||||
{:json-rpc/call
|
||||
[{:method "net_version"
|
||||
:on-success
|
||||
(fn [fetched-network-id]
|
||||
(when (not= network-id (str (int fetched-network-id)))
|
||||
;;TODO: this shouldn't happen but in case it does
|
||||
;;we probably want a better error message
|
||||
(utils/show-popup
|
||||
(i18n/label :t/ethereum-node-started-incorrectly-title)
|
||||
(i18n/label :t/ethereum-node-started-incorrectly-description
|
||||
{:network-id network-id
|
||||
:fetched-network-id fetched-network-id})
|
||||
#(re-frame/dispatch [::close-app-confirmed]))))}]})
|
||||
|
||||
(defn normalize-tokens
|
||||
[network-id tokens]
|
||||
(mapv #(-> %
|
||||
(update :symbol keyword)
|
||||
((partial tokens/update-icon (ethereum/chain-id->chain-keyword (int network-id)))))
|
||||
tokens))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::get-tokens
|
||||
(fn [[network-id accounts recovered-account?]]
|
||||
(utils/set-timeout
|
||||
(fn []
|
||||
(json-rpc/call {:method "wallet_getTokens"
|
||||
:params [(int network-id)]
|
||||
:on-success #(re-frame/dispatch [::initialize-wallet
|
||||
accounts
|
||||
(normalize-tokens network-id %)
|
||||
nil nil
|
||||
recovered-account?
|
||||
true])}))
|
||||
2000)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
;;TODO: this could be replaced by a single API call on status-go side
|
||||
::initialize-wallet
|
||||
(fn [[network-id network callback]]
|
||||
(-> (js/Promise.all
|
||||
(clj->js
|
||||
[(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(json-rpc/call {:method "accounts_getAccounts"
|
||||
:on-success resolve
|
||||
:on-error reject})))
|
||||
(js/Promise.
|
||||
(fn [resolve _]
|
||||
(json-rpc/call
|
||||
{:method "wallet_addEthereumChain"
|
||||
:params
|
||||
[{:isTest false
|
||||
:tokenOverrides []
|
||||
:rpcUrl (get-in network [:config :UpstreamConfig :URL])
|
||||
:chainColor "green"
|
||||
:chainName (:name network)
|
||||
:nativeCurrencyDecimals 10
|
||||
:shortName "erc20"
|
||||
:layer 1
|
||||
:chainId (int network-id)
|
||||
:enabled false
|
||||
:fallbackURL (get-in network [:config :UpstreamConfig :URL])}]
|
||||
:on-success resolve
|
||||
:on-error (fn [_] (resolve nil))})))
|
||||
(js/Promise.
|
||||
(fn [resolve _]
|
||||
(json-rpc/call {:method "wallet_getTokens"
|
||||
:params [(int network-id)]
|
||||
:on-success resolve
|
||||
:on-error (fn [_]
|
||||
(resolve nil))})))
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(json-rpc/call {:method "wallet_getCustomTokens"
|
||||
:on-success resolve
|
||||
:on-error reject})))
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(json-rpc/call {:method "wallet_getSavedAddresses"
|
||||
:on-success resolve
|
||||
:on-error reject})))]))
|
||||
(.then (fn [[accounts _ tokens custom-tokens favourites]]
|
||||
(callback accounts
|
||||
(normalize-tokens network-id tokens)
|
||||
(mapv #(update % :symbol keyword) custom-tokens)
|
||||
(filter :favourite favourites))))
|
||||
(.catch (fn [_]
|
||||
(log/error "Failed to initialize wallet"))))))
|
||||
|
||||
(rf/defn initialize-browser
|
||||
[_]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_getBrowsers"
|
||||
:on-success #(re-frame/dispatch [::initialize-browsers %])}
|
||||
{:method "browsers_getBookmarks"
|
||||
:on-success #(re-frame/dispatch [::initialize-bookmarks %])}
|
||||
{:method "permissions_getDappPermissions"
|
||||
:on-success #(re-frame/dispatch [::initialize-dapp-permissions %])}]})
|
||||
|
||||
(rf/defn get-group-chat-invitations
|
||||
[_]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_getGroupChatInvitations"
|
||||
:on-success #(re-frame/dispatch [::initialize-invitations %])}]})
|
||||
|
||||
(rf/defn initialize-transactions-management-enabled
|
||||
[cofx]
|
||||
{::initialize-transactions-management-enabled nil})
|
||||
|
||||
(rf/defn initialize-wallet-connect
|
||||
[cofx]
|
||||
{::initialize-wallet-connect nil})
|
||||
|
||||
(rf/defn get-node-config-callback
|
||||
{:events [::get-node-config-callback]}
|
||||
[{:keys [db] :as cofx} node-config-json]
|
||||
(let [node-config (types/json->clj node-config-json)]
|
||||
{:db (assoc-in db
|
||||
[:profile/profile :wakuv2-config]
|
||||
(get node-config :WakuV2Config))}))
|
||||
|
||||
(rf/defn get-node-config
|
||||
[_]
|
||||
(native-module/get-node-config #(re-frame/dispatch [::get-node-config-callback %])))
|
||||
|
||||
(rf/defn redirect-to-root
|
||||
"Decides which root should be initialised depending on user and app state"
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [pairing-completed? (= (get-in db [:syncing :pairing-status]) :completed)]
|
||||
(cond
|
||||
pairing-completed?
|
||||
{:db (dissoc db :syncing)
|
||||
:dispatch [:init-root :syncing-results]}
|
||||
|
||||
(get db :onboarding-2/new-account?)
|
||||
{:dispatch [:onboarding-2/finalize-setup]}
|
||||
|
||||
(get db :tos/accepted?)
|
||||
(rf/merge
|
||||
cofx
|
||||
(multiaccounts/switch-theme nil :shell-stack)
|
||||
(navigation/init-root :shell-stack))
|
||||
|
||||
:else
|
||||
{:dispatch [:init-root :tos]})))
|
||||
|
||||
(rf/defn get-settings-callback
|
||||
{:events [::get-settings-callback]}
|
||||
[{:keys [db] :as cofx} settings]
|
||||
(let [{:networks/keys [current-network networks]
|
||||
:as settings}
|
||||
(data-store.settings/rpc->settings settings)
|
||||
multiaccount (-> settings
|
||||
(dissoc :networks/current-network :networks/networks)
|
||||
(set/rename-keys {:compressedKey :compressed-key
|
||||
:emojiHash :emoji-hash}))
|
||||
;;for some reason we save default networks in db, in case when we want to modify default-networks
|
||||
;;for
|
||||
;; existing accounts we have to merge them again into networks
|
||||
merged-networks (merge networks config/default-networks-by-id)]
|
||||
(rf/merge cofx
|
||||
{:db (-> db
|
||||
(dissoc :profile/login)
|
||||
(assoc :networks/current-network current-network
|
||||
:networks/networks merged-networks
|
||||
:profile/profile multiaccount))}
|
||||
(data-store.chats/fetch-chats-rpc
|
||||
{:on-success
|
||||
#(do (re-frame/dispatch [:chats-list/load-success %])
|
||||
(rf/dispatch [:communities/get-user-requests-to-join])
|
||||
(re-frame/dispatch [::get-chats-callback]))})
|
||||
(initialize-wallet-connect)
|
||||
(get-node-config)
|
||||
(communities/fetch)
|
||||
(contract-communities/fetch-contract-communities)
|
||||
(communities/fetch-collapsed-community-categories)
|
||||
(communities/check-and-delete-pending-request-to-join)
|
||||
(logging/set-log-level (:log-level multiaccount))
|
||||
(activity-center/notifications-fetch-pending-contact-requests)
|
||||
(activity-center/update-seen-state)
|
||||
(activity-center/notifications-fetch-unread-count)
|
||||
(redirect-to-root))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::open-last-chat
|
||||
(fn [key-uid]
|
||||
(async-storage/get-item
|
||||
:chat-id
|
||||
(fn [chat-id]
|
||||
(when chat-id
|
||||
(async-storage/get-item
|
||||
:key-uid
|
||||
(fn [stored-key-uid]
|
||||
(when (= stored-key-uid key-uid)
|
||||
(re-frame/dispatch [:chat/navigate-to-chat chat-id
|
||||
shell.constants/open-screen-without-animation])))))))))
|
||||
|
||||
(rf/defn check-last-chat
|
||||
{:events [::check-last-chat]}
|
||||
[{:keys [db]}]
|
||||
{::open-last-chat (get-in db [:profile/profile :key-uid])})
|
||||
|
||||
(rf/defn update-wallet-accounts
|
||||
[{:keys [db]} accounts]
|
||||
(let [existing-accounts (into {} (map #(vector (:address %1) %1) (:profile/wallet-accounts db)))
|
||||
reduce-fn (fn [existing-accs new-acc]
|
||||
(let [address (:address new-acc)]
|
||||
(if (:removed new-acc)
|
||||
(dissoc existing-accs address)
|
||||
(assoc existing-accs address new-acc))))
|
||||
new-accounts (reduce reduce-fn existing-accounts (rpc->accounts accounts))]
|
||||
{:db (assoc db :profile/wallet-accounts (vals new-accounts))}))
|
||||
|
||||
(rf/defn get-chats-callback
|
||||
{:events [::get-chats-callback]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [{:networks/keys [current-network networks]} db
|
||||
notifications-enabled? (get-in db [:profile/profile :notifications-enabled?])
|
||||
current-network-config (get networks current-network)
|
||||
network-id (str (get-in networks
|
||||
[current-network :config :NetworkId]))
|
||||
remote-push-notifications-enabled?
|
||||
(get-in db [:profile/profile :remote-push-notifications-enabled?])]
|
||||
(rf/merge cofx
|
||||
(cond-> {::eip1559/check-eip1559-activation
|
||||
{:network-id network-id
|
||||
:on-enabled #(log/info "eip1550 is activated")
|
||||
:on-disabled #(log/info "eip1559 is not activated")}
|
||||
::initialize-wallet
|
||||
[network-id
|
||||
current-network-config
|
||||
(fn [accounts tokens custom-tokens favourites]
|
||||
(re-frame/dispatch [::initialize-wallet
|
||||
accounts tokens custom-tokens favourites]))]
|
||||
::open-last-chat (get-in db [:profile/profile :key-uid])}
|
||||
(or notifications-enabled? remote-push-notifications-enabled?)
|
||||
(assoc ::notifications/enable remote-push-notifications-enabled?))
|
||||
(transport/start-messenger)
|
||||
(initialize-transactions-management-enabled)
|
||||
(check-network-version network-id)
|
||||
(contacts/initialize-contacts)
|
||||
(initialize-browser)
|
||||
(mobile-network/on-network-status-change)
|
||||
(get-group-chat-invitations)
|
||||
(multiaccounts/get-profile-picture)
|
||||
(multiaccounts/switch-preview-privacy-mode-flag)
|
||||
(link-preview/request-link-preview-whitelist)
|
||||
(visibility-status-updates-store/fetch-visibility-status-updates-rpc)
|
||||
(switcher-cards-store/fetch-switcher-cards-rpc))))
|
||||
|
||||
(defn get-new-auth-method
|
||||
[auth-method save-password?]
|
||||
(when save-password?
|
||||
(when-not (or (= keychain/auth-method-biometric auth-method)
|
||||
(= keychain/auth-method-password auth-method))
|
||||
(if (= auth-method keychain/auth-method-biometric-prepare)
|
||||
keychain/auth-method-biometric
|
||||
keychain/auth-method-password))))
|
||||
|
||||
(rf/defn login-only-events
|
||||
[{:keys [db] :as cofx} key-uid password save-password?]
|
||||
(let [auth-method (:auth-method db)
|
||||
new-auth-method (get-new-auth-method auth-method save-password?)]
|
||||
(log/debug "[login] login-only-events"
|
||||
"auth-method" auth-method
|
||||
"new-auth-method" new-auth-method)
|
||||
(rf/merge cofx
|
||||
{:db (assoc db :chats/loading? true)
|
||||
:json-rpc/call
|
||||
[{:method "settings_getSettings"
|
||||
:on-success #(re-frame/dispatch [::get-settings-callback %])}]}
|
||||
(notifications/load-notification-preferences)
|
||||
(when save-password?
|
||||
(keychain/save-user-password key-uid password))
|
||||
(keychain/save-auth-method key-uid
|
||||
(or new-auth-method auth-method keychain/auth-method-none)))))
|
||||
|
||||
(rf/defn create-only-events
|
||||
[{:keys [db] :as cofx} recovered-account?]
|
||||
(let [{:profile/keys [profile profiles-overview wallet-accounts]}
|
||||
db
|
||||
{:keys [creating?]} (:profile/login db)
|
||||
first-account? (and creating? (empty? profiles-overview))
|
||||
tos-accepted? (get db :tos/accepted?)
|
||||
{:networks/keys [current-network networks]} db
|
||||
network-id (str (get-in networks [current-network :config :NetworkId]))]
|
||||
(shell.utils/change-selected-stack-id :communities-stack true nil)
|
||||
(rf/merge cofx
|
||||
{:db (-> db
|
||||
(dissoc :profile/login)
|
||||
(assoc :tos/next-root :enable-notifications :chats/loading? false)
|
||||
(assoc-in [:profile/profile :multiaccounts/first-account]
|
||||
first-account?))
|
||||
::get-tokens [network-id wallet-accounts recovered-account?]}
|
||||
(finish-keycard-setup)
|
||||
(transport/start-messenger)
|
||||
(communities/fetch)
|
||||
(data-store.chats/fetch-chats-rpc
|
||||
{:on-success #(re-frame/dispatch [:chats-list/load-success %])})
|
||||
(multiaccounts/switch-preview-privacy-mode-flag)
|
||||
(link-preview/request-link-preview-whitelist)
|
||||
(logging/set-log-level (:log-level profile))
|
||||
(navigation/init-root :enable-notifications))))
|
||||
|
||||
(defn- keycard-setup?
|
||||
[cofx]
|
||||
(boolean (get-in cofx [:db :keycard :flow])))
|
||||
|
||||
(defn on-login-update-db
|
||||
[db login-only? now]
|
||||
(-> db
|
||||
(dissoc :connectivity/ui-status-properties)
|
||||
(update :keycard dissoc :from-key-storage-and-migration?)
|
||||
(update :keycard dissoc
|
||||
:on-card-read
|
||||
:card-read-in-progress?
|
||||
:pin
|
||||
:profile/profile)
|
||||
(assoc :tos-accept-next-root
|
||||
(if login-only?
|
||||
:shell-stack
|
||||
:onboarding-notification))
|
||||
(assoc :logged-in-since now)
|
||||
(assoc :view-id :home)))
|
||||
|
||||
(rf/defn multiaccount-login-success
|
||||
[{:keys [db now] :as cofx}]
|
||||
(let [{:keys [key-uid password save-password? creating?]}
|
||||
(:profile/login db)
|
||||
|
||||
multiaccounts (:profile/profiles-overview db)
|
||||
recovered-account? (get db :recovered-account?)
|
||||
login-only? (not (or creating?
|
||||
recovered-account?
|
||||
(keycard-setup? cofx)))
|
||||
from-migration? (get-in db
|
||||
[:keycard
|
||||
:from-key-storage-and-migration?])
|
||||
nodes nil]
|
||||
(log/debug "[multiaccount] multiaccount-login-success"
|
||||
"login-only?" login-only?
|
||||
"recovered-account?" recovered-account?)
|
||||
(rf/merge cofx
|
||||
{:db (on-login-update-db db login-only? now)
|
||||
:json-rpc/call
|
||||
[{:method "web3_clientVersion"
|
||||
:on-success #(re-frame/dispatch [::initialize-web3-client-version %])}]}
|
||||
;;FIXME
|
||||
(when nodes
|
||||
(fleet/set-nodes :eth.contract nodes))
|
||||
(when (and (not login-only?)
|
||||
(not recovered-account?))
|
||||
(wallet/set-initial-blocks-range))
|
||||
(when from-migration?
|
||||
(utils/show-popup (i18n/label :t/migration-successful)
|
||||
(i18n/label :t/migration-successful-text)))
|
||||
(if login-only?
|
||||
(login-only-events key-uid password save-password?)
|
||||
(create-only-events recovered-account?)))))
|
||||
|
||||
(rf/defn open-login-callback
|
||||
{:events [:multiaccounts.login.callback/get-user-password-success]}
|
||||
[{:keys [db] :as cofx} password]
|
||||
(let [key-uid (get-in db [:profile/login :key-uid])
|
||||
keycard-account? (boolean (get-in db
|
||||
[:profile/profiles-overview
|
||||
key-uid
|
||||
:keycard-pairing]))
|
||||
goto-key-storage? (:goto-key-storage? db)]
|
||||
(if password
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (update-in db
|
||||
[:profile/login]
|
||||
assoc
|
||||
:password password
|
||||
:save-password? true)
|
||||
:set-root :progress}
|
||||
login)
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (dissoc db :goto-key-storage?)}
|
||||
(when keycard-account?
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :pin :status] nil)
|
||||
(assoc-in [:keycard :pin :login] []))})
|
||||
#(if keycard-account?
|
||||
{:set-root :multiaccounts-keycard}
|
||||
{:set-root :profiles})
|
||||
#(when goto-key-storage?
|
||||
(navigation/navigate-to % :actions-not-logged-in nil))))))
|
||||
|
||||
(rf/defn get-credentials
|
||||
[{:keys [db] :as cofx} key-uid]
|
||||
(let [keycard-multiaccount? (boolean (get-in db
|
||||
[:profile/profiles-overview key-uid :keycard-pairing]))]
|
||||
(log/debug "[login] get-credentials"
|
||||
"keycard-multiacc?"
|
||||
keycard-multiaccount?)
|
||||
(if keycard-multiaccount?
|
||||
(keychain/get-keycard-keys cofx key-uid)
|
||||
(keychain/get-user-password cofx key-uid))))
|
||||
|
||||
(rf/defn get-auth-method-success
|
||||
"Auth method: nil - not supported, \"none\" - not selected, \"password\", \"biometric\", \"biometric-prepare\""
|
||||
{:events [:multiaccounts.login/get-auth-method-success]}
|
||||
[{:keys [db] :as cofx} auth-method]
|
||||
(let [key-uid (get-in db [:profile/login :key-uid])
|
||||
keycard-profile? (boolean (get-in db [:profile/profiles-overview key-uid :keycard-pairing]))]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (assoc db :auth-method auth-method)}
|
||||
#(cond
|
||||
(= auth-method keychain/auth-method-biometric)
|
||||
(biometric/biometric-auth %)
|
||||
(= auth-method keychain/auth-method-password)
|
||||
(get-credentials % key-uid)
|
||||
(and keycard-profile? (get-in db [:keycard :card-connected?]))
|
||||
(keycard.common/get-application-info % nil))
|
||||
(open-login-callback nil))))
|
||||
|
||||
(rf/defn biometric-auth-done
|
||||
{:events [:biometric-auth-done]}
|
||||
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
|
||||
(let [key-uid (get-in db [:profile/login :key-uid])
|
||||
auth-method (get db :auth-method)]
|
||||
(log/debug "[biometric] biometric-auth-done"
|
||||
"bioauth-success" bioauth-success
|
||||
"bioauth-message" bioauth-message
|
||||
"bioauth-code" bioauth-code)
|
||||
(if bioauth-success
|
||||
(get-credentials cofx key-uid)
|
||||
(rf/merge cofx
|
||||
{:db (assoc-in db
|
||||
[:profile/login :save-password?]
|
||||
(= auth-method keychain/auth-method-biometric))}
|
||||
(when-not (= auth-method keychain/auth-method-biometric)
|
||||
(keychain/save-auth-method key-uid keychain/auth-method-none))
|
||||
(biometric/show-message bioauth-message bioauth-code)
|
||||
(open-login-callback nil)))))
|
||||
|
||||
(rf/defn save-password
|
||||
{:events [:multiaccounts/save-password]}
|
||||
[{:keys [db] :as cofx} save-password?]
|
||||
(let [bioauth-supported? (boolean (get db :supported-biometric-auth))
|
||||
previous-auth-method (get db :auth-method)]
|
||||
(log/debug "[login] save-password"
|
||||
"save-password?" save-password?
|
||||
"bioauth-supported?" bioauth-supported?
|
||||
"previous-auth-method" previous-auth-method)
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (cond-> db
|
||||
(not= previous-auth-method
|
||||
keychain/auth-method-biometric-prepare)
|
||||
(assoc :auth-method keychain/auth-method-none)
|
||||
(or save-password?
|
||||
(not bioauth-supported?)
|
||||
(and (not save-password?)
|
||||
bioauth-supported?
|
||||
(= previous-auth-method keychain/auth-method-none)))
|
||||
(assoc-in [:profile/login :save-password?] save-password?))}
|
||||
(when bioauth-supported?
|
||||
(if save-password?
|
||||
(popover/show-popover {:view :secure-with-biometric})
|
||||
(when-not (= previous-auth-method keychain/auth-method-none)
|
||||
(popover/show-popover {:view :disable-password-saving})))))))
|
||||
|
||||
(rf/defn welcome-lets-go
|
||||
{:events [:welcome-lets-go]}
|
||||
[_]
|
||||
{:set-root :shell-stack})
|
||||
|
||||
(rf/defn hide-keycard-banner
|
||||
{:events [:hide-keycard-banner]}
|
||||
[{:keys [db]}]
|
||||
{:db (assoc db :keycard/banner-hidden true)
|
||||
::async-storage/set! {:keycard-banner-hidden true}})
|
||||
|
||||
(rf/defn get-keycard-banner-preference-cb
|
||||
{:events [:get-keycard-banner-preference-cb]}
|
||||
[{:keys [db]} {:keys [keycard-banner-hidden]}]
|
||||
{:db (assoc db :keycard/banner-hidden keycard-banner-hidden)})
|
||||
|
||||
(rf/defn get-keycard-banner-preference
|
||||
{:events [:get-keycard-banner-preference]}
|
||||
[_]
|
||||
{::async-storage/get {:keys [:keycard-banner-hidden]
|
||||
:cb #(re-frame/dispatch [:get-keycard-banner-preference-cb %])}})
|
||||
|
||||
(rf/defn get-opted-in-to-new-terms-of-service-cb
|
||||
{:events [:get-opted-in-to-new-terms-of-service-cb]}
|
||||
[{:keys [db]} {:keys [new-terms-of-service-accepted]}]
|
||||
{:db (assoc db :tos/accepted? new-terms-of-service-accepted)})
|
||||
|
||||
(rf/defn get-opted-in-to-new-terms-of-service
|
||||
"New TOS sprint https://github.com/status-im/status-mobile/pull/12240"
|
||||
{:events [:get-opted-in-to-new-terms-of-service]}
|
||||
[{:keys [db]}]
|
||||
{::async-storage/get {:keys [:new-terms-of-service-accepted]
|
||||
:cb #(re-frame/dispatch [:get-opted-in-to-new-terms-of-service-cb %])}})
|
||||
|
||||
(rf/defn hide-terms-of-services-opt-in-screen
|
||||
{:events [:hide-terms-of-services-opt-in-screen]}
|
||||
[{:keys [db]}]
|
||||
{::async-storage/set! {:new-terms-of-service-accepted true}
|
||||
:db (assoc db :tos/accepted? true)})
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
(ns status-im.multiaccounts.login.core-test
|
||||
(:require [cljs.test :as test]
|
||||
[status-im.multiaccounts.biometric.core :as biometric]
|
||||
[status-im.multiaccounts.login.core :as login]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.keychain.core :as keychain]))
|
||||
|
||||
(test/deftest save-password-test
|
||||
(test/testing "check save password, biometric unavailable"
|
||||
(let [initial-cofx {:db {:auth-method keychain/auth-method-none
|
||||
:supported-biometric-auth false}}
|
||||
{:keys [db]} (login/save-password initial-cofx true)]
|
||||
(test/is (= false (contains? db :popover/popover)))
|
||||
(test/is (= true (get-in db [:profile/login :save-password?])))
|
||||
(test/testing "uncheck save password"
|
||||
(let [{:keys [db]} (login/save-password {:db db} false)]
|
||||
(test/is (= false (contains? db :popover/popover)))
|
||||
(test/is (= false (get-in db [:profile/login :save-password?])))))))
|
||||
(test/testing "check save password, biometric available"
|
||||
(let [initial-cofx {:db {:auth-method keychain/auth-method-none
|
||||
:supported-biometric-auth true}}
|
||||
{:keys [db]} (login/save-password initial-cofx true)]
|
||||
(test/is (= true (get-in db [:profile/login :save-password?])))
|
||||
(test/testing "enable biometric auth"
|
||||
(let [{:keys [db] :as res} (biometric/enable {:db db})]
|
||||
(test/is (contains? res :biometric-auth/authenticate))
|
||||
(test/is (= false (contains? db :popover/popover)))
|
||||
(test/testing "biometric auth successfully enabled"
|
||||
(let [{:keys [db]} (biometric/setup-done
|
||||
{:db db}
|
||||
{:bioauth-success true
|
||||
:bioauth-message nil
|
||||
:bioauth-code nil})]
|
||||
(test/is (= keychain/auth-method-biometric-prepare
|
||||
(:auth-method db)))))
|
||||
(test/testing "biometric auth canceled"
|
||||
(let [{:keys [db]} (biometric/setup-done
|
||||
{:db db}
|
||||
{:bioauth-success false
|
||||
:bioauth-message nil
|
||||
:bioauth-code "USER_CANCELED"})]
|
||||
(test/is (= nil db) "no db changes")))))))
|
||||
(test/testing (str "check save password, enable biometric auth,"
|
||||
"uncheck save password")
|
||||
(let [initial-cofx {:db {:auth-method keychain/auth-method-none
|
||||
:supported-biometric-auth true}}
|
||||
{:keys [db]} (login/save-password (rf/merge
|
||||
initial-cofx
|
||||
(login/save-password true)
|
||||
(biometric/enable)
|
||||
(biometric/setup-done
|
||||
{:bioauth-success true
|
||||
:bioauth-message nil
|
||||
:bioauth-code nil}))
|
||||
false)]
|
||||
(test/is (= true (get-in db [:profile/login :save-password?])))
|
||||
;; case 2 from https://github.com/status-im/status-mobile/issues/9573
|
||||
(test/is (= keychain/auth-method-biometric-prepare (:auth-method db)))
|
||||
(test/testing "disable biometric"
|
||||
(let [{:keys [db]} (biometric/disable {:db db})]
|
||||
(test/is (= false (get-in db [:profile/login :save-password?])))
|
||||
(test/is (= keychain/auth-method-none (:auth-method db))))))))
|
||||
@@ -1,121 +0,0 @@
|
||||
(ns status-im.multiaccounts.login.data-test)
|
||||
|
||||
(def all-contacts
|
||||
[{:last-updated 1547185503000
|
||||
:tags #{}
|
||||
:address "2f88d65f3cb52605a54a833ae118fb1363acccd2"
|
||||
:name "Darkviolet Lightgreen Halcyon"
|
||||
:added? true
|
||||
:last-online 0
|
||||
:public-key
|
||||
"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"}
|
||||
{:last-updated 1547271764000
|
||||
:address "b267ff8336ac10b3a1986c04a70ff91fb03d0b78"
|
||||
:name "rv"
|
||||
:added? true
|
||||
:last-online 0
|
||||
:public-key
|
||||
"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"}])
|
||||
|
||||
(def chats
|
||||
[{:updated-at nil
|
||||
:tags #{}
|
||||
:color "#51d0f0"
|
||||
:contacts #{}
|
||||
:last-clock-value 154990121501242
|
||||
:admins #{}
|
||||
:members-joined #{}
|
||||
:name "status"
|
||||
:membership-updates ()
|
||||
:unviewed-messages-count 0
|
||||
:last-message-content-type "text/plain"
|
||||
:last-message-content {:chat-id "status" :text "darn typos...! "}
|
||||
:debug? false
|
||||
:group-chat true
|
||||
:public? true
|
||||
:chat-id "status"
|
||||
:timestamp 1547361080397
|
||||
:deleted-at-clock-value nil}
|
||||
{:updated-at nil
|
||||
:tags #{}
|
||||
:color "#d37ef4"
|
||||
:contacts
|
||||
#{"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"}
|
||||
:last-clock-value 154727176928001
|
||||
:admins #{}
|
||||
:members-joined #{}
|
||||
:name "rv"
|
||||
:membership-updates ()
|
||||
:unviewed-messages-count 0
|
||||
:last-message-content-type "text/plain"
|
||||
:last-message-content
|
||||
{:chat-id
|
||||
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
|
||||
:text "Hey"}
|
||||
:debug? false
|
||||
:group-chat false
|
||||
:public? false
|
||||
:chat-id
|
||||
"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"
|
||||
:timestamp 1547271770816
|
||||
:deleted-at-clock-value nil}
|
||||
{:updated-at nil
|
||||
:tags #{}
|
||||
:color "#7cda00"
|
||||
:contacts
|
||||
#{"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"}
|
||||
:last-clock-value 154718689430301
|
||||
:admins #{}
|
||||
:members-joined #{}
|
||||
:name "Darkviolet Lightgreen Halcyon"
|
||||
:membership-updates ()
|
||||
:unviewed-messages-count 0
|
||||
:last-message-content-type "text/plain"
|
||||
:last-message-content
|
||||
{:chat-id
|
||||
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
|
||||
:text "Djndjd"}
|
||||
:debug? false
|
||||
:group-chat false
|
||||
:public? false
|
||||
:chat-id
|
||||
"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"
|
||||
:timestamp 1547186895328
|
||||
:deleted-at-clock-value nil}])
|
||||
|
||||
(def multiaccount
|
||||
{:last-updated 0
|
||||
:address "7540c34d6c4082391f12468580a9a4e0724c6755"
|
||||
:mnemonic "tumble gorilla neglect dumb budget involve tennis ocean diary eagle lady ring"
|
||||
:custom-bootnodes {}
|
||||
:signing-phrase "bull exam weed"
|
||||
:signed-up? true
|
||||
:name "name"
|
||||
:last-request nil
|
||||
:wallet/visible-tokens {:mainnet #{:SNT}
|
||||
:goerli #{:STT}
|
||||
:xdai #{}}
|
||||
:preview-privacy? true
|
||||
:fleet :eth.prod
|
||||
:wallet-set-up-passed? false
|
||||
:public-key
|
||||
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
|
||||
:keycard-key-uid nil
|
||||
:installation-id "618ec020-13c8-5505-8aa6-9c5444317e7f"})
|
||||
|
||||
(def multiaccounts
|
||||
{"address" multiaccount})
|
||||
|
||||
(defn get-chats [_ _] chats)
|
||||
|
||||
(def transport
|
||||
{"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"
|
||||
{:resend? nil}
|
||||
"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"
|
||||
{:resend? nil}
|
||||
"status"
|
||||
{:resend? nil}})
|
||||
|
||||
(def topics
|
||||
{"0xf8946aac" {:chat-ids #{:discovery-topic}
|
||||
:last-request 1547319670}})
|
||||