Compare commits

..
Author SHA1 Message Date
alwx 7376ae1509 Updates for unread messages indicator 2023-10-02 17:30:58 +02:00
1437 changed files with 14317 additions and 14995 deletions
+48 -7
View File
@@ -50,6 +50,13 @@ status-im2.config/rpc-networks-only?
status-im2.config/waku-enabled?
status-im.utils.pairing/has-paired-installations?
status-im.tribute-to-talk.core-test/user-cofx
quo.gesture-handler/tap-gesture-handler
quo.gesture-handler/pan-gesture-handler
quo.gesture-handler/long-press-gesture-handler
quo.gesture-handler/pure-native-button
quo.gesture-handler/createNativeWrapper
quo.gesture-handler/animated-raw-button
quo.gesture-handler/states
mocks.js-dependencies/action-button
mocks.js-dependencies/camera
mocks.js-dependencies/dismiss-keyboard
@@ -93,13 +100,46 @@ react-native.fs/read-dir
react-native.fs/mkdir
react-native.fs/unlink
react-native.fs/file-exists?
status-im.ui.components.colors/white
status-im.ui.components.colors/black
quo.animated/code
quo.animated/eq
quo.animated/neq
quo.animated/greater-or-eq
quo.animated/not*
quo.animated/or*
quo.animated/and*
quo.animated/add
quo.animated/sub
quo.animated/multiply
quo.animated/abs
quo.animated/min*
quo.animated/max*
quo.animated/set
quo.animated/start-clock
quo.animated/stop-clock
quo.animated/bezier
quo.animated/linear
quo.animated/set-value
quo.animated/clock
quo.animated/debug
quo.animated/log
quo.animated/event
quo.animated/on-change
quo.animated/cond*
quo.animated/block
quo.animated/call*
quo.animated/timing
quo.animated/spring
quo.animated/clamp
quo.animated/with-spring
quo.animated/with-timing
quo.animated/re-timing
quo.design-system.colors/white
quo.design-system.colors/black
status-im.transport.core-test/messages
status-im.ui.components.core/animated-header
status-im.ui.components.core/safe-area-provider
status-im.ui.components.core/safe-area-consumer
status-im.ui.components.core/safe-area-view
quo.core/animated-header
quo.core/safe-area-provider
quo.core/safe-area-consumer
quo.core/safe-area-view
status-im.utils.universal-links.core/open!
status-im.transport.filters.core-test/member-2
status-im.ui.screens.chat.ttt/one-to-one-chat-description-container
@@ -149,7 +189,7 @@ status-im.utils.async/async-periodic-run!
status-im.desktop.core/app-root
status-im.desktop.core/init
status-im.chat.models-test/test-db
status-im.ui.components.spacing/padding-vertical
quo.design-system.spacing/padding-vertical
status-im.utils.money/percent-change
utils.debounce/clear-all
status-im.transport.db/create-chat
@@ -166,6 +206,7 @@ status-im.ui.components.animation/add
status-im.ui.components.animation/subtract
status-im.ui.components.animation/get-layout
status-im.wallet.transactions-test/uri-query-data
quo.react-native/touchable-highlight
status-im.utils.name/shortened-name
status-im.ui.components.button.haptic/trigger
status-im.contact.db/filter-dapps
-1
View File
@@ -1 +0,0 @@
{:lint-as {babashka.fs/with-temp-dir clojure.core/let}}
-1
View File
@@ -1 +0,0 @@
{:hooks {:macroexpand {sci.core/copy-ns sci.core/copy-ns}}}
-9
View File
@@ -1,9 +0,0 @@
(ns sci.core)
(defmacro copy-ns
([ns-sym sci-ns]
`(copy-ns ~ns-sym ~sci-ns nil))
([ns-sym sci-ns opts]
`[(quote ~ns-sym)
~sci-ns
(quote ~opts)]))
+3 -13
View File
@@ -1,5 +1,4 @@
{:config-paths ["status-im"]
:lint-as {status-im.utils.views/defview clojure.core/defn
{:lint-as {status-im.utils.views/defview clojure.core/defn
status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
@@ -10,29 +9,23 @@
status-im.utils.styles/defn clojure.core/defn
test-helpers.unit/deftest-sub clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:case-duplicate-test {:level :error}
:case-quoted-test {:level :error}
:case-symbol-test {:level :error}
:clj-kondo-config {:level :error}
:linters {:clj-kondo-config {:level :error}
:cond-else {:level :error}
:consistent-alias {:level :error
:aliases {clojure.string string
clojure.set set
clojure.walk walk
taoensso.timbre log}}
:deprecated-namespace {:level :warning}
:docstring-blank {:level :error}
:equals-true {:level :error}
:inline-def {:level :error}
:invalid-arity {:skip-args [status-im.utils.fx/defn utils.re-frame/defn]}
:loop-without-recur {:level :error}
:minus-one {:level :error}
:misplaced-docstring {:level :error}
:missing-body-in-when {:level :error}
:missing-clause-in-try {:level :error}
:missing-else-branch {:level :error}
:not-empty? {:level :error}
:plus-one {:level :error}
:quoted-case-test-constant {:level :error}
:redundant-do {:level :error}
:redundant-let {:level :error}
:refer-all {:level :error}
@@ -46,7 +39,6 @@
:single-operand-comparison {:level :error}
:syntax {:level :error}
:unbound-destructuring-default {:level :error}
:uninitialized-var {:level :error}
:unknown-require-option {:level :error}
:unreachable-code {:level :error}
:unresolved-namespace {:level :error}
@@ -56,8 +48,6 @@
number
status-im.test-helpers/restore-app-db]}
:unresolved-var {:level :error}
:unsorted-required-namespaces {:level :error}
:unused-alias {:level :warning}
:unused-binding {:level :error}
:unused-import {:level :error}
:unused-namespace {:level :error}
-2
View File
@@ -1,2 +0,0 @@
{:hooks {:analyze-call {utils.i18n/label hooks.core/i18n-label}}
:linters {:status-im.linter/invalid-translation-keyword {:level :error}}}
-19
View File
@@ -1,19 +0,0 @@
(ns hooks.core
(:require [clj-kondo.hooks-api :as api]))
(defn i18n-label
"Verify call to `i18n/label` pass the translation keyword qualified with `t`."
[{:keys [node]}]
(let [[_ translation-key-node & _] (:children node)]
(when (and (api/keyword-node? translation-key-node)
(not= "t" (-> translation-key-node api/sexpr namespace)))
(api/reg-finding! (assoc (meta translation-key-node)
:message "Translation keyword should be qualified with \"t\""
:type :status-im.linter/invalid-translation-keyword)))))
(comment
;; Valid
(i18n-label {:node (api/parse-string "(i18n/label :t/foo)")})
;; Invalid
(i18n-label {:node (api/parse-string "(i18n/label :foo)")}))
-1
View File
@@ -33,4 +33,3 @@ SWAP_ENABLED=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1
TEST_STATEOFUS=1
FAST_CREATE_COMMUNITY_ENABLED=1
-1
View File
@@ -19,4 +19,3 @@ ENABLE_ROOT_ALERT=1
MAX_IMAGES_BATCH=1
DELETE_MESSAGE_ENABLED=1
COLLECTIBLES_ENABLED=1
FAST_CREATE_COMMUNITY_ENABLED=0
-1
View File
@@ -31,7 +31,6 @@
"defview" :arg1-body
"letsubs" :binding
"with-let" "let"
"reg-event-fx" :arg1-pair
"testing" :arg1-body
"deftest-sub" :arg1-body
"wait-for" :arg1-body
+2 -3
View File
@@ -309,11 +309,10 @@ $$(comm -23 <(sort <(git ls-files --cached --others --exclude-standard)) <(sort
endef
lint: export TARGET := clojure
lint: export CLJ_LINTER_PRINT_WARNINGS ?= false
lint: ##@test Run code style checks
@sh scripts/lint-re-frame-in-quo-components.sh && \
sh scripts/lint-old-quo-usage.sh && \
clj-kondo --config .clj-kondo/config.edn --cache false --fail-level error --lint src $(if $(filter $(CLJ_LINTER_PRINT_WARNINGS),true),,| grep -v ': warning: ') && \
sh scripts/lint-old-quo-usage.sh \
clj-kondo --config .clj-kondo/config.edn --cache false --fail-level error --lint src && \
ALL_CLOJURE_FILES=$(call find_all_clojure_files) && \
zprint '{:search-config? true}' -sfc $$ALL_CLOJURE_FILES && \
sh scripts/lint-trailing-newline.sh && \
+2 -2
View File
@@ -16,7 +16,7 @@ We believe in a medium of pure free trade, economies with fair, permission-less
We want to take responsibility for our data, the way we conduct ourselves privately and promote this way of life to a mass audience.
We want deep insights into our own economies so we can make informed, data-driven decisions on how to make our lives better. The Ethereum blockchain, Smart Contracts, Swarm and Whisper provide us a path forward.
We want deep insights into our own economies so we can make informed, data-driven decisions on how to make our lives better. The Ethereum blockchain, Smart Contracts, Swarm and Whisper provides us a path forward.
If this interests you, **help us make Status a reality** - anyone can contribute and we need everyone at any skill level to participate.
@@ -40,7 +40,7 @@ Check out our [coding guidelines](doc/new-guidelines.md).
Metcalfe's law states that the value of a network is proportional to the square of the number of connected users of the system - without community Status is meaningless. We're looking to create a positive, fun environment to explore new ideas, experiment and grow the Status community. Building a community takes a lot of work but the people you'll meet and long lasting relationships you form will be well worth it, check out our [Mission and Community Principles](https://status.im/about)
- **Specification / Documentation**
John Dewey once said "Education is not preparation for life; education is life *itself* ". Developers and Designers need guidance and it all starts from documentation and specifications. Our software is only as good as its documentation, check out our [docs](doc/) and see how you can improve what we have.
John Dewey once said "Education is not preparation for life; education is life *itself* ". Developers & Designers need guidance and it all starts from documentation and specifications. Our software is only as good as its documentation, check out our [docs](doc/) and see how you can improve what we have.
- **Blog Writing**
Content is King, keeping our blog up to date and informing the community of news helps keep everyone on the same page.
+1 -1
View File
@@ -48,7 +48,7 @@ pipeline {
steps {
sh """#!/bin/bash
set -eo pipefail
make lint CLJ_LINTER_PRINT_WARNINGS=true 2>&1 | tee ${LOG_FILE}
make lint 2>&1 | tee ${LOG_FILE}
"""
}
}
-37
View File
@@ -45,42 +45,6 @@ To do this:
![geth.log path](images/debugging/geth-path.png)
## Inspecting database content
Encrypted database can be found using commands:
```
cd ~/Library/Developer/CoreSimulator/Devices
find ./ -name accounts.sql
```
To get unencrypted database you need to export it first:
- open the status app in simulator
- on login screen enter the correct password without logging in
- using repl execute statement to export db:
```
(re-frame.core/dispatch [:multiaccounts.login.ui/export-db-submitted])
```
- save generated `export.db` file
Now you can locate the `export.db` and open it with preferred db viewer.
**Android:**
```sh
adb root
adb pull /storage/emulated/0/Android/data/im.status.ethereum.debug/files/Download/export.db /path/to/store/export.db
```
If you're using a release build, change the path to `/storage/emulated/0/Android/data/im.status.ethereum/files/Download`
**iOS**
```
cd ~/Library/Developer/CoreSimulator/Devices
find ./ -name export.db
```
## Tips
### From @ilmotta:
@@ -98,4 +62,3 @@ Also to inspect logs in a more flexible manner, instead of the strict output fro
```
adb logcat | grep 'ReactNativeJS\|StatusModule\|GoLog'
```
@@ -1,31 +0,0 @@
### How should the paste button behave when clipboard is empty?
This question first came up as a review comment to this PR
https://github.com/status-im/status-mobile/pull/16852
There were 2 considerations :
- Either keep the paste button disabled when there is nothing in the clipboard OR
- Always keep the paste button enabled
There were positives and negatives for both approaches.
Positives of keeping paste button disabled when there is nothing in clipboard would require us to
check the value of clipboard as soon as the component is mounted (i.e when the user first sees
the screen). In iOS this means a native permissions dialog would appear requesting for permissions
to paste from the clipboard.
Negatives of this approach is that as soon as any user navigates to this screen they are greeted with
this popup which can be annoying sometimes.
Positives of keeping paste button always enabled is that we can trigger a request to the clipboard on
tap of the paste button which would trigger the native permissions dialog requesting for permissions
to paste from the clipboard.
In this case seeing this dialog is okay because the user has initiated a paste action.
Negatives of this approach is that in the event the clipboard is empty the user will still see the
system dialog and on approving nothing will be pasted (because the clipboard was empty).
This behaviour can be confusing.
On consulting the Design Team via discord it was concluded that out of the two approaches
having the paste button always enabled is a better UX overall.
+1 -1
View File
@@ -12,4 +12,4 @@
[vector-icons/icon :icon-name {:color ...}]
```
2x@2x
4. If you want platform specific icon use `.android` or `.ios` suffixes. Example `icon_name@2x.android.png`.:w
4. If you want paltform specific icon use `.android` or `.ios` suffixes. Example `icon_name@2x.android.png`.:w
+3 -3
View File
@@ -19,7 +19,7 @@ For now we support e2e for Android only.
## What's happening when any e2e job is running
Whenever we need to push set of test scripts we create 16 parallel sessions (max, but depending on amount of cases that are included in job) and each thread: 1) uploads Android .apk file to SauceLabs -> 2) runs through the test steps -> 3) receives results whether test failed on particular step or succeeded with no errors -> 3) Parse test results and push them as a Github comment (if the suite ran against respective PR) and into TestRail.
We push **whole automation test suite (currently 155, amount is changing)** against each nightly build (if the nightly builds job succeeded). Results of the test run are saved in TestRail.
We push **whole automation test suite (currently 155, amout is changing)** against each nightly build (if the nightly builds job succeeded). Results of the test run are saved in TestRail.
And also we push set of autotests whenever PR with successful builds got moved in to `E2E Tests` column from [Pipeline for QA dashboard ](https://github.com/status-im/status-react/projects/7).
In that case we save results in TestRail as well and push a comment with test results in a respective PR.
@@ -49,8 +49,8 @@ Params to specify:
- apk: [url_to_apk_build_here]
- pr_id: pull request number (e.g. 1234)
- branch: branch name from which the test are taken (in most of cases `develop`)
- keyword expression: tests by area (let's say `ens` or `chat`, they can be combined`ens or chat or send_tx`. All keywords can be found in testrail, ping Chu for details)
- test_marks: tests by priorities (by default: `critical or high or medium`, which corresponds the whole suite; to launch the same suite as in PRs, use `critical or high`)
- keyword expression: tests by area (let's say `ens` or `chat`, thay can be combined`ens or chat or send_tx`. All keywords can be found in testrail, ping Chu for details)
- test_marks: tests by priorities (by default: `critical or high or medium`, which corresponds the whole suite; to lauch the same suite as in PRs, use `critical or high`)
- testrail_case_id: here is the list of test cases which you may find in test rail (4-digit value)
For easier access you can hit `Rerun tests` in GH comment and testrail_case_id/ apk_name/ pr_id will be filled automatically. For making sure that tests are being rerun on most recent e2e build it is recommended to paste link to the last e2e build in apk_name field. The list of PR builds can be found in Jenkins Builds block on PR page.
+1 -1
View File
@@ -90,7 +90,7 @@ Press the green run button
<img src="images/ide-setup/8_REPL_6.png" width=75% />
You should now see a dialog with the following message:
You should now see an dialog with the following message:
```shell
Connecting to remote nREPL server...
+10 -17
View File
@@ -467,28 +467,21 @@ Prefer the pure version of `:json-rpc/call` (no callbacks).
### Registering event handlers
Register events with `re-frame.core/reg-event-fx` and follow [re-frame's best
practice](https://github.com/day8/re-frame/blob/39adca93673f334dc751ee2d99d340b51a9cc6db/docs/FAQs/BestPractice.md#use-the-fx-effect)
so use only `:db` and `:fx` effects. `rf/merge` is deprecated and should not be
used in the new code in `src/status_im2/`. Don't use
Events must always be declared with the `utils.fx/defn` macro. Also, don't use
`re-frame.core/reg-event-db`.
```clojure
;; bad
(rf/defn invite-people-pressed
{:events [:communities/invite-people-pressed]}
[cofx id]
(rf/merge cofx
(reset-community-id-input id)
(bottom-sheet/hide-bottom-sheet)
(navigation/open-modal :invite-people-community {:invite? true})))
(re-frame/reg-event-fx
:wakuv2.ui/save-all-confirmed
(fn [{:keys [db] :as cofx}]
...))
;; good
(re-frame/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :invite-people-community {:invite? true}]]]}))
(fx/defn save-all
{:events [:wakuv2.ui/save-all-confirmed]}
[{:keys [db] :as cofx}]
...)
```
### Registering top-level re-frame subscriptions
@@ -812,7 +805,7 @@ alternative.
Please check the [Clojure Style](https://guide.clojure.style/#deprecated) documentation
To reduce visual clutter from deprecated methods in your text editor, consult this [example](https://rider-support.jetbrains.com/hc/en-us/community/posts/4419728641810-How-to-disable-the-the-strike-thru-for-deprecated-methods-in-Javascript-#:~:text=Try%20disabling%20%22Preferences%20%7C%20Editor%20%7C,It%20works%20for%20me). The approach can be adapted for settings in VSCode, Emacs, VIM, and others.
### Test structure
[Unit tests](#glossary) should be created alongside their respective source
+4 -4
View File
@@ -4,7 +4,7 @@ This document provides information on how to start developing Status App.
# Getting Started
To start developing start a shell for the platform you are interested in.
To start developing start a shell for platform you are interested in.
```
make shell TARGET=android
```
@@ -55,7 +55,7 @@ Some manual steps are necessary for [developing on a physical iOS Device](#physi
# Build release
To build the app, you can simply run on of the following:
To build the app, your can simply run on of the following:
```
make release-android
make release-ios
@@ -88,9 +88,9 @@ https://facebook.github.io/react-native/docs/getting-started.html
## Configure GitHub Account
The optimal way of pushing to GitHub is using SSH instead of user/pass auth.
The optimal way of pushing to GitHubis using SSH instead of user/pass auth.
It's recommended that you [add your public SSH key to your GitHub account](https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account).
It's recommented that you [add your public SSH key to your GitHub account](https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account).
## Configure GPG Keys for signing commits
+1
View File
@@ -33,6 +33,7 @@ abstract_target 'Status' do
pod 'react-native-image-resizer', :path => '../node_modules/react-native-image-resizer'
pod 'react-native-config', :path => '../node_modules/react-native-config'
pod 'SQLCipher', '~>3.0'
pod 'SSZipArchive', '2.4.3'
permissions_path = '../node_modules/react-native-permissions/ios'
+11 -3
View File
@@ -434,6 +434,11 @@ PODS:
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.10)
- secp256k1 (0.1.6)
- SQLCipher (3.4.2):
- SQLCipher/standard (= 3.4.2)
- SQLCipher/common (3.4.2)
- SQLCipher/standard (3.4.2):
- SQLCipher/common
- SSZipArchive (2.4.3)
- TOCropViewController (2.6.1)
- TouchID (4.4.1):
@@ -515,6 +520,7 @@ DEPENDENCIES:
- RNStaticSafeAreaInsets (from `../node_modules/react-native-static-safe-area-insets`)
- RNSVG (from `../node_modules/react-native-svg`)
- secp256k1 (from `https://github.com/status-im/secp256k1.swift.git`)
- SQLCipher (~> 3.0)
- SSZipArchive (= 2.4.3)
- TouchID (from `../node_modules/react-native-touch-id`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -527,6 +533,7 @@ SPEC REPOS:
- libwebp
- SDWebImage
- SDWebImageWebPCoder
- SQLCipher
- SSZipArchive
- TOCropViewController
@@ -700,7 +707,7 @@ SPEC CHECKSUMS:
FBLazyVector: a8af91c2b5a0029d12ff6b32e428863d63c48991
FBReactNativeSpec: 1b2309b096448a1dc9d0c43999216f8fda809ae8
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: d93527a855523adb8c113837db4be68fb00e230d
glog: 166d178815c300e8126de9a7900101814eb16253
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
@@ -765,18 +772,19 @@ SPEC CHECKSUMS:
RNLanguages: 962e562af0d34ab1958d89bcfdb64fafc37c513e
RNPermissions: ad71dd4f767ec254f2cd57592fbee02afee75467
RNReactNativeHapticFeedback: 2566b468cc8d0e7bb2f84b23adc0f4614594d071
RNReanimated: cf1259a8cf8dfb1d7fa586ce02565fd0610da1d7
RNReanimated: 43adb0307a62c1ce9694f36f124ca3b51a15272a
RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c
RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8
RNSVG: 80584470ff1ffc7994923ea135a3e5ad825546b9
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
secp256k1: f61d67e6fdcb85fd727acf1bf35ace6036db540c
SQLCipher: f9fcf29b2e59ced7defc2a2bdd0ebe79b40d4990
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
TouchID: ba4c656d849cceabc2e4eef722dea5e55959ecf4
Yoga: d24d6184b6b85f742536bd93bd07d69d7b9bb4c1
PODFILE CHECKSUM: 4d710b4207b0fb0f6ecef4aa92bb79b716a7d13c
PODFILE CHECKSUM: ac30a0172ff0126b6f307c20f34c47ce0ebf278f
COCOAPODS: 1.12.0
+2 -3
View File
@@ -12,13 +12,12 @@ By using the following command:
```sh
shadow-cljs classpath --force-spawn
```
We download the necessary JARs into `~/.m2` folder, but also get the classpath printed into standard output.
We skip POM files since they are not necessary, and add edge cases we don't want to handle.
We both download the necessary JARs and POMs into `~/.m2` folder, but also get the classpath printed into standard output.
We then use the classpath in combination with contents of `~/.m2` folder to generate the following files:
* `deps.list` - List of JARs relative to the `~/.m2` cache folder.
* `deps.json` - Full list of JARs including their SHAs.
* `deps.json` - Full list of JARs and POMs including their SHAs.
The `deps.list` file is just intermediate and for debugging purposes.
The `deps.json` is loaded by the derivation in [`default.nix`](./default.nix) and used to produce a derivation that contains all the necessary dependencies:
+19 -3
View File
@@ -8,6 +8,9 @@ let
# load dependencies
deps = importJSON ./deps.json;
# some .jar files have an `-aot` suffix that doesn't work for .pom files
getPOM = jarUrl: "${removeSuffix "-aot" jarUrl}.pom";
script = writeShellScriptBin "create-local-maven-repo" (''
mkdir -p $out
cd $out
@@ -15,11 +18,18 @@ let
(concatMapStrings (dep:
let
url = "${dep.host}/${dep.path}";
pom = {
sha1 = attrByPath [ "pom" "sha1" ] "" dep;
sha256 = attrByPath [ "pom" "sha256" ] "" dep;
};
pom-download = optionalString (pom.sha256 != "") (
fetchurl { url = getPOM url; inherit (pom) sha256; }
);
jar = {
sha1 = attrByPath [ "jar" "sha1" ] "" dep;
sha256 = attrByPath [ "jar" "sha256" ] "" dep;
};
jarFile = optionalString (jar.sha256 != "") (
jar-download = optionalString (jar.sha256 != "") (
fetchurl { url = "${url}.jar"; inherit (jar) sha256; }
);
fileName = last (splitString "/" dep.path);
@@ -28,8 +38,14 @@ let
''
mkdir -p ${directory}
${optionalString (jarFile != "") ''
ln -s "${jarFile}" "${dep.path}.jar"
${optionalString (pom-download != "") ''
ln -s "${pom-download}" "${getPOM dep.path}"
''}
${optionalString (pom.sha1 != "") ''
echo "${pom.sha1}" > "${getPOM dep.path}.sha1"
''}
${optionalString (jar-download != "") ''
ln -s "${jar-download}" "${dep.path}.jar"
''}
${optionalString (jar.sha1 != "") ''
echo "${jar.sha1}" > "${dep.path}.jar.sha1"
+640 -381
View File
File diff suppressed because it is too large Load Diff
+52 -69
View File
@@ -1,120 +1,103 @@
args4j/args4j/2.33/args4j-2.33.jar
babashka/fs/0.2.16/fs-0.2.16.jar
bidi/bidi/2.1.6/bidi-2.1.6.jar
binaryage/env-config/0.2.2/env-config-0.2.2.jar
binaryage/oops/0.7.2/oops-0.7.2.jar
borkdude/edamame/1.1.17/edamame-1.1.17.jar
borkdude/sci.impl.reflector/0.0.1/sci.impl.reflector-0.0.1.jar
camel-snake-kebab/camel-snake-kebab/0.4.3/camel-snake-kebab-0.4.3.jar
cheshire/cheshire/5.11.0/cheshire-5.11.0.jar
cider/cider-nrepl/0.25.3/cider-nrepl-0.25.3.jar
cider/piggieback/0.4.1/piggieback-0.4.1.jar
clj-kondo/clj-kondo/2023.09.07/clj-kondo-2023.09.07.jar
cider/cider-nrepl/0.29.0/cider-nrepl-0.29.0.jar
cider/piggieback/0.5.2/piggieback-0.5.2.jar
cljs-bean/cljs-bean/1.3.0/cljs-bean-1.3.0.jar
clout/clout/2.1.2/clout-2.1.2.jar
com/andrewmcveigh/cljs-time/0.5.2/cljs-time-0.5.2.jar
com/bhauman/cljs-test-display/0.1.1/cljs-test-display-0.1.1.jar
com/cognitect/transit-clj/1.0.329/transit-clj-1.0.329.jar
com/cognitect/transit-clj/1.0.324/transit-clj-1.0.324.jar
com/cognitect/transit-cljs/0.8.248/transit-cljs-0.8.248.jar
com/cognitect/transit-java/1.0.362/transit-java-1.0.362.jar
com/cognitect/transit-java/1.0.343/transit-java-1.0.343.jar
com/cognitect/transit-js/0.8.846/transit-js-0.8.846.jar
com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.jar
com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.13.3/jackson-dataformat-cbor-2.13.3.jar
com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.13.3/jackson-dataformat-smile-2.13.3.jar
com/github/javaparser/javaparser-core/3.25.3/javaparser-core-3.25.3.jar
com/google/auto/value/auto-value-annotations/1.6/auto-value-annotations-1.6.jar
com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar
com/google/code/gson/gson/2.9.1/gson-2.9.1.jar
com/fasterxml/jackson/core/jackson-core/2.8.7/jackson-core-2.8.7.jar
com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar
com/google/errorprone/error_prone_annotations/2.15.0/error_prone_annotations-2.15.0.jar
com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar
com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar
com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar
com/google/javascript/closure-compiler-unshaded/v20230411/closure-compiler-unshaded-v20230411.jar
com/google/protobuf/protobuf-java/3.21.12/protobuf-java-3.21.12.jar
com/google/re2j/re2j/1.3/re2j-1.3.jar
commons-codec/commons-codec/1.15/commons-codec-1.15.jar
com/google/javascript/closure-compiler-unshaded/v20210302/closure-compiler-unshaded-v20210302.jar
com/ibm/icu/icu4j/66.1/icu4j-66.1.jar
commons-codec/commons-codec/1.10/commons-codec-1.10.jar
commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar
commons-io/commons-io/2.11.0/commons-io-2.11.0.jar
commons-io/commons-io/2.6/commons-io-2.6.jar
compojure/compojure/1.5.2/compojure-1.5.2.jar
com/taoensso/encore/3.21.0/encore-3.21.0.jar
com/taoensso/encore/2.105.0/encore-2.105.0.jar
com/taoensso/timbre/4.10.0/timbre-4.10.0.jar
com/taoensso/truss/1.6.0/truss-1.6.0.jar
com/taoensso/truss/1.5.0/truss-1.5.0.jar
com/taoensso/tufte/2.1.0/tufte-2.1.0.jar
crypto-equality/crypto-equality/1.0.1/crypto-equality-1.0.1.jar
crypto-random/crypto-random/1.2.1/crypto-random-1.2.1.jar
com/wsscode/pathom/2.2.31/pathom-2.2.31.jar
com/wsscode/spec-inspec/1.0.0-alpha2/spec-inspec-1.0.0-alpha2.jar
crypto-equality/crypto-equality/1.0.0/crypto-equality-1.0.0.jar
crypto-random/crypto-random/1.2.0/crypto-random-1.2.0.jar
day8/re-frame/test/0.1.5/test-0.1.5.jar
expound/expound/0.9.0/expound-0.9.0.jar
fipp/fipp/0.6.26/fipp-0.6.26.jar
edn-query-language/eql/0.0.9/eql-0.0.9.jar
expound/expound/0.8.5/expound-0.8.5.jar
fipp/fipp/0.6.23/fipp-0.6.23.jar
hiccup/hiccup/1.0.5/hiccup-1.0.5.jar
hickory/hickory/0.7.1/hickory-0.7.1.jar
http-kit/http-kit/2.2.0/http-kit-2.2.0.jar
instaparse/instaparse/1.4.0/instaparse-1.4.0.jar
io/aviso/pretty/0.1.33/pretty-0.1.33.jar
io/methvin/directory-watcher/0.17.1/directory-watcher-0.17.1.jar
io/replikativ/datalog-parser/0.2.25/datalog-parser-0.2.25.jar
io/undertow/undertow-core/2.2.4.Final/undertow-core-2.2.4.Final.jar
javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar
javax/servlet/servlet-api/2.5/servlet-api-2.5.jar
javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar
medley/medley/0.8.2/medley-0.8.2.jar
mvxcvi/alphabase/1.0.0/alphabase-1.0.0.jar
net/cgrand/macrovich/0.2.1/macrovich-0.2.1.jar
net/java/dev/jna/jna/5.12.1/jna-5.12.1.jar
nrepl/bencode/1.1.0/bencode-1.1.0.jar
nrepl/nrepl/1.0.0/nrepl-1.0.0.jar
org/apache/ant/ant/1.10.11/ant-1.10.11.jar
org/apache/ant/ant-launcher/1.10.11/ant-launcher-1.10.11.jar
org/babashka/sci/0.7.38/sci-0.7.38.jar
org/babashka/sci.impl.types/0.0.2/sci.impl.types-0.0.2.jar
org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0.jar
org/clojure/clojure/1.11.1/clojure-1.11.1.jar
org/clojure/clojurescript/1.11.60/clojurescript-1.11.60.jar
org/clojure/core.async/1.5.648/core.async-1.5.648.jar
org/clojure/core.cache/1.0.225/core.cache-1.0.225.jar
org/clojure/core.memoize/1.0.253/core.memoize-1.0.253.jar
org/clojure/core.rrb-vector/0.1.2/core.rrb-vector-0.1.2.jar
org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.jar
org/clojure/data.json/2.4.0/data.json-2.4.0.jar
org/clojure/data.priority-map/1.1.0/data.priority-map-1.1.0.jar
org/clojure/google-closure-library/0.0-20230227-c7c0a541/google-closure-library-0.0-20230227-c7c0a541.jar
org/clojure/google-closure-library-third-party/0.0-20230227-c7c0a541/google-closure-library-third-party-0.0-20230227-c7c0a541.jar
org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.jar
org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.jar
org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2.jar
org/clojure/tools.cli/1.0.206/tools.cli-1.0.206.jar
org/clojure/clojure/1.10.1/clojure-1.10.1.jar
org/clojure/clojurescript/1.10.844/clojurescript-1.10.844.jar
org/clojure/core.async/1.3.610/core.async-1.3.610.jar
org/clojure/core.cache/1.0.207/core.cache-1.0.207.jar
org/clojure/core.memoize/1.0.236/core.memoize-1.0.236.jar
org/clojure/core.rrb-vector/0.1.1/core.rrb-vector-0.1.1.jar
org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar
org/clojure/data.json/1.0.0/data.json-1.0.0.jar
org/clojure/data.priority-map/1.0.0/data.priority-map-1.0.0.jar
org/clojure/google-closure-library/0.0-20201211-3e6c510d/google-closure-library-0.0-20201211-3e6c510d.jar
org/clojure/google-closure-library-third-party/0.0-20201211-3e6c510d/google-closure-library-third-party-0.0-20201211-3e6c510d.jar
org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar
org/clojure/test.check/1.1.0/test.check-1.1.0.jar
org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.jar
org/clojure/tools.analyzer.jvm/1.1.0/tools.analyzer.jvm-1.1.0.jar
org/clojure/tools.cli/1.0.194/tools.cli-1.0.194.jar
org/clojure/tools.logging/1.1.0/tools.logging-1.1.0.jar
org/clojure/tools.macro/0.1.5/tools.macro-0.1.5.jar
org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.jar
org/clojure/tools.reader/1.3.3/tools.reader-1.3.3.jar
org/graalvm/js/js/20.1.0/js-20.1.0.jar
org/graalvm/js/js-scriptengine/20.1.0/js-scriptengine-20.1.0.jar
org/graalvm/regex/regex/20.1.0/regex-20.1.0.jar
org/graalvm/sdk/graal-sdk/20.1.0/graal-sdk-20.1.0.jar
org/graalvm/truffle/truffle-api/20.1.0/truffle-api-20.1.0.jar
org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar
org/jboss/logging/jboss-logging/3.4.1.Final/jboss-logging-3.4.1.Final.jar
org/jboss/threads/jboss-threads/3.1.0.Final/jboss-threads-3.1.0.Final.jar
org/jboss/xnio/xnio-api/3.8.0.Final/xnio-api-3.8.0.Final.jar
org/jboss/xnio/xnio-nio/3.8.0.Final/xnio-nio-3.8.0.Final.jar
org/jsoup/jsoup/1.9.2/jsoup-1.9.2.jar
org/jspecify/jspecify/0.2.0/jspecify-0.2.0.jar
org/msgpack/msgpack/0.6.12/msgpack-0.6.12.jar
org/ow2/asm/asm/9.4/asm-9.4.jar
org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar
org/ow2/asm/asm/7.1/asm-7.1.jar
org/ow2/asm/asm-analysis/7.1/asm-analysis-7.1.jar
org/ow2/asm/asm-commons/7.1/asm-commons-7.1.jar
org/ow2/asm/asm-tree/7.1/asm-tree-7.1.jar
org/ow2/asm/asm-util/7.1/asm-util-7.1.jar
org/wildfly/client/wildfly-client-config/1.0.1.Final/wildfly-client-config-1.0.1.Final.jar
org/wildfly/common/wildfly-common/1.5.2.Final/wildfly-common-1.5.2.Final.jar
prismatic/schema/1.1.7/schema-1.1.7.jar
quoin/quoin/0.1.2/quoin-0.1.2.jar
reagent/reagent/1.2.0/reagent-1.2.0.jar
re-com/re-com/2.8.0/re-com-2.8.0.jar
refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0.jar
refactor-nrepl/refactor-nrepl/3.6.0/refactor-nrepl-3.6.0.jar
re-frame/re-frame/1.3.0/re-frame-1.3.0.jar
re-frisk-remote/re-frisk-remote/1.6.0/re-frisk-remote-1.6.0.jar
re-frisk/sente/1.15.0/sente-1.15.0.jar
ring-cors/ring-cors/0.1.8/ring-cors-0.1.8.jar
ring/ring-codec/1.2.0/ring-codec-1.2.0.jar
ring/ring-core/1.9.6/ring-core-1.9.6.jar
ring/ring-codec/1.1.2/ring-codec-1.1.2.jar
ring/ring-core/1.8.1/ring-core-1.8.1.jar
spec-coerce/spec-coerce/1.0.0-alpha6/spec-coerce-1.0.0-alpha6.jar
status-im/timbre/4.10.0-2-status/timbre-4.10.0-2-status.jar
thheller/shadow-client/1.3.3/shadow-client-1.3.3.jar
thheller/shadow-cljs/2.25.0/shadow-cljs-2.25.0-aot.jar
thheller/shadow-cljsjs/0.0.22/shadow-cljsjs-0.0.22.jar
thheller/shadow-undertow/0.3.1/shadow-undertow-0.3.1.jar
thheller/shadow-cljs/2.12.0/shadow-cljs-2.12.0-aot.jar
thheller/shadow-cljsjs/0.0.21/shadow-cljsjs-0.0.21.jar
thheller/shadow-undertow/0.1.0/shadow-undertow-0.1.0.jar
thheller/shadow-util/0.7.0/shadow-util-0.7.0.jar
tigris/tigris/0.1.2/tigris-0.1.2.jar
viebel/codox-klipse-theme/0.0.1/codox-klipse-theme-0.0.1.jar
+9
View File
@@ -53,6 +53,8 @@ function nix_entry_from_jar() {
JAR_PATH="${MAVEN_CACHE_PATH}/${JAR_REL_PATH}"
JAR_NAME=$(basename "${JAR_PATH}")
JAR_DIR=$(dirname "${JAR_PATH}")
# POM might have a slightly different name
POM_PATH=$(echo ${JAR_DIR}/*.pom)
REPO_NAME=$(get_repo_for_dir "${JAR_DIR}")
REPO_URL=${REPOS[${REPO_NAME}]}
@@ -60,11 +62,18 @@ function nix_entry_from_jar() {
JAR_SHA1=$(cat "${JAR_PATH}.sha1")
JAR_SHA256=$(get_nix_sha "${JAR_PATH}")
POM_SHA1=$(cat "${POM_PATH}.sha1")
POM_SHA256=$(get_nix_sha "${POM_PATH}")
# Format into a Nix attrset entry
echo -n "
{
\"path\": \"${JAR_REL_NAME}\",
\"host\": \"${REPO_URL}\",
\"pom\": {
\"sha1\": \"${POM_SHA1}\",
\"sha256\": \"${POM_SHA256}\"
},
\"jar\": {
\"sha1\": \"${JAR_SHA1}\",
\"sha256\": \"${JAR_SHA256}\"
-13
View File
@@ -38,19 +38,6 @@ in {
};
});
# Clojure's linter receives frequent upgrades, and we want to take advantage
# of the latest available rules.
clj-kondo = super.clj-kondo.override rec {
buildGraalvmNativeImage = args: super.buildGraalvmNativeImage (args // rec {
inherit (args) pname;
version = "2023.09.07";
src = super.fetchurl {
url = "https://github.com/clj-kondo/${pname}/releases/download/v${version}/${pname}-${version}-standalone.jar";
sha256 = "sha256-F7ePdITYKkGB6nsR3EFJ7zLDCUoT0g3i+AAjXzBd624=";
};
});
};
# Checks fail on darwin.
git-lfs = super.git-lfs.overrideAttrs (old: {
doCheck = false;
+1 -1
View File
@@ -22,7 +22,7 @@ buildGoPackage {
runHook preBuild
go build \
-buildmode='c-archive' \
-tags='gowaku_skip_migrations gowaku_no_rln' \
-tags='gowaku_skip_migrations' \
-o "$out/libstatus.a" \
$NIX_BUILD_TOP/main.go
runHook postBuild
+1 -1
View File
@@ -48,7 +48,7 @@ in buildGoPackage {
-target=${concatStringsSep "," targets} \
${optionalString isAndroid "-androidapi=${platformVersion}" } \
${optionalString isIOS "-iosversion=${platformVersion}" } \
-tags='${optionalString isIOS "nowatchdog"} gowaku_skip_migrations gowaku_no_rln' \
-tags='${optionalString isIOS "nowatchdog"} gowaku_skip_migrations' \
-o ${outputFileName} \
${source.goPackagePath}/mobile
+1
View File
@@ -35,6 +35,7 @@
"functional-red-black-tree": "^1.0.1",
"i18n-js": "^3.3.0",
"node-libs-react-native": "^1.2.1",
"qrcode": "^1.4.1",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-native": "0.69.10",
BIN
View File
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 KiB

After

Width:  |  Height:  |  Size: 539 KiB

+1 -7
View File
@@ -11,16 +11,11 @@
[com.cognitect/transit-cljs "0.8.248"]
[mvxcvi/alphabase "1.0.0"]
[camel-snake-kebab "0.4.3"]
;; Dev dependencies
[refactor-nrepl "2.5.0"]
[cider/cider-nrepl "0.25.3"]
[cider/piggieback "0.4.1"]
[re-frisk-remote "1.6.0"]
;; Use the same version specified in the Nix dependency.
[clj-kondo/clj-kondo "2023.09.07"]
;; We don't use the encore library, but re-frisk requires re-frisk/sente (fork of
;; com.taoensso/sente), which in turn requires encore. We need to bump encore to
;; 3.21.0+ to remove a warning displayed while shadow-cljs starts (commit
@@ -112,7 +107,7 @@
:optimizations :simple
:target :node-test
;; Uncomment line below to `make test-watch` a specific file
;; :ns-regexp "status-im2.subs.messages-test$"
;; :ns-regexp "status-im2.subs.chat.messages-test$"
:main
status-im.test-runner/main
;; set :ui-driven to true to let shadow-cljs inject node-repl
@@ -134,7 +129,6 @@
;; needed because we use deref in tests
:static-fns false
:optimizations :simple
:warnings {:fn-deprecated false}
:infer-externs true}}
;; mock.js-dependencies is mocking the react-native libraries
+2 -7
View File
@@ -1,7 +1,6 @@
(ns mocks.js-dependencies
(:require-macros [status-im.utils.slurp :refer [slurp]])
(:require
[status-im.fleet.default-fleet :refer (default-fleets)])
(:require [status-im.fleet.default-fleet :refer (default-fleets)])
(:require [status-im.utils.test :as utils.test]))
;; to generate a js Proxy at js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ that accept any (.xxx) call and
@@ -324,11 +323,7 @@
(def react-native-permissions #js {:default #js {}})
(def push-notification-ios
#js
{:default #js
{:abandonPermissions identity
:removeAllDeliveredNotifications identity}})
(def push-notification-ios #js {:default #js {:abandonPermissions identity}})
(def rn-emoji-keyboard
#js {:EmojiKeyboard #js {}})
+8 -8
View File
@@ -1,11 +1,11 @@
(ns native-module.core
(:require
["react-native" :as react-native]
[clojure.string :as string]
[react-native.platform :as platform]
[taoensso.timbre :as log]
[utils.transforms :as types]
[utils.validators :as validators]))
(:require ["react-native" :as react-native]
[utils.validators :as validators]
[taoensso.timbre :as log]
[react-native.platform :as platform]
[react-native.core :as rn]
[utils.transforms :as types]
[clojure.string :as string]))
(defn status
[]
@@ -14,7 +14,7 @@
(defn init
[handler]
(.addListener ^js (.-DeviceEventEmitter ^js react-native) "gethEvent" #(handler (.-jsonEvent ^js %))))
(.addListener ^js rn/device-event-emitter "gethEvent" #(handler (.-jsonEvent ^js %))))
(defn clear-web-data
[]
-43
View File
@@ -1,43 +0,0 @@
(ns native-module.push-notifications
(:require
["react-native" :as react-native]
[taoensso.timbre :as log]))
(defn push-notification
[]
(when (exists? (.-NativeModules react-native))
(.-PushNotification ^js (.-NativeModules react-native))))
(defn present-local-notification
[opts]
(.presentLocalNotification ^js (push-notification) (clj->js opts)))
(defn clear-message-notifications
[chat-id]
(.clearMessageNotifications ^js (push-notification) chat-id))
(defn clear-all-message-notifications
[]
(.clearAllMessageNotifications ^js (push-notification)))
(defn create-channel
[{:keys [channel-id channel-name]}]
(.createChannel ^js (push-notification)
#js {:channelId channel-id :channelName channel-name}
#(log/info "Notifications create channel:" %)))
(defn enable-notifications
[]
(.enableNotifications ^js (push-notification)))
(defn disable-notifications
[]
(.disableNotifications ^js (push-notification)))
(defn add-listener
[event callback]
(.addListener ^js (.-DeviceEventEmitter ^js react-native)
event
(fn [^js data]
(when (and data (.-dataJSON data) callback)
(callback (.-dataJSON data))))))
+49
View File
@@ -0,0 +1,49 @@
# Status Quo Components
All components in **Quo** should be independent of the app state. They should be _pure_
and _easy to reason about_. Avoiding the app state is also required to make the library
independent and easy to be pulled off as a separate repository when needed.
To avoid high coupling and direct use of internal styling, the components should be
exported via namespace `quo.core` and used by the Status app only from there. This will
allow a more flexible way to update components without possible breakages into the
app style.
**Quo** components should not have any dependency on the Status app, this
will avoid circular dependency and also benefit the independence of the components.
All components are stored inside `components` namespaces. They are stateless and do
not dispatch and subscribe to re-frame database. All state should be passed by props
and all events can be passed as functions. Avoiding direct connection with re-frame
will allow components to grow and be reused in different places without the
conditionals hell.
All style system constants are stored inside `design-system` namespaces. They are used
to build components and can be directly required by the status app. Avoid
duplication of these vars and do not use them in code directly as a value.
For each component introduced, add previews of all possible states.
Do not introduce components for slightly modified existing components, if they are
not a part of the design system. In case they are required in one place in the app,
use style override.
# Code style
Ensure that your changes match the style of the rest of the code.
This library uses Clojure Community code style [The Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide).
To ensure consistency, run `make lint`, which uses the [clj-kondo linter](https://github.com/borkdude/clj-kondo).
# Best practices
- Design components atomically and compose them into bigger components.
- Do not export individual atoms, only components. This way we can limit design
system to be used in too many way which can creating disjointed experiences.
- Avoid external margins for atom components, it can be added on the wrapper
where they are used but can't be removed without overriding.
[Max Stoiber article on margins](https://mxstbr.com/thoughts/margin)
- Design reusable components into [Layout Isolated Components](https://visly.app/blogposts/layout-isolated-components)
(Article more relates to web, but ideas fits also to mobile development)
- Explicit is better than implicit, do not rely on platform default, if you expect
a specific value, then override it
**TBD:**
- Components documentation
- Check props using spec in pre conditions.
+272
View File
@@ -0,0 +1,272 @@
(ns quo.animated
(:refer-clojure :exclude [abs set delay divide])
(:require ["react-native-reanimated" :default animated :refer (clockRunning EasingNode)]
["react-native-redash/lib/module/v1" :as redash]
[oops.core :refer [ocall oget]]
[quo.gesture-handler :as gh]
quo.react
[quo.react-native :as rn]
[reagent.core :as reagent])
(:require-macros [quo.react :refer [maybe-js-deps]]))
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent animated)))
(def view (reagent/adapt-react-class (.-View animated)))
(def text (reagent/adapt-react-class (.-Text animated)))
(def scroll-view (reagent/adapt-react-class (.-ScrollView animated)))
(def code (reagent/adapt-react-class (.-Code animated)))
(def animated-flat-list (create-animated-component gh/flat-list-raw))
(defn flat-list
[props]
[animated-flat-list (rn/base-list-props props)])
(def useCode (.-useCode animated))
(defn code!
([setup-fn]
(useCode
(fn [] (setup-fn))))
([setup-fn deps]
(useCode
(fn [] (setup-fn))
(maybe-js-deps deps))))
(def eq (oget animated "eq"))
(def neq (oget animated "neq"))
(def greater (oget animated "greaterThan"))
(def greater-or-eq (oget animated "greaterOrEq"))
(def less (oget animated "lessThan"))
(def less-or-eq (oget animated "lessOrEq"))
(def not* (oget animated "not"))
(def or* (oget animated "or"))
(def and* (oget animated "and"))
(def diff (oget animated "diff"))
(def add (oget animated "add"))
(def sub (oget animated "sub"))
(def multiply (oget animated "multiply"))
(def divide (oget animated "divide"))
(def abs (oget animated "abs"))
(def min* (oget animated "min"))
(def max* (oget animated "max"))
(def set (oget animated "set"))
(def start-clock (oget animated "startClock"))
(def stop-clock (oget animated "stopClock"))
(def clock-running clockRunning)
(def bezier (.-bezier ^js EasingNode))
(def linear (.-linear ^js EasingNode))
(def easings
{:linear linear
:ease-in (bezier 0.42 0 1 1)
:ease-out (bezier 0 0 0.58 1)
:ease-in-out (bezier 0.42 0 0.58 1)
:cubic (bezier 0.55 0.055 0.675 0.19)
:keyboard (bezier 0.17 0.59 0.4 0.77)})
(def springs
{:lazy {:damping 50
:mass 0.3
:stiffness 120
:overshootClamping true
:bouncyFactor 1}
:jump {:damping 13
:mass 0.5
:stiffness 170
:overshootClamping false
:bouncyFactor 1
:restSpeedThreshold 0.001
:restDisplacementThreshold 0.001}})
(defn set-value
[anim v]
(ocall anim "setValue" v))
(def Value (oget animated "Value"))
(defn value
[x]
(new Value x))
(def Clock (oget animated "Clock"))
(defn clock
[]
(new Clock))
(def debug (oget animated "debug"))
(def log (oget animated "log"))
(defn event
([config]
(event config {}))
([config options]
(ocall animated "event" (clj->js config) (clj->js options))))
(defn on-change
[state node]
(ocall animated
"onChange"
state
(if (vector? node)
(clj->js node)
node)))
(defn cond*
([condition node]
(.cond ^js animated
condition
(if (vector? node)
(clj->js node)
node)))
([condition if-node else-node]
(.cond ^js animated
condition
(if (vector? if-node)
(clj->js if-node)
if-node)
(if (vector? else-node)
(clj->js else-node)
else-node))))
(defn block
[opts]
(.block ^js animated (to-array opts)))
(defn interpolate
[anim-value config]
(.interpolateNode ^js animated anim-value (clj->js config)))
(defn call*
[args callback]
(.call ^js animated (to-array args) callback))
(defn timing
[clock-value opts config]
(.timing ^js animated
clock-value
(clj->js opts)
(clj->js config)))
(defn spring
[clock-value opts config]
(.spring ^js animated
clock-value
(clj->js opts)
(clj->js config)))
(def extrapolate {:clamp (oget animated "Extrapolate" "CLAMP")})
;; utilities
(def clamp (oget redash "clamp"))
(def diff-clamp (.-diffClamp ^js redash))
(defn with-spring
[config]
(ocall redash "withSpring" (clj->js config)))
(defn with-decay
[config]
(.withDecay ^js redash (clj->js config)))
(defn with-offset
[config]
(.withOffset ^js redash (clj->js config)))
(defn with-spring-transition
[v config]
(.withSpringTransition ^js redash v (clj->js config)))
(defn with-timing-transition
[v config]
(.withTimingTransition ^js redash v (clj->js config)))
(defn use-spring-transition
[v config]
(.useSpringTransition ^js redash v (clj->js config)))
(defn use-timing-transition
[v config]
(.useTimingTransition ^js redash v (clj->js config)))
(defn re-timing
[config]
(.timing ^js redash (clj->js config)))
(defn re-spring
[config]
(.spring ^js redash (clj->js config)))
(defn on-scroll
[opts]
(ocall redash "onScrollEvent" (clj->js opts)))
(defn on-gesture
[opts]
(let [gesture-event (event #js [#js {:nativeEvent (clj->js opts)}])]
{:onHandlerStateChange gesture-event
:onGestureEvent gesture-event}))
(def mix (.-mix ^js redash))
(def mix-color (.-mixColor ^js redash))
(def delay (.-delay ^js redash))
(defn loop*
[opts]
(ocall redash "loop" (clj->js opts)))
(def use-value (.-useValue ^js redash))
(def use-clock (.-useClock ^js redash))
(defn use-gesture
[opts]
(let [gesture (.useGestureHandler ^js redash (clj->js opts))]
{:onHandlerStateChange (.-onHandlerStateChange ^js gesture)
:onGestureEvent (.-onGestureEvent ^js gesture)}))
(defn snap-point
[v velocity snap-points]
(.snapPoint ^js redash v velocity (to-array snap-points)))
(defn with-easing
[{v :value
:keys [snap-points velocity offset state easing duration
animation-over]
:or {duration 250
animation-over (value 1)
easing (:ease-out easings)}}]
(let [position (value 0)
c (clock)
interrupted (and* (eq state (:began gh/states))
(clock-running c))
vel (multiply velocity 1.5)
to (snap-point position vel snap-points)
finish-animation [(set offset position)
(stop-clock c)
(set animation-over 1)]]
(block
[(cond* interrupted finish-animation)
(cond* animation-over
(set position offset))
(cond* (neq state (:end gh/states))
[(set animation-over 0)
(set position (add offset v))])
(cond* (and* (eq state (:end gh/states))
(not* animation-over))
[(set position
(re-timing
{:clock c
:easing easing
:duration duration
:from position
:to to}))
(cond* (not* (clock-running c))
finish-animation)])
position])))
+136
View File
@@ -0,0 +1,136 @@
(ns quo.components.animated.pressable
(:require [cljs-bean.core :as bean]
[quo.animated :as animated]
[quo.gesture-handler :as gesture-handler]
[quo.react :as react]
[reagent.core :as reagent]))
(def long-press-duration 500)
(def scale-down-small 0.95)
(def scale-down-large 0.9)
(def opactiy 0.75)
(def time-in 100)
(def time-out 200)
(defmulti type->animation :type)
(defmethod type->animation :primary
[{:keys [animation]}]
{:background {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :secondary
[{:keys [animation]}]
{:background {:transform [{:scale (animated/mix animation scale-down-small 1)}]
:opacity (animated/mix animation 0 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :icon
[{:keys [animation]}]
{:background {:transform [{:scale (animated/mix animation scale-down-large 1)}]
:opacity (animated/mix animation 0 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-large)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :list-item
[{:keys [animation]}]
{:background {:opacity (animated/mix animation 0 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :scale
[{:keys [animation]}]
{:background {:opacity 0}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(def absolute-fill
{:top 0
:bottom 0
:left 0
:right 0
:position :absolute})
(defn pressable-hooks
[props]
(let [{background-color :bgColor
border-radius :borderRadius
border-color :borderColor
border-width :borderWidth
type :type
disabled :disabled
on-press :onPress
on-long-press :onLongPress
on-press-start :onPressStart
accessibility-label :accessibilityLabel
children :children
:or {border-radius 0
type "primary"}}
(bean/bean props)
long-press-ref (react/create-ref)
state (animated/use-value (:undetermined gesture-handler/states))
active (animated/eq state (:began gesture-handler/states))
gesture-handler (animated/use-gesture {:state state})
animation (react/use-memo
(fn []
(animated/with-timing-transition active
{:duration (animated/cond* active time-in time-out)
:easing (:ease-in animated/easings)}))
[])
{:keys [background
foreground]}
(react/use-memo
(fn []
(type->animation {:type (keyword type)
:animation animation}))
[type])
handle-press (fn [] (when on-press (on-press)))
long-gesture-handler (react/callback
(fn [^js evt]
(let [gesture-state (-> evt .-nativeEvent .-state)]
(when (and on-press-start
(= gesture-state (:began gesture-handler/states)))
(on-press-start))
(when (and on-long-press
(= gesture-state (:active gesture-handler/states)))
(on-long-press)
(animated/set-value state (:undetermined gesture-handler/states)))))
[on-long-press on-press-start])]
(animated/code!
(fn []
(when on-press
(animated/cond* (animated/eq state (:end gesture-handler/states))
[(animated/set state (:undetermined gesture-handler/states))
(animated/call* [] handle-press)])))
[on-press])
(reagent/as-element
[gesture-handler/long-press-gesture-handler
{:enabled (boolean (and on-long-press (not disabled)))
:on-handler-state-change long-gesture-handler
:min-duration-ms long-press-duration
:max-dist 22
:ref long-press-ref}
[animated/view
{:accessible true
:accessibility-label accessibility-label}
[gesture-handler/tap-gesture-handler
(merge gesture-handler
{:shouldCancelWhenOutside true
:wait-for long-press-ref
:enabled (boolean (and (or on-press on-long-press on-press-start)
(not disabled)))})
[animated/view
[animated/view
{:style (merge absolute-fill
background
{:background-color background-color
:border-radius border-radius
:border-color border-color
:border-width border-width})}]
(into [animated/view {:style foreground}]
(react/get-children children))]]]])))
(def pressable (reagent/adapt-react-class (react/memo pressable-hooks)))
+103
View File
@@ -0,0 +1,103 @@
(ns quo.components.animated-header
(:require [oops.core :refer [oget]]
[quo.animated :as animated]
[quo.components.header :as header]
[quo.design-system.colors :as colors]
[quo.platform :as platform]
[reagent.core :as reagent]
[react-native.safe-area :as safe-area]))
(defn header-wrapper-style
[{:keys [value offset]}]
(merge
{:background-color (:ui-background @colors/theme)}
(when (and offset platform/android?)
{:elevation (animated/interpolate
value
{:inputRange [0 offset]
:outputRange [0 4]
:extrapolate (:clamp animated/extrapolate)})})
(when (and offset platform/ios?)
{:z-index 2
:shadow-opacity (animated/interpolate
value
{:inputRange [0 offset]
:outputRange [0 1]
:extrapolate (:clamp animated/extrapolate)})
:shadow-radius 16
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})))
(defn title-style
[layout]
{:flex 1
:justify-content :center
:padding-right (get-in layout [:right :width])})
(defn header-container
[]
(let [y (animated/value 0)
animation-value (animated/value 0)
animation (animated/with-timing-transition
animation-value
{:duration 250
:easing (:ease-in animated/easings)})
on-scroll (animated/on-scroll {:y y})
layout (reagent/atom {})
offset (reagent/atom 0)
on-layout (fn [evt]
(reset! offset (oget evt "nativeEvent" "layout" "height")))]
(fn [{:keys [extended-header refresh-control refreshing-sub refreshing-counter] :as props} children]
[animated/view
{:flex 1
:pointer-events :box-none}
[animated/code
{:key (str @offset)
:exec (animated/cond*
(animated/and* (animated/greater-or-eq y @offset)
(animated/greater-or-eq y 1))
(animated/set animation-value 1)
(animated/set animation-value 0))}]
[animated/view
{:pointer-events :box-none
:style (header-wrapper-style {:value y
:offset @offset})}
[header/header
(merge
{:get-layout (fn [el l] (swap! layout assoc el l))
:border-bottom false
:title-component [animated/view {:style (title-style @layout)}
[extended-header
{:value y
:animation animation
:minimized true
:offset @offset}]]
:title-align :left}
(dissoc props :extended-header))]]
(into [animated/scroll-view
{:on-scroll on-scroll
:refreshControl (when refresh-control
(refresh-control
(and @refreshing-sub
@refreshing-counter)))
:style {:z-index 1}
:scrollEventThrottle 16}
[animated/view {:pointer-events :box-none}
[animated/view
{:pointer-events :box-none
:on-layout on-layout}
[extended-header
{:value y
:animation animation
:offset @offset}]]]]
children)])))
(defn header
[{:keys [use-insets] :as props} & children]
(if use-insets
[header-container
(-> props
(dissoc :use-insets)
(assoc :insets (safe-area/get-insets)))
children]
[header-container props children]))
@@ -0,0 +1,45 @@
(ns quo.components.bottom-sheet.style
(:require [quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]))
(def border-radius 16)
(def vertical-padding (:tiny spacing/spacing))
(def margin-top 56)
(def container
{:position :absolute
:left 0
:top 0
:right 0
:bottom 0
:flex 1
:justify-content :flex-end})
(defn backdrop
[]
{:flex 1
:position :absolute
:left 0
:top 0
:right 0
:bottom 0})
(defn content-container
[window-height]
{:background-color (:ui-background @colors/theme)
:border-top-left-radius border-radius
:border-top-right-radius border-radius
:height (* window-height 2)})
(def content-header
{:height border-radius
:align-self :stretch
:justify-content :center
:align-items :center})
(def handle
{:width 31
:height 4
:background-color (:icon-02 @colors/theme)
:opacity 0.4
:border-radius 2})
+233
View File
@@ -0,0 +1,233 @@
(ns quo.components.bottom-sheet.view
(:require [cljs-bean.core :as bean]
[quo.animated :as animated]
[quo.components.bottom-sheet.style :as styles]
[quo.design-system.colors :as colors]
[quo.gesture-handler :as gesture-handler]
[quo.platform :as platform]
[quo.react :as react]
[quo.react-native :as rn]
[reagent.core :as reagent]
[react-native.safe-area :as safe-area]))
(def opacity-coeff 0.8)
(def close-duration 150)
(def spring-config
{:damping 15
:mass 0.7
:stiffness 150
:overshootClamping false
:restSpeedThreshold 0.1
:restDisplacementThreshold 0.1})
(defn bottom-sheet-hooks
[props]
(let [{on-cancel :onCancel
disable-drag? :disableDrag?
show-handle? :showHandle?
visible? :visible?
backdrop-dismiss? :backdropDismiss?
back-button-cancel :backButtonCancel
children :children
:or {show-handle? true
backdrop-dismiss? true
back-button-cancel true}}
(bean/bean props)
body-ref (react/create-ref)
master-ref (react/create-ref)
height (react/state 0)
{window-height :height} (rn/use-window-dimensions)
{:keys [keyboard-shown
keyboard-height]}
(rn/use-keyboard)
keyboard-height-android-delta
(if (and platform/android? keyboard-shown) (+ keyboard-height 20) 0)
safe-area (safe-area/get-insets)
window-height (- window-height
(if platform/android?
(+ 50 keyboard-height-android-delta) ;; TODO : remove 50 when
;; react-native-navigation v8 will be
;; implemented
;; https://github.com/wix/react-native-navigation/issues/7225
0))
min-height (+ (* styles/vertical-padding 2) (:bottom safe-area))
max-height (- window-height (:top safe-area))
visible (react/state false)
master-translation-y (animated/use-value 0)
master-velocity-y (animated/use-value (:undetermined gesture-handler/states))
master-state (animated/use-value (:undetermined gesture-handler/states))
tap-state (animated/use-value 0)
manual-open (animated/use-value 0)
manual-close (animated/use-value 0)
offset (animated/use-value 0)
drag-over (animated/use-value 1)
clock (animated/use-clock)
tap-gesture-handler (animated/use-gesture {:state tap-state})
on-master-event (animated/use-gesture
{:translationY master-translation-y
:state master-state
:velocityY master-velocity-y})
on-body-event on-master-event
sheet-height (min max-height
(+ styles/border-radius @height))
open-snap-point (animated/use-value 0)
close-snap-point 0
on-close (fn []
(when @visible
(reset! visible false)
(reset! height 0)
(when on-cancel
(on-cancel))))
close-sheet (fn []
(animated/set-value manual-close 1))
on-snap (fn [pos]
(when (= close-snap-point (aget pos 0))
(on-close)))
interrupted (animated/and* (animated/eq master-state (:began gesture-handler/states))
(animated/clock-running clock))
translate-y (react/use-memo
(fn []
(animated/with-easing
{:value (animated/cond* (animated/less-or-eq master-translation-y 0)
(animated/divide master-translation-y 2)
master-translation-y)
:velocity master-velocity-y
:offset offset
:state master-state
:animation-over drag-over
:snap-points [open-snap-point close-snap-point]}))
[])
opacity (react/use-memo
(fn []
(animated/cond*
open-snap-point
(animated/interpolate
translate-y
{:inputRange [(animated/multiply open-snap-point opacity-coeff) 0]
:outputRange [1 0]
:extrapolate (:clamp animated/extrapolate)})))
[])]
(animated/code!
(fn []
(animated/cond* (animated/and* (animated/eq master-state (:end gesture-handler/states))
(animated/not* drag-over))
(animated/call* [translate-y] on-snap)))
[on-snap])
(animated/code!
(fn []
(animated/block
[(animated/cond* (animated/and* interrupted manual-open)
[(animated/set manual-open 0)
(animated/set offset open-snap-point)
(animated/stop-clock clock)])
(animated/cond* (animated/and* manual-open
(animated/not* manual-close))
[(animated/set offset
(animated/re-spring {:from offset
:to open-snap-point
:clock clock
:config spring-config}))
(animated/cond* (animated/not* (animated/clock-running clock))
(animated/set manual-open 0))])]))
[])
(animated/code!
(fn []
(animated/block
[(animated/cond* (animated/and* interrupted manual-close)
[(animated/set manual-close 0)
(animated/set offset close-snap-point)
(animated/call* [] on-close)
(animated/stop-clock clock)])
(animated/cond* (animated/eq tap-state (:end gesture-handler/states))
[(animated/cond* (animated/and* (animated/not* manual-close))
[(animated/stop-clock clock)
(animated/set manual-close 1)])
(animated/set tap-state (:undetermined gesture-handler/states))])
(animated/cond* manual-close
[(animated/set offset
(animated/re-timing {:from offset
:to close-snap-point
:clock clock
:easing (:ease-out animated/easings)
:duration close-duration}))
(animated/cond* (animated/not* (animated/clock-running clock))
[(animated/set manual-close 0)
(animated/set manual-open 0)
(animated/call* [] on-close)])])]))
[on-cancel])
(animated/code!
(fn []
(animated/cond* (animated/and* (animated/not* manual-close)
(if @visible 1 0)
(if (> @height min-height) 1 0))
[(animated/stop-clock clock)
(animated/set open-snap-point (* -1 sheet-height))
(animated/set manual-open 1)]))
[@height @visible])
;; NOTE(Ferossgp): Remove me when RNGH will suport modal
(rn/use-back-handler
(fn []
(when back-button-cancel
(close-sheet))
@visible))
(react/effect!
(fn []
(cond
visible?
(do
(rn/dismiss-keyboard!)
(reset! visible visible?))
@visible
(close-sheet)))
[visible?])
(reagent/as-element
[rn/view
{:style styles/container
:pointer-events :box-none}
[gesture-handler/tap-gesture-handler
(merge {:enabled backdrop-dismiss?}
tap-gesture-handler)
[animated/view
{:style (merge (styles/backdrop)
(when platform/ios?
{:opacity opacity
:background-color (:backdrop @colors/theme)}))}]]
[animated/view
{:style (merge (styles/content-container window-height)
{:transform [{:translateY (if (= sheet-height max-height)
(animated/add translate-y
keyboard-height-android-delta)
translate-y)}
{:translateY (* window-height 2)}]})}
[gesture-handler/pan-gesture-handler
(merge on-master-event
{:ref master-ref
:wait-for body-ref
:enabled (not disable-drag?)})
[animated/view {:style styles/content-header}
(when show-handle?
[rn/view {:style styles/handle}])]]
[gesture-handler/pan-gesture-handler
(merge on-body-event
{:ref body-ref
:wait-for master-ref
:enabled (and (not disable-drag?)
(not= sheet-height max-height))})
[animated/view
{:height sheet-height
:flex 1}
[animated/view
{:style {:padding-top styles/vertical-padding
:padding-bottom (+ styles/vertical-padding
(if (and platform/ios? keyboard-shown)
keyboard-height
(:bottom safe-area)))}
:on-layout #(reset! height (.-nativeEvent.layout.height ^js %))}
(into [:<>]
(react/get-children children))]]]]])))
(def bottom-sheet (reagent/adapt-react-class (react/memo bottom-sheet-hooks)))
+125
View File
@@ -0,0 +1,125 @@
(ns quo.components.button.view
(:require [quo.components.animated.pressable :as animation]
[quo.components.text :as text] ;; FIXME:
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.haptic :as haptic]
[quo.react-native :as rn]
[status-im.ui.components.icons.icons :as icons]))
(defn style-container
[type]
(merge {:height 44
:align-items :center
:justify-content :center
:flex-direction :row}
(case type
:primary (:base spacing/padding-horizontal)
:secondary (:x-tiny spacing/padding-horizontal)
:icon {}
nil)))
(defn content-style
[type]
(case type
:primary (:base spacing/padding-horizontal)
:secondary (:x-tiny spacing/padding-horizontal)
:icon (:tiny spacing/padding-horizontal)
nil))
(defn themes
[theme]
(case theme
:main {:icon-color (:icon-04 @colors/theme)
:background-color (:interactive-02 @colors/theme)
:text-color (:text-04 @colors/theme)}
:icon {:icon-color (:icon-01 @colors/theme)
:background-color (:interactive-02 @colors/theme)
:text-color (:text-01 @colors/theme)}
:negative {:icon-color (:negative-01 @colors/theme)
:background-color (:negative-02 @colors/theme)
:text-color (:negative-01 @colors/theme)}
:positive {:icon-color (:positive-01 @colors/theme)
:background-color (:positive-02 @colors/theme)
:text-color (:positive-01 @colors/theme)}
:accent {:icon-color (:icon-05 @colors/theme)
:background-color (:interactive-01 @colors/theme)
:text-color (:text-05 @colors/theme)}
:secondary {:icon-color (:icon-02 @colors/theme)
:background-color (:interactive-02 @colors/theme)
:text-color (:text-02 @colors/theme)}
:disabled {:icon-color (:icon-02 @colors/theme)
:background-color (:ui-01 @colors/theme)
:text-color (:text-02 @colors/theme)}
:monocromatic {:icon-color (:icon-01 @colors/theme)
:background-color (:ui-background @colors/theme)
:text-color (:text-01 @colors/theme)
:border-color (:ui-01 @colors/theme)}))
(defn button
[{:keys [on-press disabled type theme before after
haptic-feedback haptic-type on-long-press on-press-start
accessibility-label loading border-radius style test-ID]
:or {theme :main
type :primary
haptic-feedback true
border-radius 8
haptic-type :selection}}
children]
(let [theme' (cond
disabled :disabled
:else theme)
{:keys [icon-color background-color text-color border-color]}
(themes theme')
optional-haptic (fn []
(when haptic-feedback
(haptic/trigger haptic-type)))]
[animation/pressable
(merge {:bg-color background-color
:border-radius border-radius
:type type
:disabled disabled
:accessibility-label accessibility-label}
(when border-color
{:border-color border-color
:border-width 1})
(when on-press
{:on-press (fn []
(optional-haptic)
(on-press))})
(when on-long-press
{:on-long-press (fn []
(optional-haptic)
(on-long-press))})
(when on-press-start
{:on-press-start (fn []
(optional-haptic)
(on-press-start))}))
[rn/view {:test-ID test-ID :style (merge (style-container type) style)}
(when before
[rn/view
[icons/icon before {:color icon-color}]])
(when loading
[rn/view {:style {:position :absolute}}
[rn/activity-indicator]])
[rn/view
{:style (merge (content-style type)
(when loading
{:opacity 0}))}
(cond
(= type :icon)
[icons/icon children {:color icon-color}]
(string? children)
[text/text
{:weight :medium
:number-of-lines 1
:style {:color text-color}}
children]
(vector? children)
children)]
(when after
[rn/view
[icons/icon after {:color icon-color}]])]]))
+90
View File
@@ -0,0 +1,90 @@
(ns quo.components.controls.styles
(:require [quo.animated :as animated]
[quo.design-system.colors :as colors]))
(defn switch-style
[state disabled]
{:width 52
:height 28
:border-radius 14
:padding 4
:background-color (animated/mix-color state
(:ui-01 @colors/theme)
(if disabled
(:interactive-04 @colors/theme)
(:interactive-01 @colors/theme)))})
(defn switch-bullet-style
[state hold]
{:width 20
:height 20
:border-radius 10
:opacity (animated/mix hold 1 0.6)
:transform [{:translateX (animated/mix state 0 24)}]
:background-color colors/white-persist
:elevation 4
:shadow-opacity 1
:shadow-radius 16
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})
(defn radio-style
[state disabled]
{:width 20
:height 20
:border-radius 10
:padding 4
:background-color (animated/mix-color state
(:ui-01 @colors/theme)
(if disabled
(:interactive-04 @colors/theme)
(:interactive-01 @colors/theme)))})
(defn radio-bullet-style
[state hold]
{:width 12
:height 12
:border-radius 6
:opacity (animated/mix hold 1 0.6)
:transform [{:scale (animated/mix state 0.0001 1)}]
:background-color colors/white-persist
:elevation 4
:shadow-opacity 1
:shadow-radius 16
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})
(defn animated-checkbox-style
[state disabled]
{:width 18
:height 18
:border-radius 4
:justify-content :center
:align-items :center
:background-color (animated/mix-color state
(:ui-01 @colors/theme)
(if disabled
(:interactive-04 @colors/theme)
(:interactive-01 @colors/theme)))})
(defn checkbox-style
[value disabled]
{:width 18
:height 18
:border-radius 4
:justify-content :center
:align-items :center
:background-color (if value
(if disabled
(:interactive-04 @colors/theme)
(:interactive-01 @colors/theme))
(:ui-01 @colors/theme))})
(defn animated-check-icon-style
[state hold]
{:opacity (animated/mix hold 1 0.6)
:transform [{:scale (animated/mix state 0.0001 1)}]})
(defn check-icon-style
[value]
{:opacity (if value 1 0)})
+99
View File
@@ -0,0 +1,99 @@
(ns quo.components.controls.view
(:require [cljs-bean.core :as bean]
[quo.animated :as animated]
[quo.components.controls.styles :as styles]
[quo.design-system.colors :as colors]
[quo.gesture-handler :as gh]
[quo.react :as react]
[quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.ui.components.icons.icons :as icons]))
(defn control-builder
[component]
(fn [props]
(let [{:keys [value onChange disabled]}
(bean/bean props)
state (animated/use-value 0)
tap-state (animated/use-value (:undetermined gh/states))
tap-handler (animated/on-gesture {:state tap-state})
hold (react/use-memo
(fn []
(animated/with-timing-transition
(animated/eq tap-state (:began gh/states))
{}))
[])
transition (react/use-memo
(fn []
(animated/with-spring-transition state
(:lazy
animated/springs)))
[])
press-end (fn []
(when (and (not disabled) onChange)
(onChange (not value))))]
(animated/code!
(fn []
(animated/cond* (animated/eq tap-state (:end gh/states))
[(animated/set state (animated/not* state))
(animated/set tap-state (:undetermined gh/states))
(animated/call* [] press-end)]))
[press-end])
(animated/code!
(fn []
(animated/set state (if (true? value) 1 0)))
[value])
(reagent/as-element
[gh/tap-gesture-handler
(merge tap-handler
{:shouldCancelWhenOutside true
:enabled (boolean (and onChange (not disabled)))})
[animated/view
[component
{:transition transition
:hold hold
:disabled disabled}]]]))))
(defn switch-view
[{:keys [transition hold disabled value]}]
[animated/view
{:style (styles/switch-style transition disabled)
:accessibility-label (str "switch-" (if value "on" "off"))
:accessibility-role :switch}
[animated/view {:style (styles/switch-bullet-style transition hold)}]])
(defn radio-view
[{:keys [transition hold disabled value]}]
[animated/view
{:style (styles/radio-style transition disabled)
:accessibility-label (str "radio-" (if value "on" "off"))
:accessibility-role :radio}
[animated/view {:style (styles/radio-bullet-style transition hold)}]])
(defn checkbox-view
[props]
(let [{:keys [value onChange disabled]} (bean/bean props)]
(reagent/as-element
[rn/touchable-without-feedback
{:on-press (when (and onChange (not disabled)) onChange)}
[rn/view
{:style (styles/checkbox-style value disabled)
:accessibility-label (str "checkbox-" (if value "on" "off"))
:accessibility-role :checkbox}
[rn/view {:style (styles/check-icon-style value)}
[icons/tiny-icon :tiny-icons/tiny-check {:color colors/white}]]]])))
(defn animated-checkbox-view
[{:keys [transition hold disabled value]}]
[animated/view
{:style (styles/animated-checkbox-style transition disabled)
:accessibility-label (str "checkbox-" (if value "on" "off"))
:accessibility-role :checkbox}
[animated/view {:style (styles/animated-check-icon-style transition hold)}
[icons/tiny-icon :tiny-icons/tiny-check {:color colors/white}]]])
(def switch (reagent/adapt-react-class (react/memo (control-builder switch-view))))
(def radio (reagent/adapt-react-class (react/memo (control-builder radio-view))))
(def animated-checkbox
(reagent/adapt-react-class (react/memo (control-builder animated-checkbox-view))))
(def checkbox (reagent/adapt-react-class (react/memo checkbox-view)))
@@ -1,13 +1,12 @@
(ns status-im.ui.components.header
(:require
[oops.core :refer [oget]]
[react-native.core :as rn]
[react-native.reanimated :as animated]
[reagent.core :as reagent]
[status-im.ui.components.button.view :as button]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.spacing :as spacing]
[status-im.ui.components.text :as text]))
(ns quo.components.header
(:require [oops.core :refer [oget]]
[quo.animated :as animated]
[quo.components.button.view :as button]
[quo.components.text :as text]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(def header-height 56)
@@ -1,9 +1,8 @@
(ns status-im.ui.components.list.footer
(:require
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.ui.components.spacing :as spacing]
[status-im.ui.components.text :as text]))
(ns quo.components.list.footer
(:require [quo.components.text :as text]
[quo.design-system.spacing :as spacing]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(defn footer
[]
@@ -1,9 +1,8 @@
(ns status-im.ui.components.list.header
(:require
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.ui.components.spacing :as spacing]
[status-im.ui.components.text :as text]))
(ns quo.components.list.header
(:require [quo.components.text :as text]
[quo.design-system.spacing :as spacing]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(defn header
[]
+18
View File
@@ -0,0 +1,18 @@
(ns quo.components.list.index
(:require [quo.components.text :as text]
[quo.design-system.colors :as colors]
[quo.react-native :as rn]))
(defn index
[{:keys [title]}]
[rn/view {:style {:padding-right 16}}
[rn/view
{:style {:border-top-width 1
:border-bottom-width 1
:border-right-width 1
:border-color (colors/get-color :border-01)
:padding-vertical 3
:padding-horizontal 16
:border-top-right-radius 16
:border-bottom-right-radius 16}}
[text/text title]]])
@@ -1,13 +1,14 @@
(ns status-im.ui.components.list.item
(:require
[react-native.core :as rn]
[react-native.platform :as platform]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.controls.view :as controls]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.spacing :as spacing]
[status-im.ui.components.text :as text]
[status-im.ui.components.tooltip :as tooltip]))
(ns quo.components.list.item
(:require [quo.components.animated.pressable :as animated]
[quo.components.controls.view :as controls]
[quo.components.text :as text]
[quo.components.tooltip :as tooltip]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.haptic :as haptic]
[quo.platform :as platform]
[quo.react-native :as rn]
[status-im.ui.components.icons.icons :as icons]))
(defn themes
[theme]
@@ -173,7 +174,7 @@
[title-column props]])
(defn right-side
[{:keys [chevron active disabled accessory accessory-text accessory-style]}]
[{:keys [chevron active disabled accessory accessory-text accessory-style animated-accessory?]}]
(when (or chevron accessory)
[rn/view
{:style (merge {:align-items :center
@@ -192,7 +193,10 @@
[rn/view {:style (:tiny spacing/padding-horizontal)}
(case accessory
:radio [controls/radio {:value active :disabled disabled}]
:checkbox [controls/checkbox {:value active :disabled disabled}]
:checkbox [(if animated-accessory?
controls/animated-checkbox
controls/checkbox)
{:value active :disabled disabled}]
:switch [controls/switch {:value active :disabled disabled}]
:text [text/text
{:color :secondary
@@ -214,12 +218,15 @@
left-side-alignment icon-color icon-bg-color
title subtitle subtitle-secondary active on-press on-long-press chevron size text-size
accessory-text accessibility-label title-accessibility-label accessory-style
error title-text-weight
haptic-feedback haptic-type error animated animated-accessory? title-text-weight
container-style
active-background-enabled background-color]
:or {subtitle-max-lines 1
theme :main
active-background-enabled true}}]
haptic-feedback true
animated platform/ios?
active-background-enabled true
haptic-type :selection}}]
(let [theme (if disabled :disabled theme)
{:keys [text-color active-background passive-background]}
(themes theme)
@@ -227,10 +234,14 @@
(:icon-color (themes theme)))
icon-bg-color (or icon-bg-color
(:icon-bg-color (themes theme)))
optional-haptic (fn []
(when haptic-feedback
(haptic/trigger haptic-type)))
component (cond
(and (not on-press)
(not on-long-press))
rn/view
animated animated/pressable
:else rn/touchable-highlight)]
[rn/view
{:background-color (cond (not= background-color nil)
@@ -246,9 +257,13 @@
:accessibility-label accessibility-label
:underlay-color (:interactive-02 @colors/theme)}
(when on-press
{:on-press on-press})
{:on-press (fn []
(optional-haptic)
(on-press))})
(when on-long-press
{:on-long-press on-long-press}))
{:on-long-press (fn []
(optional-haptic)
(on-long-press))}))
[container {:size size :container-style container-style}
[left-side
{:icon-color icon-color
@@ -269,13 +284,14 @@
:subtitle-secondary subtitle-secondary
:right-side-present? (or accessory chevron)}]
[right-side
{:chevron chevron
:active active
:disabled disabled
:on-press on-press
:accessory-text accessory-text
:accessory-style accessory-style
:accessory accessory}]]]
{:chevron chevron
:active active
:disabled disabled
:on-press on-press
:accessory-text accessory-text
:animated-accessory? animated-accessory?
:accessory-style accessory-style
:accessory accessory}]]]
(when error
[tooltip/tooltip
(merge {:bottom-value 0}
@@ -1,7 +1,6 @@
(ns status-im.ui.components.separator
(:require
[react-native.core :as react]
[status-im.ui.components.colors :as colors]))
(ns quo.components.separator
(:require [quo.design-system.colors :as colors]
[quo.react-native :as react]))
(defn separator
[{:keys [color style]}]
@@ -1,10 +1,9 @@
(ns status-im.ui.components.text
(:require
[react-native.core :as rn]
[react-native.reanimated :as animated]
[reagent.core :as reagent]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.typography :as typography]))
(ns quo.components.text
(:require [quo.animated :as animated]
[quo.design-system.colors :as colors]
[quo.design-system.typography :as typography]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(defn text-style
[{:keys [size align weight monospace color style]}]
@@ -1,16 +1,15 @@
(ns status-im.ui.components.text-input
(:require
[clojure.spec.alpha :as s]
[oops.core :refer [ocall]]
[react-native.core :as rn] ;; TODO(Ferossgp): Move icon component to lib
[react-native.platform :as platform]
[reagent.core :as reagent]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.spacing :as spacing]
[status-im.ui.components.text :as text]
[status-im.ui.components.tooltip :as tooltip]
[status-im.ui.components.typography :as typography]))
(ns quo.components.text-input
(:require [clojure.spec.alpha :as s]
[oops.core :refer [ocall]]
[quo.components.text :as text]
[quo.components.tooltip :as tooltip]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.design-system.typography :as typography]
[quo.platform :as platform]
[quo.react-native :as rn] ;; TODO(Ferossgp): Move icon component to lib
[reagent.core :as reagent]
[status-im.ui.components.icons.icons :as icons]))
;; NOTE(Ferossgp): Refactor with hooks when available
;; We track all currently mounted text input refs
@@ -1,17 +1,17 @@
(ns status-im.ui.components.tooltip
(:require
[oops.core :refer [oget]]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.spacing :as spacing]))
(ns quo.components.tooltip
(:require [oops.core :refer [oget]]
[quo.animated :as animated]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.platform :as platform] ;; FIXME(Ferossgp): Dependecy on status
[quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.ui.components.icons.icons :as icons]))
(def ^:private initial-height 22)
(defn tooltip-style
[{:keys [bottom-value]}]
[{:keys [bottom-value animation]}]
(merge
(:base spacing/padding-horizontal)
{:position :absolute
@@ -19,8 +19,8 @@
:left 0
:right 0
:top (- bottom-value)
:opacity 1
:transform [{:translateY 10}]}))
:opacity animation
:transform [{:translateY (animated/mix animation 10 0)}]}))
(defn container-style
[]
@@ -41,19 +41,27 @@
(defn tooltip
[]
(let [layout (reagent/atom {:height initial-height})
on-layout (fn [evt]
(let [width (oget evt "nativeEvent" "layout" "width")
height (oget evt "nativeEvent" "layout" "height")]
(reset! layout {:width width
:height height})))]
(let [layout (reagent/atom {:height initial-height})
animation-v (animated/value 0)
animation (animated/with-timing-transition
animation-v
{:easing (:ease-in animated/easings)})
on-layout (fn [evt]
(let [width (oget evt "nativeEvent" "layout" "width")
height (oget evt "nativeEvent" "layout" "height")]
(reset! layout {:width width
:height height})))]
(fn [{:keys [bottom-value accessibility-label]} & children]
[:<>
[rn/view
[animated/code
{:exec (animated/cond* (animated/not* animation-v)
(animated/set animation-v 1))}]
[animated/view
{:style (tooltip-style {:bottom-value (- (get @layout :height)
bottom-value)})
bottom-value)
:animation animation})
:pointer-events :box-none}
[rn/view
[animated/view
{:style (container-style)
:pointer-events :box-none}
(into [rn/view
+32
View File
@@ -0,0 +1,32 @@
(ns quo.core
(:require [quo.components.animated-header :as animated-header]
[quo.components.bottom-sheet.view :as bottom-sheet]
[quo.components.button.view :as button]
[quo.components.controls.view :as controls]
[quo.components.header :as header]
[quo.components.list.footer :as list-footer]
[quo.components.list.header :as list-header]
[quo.components.list.index :as list-index]
[quo.components.list.item :as list-item]
[quo.components.separator :as separator]
[quo.components.text :as text]
[quo.components.text-input :as text-input]
[quo.components.tooltip :as tooltip]
[quo.design-system.colors :as colors]))
(def text text/text)
(def header header/header)
(def animated-header animated-header/header)
(def text-input text-input/text-input)
(def tooltip tooltip/tooltip)
(def button button/button)
(def list-header list-header/header)
(def list-footer list-footer/footer)
(def list-item list-item/list-item)
(def list-index list-index/index)
(def bottom-sheet bottom-sheet/bottom-sheet)
(def switch controls/switch)
(def radio controls/radio)
(def checkbox controls/checkbox)
(def separator separator/separator)
(def get-color colors/get-color)
@@ -1,7 +1,6 @@
(ns status-im.ui.components.colors
(:require
[clojure.string :as string]
[reagent.core :as reagent]))
(ns quo.design-system.colors
(:require [clojure.string :as string]
[reagent.core :as reagent]))
;; Colors mapping from figma to code, note that theme is more extended and
;; one can follow the comments from the light theme to choose what to use in a component.
@@ -21,16 +20,16 @@
(def light-theme
{:positive-01 "rgba(68,208,88,1)" ; Primary Positive, text, icons color
:positive-02 "rgba(78,188,96,0.1)" ; Secondary Positive, Supporting color for success
; illustrations
; illustrations
:positive-03 "rgba(78,188,96,1)" ; Lighter Positive, Supporting color for success illustrations
:negative-01 "rgba(255,45,85,1)" ; Primary Negative, text, icons color
:negative-02 "rgba(255,45,85,0.1))" ; Secondary Negative, Supporting color for errors
; illustrations
; illustrations
:warning-01 "rgba(255, 202, 15, 1)"
:warning-02 "rgba(255, 202, 15, 0.1)"
:interactive-01 "rgba(67,96,223,1)" ; Accent color, buttons, own message, actions,active state
:interactive-02 "rgba(236,239,252,1)" ; Light Accent, buttons background, actions background,
; messages
; messages
:interactive-03 "rgba(255,255,255,0.1)" ; Background for interactive above accent
:interactive-04 "rgba(147,155,161,1)" ; Disabled state
:ui-background "rgba(255,255,255,1)" ; Default view background
@@ -136,7 +135,7 @@
(def black (:text-01 light-theme)) ;; Used as the default text color
(def black-persist (:ui-background dark-theme)) ;; this doesn't with theme
(def black-transparent (:ui-02 light-theme)) ;; Used as background color for rounded button on dark background and as background
;; color for containers like "Backup recovery phrase"
;; color for containers like "Backup recovery phrase"
(def black-transparent-20 (:backdrop light-theme)) ; accounts divider
(def black-transparent-40 (:backdrop light-theme))
(def black-transparent-40-persist (:backdrop light-theme))
@@ -146,7 +145,7 @@
;; DARK GREY
(def gray (:text-02 light-theme)) ;; Dark grey, used as a background for a light foreground and as
;; section header and secondary text color
;; section header and secondary text color
(def gray-transparent-10 (alpha gray 0.1))
(def gray-transparent-40 (alpha gray 0.4))
;; LIGHT GREY
@@ -1,4 +1,4 @@
(ns status-im.ui.components.spacing)
(ns quo.design-system.spacing)
(def spacing
{:x-tiny 4
+39
View File
@@ -0,0 +1,39 @@
(ns quo.design-system.typography)
(def tiny
{:font-size 10
:line-height 14})
(def x-small
{:font-size 12
:line-height 16})
(def small
{:font-size 13
:line-height 18})
(def base
{:font-size 15
:line-height 22})
(def large
{:font-size 17
:line-height 24})
(def x-large
{:font-size 22
:line-height 30})
(def xx-large
{:font-size 28
:line-height 38})
(def font-regular {:font-family "Inter-Regular"}) ; 400
(def font-medium {:font-family "Inter-Medium"}) ; 500 ff
(def font-semi-bold {:font-family "Inter-SemiBold"}) ; 600
(def font-bold {:font-family "Inter-Bold"}) ; 700
(def monospace {:font-family "InterStatus-Regular"})
+60
View File
@@ -0,0 +1,60 @@
(ns quo.gesture-handler
(:require ["react-native-gesture-handler" :refer
(TapGestureHandler PanGestureHandler
LongPressGestureHandler
TouchableWithoutFeedback
TouchableOpacity
TouchableHighlight
State
NativeViewGestureHandler
FlatList
ScrollView)]
[oops.core :refer [oget]]
[quo.design-system.colors :as colors]
[reagent.core :as reagent]))
(def flat-list-raw FlatList)
(def flat-list (reagent/adapt-react-class FlatList))
(def scroll-view (reagent/adapt-react-class ScrollView))
(def tap-gesture-handler
(reagent/adapt-react-class TapGestureHandler))
(def pan-gesture-handler
(reagent/adapt-react-class PanGestureHandler))
(def long-press-gesture-handler
(reagent/adapt-react-class LongPressGestureHandler))
;; NOTE - Touchables provided by react-native-gesture-handler has an issue,
;; if it is pressed rapidly it becomes unresponsive and ignores further on-press calls.
;; Issue: https://github.com/status-im/status-mobile/issues/14020
(def touchable-without-feedback-class TouchableWithoutFeedback)
(def touchable-without-feedback
(reagent/adapt-react-class touchable-without-feedback-class))
(def touchable-highlight-class (reagent/adapt-react-class TouchableHighlight))
(defn touchable-highlight
[props & children]
(into [touchable-highlight-class
(merge {:underlay-color (:interactive-02 @colors/theme)}
props)]
children))
(def touchable-opacity
(reagent/adapt-react-class TouchableOpacity))
(def native-view-gesture-handler (reagent/adapt-react-class NativeViewGestureHandler))
(def states
{:began (oget State "BEGAN")
:active (oget State "ACTIVE")
:cancelled (oget State "CANCELLED")
:end (oget State "END")
:failed (oget State "FAILED")
:undetermined (oget State "UNDETERMINED")})
+25
View File
@@ -0,0 +1,25 @@
(ns quo.haptic
(:require [react-native-haptic-feedback :default react-native-haptic-feedback]))
(def haptic-methods
{:selection "selection"
:impact-light "impactLight"
:impact-medium "impactMedium"
:impact-heavy "impactHeavy"
:notification-success "notificationSuccess"
:notification-warning "notificationWarning"
:notification-error "notificationError"
:clock-tick "clockTick" ; (Android only)
:context-click "contextClick" ; (Android only)
:keyboard-press "keyboardPress" ; (Android only)
:keyboard-release "keyboardRelease" ; (Android only)
:keyboard-tap "keyboardTap" ; (Android only)
:long-press "longPress" ; (Android only)
:text-handle-move "textHandleMove" ; (Android only)
:virtual-key "virtualKey" ; (Android only)
:virtual-key-release "virtualKeyRelease" ; (Android only)
})
(defn trigger
[method]
(.trigger ^js react-native-haptic-feedback (get haptic-methods method)))
+9
View File
@@ -0,0 +1,9 @@
(ns quo.platform
(:require ["react-native" :as rn]))
(def platform (.-Platform ^js rn))
(def os (when platform (.-OS platform)))
(def android? (= os "android"))
(def ios? (= os "ios"))
+77
View File
@@ -0,0 +1,77 @@
(ns quo.previews.bottom-sheet
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(def descriptor
[{:label "Show handle:"
:key :show-handle?
:type :boolean}
{:label "Backdrop dismiss:"
:key :backdrop-dismiss?
:type :boolean}
{:label "Disable drag:"
:key :disable-drag?
:type :boolean}
{:label "Android back cancel:"
:key :back-button-cancel
:type :boolean}
{:label "Scrollable:"
:key :scrollable
:type :boolean}])
(defn cool-preview
[]
(let [state (reagent/atom {:show-handle? true
:backdrop-dismiss? true
:disable-drag? false
:back-button-cancel true})
visible (reagent/atom false)
scrollable (reagent/cursor state [:scrollable])]
(fn []
[rn/view
{:margin-bottom 50
:padding 16}
[preview/customizer state descriptor]
[:<>
[rn/view
{:style {:align-items :center
:padding 16}}
[rn/touchable-opacity {:on-press #(reset! visible true)}
[rn/view
{:style {:padding-horizontal 16
:padding-vertical 8
:border-radius 4
:background-color (:interactive-01 @colors/theme)}}
[quo/text {:color :secondary-inverse}
(str "Open sheet: " @visible)]]]]
[quo/bottom-sheet
(merge @state
{:visible? @visible
:on-cancel #(reset! visible false)})
[rn/view
{:style {:height (if @scrollable 1200 400)
:justify-content :center
:align-items :center}}
[rn/touchable-opacity {:on-press #(reset! visible false)}
[quo/text {:color :link} "Close"]]
[rn/touchable-opacity
{:on-press #(swap! scrollable not)
:style {:padding-vertical 16}}
[quo/text {:color :link} "Toggle size"]]
[quo/text "Hello world!"]]]]])))
(defn preview
[]
(fn []
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:key-fn str}]]))
+85
View File
@@ -0,0 +1,85 @@
(ns quo.previews.button
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(def descriptor
[{:label "Type:"
:key :type
:type :select
:options [{:key :primary
:value "Primary"}
{:key :secondary
:value "Secondary"}
{:key :icon
:value "Icon"}]}
{:label "Theme:"
:key :theme
:type :select
:options [{:key :main
:value "Main"}
{:key :negative
:value "Negative"}
{:key :positive
:value "Positive"}
{:key :accent
:value "Accent"}]}
{:label "After icon:"
:key :after
:type :boolean}
{:label "Before icon:"
:key :before
:type :boolean}
{:label "Disabled:"
:key :disabled
:type :boolean}
{:label "Label"
:key :label
:type :text}])
(defn cool-preview
[]
(let [state (reagent/atom {:label "Press Me"
:type :primary
:theme :main
:icon :main-icons/share})
theme (reagent/cursor state [:theme])
label (reagent/cursor state [:label])
before (reagent/cursor state [:before])
after (reagent/cursor state [:after])]
(fn []
[rn/view
{:margin-bottom 50
:padding 16}
[rn/view {:flex 1}
[preview/customizer state descriptor]]
[rn/view
{:padding-vertical 16
:flex-direction :row
:justify-content :center}
[quo/button
(merge (dissoc @state
:theme
:before
:after)
{:on-press #(println "Hello world!")}
(when @theme
{:theme @theme})
(when @before
{:before :main-icons/back})
(when @after
{:after :main-icons/next}))
@label]]])))
(defn preview-button
[]
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:key-fn str}]])
+55
View File
@@ -0,0 +1,55 @@
(ns quo.previews.controls
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(defn preview
[]
(let [switch-state (reagent/atom true)
radio-state (reagent/atom true)
checkbox-state (reagent/atom true)]
(fn []
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/view
{:padding 20
:flex-direction :row
:align-items :center
:justify-content :space-between}
[rn/touchable-opacity
{:style {:margin-vertical 10
:padding 10}
:on-press #(swap! switch-state not)}
[quo/text (str "Switch state: " @switch-state)]]
[quo/switch
{:value @switch-state
:on-change #(reset! switch-state %)}]]
[rn/view
{:padding 20
:flex-direction :row
:align-items :center
:justify-content :space-between}
[rn/touchable-opacity
{:style {:margin-vertical 10
:padding 10}
:on-press #(swap! radio-state not)}
[quo/text (str "Radio state: " @radio-state)]]
[quo/radio
{:value @radio-state
:on-change #(reset! radio-state %)}]]
[rn/view
{:padding 20
:flex-direction :row
:align-items :center
:justify-content :space-between}
[rn/touchable-opacity
{:style {:margin-vertical 10
:padding 10}
:on-press #(swap! checkbox-state not)}
[quo/text (str "Checkbox state: " @checkbox-state)]]
[quo/checkbox
{:value @checkbox-state
:on-change #(reset! checkbox-state %)}]]])))
+51
View File
@@ -0,0 +1,51 @@
(ns quo.previews.header
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react-native :as rn])
(:require-macros [quo.previews.preview :as preview]))
(def accessories
[nil
[{:icon :main-icons/close
:on-press identity}]
[{:icon :main-icons/close
:on-press identity}
{:icon :main-icons/add
:on-press identity}]
[{:icon :main-icons/add
:on-press identity}
{:label "Text"
:on-press identity}]
[{:label "Text"
:on-press identity}]])
(def all-props
(preview/list-comp [left-accessories accessories
right-accessories accessories
title [nil "This is a title" "This is a very long super title"]
subtitle [nil "This is a subtitle"]
title-align [:left :center]]
{:left-accessories left-accessories
:right-accessories right-accessories
:title title
:subtitle subtitle
:title-align title-align}))
(defn render-item
[props]
[rn/view
{:border-bottom-color "#EEF2F5"
:border-bottom-width 2}
[quo/header props]])
(defn preview-header
[]
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:data all-props
:render-fn render-item
:key-fn str}]])
+14
View File
@@ -0,0 +1,14 @@
(ns quo.previews.icons
(:require [quo.design-system.colors :as colors]
[quo.react-native :as rn]
[status-im.ui.components.icons.icons :as icons]))
(defn preview
[]
[rn/scroll-view
{:background-color (:ui-background @colors/theme)
:flex 1}
(for [i (keys icons/icons)]
[rn/view {:flex-direction :row}
[icons/icon (keyword i)]
[rn/text i]])])
+121
View File
@@ -0,0 +1,121 @@
(ns quo.previews.lists
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(def all-props (preview/list-comp [] {}))
(defn avatar
[]
[rn/view
{:border-radius 20
:width 40
:height 40
:justify-content :center
:align-items :center
:background-color :red}
[quo/text
{:weight :bold
:size :large}
"T"]])
(defn icon-element
[type]
(case type
:icon :main-icons/add-contact
:component [avatar]
nil))
(def descriptor
[{:label "Accessory:"
:key :accessory
:type :select
:options [{:key :radio
:value "Radio"}
{:key :checkbox
:value "Checkbox"}
{:key :switch
:value "Switch"}
{:key :text
:value "Text"}
{:key :default
:value "Default"}]}
{:label "Size:"
:key :size
:type :select
:options [{:key :small
:value "Small"}
{:key :default
:value "Default"}]}
{:label "Icon:"
:key :icon
:type :select
:options [{:key :icon
:value "Icon"}
{:key :component
:value "Component"}]}
{:label "Theme:"
:key :theme
:type :select
:options [{:key :main
:value "Main"}
{:key :accent
:value "Accent"}
{:key :negative
:value "Negative"}
{:key :positive
:value "Positive"}]}
{:label "Selectable"
:key :selectable
:type :boolean}
{:label "Chevron"
:key :chevron
:type :boolean}
{:label "Disabled:"
:key :disabled
:type :boolean}
{:label "Title"
:key :title
:type :text}
{:label "Subtitle"
:key :subtitle
:type :text}])
(defn render-item
[_]
[rn/view {:style {:padding-vertical 24}}])
(defn cool-preview
[]
(let [state (reagent/atom {:title "Title"
:active false})
icon (reagent/cursor state [:icon])
active (reagent/cursor state [:active])
selectable (reagent/cursor state [:selectable])]
(fn []
[rn/view {:margin-bottom 50}
[rn/view {:padding-horizontal 16}
[preview/customizer state descriptor]]
[rn/view {:padding-vertical 16}
[quo/list-item
(merge (dissoc @state :active :selectable)
(when @selectable
{:active @active
:on-press #(swap! active not)})
{:accessory-text "Accessory"
:icon (icon-element @icon)})]]])))
(defn preview
[]
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
+92
View File
@@ -0,0 +1,92 @@
(ns quo.previews.main
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.previews.bottom-sheet :as bottom-sheet]
[quo.previews.button :as button]
[quo.previews.controls :as controls]
[quo.previews.header :as header]
[quo.previews.icons :as icons]
[quo.previews.lists :as lists]
[quo.previews.text :as text]
[quo.previews.text-input :as text-input]
[quo.previews.tooltip :as tooltip]
[quo.react-native :as rn]
[quo.theme :as theme]
[re-frame.core :as re-frame]))
(def screens
[{:name :texts
:insets {:top false}
:component text/preview-text}
{:name :tooltip
:insets {:top false}
:component tooltip/preview-tooltip}
{:name :text-input
:insets {:top false}
:component text-input/preview-text}
{:name :headers
:insets {:top false}
:component header/preview-header}
{:name :button
:insets {:top false}
:component button/preview-button}
{:name :lists
:instes {:top false}
:component lists/preview}
{:name :bottom-sheet
:insets {:top false}
:component bottom-sheet/preview}
{:name :controls
:insets {:top false}
:component controls/preview}
{:name :icons
:insets {:top false}
:component icons/preview}])
(defn theme-switcher
[]
[rn/view
{:style {:flex-direction :row
:margin-vertical 8
:border-radius 4
:background-color (:ui-01 @colors/theme)
:border-width 1
:border-color (:ui-02 @colors/theme)}}
[rn/touchable-opacity
{:style {:padding 8
:flex 1
:justify-content :center
:align-items :center}
:on-press #(theme/set-theme :light)}
[quo/text "Set light theme"]]
[rn/view
{:width 1
:margin-vertical 4
:background-color (:ui-02 @colors/theme)}]
[rn/touchable-opacity
{:style {:padding 8
:flex 1
:justify-content :center
:align-items :center}
:on-press #(theme/set-theme :dark)}
[quo/text "Set dark theme"]]])
(defn main-screen
[]
[rn/scroll-view
{:flex 1
:padding-vertical 8
:padding-horizontal 16
:background-color (:ui-background @colors/theme)}
[theme-switcher]
[rn/view
(for [{:keys [name]} screens]
^{:key name}
[rn/touchable-opacity {:on-press #(re-frame/dispatch [:navigate-to name])}
[rn/view {:style {:padding-vertical 8}}
[quo/text (str "Preview " name)]]])]])
(def main-screens
[{:name :quo-preview
:insets {:top false}
:component main-screen}])
+10
View File
@@ -0,0 +1,10 @@
(ns quo.previews.preview)
(defmacro list-comp
[[the-binding seq-expr & bindings] body-expr]
(cond (not the-binding)
`(list ~body-expr)
:else
`(mapcat (fn [~the-binding] (list-comp ~bindings ~body-expr))
~seq-expr)))
+199
View File
@@ -0,0 +1,199 @@
(ns quo.previews.preview
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react-native :as rn]
[reagent.core :as reagent])
(:require-macros quo.previews.preview))
(def container
{:flex-direction :row
:padding-vertical 8
:flex 1
:align-items :center})
(defn touchable-style
[]
{:flex 1
:align-items :center
:justify-content :center
:padding-horizontal 16
:height 44})
(defn select-style
[]
{:flex 1
:flex-direction :row
:align-items :center
:padding-horizontal 16
:height 44
:border-radius 4
:background-color (:ui-01 @colors/theme)
:border-width 1
:border-color (:ui-02 @colors/theme)})
(defn select-option-style
[selected]
(merge (select-style)
{:margin-vertical 8
:justify-content :center}
(if selected
{:background-color (:interactive-02 @colors/theme)}
{:background-color (:ui-01 @colors/theme)})))
(def label-style
{:flex 0.4
:padding-right 8})
(defn label-view
[state label]
[rn/view {:style label-style}
[quo/text
(when-let [label-color (:preview-label-color @state)]
{:style {:color label-color}})
label]])
(defn modal-container
[]
{:flex 1
:justify-content :center
:padding-horizontal 24
:background-color "rgba(0,0,0,0.4)"})
(defn modal-view
[]
{:padding-horizontal 16
:padding-vertical 8
:border-radius 8
:flex-direction :column
:background-color (:ui-background @colors/theme)})
(defn customizer-boolean
[{:keys [label state] :as args}]
(let [state* (reagent/cursor state [(:key args)])]
[rn/view {:style container}
[label-view state label]
[rn/view
{:style {:flex-direction :row
:flex 0.6
:border-radius 4
:background-color (:ui-01 @colors/theme)
:border-width 1
:border-color (:ui-02 @colors/theme)}}
[rn/touchable-opacity
{:style (touchable-style)
:on-press #(reset! state* true)}
[quo/text {:color (if @state* :link :secondary)}
"True"]]
[rn/view
{:width 1
:margin-vertical 4
:background-color (:ui-02 @colors/theme)}]
[rn/touchable-opacity
{:style (touchable-style)
:on-press #(reset! state* false)}
[quo/text {:color (if (not @state*) :link :secondary)}
"False"]]]]))
(defn customizer-text
[{: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}}
[quo/text-input
{:value @state*
:show-cancel false
:style {:border-radius 4
:border-width 1
:border-color (:ui-02 @colors/theme)}
:on-change-text #(do
(reset! state* %)
(reagent/flush))}]]]))
(defn value-for-key
[id v]
(:value (first (filter #(= (:key %) id) v))))
(defn customizer-select
[]
(let [open (reagent/atom nil)]
(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]
[rn/view {:style {:flex 0.6}}
[rn/modal
{:visible @open
:on-request-close #(reset! open false)
:statusBarTranslucent true
:transparent true
:animation :slide}
[rn/view {:style (modal-container)}
[rn/view {:style (modal-view)}
[rn/scroll-view
(doall
(for [{k :key v :value} options]
^{:key k}
[rn/touchable-opacity
{:style (select-option-style (= @state* k))
:on-press #(do
(reset! open false)
(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)
:on-press #(do
(reset! state* nil)
(reset! open false))}
[quo/text "Clear"]]
[rn/view {:width 16}]
[rn/touchable-opacity
{:style (select-option-style false)
:on-press #(reset! open false)}
[quo/text "Close"]]]]]]]
[rn/touchable-opacity
{:style (select-style)
:on-press #(reset! open true)}
(if selected
[quo/text {:color :link} selected]
[quo/text "Select option"])
[rn/view
{:position :absolute
:right 16
:top 0
:bottom 0
:justify-content :center}
[quo/text "↓"]]]]]))))
(defn customizer
[state descriptors]
[rn/view
{:style {:flex 1}
:padding-horizontal 16}
(doall
(for [desc descriptors
:let [descriptor (merge desc {:state state})]]
^{:key (:key desc)}
[:<>
(case (:type desc)
:boolean [customizer-boolean descriptor]
:text [customizer-text descriptor]
:select [customizer-select descriptor]
nil)]))])
(comment
[{:label "Show error:"
:key :error
:type :boolean}
{:label "Label:"
:key :label
:type :text}
{:label "Type:"
:key :type
:type :select
:options [{:key :primary :value "Primary"}
{:key :secondary :value "Secondary"}]}])
+99
View File
@@ -0,0 +1,99 @@
(ns quo.previews.text
(:require [quo.animated :as animated]
[quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(def all-props
(preview/list-comp [size [:tiny :small :base :large :x-large :xx-large]
weight [:regular :medium :semi-bold :bold :monospace]]
{:weight weight
:size size}))
(def descriptor
[{:label "Size:"
:key :size
:type :select
:options [{:key :tiny
:value "Tiny"}
{:key :small
:value "Small"}
{:key :base
:value "Base"}
{:key :large
:value "Large"}
{:key :x-large
:value "X-Large"}
{:key :xx-large
:value "XX-Large"}]}
{:label "Weight:"
:key :weight
:type :select
:options [{:key :regular
:value "Regular"}
{:key :medium
:value "Medium"}
{:key :semi-bold
:value "Semi-bold"}
{:key :bold
:value "Bold"}
{:key :monospace
:value "Monospace"}]}
{:label "Color:"
:key :color
:type :select
:options [{:key :main
:value "main"}
{:key :secondary
:value "secondary"}
{:key :secondary-inverse
:value "secondary-inverse"}
{:key :link
:value "link"}
{:key :negative
:value "negative"}
{:key :positive
:value "positive"}]}
{:label "Animated:"
:key :animated?
:type :boolean}])
(defn render-item
[props]
[rn/view
{:style {:padding-vertical 24
:padding-horizontal 16}}
[quo/text props
(str "Text size " props " number 0 1x2")]])
(defn cool-preview
[]
(let [state (reagent/atom {})
animation (animated/value 0)]
(fn []
[rn/view
{:margin-bottom 50
:padding 16}
[animated/code {:exec (animated/set animation (animated/loop* {:duration 1000}))}]
[preview/customizer state descriptor]
[rn/view {:padding-vertical 16}
[quo/text
(merge @state
(when (:animated? @state)
{:opacity animation}))
"This is a demo text 1 2 0 2x2 0x0"]]])))
(defn preview-text
[]
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
+91
View File
@@ -0,0 +1,91 @@
(ns quo.previews.text-input
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.previews.preview :as preview]
[quo.react-native :as rn]
[reagent.core :as reagent]))
(def all-props
(preview/list-comp [multiline [false true]
label [nil "Input label"]
default-value [nil "Test initial value"]
placeholder [nil "Placeholder value"]
before [nil {:icon :main-icons/search}]
after [nil {:icon :main-icons/close}]
error [nil "Something went wrong!"]
secure [false true]
show-cancel [false true]]
{:label label
:default-value default-value
:placeholder placeholder
:multiline multiline
:before before
:after after
:error error
:show-cancel show-cancel
:secure-text-entry secure}))
(def descriptor
[{:label "Multiline:"
:key :multiline
:type :boolean}
{:label "Show cancel:"
:key :show-cancel
:type :boolean}
{:label "Secure:"
:key :secure-text-entry
:type :boolean}
{:label "After icon:"
:key :after
:type :boolean}
{:label "Before icon:"
:key :before
:type :boolean}
{:label "Show error:"
:key :error
:type :boolean}
{:label "Label"
:key :label
:type :text}])
(defn render-item
[props]
[rn/view
{:style {:padding-horizontal 16
:padding-vertical 24}}
[quo/text-input props]])
(defn cool-preview
[]
(let [state (reagent/atom {:secure false
:show-cancel false
:multiline false
:label "I'm a cool label"})
before (reagent/cursor state [:before])
after (reagent/cursor state [:after])
error (reagent/cursor state [:error])]
(fn []
[rn/view
{:margin-bottom 50
:padding 16}
[preview/customizer state descriptor]
[quo/text-input
(merge @state
{:default-value nil
:placeholder "I'm a cool placeholder"
:before (when @before {:icon :main-icons/search})
:after (when @after {:icon :main-icons/close})
:error (when @error "Something went wrong!")})]])))
(defn preview-text
[]
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:header [cool-preview]
:data all-props
:render-fn render-item
:key-fn str}]])
+38
View File
@@ -0,0 +1,38 @@
(ns quo.previews.tooltip
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react-native :as rn])
(:require-macros [quo.previews.preview :as preview]))
(def all-props
(preview/list-comp
[child [[quo/text {:size :small} "Simple text"]
[quo/text
{:color :negative
:size :small}
"Error text"]
[rn/view {:width 100 :height 20 :background-color :red}]
[quo/text
"Just text, but long. Officia autem est repellendus ad quia exercitationem veniam."]]]
child))
(defn render-item
[children]
[rn/view {:margin-vertical 50}
[rn/view
{:height 20
:background-color "rgba(0,0,0,0.1)"}]
[quo/tooltip {}
children]])
(defn preview-tooltip
[]
[rn/view
{:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list
{:flex 1
:keyboard-should-persist-taps :always
:data all-props
:render-fn render-item
:key-fn str}]])
+30
View File
@@ -0,0 +1,30 @@
(ns quo.react)
(defmacro maybe-js-deps
[deps]
`(if ~deps (into-array ~deps) js/undefined))
(defmacro with-deps-check
[[prev-deps] f deps]
`(let [~prev-deps (quo.react/ref ~deps)]
(when (not= @~prev-deps ~deps)
(reset! ~prev-deps ~deps))
~f))
(defmacro with-effect
"Takes optional vector of dependencies and body to be executed in an effect."
[deps & body]
(let [[deps setup-fn] (if (vector? deps)
[deps body]
[nil (cons deps body)])]
`(effect! #(do ~@setup-fn) ~deps)))
(defmacro with-layout-effect
"Takes optional vector of dependencies and body to be executed in a layout effect."
[deps & body]
(let [[deps setup-fn] (if (vector? deps)
[deps body]
[nil (cons deps body)])]
`(layout-effect! #(do ~@setup-fn) ~deps)))
+136
View File
@@ -0,0 +1,136 @@
(ns quo.react
(:refer-clojure :exclude [ref])
(:require ["react" :as react]
[oops.core :refer [oget oset!]])
(:require-macros [quo.react :refer [with-deps-check maybe-js-deps]]))
(def create-ref react/createRef)
(defn current-ref
[ref]
(oget ref "current"))
;; Inspired from UIX, Rum and Rumext
(defn set-ref-val!
[ref v]
(oset! ref "current" v)
v)
(defn set-native-props
[^js ref ^js props]
(when-let [curr-ref ^js (current-ref ref)]
(.setNativeProps curr-ref props)))
(deftype StateHook [value set-value]
cljs.core/IHash
(-hash [o] (goog/getUid o))
cljs.core/IDeref
(-deref [_o]
value)
cljs.core/IReset
(-reset! [_o new-value]
(set-value new-value))
cljs.core/ISwap
(-swap! [_o f]
(set-value f))
(-swap! [_o f a]
(set-value #(f % a)))
(-swap! [_o f a b]
(set-value #(f % a b)))
(-swap! [_o f a b xs]
(set-value #(apply f % a b xs))))
(defn state
[value]
(let [[value set-value] (react/useState value)
sh (react/useMemo #(StateHook. value set-value) #js [])]
(react/useMemo (fn []
(set! (.-value sh) value)
(set! (.-set-value sh) set-value)
sh)
#js [value set-value])))
(defn use-ref
[v]
(let [ref (react/useRef v)]
(reify
cljs.core/IHash
(-hash [_] (goog/getUid ref))
cljs.core/IDeref
(-deref [_]
(current-ref ref))
cljs.core/IReset
(-reset! [_ new-value]
(set-ref-val! ref new-value))
cljs.core/ISwap
(-swap! [_ f]
(-reset! ref (f (current-ref ref))))
(-swap! [_ f a]
(-reset! ref (f (current-ref ref) a)))
(-swap! [_ f a b]
(-reset! ref (f (current-ref ref) a b)))
(-swap! [_ f a b xs]
(-reset! ref (apply f (current-ref ref) a b xs))))))
(defn ref
[value]
(let [vref (use-ref value)]
(react/useMemo (fn [] vref) #js [])))
(defn effect!
([setup-fn]
(react/useEffect
#(let [ret (setup-fn)]
(if (fn? ret) ret js/undefined))))
([setup-fn deps]
(with-deps-check [prev-deps*]
(react/useEffect
(fn []
(reset! prev-deps* deps)
(let [ret (setup-fn)]
(if (fn? ret) ret js/undefined)))
(maybe-js-deps @prev-deps*))
deps)))
(defn layout-effect!
([setup-fn]
(react/useLayoutEffect
#(let [ret (setup-fn)]
(if (fn? ret) ret js/undefined))))
([setup-fn deps]
(with-deps-check [prev-deps*]
(react/useLayoutEffect
(fn []
(reset! prev-deps* deps)
(let [ret (setup-fn)]
(if (fn? ret) ret js/undefined)))
(maybe-js-deps @prev-deps*))
deps)))
(defn callback
([f] (react/useCallback f))
([f deps]
(with-deps-check [prev-deps*]
(react/useCallback f (maybe-js-deps @prev-deps*))
deps)))
(defn use-memo
([f] (react/useMemo f))
([f deps]
(with-deps-check [prev-deps*]
(react/useMemo f (maybe-js-deps @prev-deps*))
deps)))
(def memo react/memo)
(defn get-children
[^js children]
(->> children
(react/Children.toArray)
(into [])))
+200
View File
@@ -0,0 +1,200 @@
(ns quo.react-native
(:require ["@react-native-community/hooks" :as hooks]
["react-native" :as rn]
["react-native-draggable-flatlist" :default DraggableFlatList]
["react-native-hole-view" :refer (RNHoleView)]
["react-native-navigation" :refer (Navigation)]
["rn-emoji-keyboard" :refer (EmojiKeyboard)]
[cljs-bean.core :as bean]
[quo.platform :as platform]
[reagent.core :as reagent]))
(def hole-view (reagent/adapt-react-class RNHoleView))
(def rn-draggable-flatlist (reagent/adapt-react-class DraggableFlatList))
(def emoji-keyboard (reagent/adapt-react-class EmojiKeyboard))
(def app-registry (.-AppRegistry rn))
(def platform (.-Platform ^js rn))
(def find-node-handle (.-findNodeHandle ^js rn))
(def view (reagent/adapt-react-class (.-View ^js rn)))
(def image (reagent/adapt-react-class (.-Image rn)))
(def text (reagent/adapt-react-class (.-Text ^js rn)))
(defn resolve-asset-source
[uri]
(js->clj (.resolveAssetSource ^js (.-Image ^js rn) uri) :keywordize-keys true))
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn)))
(def modal (reagent/adapt-react-class (.-Modal ^js rn)))
(def refresh-control (reagent/adapt-react-class (.-RefreshControl ^js rn)))
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js rn)))
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js rn)))
(def touchable-without-feedback (reagent/adapt-react-class (.-TouchableWithoutFeedback ^js rn)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js rn)))
(def keyboard-avoiding-view-class (reagent/adapt-react-class (.-KeyboardAvoidingView ^js rn)))
(def navigation-const (atom nil))
(.then (.constants Navigation)
(fn [^js consts]
(reset! navigation-const {:top-bar-height (.-topBarHeight consts)
:bottom-tabs-height (.-bottomTabsHeight consts)
:status-bar-height (.-statusBarHeight consts)})))
(defn keyboard-avoiding-view
[]
(let [this (reagent/current-component)
props (reagent/props this)]
(into [keyboard-avoiding-view-class
(merge (when platform/ios?
{:behavior :padding})
props
{:keyboardVerticalOffset (+ 44 (:status-bar-height @navigation-const))})]
(reagent/children this))))
(def status-bar (.-StatusBar ^js rn))
(def keyboard (.-Keyboard ^js rn))
(def dismiss-keyboard! #(.dismiss ^js keyboard))
(def dimensions (.-Dimensions ^js rn))
(def pan-responder (.-PanResponder ^js rn))
(defn create-pan-responder
[opts]
(.create ^js pan-responder (clj->js opts)))
(def animated (.-Animated rn))
(def subtract (.-subtract ^js animated))
(def animated-flat-list-class
(reagent/adapt-react-class (.-FlatList ^js animated)))
(def animated-view
(reagent/adapt-react-class (.-View ^js animated)))
(def ui-manager (.-UIManager ^js rn))
(def layout-animation (.-LayoutAnimation ^js rn))
(def configure-next (.-configureNext ^js layout-animation))
(def create-animation (.-create ^js layout-animation))
(def layout-animation-presets
{:ease-in-ease-out (-> ^js layout-animation .-Presets .-easeInEaseOut)
:linear (-> ^js layout-animation .-Presets .-linear)
:spring (-> ^js layout-animation .-Presets .-spring)})
(def layout-animation-types
{:spring (-> ^js layout-animation .-Types .-spring)
:linear (-> ^js layout-animation .-Types .-linear)
:ease-in-ease-out (-> ^js layout-animation .-Types .-easeInEaseOut)
:ease-in (-> ^js layout-animation .-Types .-easeIn)
:ease-out (-> ^js layout-animation .-Types .-easeOut)})
(def layout-animation-properties
{:opacity (-> ^js layout-animation .-Properties .-opacity)
:scale-x (-> ^js layout-animation .-Properties .-scaleX)
:scale-y (-> ^js layout-animation .-Properties .-scaleY)
:scale-xy (-> ^js layout-animation .-Properties .-scaleXY)})
(def custom-animations
{:ease-opacity-200 #js
{:duration 200
:create #js
{:type (:ease-in-ease-out layout-animation-types)
:property (:opacity layout-animation-properties)}
:update #js
{:type (:ease-in-ease-out layout-animation-types)
:property (:opacity layout-animation-properties)}
:delete #js
{:type (:ease-in-ease-out layout-animation-types)
:property (:opacity layout-animation-properties)}}})
(defonce enable-layout-animations
(when platform/android?
(.setLayoutAnimationEnabledExperimental ^js ui-manager true)))
(def activity-indicator (reagent/adapt-react-class (.-ActivityIndicator ^js rn)))
;; Flat-list
(def ^:private rn-flat-list (reagent/adapt-react-class (.-FlatList ^js rn)))
(defn- wrap-render-fn
[f render-data]
(fn [data]
(reagent/as-element [f (.-item ^js data) (.-index ^js data)
(.-separators ^js data) render-data
(.-isActive ^js data) (.-drag ^js data)])))
(defn- wrap-on-drag-end-fn
[f]
(fn [data]
(f (.-from ^js data) (.-to ^js data) (.-data ^js data))))
(defn- wrap-key-fn
[f]
(fn [data index]
{:post [(some? %)]}
(f data index)))
(defn base-list-props
[{:keys [key-fn render-fn empty-component header footer separator data render-data on-drag-end-fn]
:as props}]
(merge
{:data (to-array data)}
(when key-fn {:keyExtractor (wrap-key-fn key-fn)})
(when render-fn {:renderItem (wrap-render-fn render-fn render-data)})
(when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))})
(when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))})
(when header {:ListHeaderComponent (reagent/as-element header)})
(when footer {:ListFooterComponent (reagent/as-element footer)})
(when on-drag-end-fn {:onDragEnd (wrap-on-drag-end-fn on-drag-end-fn)})
(dissoc props :data :header :footer :empty-component :separator :render-fn :key-fn :on-drag-end-fn)))
(defn flat-list
[props]
[rn-flat-list (base-list-props props)])
(defn draggable-flat-list
[props]
[rn-draggable-flatlist (base-list-props props)])
(defn animated-flat-list
[props]
[animated-flat-list-class (base-list-props props)])
;; Hooks
(defn use-window-dimensions
[]
(let [window (rn/useWindowDimensions)]
{:font-scale (.-fontScale window)
:height (.-height ^js window)
:scale (.-scale ^js window)
:width (.-width ^js window)}))
(def use-back-handler (.-useBackHandler hooks))
(defn use-keyboard
[]
(let [kb (.useKeyboard hooks)]
{:keyboard-shown (.-keyboardShown ^js kb)
:keyboard-height (.-keyboardHeight ^js kb)}))
(defn use-layout
[]
(let [{:keys [onLayout x y height width]} (bean/bean (.useLayout hooks))]
{:on-layout onLayout
:x x
:y y
:height height
:width width}))
+4
View File
@@ -0,0 +1,4 @@
(ns quo.spec
(:require [clojure.spec.alpha :as s]))
(s/def ::style (s/nilable map?))
+21
View File
@@ -0,0 +1,21 @@
(ns quo.theme
(:require [quo.design-system.colors :as colors]
[reagent.core :as reagent]))
(def theme (reagent/atom nil))
(defn dark?
[]
(= :dark @theme))
(defn get-theme
[]
@theme)
(defn set-theme
[value]
(reset! theme value)
(reset! colors/theme (case value
:dark colors/dark-theme
colors/light-theme))
(colors/set-legacy-theme-type value))
@@ -1,7 +1,6 @@
(ns quo2.components.animated-header-flatlist.style
(:require
[quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(:require [quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(defn container-view
[view-height]
@@ -1,16 +1,16 @@
(ns quo2.components.animated-header-flatlist.view
(:require
[oops.core :as oops]
[quo2.components.animated-header-flatlist.style :as style]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[quo2.theme :as quo.theme]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]))
[react-native.fast-image :as fast-image]
[reagent.core :as reagent]
[quo2.foundations.colors :as colors]
[quo2.components.animated-header-flatlist.style :as style]
[oops.core :as oops]
[quo2.theme :as quo.theme]))
(def header-height 234)
(def cover-height 192)
@@ -1,9 +1,8 @@
(ns quo2.components.avatars.account-avatar.component-spec
(:require
[quo2.components.avatars.account-avatar.style :as style]
[quo2.components.avatars.account-avatar.view :as account-avatar]
[quo2.foundations.colors :as colors]
[test-helpers.component :as h]))
(:require [quo2.components.avatars.account-avatar.view :as account-avatar]
[quo2.components.avatars.account-avatar.style :as style]
[test-helpers.component :as h]
[quo2.foundations.colors :as colors]))
(h/describe "Account Avatar"
(h/test "default render"
@@ -29,7 +28,7 @@
{:height (:size opts)
:width (:size opts)
:borderRadius (style/get-border-radius (:size opts))
:backgroundColor (colors/resolve-color (:customization-color opts) :dark)})
:backgroundColor (colors/custom-color-by-theme (:customization-color opts) 50 60)})
(h/is-truthy (h/query-by-label-text :account-emoji))
(h/has-style (h/query-by-label-text :account-emoji)
{:fontSize (style/get-emoji-size (:size opts))})
@@ -48,7 +47,7 @@
:width (:size opts)
:borderRadius (style/get-border-radius (:size opts))
:borderWidth 1
:backgroundColor (colors/resolve-color (:customization-color opts) :light 10)})
:backgroundColor (colors/custom-color-by-theme (:customization-color opts) 50 50 10 10)})
(h/is-truthy (h/query-by-label-text :account-emoji))
(h/has-style (h/query-by-label-text :account-emoji)
{:fontSize (style/get-emoji-size (:size opts))})
@@ -65,7 +64,7 @@
{:height (:size opts)
:width (:size opts)
:borderRadius (style/get-border-radius (:size opts))
:backgroundColor (colors/resolve-color (:customization-color opts) :dark)})
:backgroundColor (colors/custom-color-by-theme (:customization-color opts) 50 60)})
(h/is-truthy (h/query-by-label-text :account-emoji))
(h/has-style (h/query-by-label-text :account-emoji)
{:fontSize (style/get-emoji-size (:size opts))})
@@ -84,7 +83,7 @@
:width (:size opts)
:borderRadius (style/get-border-radius (:size opts))
:borderWidth 0.8
:backgroundColor (colors/resolve-color (:customization-color opts) :light 10)})
:backgroundColor (colors/custom-color-by-theme (:customization-color opts) 50 50 10 10)})
(h/is-truthy (h/query-by-label-text :account-emoji))
(h/has-style (h/query-by-label-text :account-emoji)
{:fontSize (style/get-emoji-size (:size opts))})
@@ -1,6 +1,5 @@
(ns quo2.components.avatars.account-avatar.style
(:require
[quo2.foundations.colors :as colors]))
(:require [quo2.foundations.colors :as colors]))
(def default-size 80)
(def default-border-radius 16)
@@ -64,14 +63,14 @@
(keyword? size) (container-size size))]
(cond-> {:width width
:height width
:background-color (colors/resolve-color customization-color theme)
:background-color (colors/custom-color-by-theme customization-color 50 60 nil nil theme)
:border-radius (get-border-radius size)
:border-color (colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-5 theme)
:border-color (if (= theme :light) colors/neutral-80-opa-5 colors/white-opa-5)
:padding (get-padding size)
:align-items :center
:justify-content :center}
watch-only?
(assoc :border-width (get-border-width size)
:background-color (colors/resolve-color customization-color theme 10)))))
:background-color (colors/custom-color-by-theme customization-color 50 50 10 10 theme)))))
@@ -1,9 +1,8 @@
(ns quo2.components.avatars.account-avatar.view
(:require
[clojure.string :as string]
[quo2.components.avatars.account-avatar.style :as style]
[quo2.theme :as quo.theme]
[react-native.core :as rn]))
(:require [clojure.string :as string]
[quo2.components.avatars.account-avatar.style :as style]
[quo2.theme :as quo.theme]
[react-native.core :as rn]))
(defn- view-internal
"Opts:
@@ -1,7 +1,6 @@
(ns quo2.components.avatars.channel-avatar.component-spec
(:require
[quo2.components.avatars.channel-avatar.view :as component]
[test-helpers.component :as h]))
(:require [quo2.components.avatars.channel-avatar.view :as component]
[test-helpers.component :as h]))
(h/describe "Channel Avatar"
(h/test "default render"
@@ -1,11 +1,10 @@
(ns quo2.components.avatars.channel-avatar.style
(:require
[quo2.foundations.colors :as colors]))
(:require [quo2.foundations.colors :as colors]))
(def lock-icon-size 12)
(defn outer-container
[{:keys [size customization-color theme]}]
[{:keys [size color]}]
(let [container-size (case size
:size-64 64
:size-32 32
@@ -15,7 +14,7 @@
:border-radius container-size
:justify-content :center
:align-items :center
:background-color (colors/resolve-color customization-color theme 10)}))
:background-color (colors/theme-alpha color 0.1 0.1)}))
(defn emoji-size
[size]
@@ -25,12 +24,12 @@
11)})
(defn lock-container
[size theme]
[size]
(let [distance (if (= size :size-32) 20 12)]
{:position :absolute
:left distance
:top distance
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:background-color (colors/theme-colors colors/white colors/neutral-95)
:border-radius (* 2 lock-icon-size)
:padding 2}))
@@ -1,21 +1,19 @@
(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]
[quo2.theme :as quo.theme]
[react-native.core :as rn]
[utils.string]))
(: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
[{:keys [full-name size customization-color theme]}]
[full-name size color]
(let [amount-initials (if (#{:size-32 :size-64} size) 2 1)
channel-name (string/replace full-name "#" "")]
[text/text
(cond-> {:accessibility-label :initials
:style {:color (colors/resolve-color customization-color theme)}
:style {:color color}
:size :paragraph-2
:weight :semi-bold}
(= size :size-64) (assoc :size :heading-1
@@ -23,18 +21,18 @@
(utils.string/get-initials channel-name amount-initials)]))
(defn- lock
[locked? size theme]
[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 theme)}
:style (style/lock-container size)}
[icons/icon (if locked? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:container-style style/lock-icon
:size 12}]]))
(defn- view-internal
(defn view
"Options:
:size - keyword (default nil) - Container size, for the moment,
@@ -52,22 +50,14 @@
: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 theme]}]
[{:keys [size emoji customization-color locked? full-name]}]
[rn/view
{:accessibility-label :channel-avatar
:style (style/outer-container {:theme theme
:size size
:customization-color customization-color})}
:style (style/outer-container {:size size :color customization-color})}
(if (string/blank? emoji)
[initials
{:full-name full-name
:size size
:customization-color customization-color
:theme theme}]
[initials full-name size customization-color]
[rn/text
{:style (style/emoji-size size)
:accessibility-label :emoji}
(string/trim emoji)])
[lock locked? size theme]])
(def view (quo.theme/with-theme view-internal))
[lock locked? size]])
@@ -1,17 +0,0 @@
(ns quo2.components.avatars.collection-avatar.component-spec
(:require
[quo2.components.avatars.collection-avatar.view :as collection-avatar]
[quo2.foundations.resources :as resources]
[test-helpers.component :as h]))
(h/describe "collection avatar"
(h/describe "Profile picture"
(h/test "Doesn't crash without image"
(h/render
[collection-avatar/view])
(h/is-truthy (h/get-by-label-text :collection-avatar)))
(h/test "Renders with image"
(h/render
[collection-avatar/view {:image (resources/get-image :bored-ape)}])
(h/is-truthy (h/get-by-label-text :collection-avatar)))))
@@ -1,11 +0,0 @@
(ns quo2.components.avatars.collection-avatar.style
(:require
[quo2.foundations.colors :as colors]))
(defn collection-avatar
[theme]
{:width 24
:height 24
:border-width 1
:border-color (colors/theme-colors colors/neutral-80-opa-10 colors/white-opa-10 theme)
:border-radius 6})

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