Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a440ef3fcd | ||
|
|
c3065c9d2a | ||
|
|
b385e5ab4b | ||
|
|
8407cd66d1 | ||
|
|
ba2f037f48 | ||
|
|
eea737625d | ||
|
|
39ea12cf29 | ||
|
|
c5166ac48e | ||
|
|
c5e6bd790d | ||
|
|
b29d248a9b | ||
|
|
396ee208bf | ||
|
|
adb50fa0ee | ||
|
|
5017e13013 | ||
|
|
a6fe626d78 | ||
|
|
8f92fe344a | ||
|
|
19a76c22d1 | ||
|
|
c62204121d | ||
|
|
a4bc18ee3f | ||
|
|
68e53e05bd | ||
|
|
d142d58677 | ||
|
|
56dbb77ee5 | ||
|
|
0dd3cb51fd |
@@ -67,8 +67,8 @@ public class PushNotificationHelper {
|
||||
private static final long DEFAULT_VIBRATION = 300L;
|
||||
private static final String CHANNEL_ID = "status-im-notifications";
|
||||
public static final String ACTION_DELETE_NOTIFICATION = "im.status.ethereum.module.DELETE_NOTIFICATION";
|
||||
public static final String ACTION_TAP_NOTIFICATION = "im.status.ethereum.module.TAP_NOTIFICATION";
|
||||
public static final String ACTION_TAP_STOP = "im.status.ethereum.module.TAP_STOP";
|
||||
final int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_CANCEL_CURRENT;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
|
||||
@@ -119,13 +119,8 @@ public class PushNotificationHelper {
|
||||
private final BroadcastReceiver notificationActionReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction() == ACTION_TAP_NOTIFICATION ||
|
||||
intent.getAction() == ACTION_DELETE_NOTIFICATION) {
|
||||
String deepLink = intent.getExtras().getString("im.status.ethereum.deepLink");
|
||||
if (intent.getAction() == ACTION_DELETE_NOTIFICATION) {
|
||||
String groupId = intent.getExtras().getString("im.status.ethereum.groupId");
|
||||
if (intent.getAction() == ACTION_TAP_NOTIFICATION) {
|
||||
context.startActivity(getOpenAppIntent(deepLink));
|
||||
}
|
||||
if (groupId != null) {
|
||||
cleanGroup(groupId);
|
||||
}
|
||||
@@ -141,7 +136,6 @@ public class PushNotificationHelper {
|
||||
private void registerBroadcastReceiver() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_DELETE_NOTIFICATION);
|
||||
filter.addAction(ACTION_TAP_NOTIFICATION);
|
||||
filter.addAction(ACTION_TAP_STOP);
|
||||
context.registerReceiver(notificationActionReceiver, filter);
|
||||
Log.e(LOG_TAG, "Broadcast Receiver registered");
|
||||
@@ -701,37 +695,31 @@ public class PushNotificationHelper {
|
||||
|
||||
private StatusMessage createMessage(Bundle data) {
|
||||
Person author = getPerson(data.getBundle("notificationAuthor"));
|
||||
return new StatusMessage(data.getString("id"), author, data.getLong("timestamp"), data.getString("message"));
|
||||
long timeStampLongValue = (long) data.getDouble("timestamp");
|
||||
return new StatusMessage(data.getString("id"), author, timeStampLongValue, data.getString("message"));
|
||||
}
|
||||
|
||||
private PendingIntent createGroupOnDismissedIntent(Context context, int notificationId, String groupId, String deepLink) {
|
||||
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
|
||||
intent.putExtra("im.status.ethereum.deepLink", deepLink);
|
||||
intent.putExtra("im.status.ethereum.groupId", groupId);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag);
|
||||
}
|
||||
|
||||
private PendingIntent createGroupOnTapIntent(Context context, int notificationId, String groupId, String deepLink) {
|
||||
Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
|
||||
intent.putExtra("im.status.ethereum.deepLink", deepLink);
|
||||
intent.putExtra("im.status.ethereum.groupId", groupId);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
Intent intent = getOpenAppIntent(deepLink);
|
||||
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag);
|
||||
}
|
||||
|
||||
private PendingIntent createOnTapIntent(Context context, int notificationId, String deepLink) {
|
||||
Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
|
||||
intent.putExtra("im.status.ethereum.deepLink", deepLink);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
Intent intent = getOpenAppIntent(deepLink);
|
||||
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag);
|
||||
}
|
||||
|
||||
private PendingIntent createOnDismissedIntent(Context context, int notificationId, String deepLink) {
|
||||
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
|
||||
intent.putExtra("im.status.ethereum.deepLink", deepLink);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag);
|
||||
}
|
||||
|
||||
public void removeStatusMessage(Bundle bundle) {
|
||||
|
||||
@@ -32,18 +32,6 @@ in {
|
||||
# For parsing gradle.properties into an attrset
|
||||
gradlePropParser = callPackage ./tools/gradlePropParser.nix { };
|
||||
|
||||
# Clojure formatting tool
|
||||
zprint = super.zprint.override rec {
|
||||
buildGraalvmNativeImage = args: super.buildGraalvmNativeImage ( args // rec {
|
||||
inherit (args) pname;
|
||||
version = "1.2.5";
|
||||
src = self.fetchurl {
|
||||
url = "https://github.com/kkinnear/${pname}/releases/download/${version}/${pname}-filter-${version}";
|
||||
sha256 = "sha256-PWdR5jqyzvTk9HoxqDldwtZNik34dmebBtZZ5vtva4A=";
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
# Fix for missing libarclite_macosx.a in Xcode 14.3.
|
||||
# https://github.com/ios-control/ios-deploy/issues/580
|
||||
ios-deploy = super.darwin.ios-deploy.overrideAttrs (old: rec {
|
||||
@@ -60,8 +48,8 @@ in {
|
||||
gradle = super.gradle_6;
|
||||
nodejs = super.nodejs-18_x;
|
||||
yarn = super.yarn.override { nodejs = super.nodejs-18_x; };
|
||||
openjdk = super.openjdk8_headless;
|
||||
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
|
||||
openjdk = super.openjdk11_headless;
|
||||
xcodeWrapper = super.xcodeenv.composeXcodeWrapper {
|
||||
version = "14.0";
|
||||
allowHigher = true;
|
||||
};
|
||||
|
||||
@@ -11,11 +11,10 @@ let
|
||||
# We follow the master branch of official nixpkgs.
|
||||
nixpkgsSrc = fetchFromGitHub {
|
||||
name = "nixpkgs-source";
|
||||
# FIXME: Fork used to get Cocoapods 1.12.0 and apksigner macOS build.
|
||||
owner = "status-im";
|
||||
owner = "NixOS";
|
||||
repo = "nixpkgs";
|
||||
rev = "d0c06fa3d3982a91aa01bd63ed84020cbde3d3ab";
|
||||
sha256 = "sha256-8blvuUHnuf0hFr/PpBxVohJp5CaGXIXhgJlFN/cv7us=";
|
||||
rev = "e7603eba51f2c7820c0a182c6bbb351181caa8e7";
|
||||
sha256 = "sha256-XJZ/o17eOd2sEsGif+/MQBnfa2DKmndWgJyc7CWajFc=";
|
||||
# To get the compressed Nix sha256, use:
|
||||
# nix-prefetch-url --unpack https://github.com/${ORG}/nixpkgs/archive/${REV}.tar.gz
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# by setting android_sdk.accept_license = true.
|
||||
androidenv.composeAndroidPackages {
|
||||
toolsVersion = "26.1.1";
|
||||
platformToolsVersion = "33.0.2";
|
||||
platformToolsVersion = "33.0.3";
|
||||
buildToolsVersions = [ "30.0.0" ];
|
||||
platformVersions = [ "31" ];
|
||||
cmakeVersions = [ "3.18.1" ];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
mkShell {
|
||||
name = "android-sdk-shell";
|
||||
buildInputs = [ openjdk ];
|
||||
shellHook = ''
|
||||
export JAVA_HOME="${openjdk}"
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
{ stdenv }:
|
||||
|
||||
{ version ? "11.1"
|
||||
, allowHigher ? false
|
||||
, xcodeBaseDir ? "/Applications/Xcode.app" }:
|
||||
|
||||
assert stdenv.isDarwin;
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "xcode-wrapper-${version}${if allowHigher then "-plus" else ""}";
|
||||
# Fix 'xcodebuild: Operation not permitted' when 'sandbox=relaxed' is used.
|
||||
# https://github.com/NixOS/nixpkgs/pull/228696
|
||||
__noChroot = stdenv.isDarwin;
|
||||
buildCommand = ''
|
||||
mkdir -p $out/bin
|
||||
cd $out/bin
|
||||
ln -s /usr/bin/xcode-select
|
||||
ln -s /usr/bin/security
|
||||
ln -s /usr/bin/codesign
|
||||
ln -s /usr/bin/xcrun
|
||||
ln -s /usr/bin/plutil
|
||||
ln -s /usr/bin/clang
|
||||
ln -s /usr/bin/lipo
|
||||
ln -s /usr/bin/file
|
||||
ln -s /usr/bin/rev
|
||||
ln -s "${xcodeBaseDir}/Contents/Developer/usr/bin/xcodebuild"
|
||||
ln -s "${xcodeBaseDir}/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator"
|
||||
|
||||
cd ..
|
||||
ln -s "${xcodeBaseDir}/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs"
|
||||
|
||||
# Check if we have the xcodebuild version that we want
|
||||
currVer=$($out/bin/xcodebuild -version)
|
||||
${if allowHigher then ''
|
||||
if [ -z "$(printf '%s\n' "${version}" "$currVer" | sort -V | head -n1)""" != "${version}" ]
|
||||
'' else ''
|
||||
if [ -z "$(echo $currVer | grep -x 'Xcode ${version}')" ]
|
||||
''}
|
||||
then
|
||||
echo "We require xcodebuild version${if allowHigher then " or higher" else ""}: ${version}"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
}
|
||||
@@ -20,7 +20,7 @@ in mkShell {
|
||||
# lint specific utilities
|
||||
clj-kondo zprint
|
||||
# other nice to have stuff
|
||||
yarn nodejs python27
|
||||
yarn nodejs python310
|
||||
] # and some special cases
|
||||
++ lib.optionals stdenv.isDarwin [ cocoapods clang tcl ]
|
||||
++ lib.optionals (!stdenv.isDarwin) [ gcc8 ]
|
||||
|
||||
@@ -57,7 +57,7 @@ let
|
||||
|
||||
# for 'scripts/generate-keystore.sh'
|
||||
keytool = mkShell {
|
||||
buildInputs = with pkgs; [ openjdk8 apksigner ];
|
||||
buildInputs = with pkgs; [ openjdk apksigner ];
|
||||
};
|
||||
|
||||
# for targets needing 'adb', 'apkanalyzer' and other SDK/NDK tools
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1011 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -220,10 +220,10 @@
|
||||
only icon
|
||||
[button {:icon true} :i/close-circle]"
|
||||
[_ _]
|
||||
(let [pressed (reagent/atom false)]
|
||||
(let [pressed-in (reagent/atom false)]
|
||||
(fn
|
||||
[{:keys [on-press disabled type size before after above
|
||||
width customization-color override-theme override-background-color
|
||||
[{:keys [on-press disabled type size before after above icon-secondary-no-color
|
||||
width customization-color override-theme override-background-color pressed
|
||||
on-long-press accessibility-label icon icon-no-color style inner-style test-ID]
|
||||
:or {type :primary
|
||||
size 40
|
||||
@@ -234,17 +234,17 @@
|
||||
[(or
|
||||
override-theme
|
||||
(theme/get-theme)) type])
|
||||
state (cond disabled :disabled
|
||||
@pressed :pressed
|
||||
:else :default)
|
||||
state (cond disabled :disabled
|
||||
(or @pressed-in pressed) :pressed
|
||||
:else :default)
|
||||
icon-size (when (= 24 size) 12)
|
||||
icon-secondary-color (or icon-secondary-color icon-color)]
|
||||
[rn/touchable-without-feedback
|
||||
(merge {:test-ID test-ID
|
||||
:disabled disabled
|
||||
:accessibility-label accessibility-label
|
||||
:on-press-in #(reset! pressed true)
|
||||
:on-press-out #(reset! pressed nil)}
|
||||
:on-press-in #(reset! pressed-in true)
|
||||
:on-press-out #(reset! pressed-in nil)}
|
||||
(when on-press
|
||||
{:on-press on-press})
|
||||
(when on-long-press
|
||||
@@ -306,5 +306,6 @@
|
||||
[quo2.icons/icon after
|
||||
{:container-style {:margin-left 4
|
||||
:margin-right (if (= size 40) 12 8)}
|
||||
:no-color icon-secondary-no-color
|
||||
:color icon-secondary-color
|
||||
:size icon-size}]])]]]))))
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
(case type
|
||||
:words
|
||||
[rn/flat-list
|
||||
{:data words
|
||||
{:keyboard-should-persist-taps :always
|
||||
:data words
|
||||
:content-container-style style/word-list
|
||||
:render-fn word-component
|
||||
:render-data {:blur? blur?
|
||||
|
||||
@@ -1,53 +1,62 @@
|
||||
(ns quo2.components.drawers.action-drawers.component-spec
|
||||
(:require ["@testing-library/react-native" :as rtl]
|
||||
[quo2.components.drawers.action-drawers.view :as action-drawer]
|
||||
[reagent.core :as reagent]))
|
||||
(:require [quo2.components.drawers.action-drawers.view :as action-drawer]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(defn render-action-drawer
|
||||
([options]
|
||||
(rtl/render (reagent/as-element [action-drawer/action-drawer options]))))
|
||||
(h/describe "action drawer"
|
||||
(h/test "default render"
|
||||
(h/render [action-drawer/action-drawer
|
||||
[[{:icon :i/friend
|
||||
:label "a sample label"}]]])
|
||||
(h/is-truthy (h/get-by-text "a sample label")))
|
||||
|
||||
(js/global.test "action-drawer renders with elements label displaying"
|
||||
(fn []
|
||||
(render-action-drawer [[{:icon :i/friend
|
||||
:label "a sample label"}]])
|
||||
(-> (js/expect (rtl/screen.getByText "a sample label"))
|
||||
(.toBeTruthy))))
|
||||
(h/test "renders with elements sub-label displaying"
|
||||
(h/render [action-drawer/action-drawer
|
||||
[[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:sub-label "a sample sub label"}]]])
|
||||
(h/is-truthy (h/get-by-text "a sample sub label")))
|
||||
|
||||
(js/global.test "action-drawer renders with elements sub-label displaying"
|
||||
(fn []
|
||||
(render-action-drawer [[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:sub-label "a sample sub label"}]])
|
||||
(-> (js/expect (rtl/screen.getByText "a sample sub label"))
|
||||
(.toBeTruthy))))
|
||||
(h/test "on click action works on element"
|
||||
(let [on-press (js/jest.fn)]
|
||||
(h/render [action-drawer/action-drawer
|
||||
[[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:on-press on-press}]]])
|
||||
(h/fire-event :press (h/get-by-text "a sample label"))
|
||||
(h/was-called on-press)))
|
||||
|
||||
(js/global.test "action-drawer on click action works on element"
|
||||
(let [event (js/jest.fn)]
|
||||
(fn []
|
||||
(render-action-drawer [[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:on-press event}]])
|
||||
(rtl/fireEvent.press (rtl/screen.getByText "a sample label"))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalled)))))
|
||||
(h/test "disabled state"
|
||||
(let [on-press-muted (js/jest.fn)
|
||||
on-press-friend (js/jest.fn)
|
||||
label-mute "Mute"
|
||||
label-friend "View member details"]
|
||||
(h/render [action-drawer/action-drawer
|
||||
[[{:icon :i/muted
|
||||
:label label-mute
|
||||
:on-press on-press-muted}
|
||||
{:icon :i/friend
|
||||
:label label-friend
|
||||
:disabled? true
|
||||
:on-press on-press-friend}]]])
|
||||
(h/fire-event :press (h/get-by-text label-mute))
|
||||
(h/was-called on-press-muted)
|
||||
|
||||
(js/global.test "action-drawer renders two icons when set"
|
||||
(fn []
|
||||
(render-action-drawer [[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:right-icon :i/friend
|
||||
:accessibility-label :first-element}]])
|
||||
(-> (js/expect (rtl/screen.getByLabelText "right-icon-for-action"))
|
||||
(.toBeTruthy))
|
||||
(-> (js/expect (rtl/screen.queryByLabelText "left-icon-for-action"))
|
||||
(.toBeTruthy))))
|
||||
(h/fire-event :press (h/get-by-text label-friend))
|
||||
(h/was-not-called on-press-friend)))
|
||||
|
||||
(js/global.test "action-drawer renders a divider when the add-divider? prop is true"
|
||||
(fn []
|
||||
(render-action-drawer [[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:add-divider? true
|
||||
:accessibility-label :first-element}]])
|
||||
(-> (js/expect (rtl/screen.getAllByLabelText "divider"))
|
||||
(.toBeTruthy))))
|
||||
(h/test "renders two icons when set"
|
||||
(h/render [action-drawer/action-drawer
|
||||
[[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:right-icon :i/friend
|
||||
:accessibility-label :first-element}]]])
|
||||
(h/is-truthy (h/get-by-label-text "right-icon-for-action"))
|
||||
(h/is-truthy (h/query-by-label-text "left-icon-for-action")))
|
||||
|
||||
(h/test "renders a divider when the add-divider? prop is true"
|
||||
(h/render [action-drawer/action-drawer
|
||||
[[{:icon :i/friend
|
||||
:label "a sample label"
|
||||
:add-divider? true
|
||||
:accessibility-label :first-element}]]])
|
||||
(h/is-truthy (h/get-all-by-label-text "divider"))))
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
:flex-direction :row})
|
||||
|
||||
(defn container
|
||||
[sub-label]
|
||||
[sub-label disabled?]
|
||||
{:border-radius 12
|
||||
:height (if sub-label 58 50)
|
||||
:opacity (when disabled? 0.3)
|
||||
:margin-horizontal 8})
|
||||
|
||||
(defn row-container
|
||||
|
||||
@@ -18,12 +18,19 @@
|
||||
:accessible true
|
||||
:accessibility-label :divider}])
|
||||
|
||||
(defn action
|
||||
(defn- maybe-pressable
|
||||
[disabled? props child]
|
||||
(if disabled?
|
||||
[rn/view (dissoc props :on-press) child]
|
||||
[rn/touchable-highlight props child]))
|
||||
|
||||
(defn- action
|
||||
[{:keys [icon
|
||||
label
|
||||
sub-label
|
||||
right-icon
|
||||
danger?
|
||||
disabled?
|
||||
on-press
|
||||
add-divider?
|
||||
override-theme
|
||||
@@ -33,9 +40,9 @@
|
||||
[:<> {:key label}
|
||||
(when add-divider?
|
||||
[divider])
|
||||
[rn/touchable-highlight
|
||||
[maybe-pressable disabled?
|
||||
{:accessibility-label accessibility-label
|
||||
:style (style/container sub-label)
|
||||
:style (style/container sub-label disabled?)
|
||||
:underlay-color (colors/theme-colors colors/neutral-5 colors/neutral-90 override-theme)
|
||||
:on-press on-press}
|
||||
[rn/view
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
(ns quo2.components.drawers.drawer-buttons.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.platform :as platform]))
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def outer-container
|
||||
{:height 216
|
||||
:border-radius 20})
|
||||
{:height 216
|
||||
:border-top-left-radius 20
|
||||
:border-top-right-radius 20
|
||||
:overflow :hidden})
|
||||
|
||||
(def top-card
|
||||
{:flex 1
|
||||
@@ -12,9 +13,7 @@
|
||||
:padding-horizontal 20
|
||||
:border-top-left-radius 20
|
||||
:border-top-right-radius 20
|
||||
:background-color (if platform/ios?
|
||||
colors/neutral-80-opa-80-blur
|
||||
colors/neutral-80)})
|
||||
:background-color colors/neutral-80-opa-80-blur})
|
||||
|
||||
(def bottom-card
|
||||
{:position :absolute
|
||||
@@ -33,11 +32,11 @@
|
||||
:justify-content :space-between})
|
||||
|
||||
(def bottom-icon
|
||||
{:border-radius 40
|
||||
{:border-radius 32
|
||||
:border-width 1
|
||||
:margin-left 24
|
||||
:height 28
|
||||
:width 28
|
||||
:height 32
|
||||
:width 32
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:border-color colors/white-opa-5})
|
||||
@@ -52,4 +51,4 @@
|
||||
(defn heading-text
|
||||
[gap]
|
||||
{:color colors/white
|
||||
:margin-bottom gap})
|
||||
:margin-bottom gap})
|
||||
@@ -71,14 +71,28 @@
|
||||
child-2 string, keyword or hiccup
|
||||
"
|
||||
[{:keys [container-style top-card bottom-card]} child-1 child-2]
|
||||
[blur/ios-view
|
||||
[rn/view
|
||||
{:style (merge container-style style/outer-container)}
|
||||
[card
|
||||
(merge {:gap 4
|
||||
:top? true
|
||||
:style style/top-card}
|
||||
top-card) child-1]
|
||||
[card
|
||||
(merge {:style style/bottom-card
|
||||
:gap 20}
|
||||
bottom-card) child-2]])
|
||||
[blur/view
|
||||
{:blur-type :dark
|
||||
:blur-amount 10
|
||||
:style {:flex 1
|
||||
:border-top-left-radius 20
|
||||
:border-top-right-radius 20}}]
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:background-color :transparent
|
||||
:position :absolute
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom 0}}
|
||||
[card
|
||||
(merge {:gap 4
|
||||
:top? true
|
||||
:style style/top-card}
|
||||
top-card) child-1]
|
||||
[card
|
||||
(merge {:style style/bottom-card
|
||||
:gap 20}
|
||||
bottom-card) child-2]]])
|
||||
@@ -1,165 +1,14 @@
|
||||
(ns quo2.components.dropdowns.dropdown
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn apply-anim
|
||||
[dd-height val]
|
||||
(reanimated/animate-shared-value-with-delay dd-height
|
||||
val
|
||||
300
|
||||
:easing1
|
||||
0))
|
||||
|
||||
(def sizes
|
||||
{:big {:icon-size 20
|
||||
:font {:font-size :paragraph-1}
|
||||
:height 40
|
||||
:padding {:padding-with-icon {:padding-vertical 9
|
||||
:padding-horizontal 12}
|
||||
:padding-with-no-icon {:padding-vertical 9
|
||||
:padding-left 12
|
||||
:padding-right 8}}}
|
||||
:medium {:icon-size 20
|
||||
:font {:font-size :paragraph-1}
|
||||
:height 32
|
||||
:padding {:padding-with-icon {:padding-vertical 5
|
||||
:padding-horizontal 8}
|
||||
:padding-with-no-icon {:padding-vertical 5
|
||||
:padding-left 12
|
||||
:padding-right 8}}}
|
||||
:small {:icon-size 15
|
||||
:font {:font-size :paragraph-2}
|
||||
:height 24
|
||||
:padding {:padding-with-icon {:padding-vertical 3
|
||||
:padding-horizontal 6}
|
||||
:padding-with-no-icon {:padding-vertical 3
|
||||
:padding-horizontal 6}}}})
|
||||
(defn color-by-10
|
||||
[color]
|
||||
(colors/alpha color 0.6))
|
||||
|
||||
(defn dropdown-comp
|
||||
[{:keys [icon dd-height size disabled? dd-color use-border? border-color]}]
|
||||
(let [dark? (colors/dark?)
|
||||
{:keys [width height width-with-icon padding font icon-size]} (size sizes)
|
||||
{:keys [padding-with-icon padding-with-no-icon]} padding
|
||||
font-size (:font-size font)
|
||||
spacing (case size
|
||||
:big 4
|
||||
:medium 2
|
||||
:small 2)
|
||||
open? (reagent/atom false)]
|
||||
(fn []
|
||||
[rn/touchable-opacity
|
||||
(cond->
|
||||
{:on-press (fn []
|
||||
(if (swap! open? not)
|
||||
(apply-anim dd-height 120)
|
||||
(apply-anim dd-height 0)))
|
||||
:style (cond->
|
||||
(merge
|
||||
(if icon
|
||||
padding-with-icon
|
||||
padding-with-no-icon)
|
||||
{:width (if icon
|
||||
width-with-icon
|
||||
width)
|
||||
:height height
|
||||
:border-radius (case size
|
||||
:big 12
|
||||
:medium 10
|
||||
:small 8)
|
||||
:flex-direction :row
|
||||
:align-items :center
|
||||
:background-color (if @open?
|
||||
dd-color
|
||||
(color-by-10 dd-color))})
|
||||
use-border? (assoc :border-width 1
|
||||
:border-color (if @open?
|
||||
border-color
|
||||
(color-by-10 border-color))))}
|
||||
disabled? (assoc-in [:style :opacity] 0.3)
|
||||
disabled? (assoc :disabled true))
|
||||
(when icon
|
||||
[icons/icon icon
|
||||
{:no-color true
|
||||
:size 20
|
||||
:container-style {:margin-right spacing
|
||||
:margin-top 1
|
||||
:width icon-size
|
||||
:height icon-size}}])
|
||||
[text/text
|
||||
{:size font-size
|
||||
:weight :medium
|
||||
:font :font-medium
|
||||
:color :main} "Dropdown"]
|
||||
[icons/icon
|
||||
(if @open?
|
||||
(if dark?
|
||||
:main-icons/pullup-dark
|
||||
:main-icons/pullup)
|
||||
(if dark?
|
||||
:main-icons/dropdown-dark
|
||||
:main-icons/dropdown))
|
||||
{:size 20
|
||||
:no-color true
|
||||
:container-style {:width (+ icon-size 3)
|
||||
:border-radius 20
|
||||
:margin-left (if (= :small size)
|
||||
2
|
||||
4)
|
||||
:margin-top 1
|
||||
:height (+ icon-size 4)}}]])))
|
||||
|
||||
(defn items-comp
|
||||
[{:keys [items on-select]}]
|
||||
(let [items-count (count items)]
|
||||
[rn/scroll-view
|
||||
{:horizontal false
|
||||
:nestedScrollEnabled true}
|
||||
(doall
|
||||
(map-indexed (fn [index item]
|
||||
[rn/touchable-opacity
|
||||
{:key (str item index)
|
||||
:style {:padding 4
|
||||
:border-bottom-width (if (= index (- items-count 1))
|
||||
0
|
||||
1)
|
||||
:border-color (colors/theme-colors
|
||||
colors/neutral-100
|
||||
colors/white)
|
||||
:text-align :center}
|
||||
:on-press #(on-select item)}
|
||||
[text/text {:style {:text-align :center}} item]])
|
||||
items))]))
|
||||
|
||||
(defn- f-dropdown
|
||||
[{:keys [items icon text default-item on-select size disabled? border-color use-border? dd-color]}]
|
||||
(let [dd-height (reanimated/use-shared-value 0)]
|
||||
[rn/view {:style {:flex-grow 1}}
|
||||
[dropdown-comp
|
||||
{:items items
|
||||
:icon icon
|
||||
:disabled? disabled?
|
||||
:size size
|
||||
:dd-color dd-color
|
||||
:text text
|
||||
:border-color (colors/custom-color-by-theme border-color 50 60)
|
||||
:use-border? use-border?
|
||||
:default-item default-item
|
||||
:dd-height dd-height}]
|
||||
[reanimated/view
|
||||
{:style (reanimated/apply-animations-to-style
|
||||
{:height dd-height}
|
||||
{})}
|
||||
[items-comp
|
||||
{:items items
|
||||
:on-select on-select}]]]))
|
||||
(:require [quo2.components.buttons.button :as button]))
|
||||
|
||||
(defn dropdown
|
||||
[params]
|
||||
[:f> f-dropdown params])
|
||||
[_ _]
|
||||
(fn [{:keys [on-change selected] :as opts} children]
|
||||
[button/button
|
||||
(merge
|
||||
opts
|
||||
{:after (if selected :i/pullup :i/dropdown)
|
||||
:icon-secondary-no-color true
|
||||
:pressed selected
|
||||
:on-press #(when on-change (on-change selected))})
|
||||
children]))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
(ns quo2.components.empty-state.empty-state.styles
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def container
|
||||
{:padding 12
|
||||
:align-items :center})
|
||||
|
||||
(def image
|
||||
{:width 80
|
||||
:height 80})
|
||||
|
||||
(def text-container
|
||||
{:margin-top 12
|
||||
:align-items :center})
|
||||
|
||||
(defn title
|
||||
[blur?]
|
||||
(cond-> {:margin-bottom 2}
|
||||
blur? (assoc :color colors/white)))
|
||||
|
||||
(defn description
|
||||
[blur?]
|
||||
(when blur?
|
||||
{:color colors/white}))
|
||||
|
||||
(def button-container {:margin-top 20})
|
||||
|
||||
(defn upper-button-color
|
||||
[customization-color]
|
||||
(colors/custom-color-by-theme customization-color 50 60))
|
||||
@@ -0,0 +1,50 @@
|
||||
(ns quo2.components.empty-state.empty-state.view
|
||||
(:require [quo2.components.buttons.button :as button]
|
||||
[quo2.components.empty-state.empty-state.styles :as styles]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
(defn empty-state
|
||||
[{:keys [customization-color image title description blur?]
|
||||
upper-button :upper-button
|
||||
lower-button :lower-button
|
||||
:or {customization-color :blue}}]
|
||||
[rn/view {:style styles/container}
|
||||
[fast-image/fast-image
|
||||
{:style styles/image
|
||||
:source image}]
|
||||
[rn/view {:style styles/text-container}
|
||||
[text/text
|
||||
{:style (styles/title blur?)
|
||||
:number-of-lines 1
|
||||
:weight :semi-bold
|
||||
:size :paragraph-1}
|
||||
title]
|
||||
[text/text
|
||||
{:style (styles/description blur?)
|
||||
:number-of-lines 1
|
||||
:text-align :center
|
||||
:weight :regular
|
||||
:size :paragraph-2}
|
||||
description]]
|
||||
(when-let [{upper-button-text :text
|
||||
upper-button-on-press :on-press} upper-button]
|
||||
[rn/view {:style styles/button-container}
|
||||
[button/button
|
||||
(cond-> {:type :primary
|
||||
:size 32
|
||||
:override-background-color (styles/upper-button-color customization-color)
|
||||
:on-press upper-button-on-press}
|
||||
blur? (assoc :override-theme :dark))
|
||||
upper-button-text]
|
||||
|
||||
(when-let [{lower-button-text :text
|
||||
lower-button-on-press :on-press} lower-button]
|
||||
[button/button
|
||||
(cond-> {:style {:margin-top 12}
|
||||
:size 32
|
||||
:type :blur-bg
|
||||
:on-press lower-button-on-press}
|
||||
blur? (assoc :override-theme :dark))
|
||||
lower-button-text])])])
|
||||
@@ -28,8 +28,9 @@
|
||||
(h/describe "Error text"
|
||||
(h/test "Marked when words doesn't satisfy a predicate"
|
||||
(h/render [recovery-phrase/recovery-phrase-input
|
||||
{:mark-errors? true
|
||||
:error-pred #(>= (count %) 5)}
|
||||
{:mark-errors? true
|
||||
:error-pred-current-word #(>= (count %) 5)
|
||||
:error-pred-written-words #(>= (count %) 5)}
|
||||
"Text with some error words that don't satisfy the predicate"])
|
||||
(let [children-text-nodes (-> (h/get-by-label-text :recovery-phrase-input)
|
||||
(oops/oget "props" "children" "props" "children")
|
||||
|
||||
@@ -15,15 +15,27 @@
|
||||
text])
|
||||
|
||||
(defn- mark-error-words
|
||||
[pred text word-limit]
|
||||
(let [word-limit (or word-limit ##Inf)]
|
||||
(into [:<>]
|
||||
(comp (map-indexed (fn [idx word]
|
||||
(if (or (pred word) (>= idx word-limit))
|
||||
[error-word word]
|
||||
word)))
|
||||
(interpose " "))
|
||||
(string/split text #" "))))
|
||||
[pred-last-word pred-previous-words text word-limit]
|
||||
(let [last-index (dec (count (string/split text #"\s+")))
|
||||
words (map #(apply str %)
|
||||
(partition-by #(= " " %) text))]
|
||||
(->> words
|
||||
(reduce (fn [{:keys [idx] :as acc} word]
|
||||
(let [error-pred (if (= last-index idx) pred-last-word pred-previous-words)
|
||||
invalid-word? (and (or (error-pred word)
|
||||
(>= idx word-limit))
|
||||
(not (string/blank? word)))
|
||||
not-blank-spaces? (not (string/blank? word))]
|
||||
(cond-> acc
|
||||
not-blank-spaces? (update :idx inc)
|
||||
:always (update :result
|
||||
conj
|
||||
(if invalid-word?
|
||||
[error-word word]
|
||||
word)))))
|
||||
{:result [:<>]
|
||||
:idx 0})
|
||||
:result)))
|
||||
|
||||
(defn recovery-phrase-input
|
||||
[_ _]
|
||||
@@ -31,9 +43,11 @@
|
||||
set-focused #(reset! state :focused)
|
||||
set-default #(reset! state :default)]
|
||||
(fn [{:keys [customization-color override-theme blur? on-focus on-blur mark-errors?
|
||||
error-pred word-limit]
|
||||
:or {customization-color :blue
|
||||
error-pred (constantly false)}
|
||||
error-pred-current-word error-pred-written-words word-limit]
|
||||
:or {customization-color :blue
|
||||
word-limit ##Inf
|
||||
error-pred-current-word (constantly false)
|
||||
error-pred-written-words (constantly false)}
|
||||
:as props}
|
||||
text]
|
||||
(let [extra-props (apply dissoc props custom-props)]
|
||||
@@ -53,5 +67,5 @@
|
||||
(when on-blur (on-blur)))}
|
||||
extra-props)
|
||||
(if mark-errors?
|
||||
(mark-error-words error-pred text word-limit)
|
||||
(mark-error-words error-pred-current-word error-pred-written-words text word-limit)
|
||||
text)]]))))
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
:default-value default-value
|
||||
:accessibility-label :profile-title-input
|
||||
:keyboard-appearance (theme/theme-value :light :dark override-theme)
|
||||
:on-focus #(swap! focused? (fn [] true))
|
||||
:on-blur #(swap! focused? (fn [] false))
|
||||
:on-focus #(swap! focused? (constantly true))
|
||||
:on-blur #(swap! focused? (constantly false))
|
||||
:input-mode :text
|
||||
:on-change-text on-change
|
||||
:editable (not disabled?)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns quo2.components.reactions.reaction
|
||||
(:require [quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.components.reactions.resource :as resource]
|
||||
[quo2.components.reactions.style :as style]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.theme :as theme]
|
||||
@@ -13,7 +14,7 @@
|
||||
{:on-press on-press
|
||||
:accessibility-label :emoji-reaction-add
|
||||
:style (style/add-reaction)}
|
||||
[icons/icon :i/add
|
||||
[icons/icon :i/add-reaction
|
||||
{:size 20
|
||||
:color (if dark?
|
||||
colors/white
|
||||
@@ -21,15 +22,17 @@
|
||||
|
||||
(defn reaction
|
||||
"Add your emoji as a param here"
|
||||
[{:keys [emoji clicks neutral? on-press accessibility-label]}]
|
||||
[{:keys [emoji clicks neutral? on-press accessibility-label on-long-press]}]
|
||||
(let [numeric-value (int clicks)]
|
||||
[rn/touchable-opacity
|
||||
{:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:accessibility-label accessibility-label
|
||||
:style (style/reaction neutral?)}
|
||||
[icons/icon emoji
|
||||
{:no-color true
|
||||
:size 16}]
|
||||
[rn/image
|
||||
{:style {:width 16 :height 16}
|
||||
:accessibility-label :emoji
|
||||
:source (resource/get-reaction emoji)}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
(ns quo2.components.reactions.resource
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(def ^:private reactions-dir "./resources/images/reactions/")
|
||||
|
||||
(defn- resolve-reaction
|
||||
[reaction]
|
||||
(let [path (str reactions-dir (name reaction) ".png")
|
||||
file (io/file path)]
|
||||
(when (.exists file)
|
||||
`(js/require ~(str "." path)))))
|
||||
|
||||
(defn- find-all-image-base-names
|
||||
[]
|
||||
(let [dir (io/file reactions-dir)]
|
||||
(->> dir
|
||||
file-seq
|
||||
(filter #(string/ends-with? % "png"))
|
||||
(map #(.getName %))
|
||||
(map #(string/replace % #"\.png$" ""))
|
||||
(map #(first (string/split % #"@")))
|
||||
distinct)))
|
||||
|
||||
(defmacro resolve-all-reactions
|
||||
[]
|
||||
(reduce (fn [acc reaction]
|
||||
(assoc acc reaction (resolve-reaction reaction)))
|
||||
{}
|
||||
(find-all-image-base-names)))
|
||||
@@ -0,0 +1,12 @@
|
||||
(ns quo2.components.reactions.resource
|
||||
(:require-macros [quo2.components.reactions.resource :refer [resolve-all-reactions]]))
|
||||
|
||||
(def ^:private reactions
|
||||
(resolve-all-reactions))
|
||||
|
||||
(defn get-reaction
|
||||
[reaction]
|
||||
(assert (keyword? reaction) "Reaction should be a keyword")
|
||||
(assert (= "reaction" (namespace reaction))
|
||||
"Reaction keyword should be namespaced with :reaction")
|
||||
(get reactions (name reaction)))
|
||||
@@ -0,0 +1,23 @@
|
||||
(ns quo2.components.selectors.reactions.component-spec
|
||||
(:require [quo2.components.selectors.reactions.view :as view]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Selectors > Reactions"
|
||||
(h/test "renders component"
|
||||
(h/render [view/view :reaction/sad])
|
||||
(h/is-truthy (h/get-by-label-text :reaction)))
|
||||
|
||||
(h/describe "on-press event"
|
||||
(h/test "starts with released state"
|
||||
(let [on-press (h/mock-fn)]
|
||||
(h/render [view/view :reaction/love {:on-press on-press}])
|
||||
(h/fire-event :press (h/get-by-label-text :reaction))
|
||||
(h/was-called on-press)))
|
||||
|
||||
(h/test "starts with pressed state"
|
||||
(let [on-press (h/mock-fn)]
|
||||
(h/render [view/view :reaction/love
|
||||
{:on-press on-press
|
||||
:start-pressed? true}])
|
||||
(h/fire-event :press (h/get-by-label-text :reaction))
|
||||
(h/was-called on-press)))))
|
||||
@@ -0,0 +1,11 @@
|
||||
(ns quo2.components.selectors.reactions.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(defn container
|
||||
[pressed?]
|
||||
{:padding 10
|
||||
:border-radius 12
|
||||
:border-width 1
|
||||
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)
|
||||
:background-color (when pressed?
|
||||
(colors/theme-colors colors/neutral-10 colors/neutral-80-opa-40))})
|
||||
@@ -0,0 +1,25 @@
|
||||
(ns quo2.components.selectors.reactions.view
|
||||
(:require [quo2.components.reactions.resource :as reactions.resource]
|
||||
[quo2.components.selectors.reactions.style :as style]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn view
|
||||
[_ {:keys [start-pressed?]}]
|
||||
(let [pressed? (reagent/atom start-pressed?)]
|
||||
(fn [emoji
|
||||
{:keys [container-style on-press
|
||||
accessibility-label]
|
||||
:or {accessibility-label :reaction}}]
|
||||
[rn/touchable-without-feedback
|
||||
{:accessibility-label accessibility-label
|
||||
:on-press (fn [e]
|
||||
(swap! pressed? not)
|
||||
(when on-press
|
||||
(on-press e)))}
|
||||
[rn/view
|
||||
{:style (merge (style/container @pressed?)
|
||||
container-style)}
|
||||
[rn/image
|
||||
{:source (reactions.resource/get-reaction emoji)
|
||||
:style {:width 20 :height 20}}]]])))
|
||||
@@ -6,7 +6,8 @@
|
||||
[react-native.masked-view :as masked-view]
|
||||
[reagent.core :as reagent]
|
||||
[utils.collection :as utils.collection]
|
||||
[utils.number :as utils.number]))
|
||||
[utils.number :as utils.number]
|
||||
[react-native.gesture :as gesture]))
|
||||
|
||||
(def default-tab-size 32)
|
||||
(def unread-count-offset 3)
|
||||
@@ -131,7 +132,8 @@
|
||||
style
|
||||
size
|
||||
blur?
|
||||
override-theme]
|
||||
override-theme
|
||||
in-scroll-view?]
|
||||
:or {fade-end-percentage fade-end-percentage
|
||||
fade-end? false
|
||||
scrollable? false
|
||||
@@ -142,7 +144,9 @@
|
||||
[rn/view {:style {:margin-top (- (dec unread-count-offset))}}
|
||||
[masked-view-wrapper
|
||||
{:fade-end-percentage (get @fading :fade-end-percentage) :fade-end? fade-end?}
|
||||
[rn/flat-list
|
||||
[(if in-scroll-view?
|
||||
gesture/flat-list
|
||||
rn/flat-list)
|
||||
(merge
|
||||
(dissoc props
|
||||
:default-active
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
quo2.components.drawers.permission-context.view
|
||||
quo2.components.dropdowns.dropdown
|
||||
quo2.components.header
|
||||
quo2.components.empty-state.empty-state.view
|
||||
quo2.components.icon
|
||||
quo2.components.info.info-message
|
||||
quo2.components.info.information-box
|
||||
@@ -64,6 +65,7 @@
|
||||
quo2.components.profile.profile-card.view
|
||||
quo2.components.profile.select-profile.view
|
||||
quo2.components.reactions.reaction
|
||||
quo2.components.selectors.reactions.view
|
||||
quo2.components.record-audio.record-audio.view
|
||||
quo2.components.record-audio.soundtrack.view
|
||||
quo2.components.selectors.disclaimer.view
|
||||
@@ -110,6 +112,9 @@
|
||||
(def skeleton quo2.components.loaders.skeleton/skeleton)
|
||||
(def author quo2.components.messages.author.view/author)
|
||||
|
||||
;;;; SELECTORS
|
||||
(def reactions quo2.components.selectors.reactions.view/view)
|
||||
|
||||
;;;; AVATAR
|
||||
(def account-avatar quo2.components.avatars.account-avatar/account-avatar)
|
||||
(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar)
|
||||
@@ -164,6 +169,9 @@
|
||||
(def drawer-buttons quo2.components.drawers.drawer-buttons.view/view)
|
||||
(def permission-context quo2.components.drawers.permission-context.view/view)
|
||||
|
||||
;;;; EMPTY STATE
|
||||
(def empty-state quo2.components.empty-state.empty-state.view/empty-state)
|
||||
|
||||
;;;; INPUTS
|
||||
(def input quo2.components.inputs.input.view/input)
|
||||
(def profile-input quo2.components.inputs.profile-input.view/profile-input)
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
[quo2.components.drawers.drawer-buttons.component-spec]
|
||||
[quo2.components.drawers.permission-context.component-spec]
|
||||
[quo2.components.inputs.input.component-spec]
|
||||
[quo2.components.keycard.component-spec]
|
||||
[quo2.components.inputs.profile-input.component-spec]
|
||||
[quo2.components.inputs.recovery-phrase.component-spec]
|
||||
[quo2.components.inputs.title-input.component-spec]
|
||||
[quo2.components.keycard.component-spec]
|
||||
[quo2.components.links.link-preview.component-spec]
|
||||
[quo2.components.links.url-preview-list.component-spec]
|
||||
[quo2.components.links.url-preview.component-spec]
|
||||
[quo2.components.links.link-preview.component-spec]
|
||||
[quo2.components.markdown.--tests--.text-component-spec]
|
||||
[quo2.components.notifications.notification.component-spec]
|
||||
[quo2.components.onboarding.small-option-card.component-spec]
|
||||
@@ -30,6 +30,7 @@
|
||||
[quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec]
|
||||
[quo2.components.selectors.disclaimer.component-spec]
|
||||
[quo2.components.selectors.filter.component-spec]
|
||||
[quo2.components.selectors.reactions.component-spec]
|
||||
[quo2.components.selectors.selectors.component-spec]
|
||||
[quo2.components.share.share-qr-code.component-spec]
|
||||
[quo2.components.tags.--tests--.status-tags-component-spec]))
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
(ns react-native.cameraroll
|
||||
(:require ["@react-native-community/cameraroll" :as CameraRoll]
|
||||
[utils.transforms :as transforms]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn get-photos
|
||||
[opts callback]
|
||||
(-> (.getPhotos CameraRoll (clj->js opts))
|
||||
(.then #(callback (transforms/js->clj %)))
|
||||
(.catch #(log/warn "could not get camera roll photos" %))))
|
||||
|
||||
(defn get-albums
|
||||
[opts callback]
|
||||
(-> (.getAlbums CameraRoll (clj->js opts))
|
||||
(.then #(callback (transforms/js->clj %)))
|
||||
(.catch #(log/warn "could not get camera roll albums" %))))
|
||||
@@ -17,6 +17,7 @@
|
||||
(def view (reagent/adapt-react-class (.-View ^js react-native)))
|
||||
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js react-native)))
|
||||
(def image (reagent/adapt-react-class (.-Image ^js react-native)))
|
||||
(defn image-get-size [uri callback] (.getSize ^js (.-Image ^js react-native) uri callback))
|
||||
(def text (reagent/adapt-react-class (.-Text ^js react-native)))
|
||||
(def text-input (reagent/adapt-react-class (.-TextInput ^js react-native)))
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
[flat-list
|
||||
(merge props
|
||||
{:data data
|
||||
:render-fn (fn [item]
|
||||
(if (:header? item)
|
||||
(render-section-header-fn item)
|
||||
(render-fn item)))})]))
|
||||
:render-fn (fn [p1 p2 p3 p4]
|
||||
(if (:header? p1)
|
||||
[render-section-header-fn p1 p2 p3 p4]
|
||||
[render-fn p1 p2 p3 p4]))})]))
|
||||
|
||||
@@ -1,47 +1,17 @@
|
||||
(ns status-im.chat.models.images
|
||||
(:require ["@react-native-community/cameraroll" :as CameraRoll]
|
||||
["react-native-blob-util" :default ReactNativeBlobUtil]
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im.ui.components.permissions :as permissions]
|
||||
[react-native.permissions :as permissions]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im2.config :as config]
|
||||
[status-im.utils.fs :as fs]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.image-processing :as image-processing]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.utils :as utils]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(def maximum-image-size-px 2000)
|
||||
|
||||
(defn- resize-and-call
|
||||
[uri cb]
|
||||
(react/image-get-size
|
||||
uri
|
||||
(fn [width height]
|
||||
(let [resize? (> (max width height) maximum-image-size-px)]
|
||||
(image-processing/resize
|
||||
uri
|
||||
(if resize? maximum-image-size-px width)
|
||||
(if resize? maximum-image-size-px height)
|
||||
60
|
||||
(fn [^js resized-image]
|
||||
(let [path (.-path resized-image)
|
||||
path (if (string/starts-with? path "file") path (str "file://" path))]
|
||||
(cb {:resized-uri path
|
||||
:width width
|
||||
:height height})))
|
||||
#(log/error "could not resize image" %))))))
|
||||
|
||||
(defn result->id
|
||||
[^js result]
|
||||
(if platform/ios?
|
||||
(.-localIdentifier result)
|
||||
(.-path result)))
|
||||
|
||||
(def temp-image-url (str (fs/cache-dir) "/StatusIm_Image.jpeg"))
|
||||
|
||||
(defn download-image-http
|
||||
@@ -78,108 +48,6 @@
|
||||
#(re-frame/dispatch [:chat.ui/image-captured current-chat-id (.-path %)])
|
||||
{})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::chat-open-image-picker
|
||||
(fn [chat-id]
|
||||
(react/show-image-picker
|
||||
(fn [^js images]
|
||||
;; NOTE(Ferossgp): Because we can't highlight the already selected images inside
|
||||
;; gallery, we just clean previous state and set all newly picked images
|
||||
(when (and platform/ios? (pos? (count images)))
|
||||
(re-frame/dispatch [:chat.ui/clear-sending-images chat-id]))
|
||||
(doseq [^js result (if platform/ios?
|
||||
(take config/max-images-batch images)
|
||||
[images])]
|
||||
(resize-and-call (.-path result)
|
||||
#(re-frame/dispatch [:chat.ui/image-selected chat-id (result->id result) %]))))
|
||||
;; NOTE(Ferossgp): On android you cannot set max limit on images, when a user
|
||||
;; selects too many images the app crashes.
|
||||
{:media-type "photo"
|
||||
:multiple platform/ios?})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::image-selected
|
||||
(fn [[image chat-id]]
|
||||
(resize-and-call
|
||||
(:uri image)
|
||||
#(re-frame/dispatch [:chat.ui/image-selected chat-id image %]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::camera-roll-get-photos
|
||||
(fn [[num end-cursor album]]
|
||||
(permissions/request-permissions
|
||||
{:permissions [:read-external-storage]
|
||||
:on-allowed (fn []
|
||||
(-> (if end-cursor
|
||||
(.getPhotos
|
||||
CameraRoll
|
||||
#js
|
||||
{:first num
|
||||
:after end-cursor
|
||||
:assetType "Photos"
|
||||
:groupTypes (if (= album (i18n/label :t/recent)) "All" "Albums")
|
||||
:groupName (when (not= album (i18n/label :t/recent)) album)
|
||||
:include (clj->js ["imageSize"])})
|
||||
(.getPhotos
|
||||
CameraRoll
|
||||
#js
|
||||
{:first num
|
||||
:assetType "Photos"
|
||||
:groupTypes (if (= album (i18n/label :t/recent)) "All" "Albums")
|
||||
:groupName (when (not= album (i18n/label :t/recent)) album)
|
||||
:include (clj->js ["imageSize"])}))
|
||||
(.then #(let [response (types/js->clj %)]
|
||||
(re-frame/dispatch [:on-camera-roll-get-photos (:edges response)
|
||||
(:page_info response) end-cursor])))
|
||||
(.catch #(log/warn "could not get camera roll photos"))))})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:chat.ui/camera-roll-get-albums
|
||||
(fn []
|
||||
(let [albums (atom [{:title :smart-albums :data []}
|
||||
{:title (i18n/label :t/my-albums) :data []}])]
|
||||
;; Get the "recent" album first
|
||||
(->
|
||||
(.getPhotos CameraRoll
|
||||
#js
|
||||
{:first 1
|
||||
:groupTypes "All"})
|
||||
(.then
|
||||
(fn [res]
|
||||
(let [recent-album {:title (i18n/label :t/recent)
|
||||
:count "--"
|
||||
:uri (get-in (first (:edges (types/js->clj res))) [:node :image :uri])}]
|
||||
(swap! albums update-in [0 :data] conj recent-album)
|
||||
;; Get albums, then loop over albums and get each one's cover (first photo)
|
||||
(->
|
||||
(.getAlbums CameraRoll #js {:assetType "All"})
|
||||
(.then
|
||||
(fn [response]
|
||||
(let [response (types/js->clj response)]
|
||||
(reduce
|
||||
(fn [_ album]
|
||||
(->
|
||||
(.getPhotos
|
||||
CameraRoll
|
||||
#js
|
||||
{:first 1
|
||||
:groupTypes "Albums"
|
||||
:groupName (:title album)})
|
||||
(.then (fn [res]
|
||||
(let [uri (get-in (first (:edges (types/js->clj res)))
|
||||
[:node :image :uri])]
|
||||
(swap! albums update-in [1 :data] conj (merge album {:uri uri}))
|
||||
(when (= (count (get-in @albums [1 :data])) (count response))
|
||||
(swap! albums update-in
|
||||
[1 :data]
|
||||
#(->> %
|
||||
(sort-by :title)))
|
||||
(re-frame/dispatch [:on-camera-roll-get-albums @albums])))))))
|
||||
nil
|
||||
response))))
|
||||
(.catch #(log/warn "could not get camera roll albums"))))))))))
|
||||
|
||||
|
||||
(rf/defn image-captured
|
||||
{:events [:chat.ui/image-captured]}
|
||||
[{:keys [db]} chat-id uri]
|
||||
@@ -189,81 +57,17 @@
|
||||
(not (get images uri)))
|
||||
{::image-selected [uri current-chat-id]})))
|
||||
|
||||
(rf/defn on-end-reached
|
||||
{:events [:camera-roll/on-end-reached]}
|
||||
[_ end-cursor selected-album loading? has-next-page?]
|
||||
(when (and (not loading?) has-next-page?)
|
||||
(re-frame/dispatch [:chat.ui/camera-roll-loading-more true])
|
||||
(re-frame/dispatch [:chat.ui/camera-roll-get-photos 20 end-cursor selected-album])))
|
||||
|
||||
(rf/defn camera-roll-get-photos
|
||||
{:events [:chat.ui/camera-roll-get-photos]}
|
||||
[_ num end-cursor selected-album]
|
||||
{::camera-roll-get-photos [num end-cursor selected-album]})
|
||||
|
||||
(rf/defn camera-roll-get-albums
|
||||
{:events [:chat.ui/camera-roll-get-albums]}
|
||||
[_]
|
||||
{:chat.ui/camera-roll-get-albums []})
|
||||
|
||||
(rf/defn camera-roll-loading-more
|
||||
{:events [:chat.ui/camera-roll-loading-more]}
|
||||
[{:keys [db]} is-loading]
|
||||
{:db (assoc db :camera-roll/loading-more is-loading)})
|
||||
|
||||
(rf/defn on-camera-roll-get-photos
|
||||
{:events [:on-camera-roll-get-photos]}
|
||||
[{:keys [db] :as cofx} photos page-info end-cursor]
|
||||
(let [photos_x (when end-cursor (:camera-roll/photos db))]
|
||||
{:db (-> db
|
||||
(assoc :camera-roll/photos (concat photos_x (map #(get-in % [:node :image]) photos)))
|
||||
(assoc :camera-roll/end-cursor (:end_cursor page-info))
|
||||
(assoc :camera-roll/has-next-page (:has_next_page page-info))
|
||||
(assoc :camera-roll/loading-more false))}))
|
||||
|
||||
(rf/defn on-camera-roll-get-albums
|
||||
{:events [:on-camera-roll-get-albums]}
|
||||
[{:keys [db]} albums]
|
||||
{:db (-> db
|
||||
(assoc :camera-roll/albums albums)
|
||||
(assoc :camera-roll/loading-albums false))})
|
||||
|
||||
(rf/defn clear-sending-images
|
||||
{:events [:chat.ui/clear-sending-images]}
|
||||
[{:keys [db]}]
|
||||
{:db (update-in db [:chat/inputs (:current-chat-id db) :metadata] assoc :sending-image {})})
|
||||
|
||||
(rf/defn cancel-sending-image
|
||||
{:events [:chat.ui/cancel-sending-image]}
|
||||
[{:keys [db] :as cofx} chat-id]
|
||||
(clear-sending-images cofx))
|
||||
|
||||
(rf/defn image-selected
|
||||
{:events [:chat.ui/image-selected]}
|
||||
[{:keys [db]} current-chat-id original {:keys [resized-uri width height]}]
|
||||
{:db
|
||||
(update-in db
|
||||
[:chat/inputs current-chat-id :metadata :sending-image (:uri original)]
|
||||
merge
|
||||
original
|
||||
{:resized-uri resized-uri
|
||||
:width width
|
||||
:height height})})
|
||||
|
||||
(rf/defn image-unselected
|
||||
{:events [:chat.ui/image-unselected]}
|
||||
[{:keys [db]} original]
|
||||
(let [current-chat-id (:current-chat-id db)]
|
||||
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image] dissoc (:uri original))}))
|
||||
|
||||
(rf/defn chat-open-image-picker
|
||||
{:events [:chat.ui/open-image-picker]}
|
||||
[{:keys [db]} chat-id]
|
||||
(let [current-chat-id (or chat-id (:current-chat-id db))
|
||||
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
|
||||
(when (< (count images) config/max-images-batch)
|
||||
{::chat-open-image-picker current-chat-id})))
|
||||
|
||||
(rf/defn chat-show-image-picker-camera
|
||||
{:events [:chat.ui/show-image-picker-camera]}
|
||||
[{:keys [db]} chat-id]
|
||||
@@ -271,25 +75,3 @@
|
||||
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
|
||||
(when (< (count images) config/max-images-batch)
|
||||
{::chat-open-image-picker-camera current-chat-id})))
|
||||
|
||||
(rf/defn camera-roll-pick
|
||||
{:events [:chat.ui/camera-roll-pick]}
|
||||
[{:keys [db]} image chat-id]
|
||||
(let [current-chat-id (or chat-id (:current-chat-id db))
|
||||
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
|
||||
(if (get-in db [:chats current-chat-id :timeline?])
|
||||
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-image] {})
|
||||
::image-selected [image current-chat-id]}
|
||||
(when (and (< (count images) config/max-images-batch)
|
||||
(not (some #(= (:uri image) (:uri %)) images)))
|
||||
{::image-selected [image current-chat-id]}))))
|
||||
|
||||
(rf/defn camera-roll-select-album
|
||||
{:events [:chat.ui/camera-roll-select-album]}
|
||||
[{:keys [db]} album]
|
||||
{:db (assoc db :camera-roll/selected-album album)})
|
||||
|
||||
(rf/defn save-image-to-gallery
|
||||
{:events [:chat.ui/save-image-to-gallery]}
|
||||
[_ base64-uri]
|
||||
{::save-image-to-gallery base64-uri})
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
(rf/defn messages-loaded
|
||||
"Loads more messages for current chat"
|
||||
{:events [::messages-loaded]}
|
||||
[{db :db} chat-id session-id {:keys [cursor messages]}]
|
||||
[{db :db} chat-id session-id {:keys [cursor messages]} on-loaded]
|
||||
(when-not (and (get-in db [:pagination-info chat-id :messages-initialized?])
|
||||
(not= session-id
|
||||
(get-in db [:pagination-info chat-id :messages-initialized?])))
|
||||
@@ -141,6 +141,8 @@
|
||||
[:pagination-info chat-id
|
||||
:cursor-clock-value])
|
||||
clock-value (when cursor (cursor->clock-value cursor))]
|
||||
(when on-loaded
|
||||
(on-loaded (count new-messages)))
|
||||
{:db (-> db
|
||||
(update-in [:pagination-info chat-id :cursor-clock-value]
|
||||
#(if (and (seq cursor) (or (not %) (< clock-value %)))
|
||||
@@ -161,7 +163,7 @@
|
||||
|
||||
(rf/defn load-more-messages
|
||||
{:events [:chat.ui/load-more-messages]}
|
||||
[{:keys [db]} chat-id first-request]
|
||||
[{:keys [db]} chat-id first-request on-loaded]
|
||||
(when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])]
|
||||
(when (and
|
||||
(not (get-in db [:pagination-info chat-id :all-loaded?]))
|
||||
@@ -175,13 +177,13 @@
|
||||
chat-id
|
||||
cursor
|
||||
constants/default-number-of-messages
|
||||
#(re-frame/dispatch [::messages-loaded chat-id session-id %])
|
||||
#(re-frame/dispatch [::messages-loaded chat-id session-id % on-loaded])
|
||||
#(re-frame/dispatch [::failed-loading-messages chat-id session-id %]))))))))
|
||||
|
||||
(rf/defn load-more-messages-for-current-chat
|
||||
{:events [:chat.ui/load-more-messages-for-current-chat]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(load-more-messages cofx (:current-chat-id db) false))
|
||||
[{:keys [db] :as cofx} on-loaded]
|
||||
(load-more-messages cofx (:current-chat-id db) false on-loaded))
|
||||
|
||||
(rf/defn load-messages
|
||||
[{:keys [db now] :as cofx} chat-id]
|
||||
@@ -191,4 +193,4 @@
|
||||
:utils/dispatch-later [{:ms 50 :dispatch [:chat.ui/mark-all-read-pressed chat-id]}
|
||||
(when-not (get-in cofx [:db :chats chat-id :public?])
|
||||
{:ms 100 :dispatch [:pin-message/load-pin-messages chat-id]})]}
|
||||
(load-more-messages chat-id true))))
|
||||
(load-more-messages chat-id true nil))))
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
[status-im.data-store.reactions :as data-store.reactions]
|
||||
[status-im.transport.message.protocol :as message.protocol]
|
||||
[utils.re-frame :as rf]
|
||||
[taoensso.timbre :as log]))
|
||||
[taoensso.timbre :as log]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn update-reaction
|
||||
[acc retracted chat-id message-id emoji-id emoji-reaction-id reaction]
|
||||
@@ -94,3 +95,42 @@
|
||||
acc))
|
||||
[]
|
||||
reactions))
|
||||
|
||||
(defn- <-rpc
|
||||
[{compressed-key :compressedKey
|
||||
emoji-id :emojiId
|
||||
:keys [from]}]
|
||||
{:compressed-key compressed-key
|
||||
:emoji-id emoji-id
|
||||
:from from})
|
||||
|
||||
|
||||
(defn- format-response
|
||||
[response]
|
||||
(->> (transforms/js->clj response)
|
||||
(map <-rpc)
|
||||
(group-by :emoji-id)))
|
||||
|
||||
(rf/defn save-emoji-reaction-details
|
||||
{:events [:chat/save-emoji-reaction-details]}
|
||||
[{:keys [db]} message-reactions long-pressed-emoji]
|
||||
{:db (assoc db
|
||||
:chat/reactions-authors
|
||||
{:reaction-authors-list message-reactions
|
||||
:selected-reaction long-pressed-emoji})})
|
||||
|
||||
(rf/defn clear-emoji-reaction-details
|
||||
{:events [:chat/clear-emoji-reaction-author-details]}
|
||||
[{:keys [db]} message-reactions]
|
||||
{:db (update db dissoc :chat/reactions-authors)})
|
||||
|
||||
(rf/defn emoji-reactions-by-message-id
|
||||
{:events [:chat.ui/emoji-reactions-by-message-id]}
|
||||
[{:keys [db]} {:keys [message-id long-pressed-emoji]}]
|
||||
{:json-rpc/call [{:method "wakuext_emojiReactionsByChatIDMessageID"
|
||||
:params [(:current-chat-id db) message-id]
|
||||
:js-response true
|
||||
:on-error #(log/error "failed to fetch emoji reaction by message-id: "
|
||||
{:message-id message-id :error %})
|
||||
:on-success #(rf/dispatch [:chat/save-emoji-reaction-details
|
||||
(format-response %) long-pressed-emoji])}]})
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
status-im.signals.core
|
||||
status-im.stickers.core
|
||||
status-im.transport.core
|
||||
[status-im.ui.components.permissions :as permissions]
|
||||
[react-native.permissions :as permissions]
|
||||
[status-im.ui.components.react :as react]
|
||||
status-im.ui.screens.privacy-and-security-settings.events
|
||||
[status-im.utils.dimensions :as dimensions]
|
||||
|
||||
@@ -165,6 +165,15 @@
|
||||
(wallet/request-current-block-update))
|
||||
(prices/update-prices)))
|
||||
|
||||
(rf/defn login-local-paired-user
|
||||
{:events [:multiaccounts.login/local-paired-user]}
|
||||
[{:keys [db]}]
|
||||
(let [{:keys [key-uid name password]} (get-in db [:syncing :multiaccount])]
|
||||
{::login [key-uid
|
||||
(types/clj->json {:name name
|
||||
:key-uid key-uid})
|
||||
password]}))
|
||||
|
||||
(rf/defn login
|
||||
{:events [:multiaccounts.login.ui/password-input-submitted]}
|
||||
[{:keys [db]}]
|
||||
@@ -384,22 +393,27 @@
|
||||
(rf/defn redirect-to-root
|
||||
"Decides which root should be initialised depending on user and app state"
|
||||
[{:keys [db] :as cofx}]
|
||||
(cond
|
||||
(get db :onboarding-2/new-account?)
|
||||
{:dispatch [:onboarding-2/finalize-setup]}
|
||||
(let [pairing-completed? (= (get-in db [:syncing :pairing-status]) :completed)]
|
||||
(cond
|
||||
pairing-completed?
|
||||
{:db (dissoc db :syncing)
|
||||
:dispatch [:init-root :syncing-results]}
|
||||
|
||||
(get db :tos/accepted?)
|
||||
(rf/merge
|
||||
cofx
|
||||
(multiaccounts/switch-theme nil :shell-stack)
|
||||
(navigation/init-root :shell-stack))
|
||||
(get db :onboarding-2/new-account?)
|
||||
{:dispatch [:onboarding-2/finalize-setup]}
|
||||
|
||||
:else
|
||||
{:dispatch [:init-root :tos]}))
|
||||
(get db :tos/accepted?)
|
||||
(rf/merge
|
||||
cofx
|
||||
(multiaccounts/switch-theme nil :shell-stack)
|
||||
(navigation/init-root :shell-stack))
|
||||
|
||||
:else
|
||||
{:dispatch [:init-root :tos]})))
|
||||
|
||||
(rf/defn get-settings-callback
|
||||
{:events [::get-settings-callback]}
|
||||
[{:keys [db] :as cofx} settings pairing-in-progress?]
|
||||
[{:keys [db] :as cofx} settings]
|
||||
(let [{:networks/keys [current-network networks]
|
||||
:as settings}
|
||||
(data-store.settings/rpc->settings settings)
|
||||
@@ -431,8 +445,7 @@
|
||||
(activity-center/notifications-fetch-pending-contact-requests)
|
||||
(activity-center/update-seen-state)
|
||||
(activity-center/notifications-fetch-unread-count)
|
||||
(when-not pairing-in-progress?
|
||||
(redirect-to-root)))))
|
||||
(redirect-to-root))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::open-last-chat
|
||||
@@ -511,9 +524,8 @@
|
||||
|
||||
(rf/defn login-only-events
|
||||
[{:keys [db] :as cofx} key-uid password save-password?]
|
||||
(let [auth-method (:auth-method db)
|
||||
new-auth-method (get-new-auth-method auth-method save-password?)
|
||||
pairing-in-progress? (get-in db [:syncing :pairing-in-progress?])]
|
||||
(let [auth-method (:auth-method db)
|
||||
new-auth-method (get-new-auth-method auth-method save-password?)]
|
||||
(log/debug "[login] login-only-events"
|
||||
"auth-method" auth-method
|
||||
"new-auth-method" new-auth-method)
|
||||
@@ -521,7 +533,7 @@
|
||||
{:db (assoc db :chats/loading? true)
|
||||
:json-rpc/call
|
||||
[{:method "settings_getSettings"
|
||||
:on-success #(re-frame/dispatch [::get-settings-callback % pairing-in-progress?])}]}
|
||||
:on-success #(re-frame/dispatch [::get-settings-callback %])}]}
|
||||
(notifications/load-notification-preferences)
|
||||
(when save-password?
|
||||
(keychain/save-user-password key-uid password))
|
||||
|
||||
@@ -89,8 +89,9 @@
|
||||
{:events [:chat.ui/show-profile]}
|
||||
[{:keys [db]} identity ens-name]
|
||||
(let [my-public-key (get-in db [:multiaccount :public-key])]
|
||||
(when (not= my-public-key identity)
|
||||
(if (not= my-public-key identity)
|
||||
{:db (-> db
|
||||
(assoc :contacts/identity identity)
|
||||
(assoc :contacts/ens-name ens-name))
|
||||
:dispatch [:contacts/build-contact identity ens-name true]})))
|
||||
:dispatch [:contacts/build-contact identity ens-name true]}
|
||||
{:dispatch [:navigate-to :my-profile]})))
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.messages.link-preview.events :as link-preview]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.multiaccounts.model :as multiaccounts.model]))
|
||||
[status-im2.constants :as constants]))
|
||||
|
||||
(rf/defn status-node-started
|
||||
[{db :db :as cofx} {:keys [error]}]
|
||||
@@ -56,36 +55,49 @@
|
||||
:peers-count (count (:peers peer-stats)))}))
|
||||
|
||||
(rf/defn handle-local-pairing-signals
|
||||
[{:keys [db] :as cofx} event]
|
||||
[{:keys [db] :as cofx} {:keys [type action data] :as event}]
|
||||
(log/info "local pairing signal received"
|
||||
{:event event})
|
||||
(let [connection-success? (and (= (:type event)
|
||||
(let [{:keys [account password]} data
|
||||
role (get-in db [:syncing :role])
|
||||
receiver? (= role constants/local-pairing-role-receiver)
|
||||
sender? (= role constants/local-pairing-role-sender)
|
||||
connection-success? (and (= type
|
||||
constants/local-pairing-event-connection-success)
|
||||
(= (:action event)
|
||||
(= action
|
||||
constants/local-pairing-action-connect))
|
||||
error-on-pairing? (contains? constants/local-pairing-event-errors (:type event))
|
||||
completed-pairing? (and (= (:type event)
|
||||
error-on-pairing? (contains? constants/local-pairing-event-errors type)
|
||||
completed-pairing? (and (= type
|
||||
constants/local-pairing-event-transfer-success)
|
||||
(= (:action event)
|
||||
(= action
|
||||
constants/local-pairing-action-pairing-installation))
|
||||
logged-in? (multiaccounts.model/logged-in? db)
|
||||
;; since `connection-success` event is received on both sender and receiver devices
|
||||
;; we check the `logged-in?` status to identify the receiver and take the user to next screen
|
||||
navigate-to-syncing-devices? (and connection-success? (not logged-in?))
|
||||
received-account? (and (= type
|
||||
constants/local-pairing-event-received-account)
|
||||
(= action
|
||||
constants/local-pairing-action-pairing-account)
|
||||
(and (some? account) (some? password)))
|
||||
multiaccount-data (when received-account?
|
||||
(merge account {:password password}))
|
||||
navigate-to-syncing-devices? (and connection-success? receiver?)
|
||||
user-in-syncing-devices-screen? (= (:view-id db) :syncing-progress)]
|
||||
(merge {:db (cond-> db
|
||||
connection-success?
|
||||
(assoc-in [:syncing :pairing-in-progress?] :connected)
|
||||
(assoc-in [:syncing :pairing-status] :connected)
|
||||
|
||||
received-account?
|
||||
(assoc-in [:syncing :multiaccount] multiaccount-data)
|
||||
|
||||
error-on-pairing?
|
||||
(assoc-in [:syncing :pairing-in-progress?] :error)
|
||||
(assoc-in [:syncing :pairing-status] :error)
|
||||
|
||||
completed-pairing?
|
||||
(assoc-in [:syncing :pairing-in-progress?] :completed))}
|
||||
(assoc-in [:syncing :pairing-status] :completed))}
|
||||
(when (and navigate-to-syncing-devices? (not user-in-syncing-devices-screen?))
|
||||
{:dispatch [:navigate-to :syncing-progress]})
|
||||
(when completed-pairing?
|
||||
{:dispatch [:syncing/pairing-completed]}))))
|
||||
(when (and completed-pairing? sender?)
|
||||
{:dispatch [:syncing/clear-states]})
|
||||
(when (and completed-pairing? receiver?)
|
||||
{:dispatch [:multiaccounts.login/local-paired-user]}))))
|
||||
|
||||
(rf/defn process
|
||||
{:events [:signals/signal-received]}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
(ns status-im.ui.components.permissions
|
||||
(:require ["react-native-permissions" :refer (requestMultiple PERMISSIONS RESULTS)]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(def permissions-map
|
||||
{:read-external-storage (cond
|
||||
platform/android? (.-READ_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
|
||||
:write-external-storage (cond
|
||||
platform/low-device? (.-WRITE_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
|
||||
:camera (cond
|
||||
platform/android? (.-CAMERA (.-ANDROID PERMISSIONS))
|
||||
platform/ios? (.-CAMERA (.-IOS PERMISSIONS)))
|
||||
:record-audio (cond
|
||||
platform/android? (.-RECORD_AUDIO (.-ANDROID PERMISSIONS))
|
||||
platform/ios? (.-MICROPHONE (.-IOS PERMISSIONS)))})
|
||||
|
||||
(defn all-granted?
|
||||
[permissions]
|
||||
(let [permission-vals (distinct (vals permissions))]
|
||||
(and (= (count permission-vals) 1)
|
||||
(not (#{(.-BLOCKED RESULTS) (.-DENIED RESULTS)} (first permission-vals))))))
|
||||
|
||||
(defn request-permissions
|
||||
[{:keys [permissions on-allowed on-denied]
|
||||
:or {on-allowed #()
|
||||
on-denied #()}}]
|
||||
(let [permissions (remove nil? (mapv #(get permissions-map %) permissions))]
|
||||
(if (empty? permissions)
|
||||
(on-allowed)
|
||||
(-> (requestMultiple (clj->js permissions))
|
||||
(.then #(if (all-granted? (js->clj %))
|
||||
(on-allowed)
|
||||
(on-denied)))
|
||||
(.catch on-denied)))))
|
||||
@@ -11,7 +11,7 @@
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon]
|
||||
[status-im.ui.components.connectivity.view :as connectivity]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.ui.components.permissions :as components.permissions]
|
||||
[react-native.permissions :as components.permissions]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.tooltip.views :as tooltip]
|
||||
[status-im.ui.components.webview :as components.webview]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
:margin-vertical 8})
|
||||
|
||||
(defn sheet
|
||||
[{:keys [top bottom]} window-height override-theme shell?]
|
||||
[{:keys [top bottom]} window-height override-theme padding-bottom-override shell?]
|
||||
{:position :absolute
|
||||
:max-height (- window-height top 20)
|
||||
:z-index 1
|
||||
@@ -22,9 +22,9 @@
|
||||
:right 0
|
||||
:border-top-left-radius 20
|
||||
:border-top-right-radius 20
|
||||
:overflow :hidden
|
||||
:overflow (when shell? :hidden)
|
||||
:flex 1
|
||||
:padding-bottom (max 20 bottom)
|
||||
:padding-bottom (or padding-bottom-override (max 20 bottom))
|
||||
:background-color (if shell?
|
||||
:transparent
|
||||
(colors/theme-colors colors/white colors/neutral-90 override-theme))})
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
(def timing-options #js {:duration duration})
|
||||
|
||||
(defn hide
|
||||
[translate-y bg-opacity window-height]
|
||||
[translate-y bg-opacity window-height on-close]
|
||||
(when (fn? on-close)
|
||||
(on-close))
|
||||
;; it will be better to use animation callback, but it doesn't work
|
||||
;; so we have to use timeout, also we add 50ms for safety
|
||||
(js/setTimeout #(rf/dispatch [:bottom-sheet-hidden]) (+ duration 50))
|
||||
@@ -29,7 +31,7 @@
|
||||
(def gesture-values (atom {}))
|
||||
|
||||
(defn get-sheet-gesture
|
||||
[translate-y bg-opacity window-height]
|
||||
[translate-y bg-opacity window-height on-close]
|
||||
(-> (gesture/gesture-pan)
|
||||
(gesture/on-start
|
||||
(fn [_]
|
||||
@@ -47,17 +49,22 @@
|
||||
(fn [_]
|
||||
(if (< (:dy @gesture-values) 0)
|
||||
(show translate-y bg-opacity)
|
||||
(hide translate-y bg-opacity window-height))))))
|
||||
(hide translate-y bg-opacity window-height on-close))))))
|
||||
|
||||
(defn view
|
||||
[{:keys [hide? insets]} {:keys [content override-theme selected-item shell?]}]
|
||||
[{:keys [hide? insets]}
|
||||
{:keys [content override-theme selected-item padding-bottom-override on-close shell?]}]
|
||||
(let [{window-height :height} (rn/get-window)
|
||||
bg-opacity (reanimated/use-shared-value 0)
|
||||
translate-y (reanimated/use-shared-value window-height)
|
||||
sheet-gesture (get-sheet-gesture translate-y bg-opacity window-height)]
|
||||
(rn/use-effect #(if hide? (hide translate-y bg-opacity window-height) (show translate-y bg-opacity))
|
||||
[hide?])
|
||||
(hooks/use-back-handler #(do (rf/dispatch [:hide-bottom-sheet]) true))
|
||||
sheet-gesture (get-sheet-gesture translate-y bg-opacity window-height on-close)]
|
||||
(rn/use-effect
|
||||
#(if hide? (hide translate-y bg-opacity window-height on-close) (show translate-y bg-opacity))
|
||||
[hide?])
|
||||
(hooks/use-back-handler #(do (when (fn? on-close)
|
||||
(on-close))
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
true))
|
||||
[rn/view {:flex 1}
|
||||
;; backdrop
|
||||
[rn/touchable-without-feedback {:on-press #(rf/dispatch [:hide-bottom-sheet])}
|
||||
@@ -70,7 +77,7 @@
|
||||
[reanimated/view
|
||||
{:style (reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY translate-y}]}
|
||||
(styles/sheet insets window-height override-theme shell?))}
|
||||
(styles/sheet insets window-height override-theme padding-bottom-override shell?))}
|
||||
|
||||
(when shell?
|
||||
[blur/ios-view
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/clear-history chat-id])}])}]))
|
||||
|
||||
(defn delete-chat-action
|
||||
[{:keys [chat-id] :as item}]
|
||||
[{:keys [chat-id] :as item} inside-chat?]
|
||||
(hide-sheet-and-dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
@@ -81,7 +81,10 @@
|
||||
:context item
|
||||
:accessibility-label :delete-chat-confirm
|
||||
:button-text (i18n/label :t/delete-chat)
|
||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/remove-chat chat-id])}])}]))
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:chat.ui/remove-chat chat-id])
|
||||
(when inside-chat?
|
||||
(rf/dispatch [:navigate-back])))}])}]))
|
||||
|
||||
(defn leave-group-action
|
||||
[item chat-id]
|
||||
@@ -94,10 +97,10 @@
|
||||
:context item
|
||||
:accessibility-label :leave-group
|
||||
:button-text (i18n/label :t/leave-group)
|
||||
:on-press #(do
|
||||
(rf/dispatch [:navigate-back])
|
||||
(hide-sheet-and-dispatch [:group-chats.ui/leave-chat-confirmed
|
||||
chat-id]))}])}]))
|
||||
:on-press (fn []
|
||||
(hide-sheet-and-dispatch [:group-chats.ui/leave-chat-confirmed
|
||||
chat-id])
|
||||
(rf/dispatch [:navigate-back]))}])}]))
|
||||
|
||||
(defn block-user-action
|
||||
[{:keys [public-key] :as item}]
|
||||
@@ -152,10 +155,10 @@
|
||||
:add-divider? true}))
|
||||
|
||||
(defn delete-chat-entry
|
||||
[item]
|
||||
[item inside-chat?]
|
||||
(entry {:icon :i/delete
|
||||
:label (i18n/label :t/delete-chat)
|
||||
:on-press #(delete-chat-action item)
|
||||
:on-press #(delete-chat-action item inside-chat?)
|
||||
:danger? true
|
||||
:accessibility-label :delete-chat
|
||||
:sub-label nil
|
||||
@@ -393,11 +396,11 @@
|
||||
:chevron? false}))
|
||||
|
||||
(defn destructive-actions
|
||||
[{:keys [group-chat] :as item}]
|
||||
[{:keys [group-chat] :as item} inside-chat?]
|
||||
[(clear-history-entry item)
|
||||
(if group-chat
|
||||
(leave-group-entry item nil)
|
||||
(delete-chat-entry item))])
|
||||
(delete-chat-entry item inside-chat?))])
|
||||
|
||||
(defn notification-actions
|
||||
[{:keys [chat-id group-chat public?]} inside-chat?]
|
||||
@@ -431,14 +434,14 @@
|
||||
[[(view-profile-entry chat-id)
|
||||
(edit-nickname-entry chat-id)]
|
||||
(notification-actions item inside-chat?)
|
||||
(destructive-actions item)]])
|
||||
(destructive-actions item inside-chat?)]])
|
||||
|
||||
(defn private-group-chat-actions
|
||||
[item inside-chat?]
|
||||
[quo/action-drawer
|
||||
[(group-actions item inside-chat?)
|
||||
(notification-actions item inside-chat?)
|
||||
(destructive-actions item)]])
|
||||
(destructive-actions item inside-chat?)]])
|
||||
|
||||
(defn contact-actions
|
||||
[{:keys [public-key] :as contact} {:keys [chat-id admin?] :as extra-data}]
|
||||
@@ -471,4 +474,4 @@
|
||||
[quo/action-drawer
|
||||
[(when admin? [(edit-name-image-entry)])
|
||||
[(notifications-entry admin?)]
|
||||
(destructive-actions group)]]))
|
||||
(destructive-actions group false)]]))
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
:discover (js/require "../resources/images/ui2/discover.png")
|
||||
:invite-friends (js/require "../resources/images/ui2/invite-friends.png")
|
||||
:no-contacts-light (js/require "../resources/images/ui2/no-contacts-light.png")
|
||||
:no-contacts-dark (js/require "../resources/images/ui2/no-contacts-dark.png")})
|
||||
:no-contacts-dark (js/require "../resources/images/ui2/no-contacts-dark.png")
|
||||
:no-messages-light (js/require "../resources/images/ui2/no-messages-light.png")
|
||||
:no-messages-dark (js/require "../resources/images/ui2/no-messages-dark.png")})
|
||||
|
||||
(def mock-images
|
||||
{:coinbase (js/require "../resources/images/mock2/coinbase.png")
|
||||
|
||||
@@ -71,12 +71,12 @@
|
||||
(def request-to-join-pending-state 1)
|
||||
|
||||
(def reactions
|
||||
{emoji-reaction-love :i/love
|
||||
emoji-reaction-thumbs-up :i/thumbs-up
|
||||
emoji-reaction-thumbs-down :i/thumbs-down
|
||||
emoji-reaction-laugh :i/laugh
|
||||
emoji-reaction-sad :i/sad
|
||||
emoji-reaction-angry :i/angry})
|
||||
{emoji-reaction-love :reaction/love
|
||||
emoji-reaction-thumbs-up :reaction/thumbs-up
|
||||
emoji-reaction-thumbs-down :reaction/thumbs-down
|
||||
emoji-reaction-laugh :reaction/laugh
|
||||
emoji-reaction-sad :reaction/sad
|
||||
emoji-reaction-angry :reaction/angry})
|
||||
|
||||
(def ^:const invitation-state-unknown 0)
|
||||
(def ^:const invitation-state-requested 1)
|
||||
@@ -277,9 +277,10 @@
|
||||
(def ^:const local-pairing-event-transfer-error "transfer-error")
|
||||
|
||||
;; receiver events
|
||||
(def ^:const local-pairing-event-received-amount "received-account")
|
||||
(def ^:const local-pairing-event-received-account "received-account")
|
||||
(def ^:const local-pairing-event-process-success "process-success")
|
||||
(def ^:const local-pairing-event-process-error "process-error")
|
||||
(def ^:const local-pairing-event-received-installation "received-installation")
|
||||
|
||||
(def ^:const local-pairing-event-errors
|
||||
#{local-pairing-event-connection-error
|
||||
|
||||
@@ -38,22 +38,18 @@
|
||||
[]
|
||||
(let [unread-count (rf/sub [:activity-center/unread-count])]
|
||||
[quo/action-drawer
|
||||
[[{:icon :i/check
|
||||
[[{:icon :i/mark-as-read
|
||||
:override-theme :dark
|
||||
:label (i18n/label :t/mark-all-notifications-as-read)
|
||||
:disabled? (zero? unread-count)
|
||||
:on-press (fn []
|
||||
(if (pos? unread-count)
|
||||
(rf/dispatch [:activity-center.notifications/mark-all-as-read-locally
|
||||
(fn []
|
||||
{:icon :up-to-date
|
||||
:icon-color colors/success-50
|
||||
:text (i18n/label :t/notifications-marked-as-read
|
||||
{:count unread-count})
|
||||
:override-theme :dark})])
|
||||
;; Need design improvements if there is NO unread
|
||||
;; notifications to mark as read
|
||||
;; https://github.com/status-im/status-mobile/issues/14983
|
||||
(js/alert "No unread notifications to mark as read"))
|
||||
(rf/dispatch [:activity-center.notifications/mark-all-as-read-locally
|
||||
(fn []
|
||||
{:icon :up-to-date
|
||||
:icon-color colors/success-50
|
||||
:text (i18n/label :t/notifications-marked-as-read
|
||||
{:count unread-count})
|
||||
:override-theme :dark})])
|
||||
(rf/dispatch [:hide-bottom-sheet]))}]]]))
|
||||
|
||||
(defn empty-tab
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns status-im2.contexts.chat.events-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im2.contexts.chat.events :as chat]
|
||||
[status-im.chat.models.images :as images]
|
||||
[status-im.utils.clocks :as utils.clocks]))
|
||||
|
||||
(deftest clear-history-test
|
||||
@@ -90,8 +89,3 @@
|
||||
(testing "Pagination info should be reset on navigation"
|
||||
(let [res (chat/navigate-to-chat {:db db} chat-id)]
|
||||
(is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?])))))))
|
||||
|
||||
(deftest camera-roll-loading-more-test
|
||||
(let [cofx {:db {:camera-roll/loading-more false}}]
|
||||
(is (= {:db {:camera-roll/loading-more true}}
|
||||
(images/camera-roll-loading-more cofx true)))))
|
||||
|
||||
@@ -15,24 +15,37 @@
|
||||
[status-im2.contexts.chat.lightbox.constants :as constants]
|
||||
[utils.worklets.lightbox :as worklet]))
|
||||
|
||||
(defn clear-timers
|
||||
[timers]
|
||||
(js/clearTimeout (:mount-animation @timers))
|
||||
(js/clearTimeout (:mount-index-lock @timers))
|
||||
(js/clearTimeout (:hide-0 @timers))
|
||||
(js/clearTimeout (:hide-1 @timers))
|
||||
(js/clearTimeout (:show-0 @timers))
|
||||
(js/clearTimeout (:show-1 @timers))
|
||||
(js/clearTimeout (:show-2 @timers)))
|
||||
|
||||
(defn effect
|
||||
[{:keys [flat-list-ref scroll-index-lock?]} {:keys [opacity layout border]} index]
|
||||
(rn/use-effect (fn []
|
||||
(reagent/next-tick (fn []
|
||||
(when @flat-list-ref
|
||||
(.scrollToIndex ^js @flat-list-ref
|
||||
#js {:animated false :index index}))))
|
||||
(js/setTimeout (fn []
|
||||
(anim/animate opacity 1)
|
||||
(anim/animate layout 0)
|
||||
(anim/animate border 12))
|
||||
(if platform/ios? 250 100))
|
||||
(js/setTimeout #(reset! scroll-index-lock? false) 300)
|
||||
(fn []
|
||||
(rf/dispatch [:chat.ui/zoom-out-signal nil])
|
||||
(when platform/android?
|
||||
(rf/dispatch [:chat.ui/lightbox-scale 1]))))))
|
||||
[{:keys [flat-list-ref scroll-index-lock? timers]} {:keys [opacity layout border]} index]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(reagent/next-tick (fn []
|
||||
(when @flat-list-ref
|
||||
(.scrollToIndex ^js @flat-list-ref
|
||||
#js {:animated false :index index}))))
|
||||
(swap! timers assoc
|
||||
:mount-animation
|
||||
(js/setTimeout (fn []
|
||||
(anim/animate opacity 1)
|
||||
(anim/animate layout 0)
|
||||
(anim/animate border 12))
|
||||
(if platform/ios? 250 100)))
|
||||
(swap! timers assoc :mount-index-lock (js/setTimeout #(reset! scroll-index-lock? false) 300))
|
||||
(fn []
|
||||
(rf/dispatch [:chat.ui/zoom-out-signal nil])
|
||||
(when platform/android?
|
||||
(rf/dispatch [:chat.ui/lightbox-scale 1]))
|
||||
(clear-timers timers)))))
|
||||
|
||||
(defn handle-orientation
|
||||
[result {:keys [flat-list-ref]} {:keys [scroll-index]} animations]
|
||||
@@ -102,7 +115,7 @@
|
||||
(anim/animate opacity 0)
|
||||
(rf/dispatch [:navigate-back]))
|
||||
(do
|
||||
#(reset! set-full-height? true)
|
||||
(reset! set-full-height? true)
|
||||
(anim/animate (if x? pan-x pan-y) 0)
|
||||
(anim/animate opacity 1)
|
||||
(anim/animate layout 0)))))))
|
||||
@@ -111,7 +124,8 @@
|
||||
[]
|
||||
{:flat-list-ref (atom nil)
|
||||
:small-list-ref (atom nil)
|
||||
:scroll-index-lock? (atom true)})
|
||||
:scroll-index-lock? (atom true)
|
||||
:timers (atom {})})
|
||||
|
||||
(defn init-state
|
||||
[messages index]
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
:screen-width screen-width
|
||||
:window-height window-height
|
||||
:window-width window-width
|
||||
:props props}
|
||||
:props props
|
||||
:curr-orientation curr-orientation}
|
||||
:horizontal horizontal?
|
||||
:inverted inverted?
|
||||
:paging-enabled true
|
||||
@@ -113,7 +114,8 @@
|
||||
(on-viewable-items-changed e props state))]
|
||||
(anim/animate (:background-color animations) "rgba(0,0,0,1)")
|
||||
(reset! (:data state) messages)
|
||||
(utils/orientation-change props state animations)
|
||||
(when platform/ios? ; issue: https://github.com/wix/react-native-navigation/issues/7726
|
||||
(utils/orientation-change props state animations))
|
||||
(utils/effect props animations index)
|
||||
[:f> lightbox-content props state animations derived messages index callback]))))
|
||||
|
||||
|
||||
@@ -107,23 +107,34 @@
|
||||
|
||||
(defn toggle-opacity
|
||||
[index {:keys [opacity-value border-value transparent? props]} portrait?]
|
||||
(let [{:keys [small-list-ref]} props
|
||||
opacity (reanimated/get-shared-value opacity-value)]
|
||||
(let [{:keys [small-list-ref timers]} props
|
||||
opacity (reanimated/get-shared-value opacity-value)]
|
||||
(if (= opacity 1)
|
||||
(do
|
||||
(when platform/ios?
|
||||
;; status-bar issue: https://github.com/status-im/status-mobile/issues/15343
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}}) 75))
|
||||
(js/clearTimeout (:show-0 @timers))
|
||||
(js/clearTimeout (:show-1 @timers))
|
||||
(js/clearTimeout (:show-2 @timers))
|
||||
(swap! timers assoc
|
||||
:hide-0
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}})
|
||||
(if platform/ios? 75 0)))
|
||||
(anim/animate opacity-value 0)
|
||||
(js/setTimeout #(reset! transparent? (not @transparent?)) 400))
|
||||
(swap! timers assoc :hide-1 (js/setTimeout #(reset! transparent? (not @transparent?)) 400)))
|
||||
(do
|
||||
(js/clearTimeout (:hide-0 @timers))
|
||||
(js/clearTimeout (:hide-1 @timers))
|
||||
(reset! transparent? (not @transparent?))
|
||||
(js/setTimeout #(anim/animate opacity-value 1) 50)
|
||||
(js/setTimeout #(when @small-list-ref
|
||||
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
|
||||
100)
|
||||
(when (and platform/ios? portrait?)
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}}) 150))))
|
||||
(swap! timers assoc :show-0 (js/setTimeout #(anim/animate opacity-value 1) 50))
|
||||
(swap! timers assoc
|
||||
:show-1
|
||||
(js/setTimeout #(when @small-list-ref
|
||||
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
|
||||
100))
|
||||
(when portrait?
|
||||
(swap! timers assoc
|
||||
:show-2
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}})
|
||||
(if platform/ios? 150 50))))))
|
||||
(anim/animate border-value (if (= opacity 1) 0 12))))
|
||||
|
||||
;;; Dimensions
|
||||
|
||||
@@ -228,36 +228,35 @@
|
||||
:style (style/image dimensions animations (:border-value render-data))}]]]))
|
||||
|
||||
(defn zoomable-image
|
||||
[{:keys [image-width image-height content message-id]} index render-data]
|
||||
(let [state (utils/init-state)
|
||||
shared-element-id (rf/sub [:shared-element-id])
|
||||
exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
|
||||
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
|
||||
curr-orientation (or (rf/sub [:lightbox/orientation])
|
||||
orientation/portrait)
|
||||
{:keys [set-full-height?]} render-data
|
||||
focused? (= shared-element-id message-id)
|
||||
dimensions (utils/get-dimensions
|
||||
(or image-width c/default-dimension)
|
||||
(or image-height c/default-duration)
|
||||
curr-orientation
|
||||
render-data)
|
||||
animations (utils/init-animations)
|
||||
rescale (fn [value exit?]
|
||||
(utils/rescale-image value
|
||||
exit?
|
||||
dimensions
|
||||
animations
|
||||
state))]
|
||||
(rn/use-effect (fn []
|
||||
(js/setTimeout #(reset! set-full-height? true) 500)))
|
||||
(when platform/ios?
|
||||
(utils/handle-orientation-change curr-orientation focused? dimensions animations state)
|
||||
(utils/handle-exit-lightbox-signal exit-lightbox-signal
|
||||
index
|
||||
(anim/get-val (:scale animations))
|
||||
rescale
|
||||
set-full-height?))
|
||||
(utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
|
||||
[:f> f-zoomable-image dimensions animations state rescale curr-orientation content focused?
|
||||
index render-data]))
|
||||
[]
|
||||
(let [state (utils/init-state)]
|
||||
(fn [{:keys [image-width image-height content message-id]} index render-data]
|
||||
(let [shared-element-id (rf/sub [:shared-element-id])
|
||||
exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
|
||||
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
|
||||
{:keys [set-full-height? curr-orientation]} render-data
|
||||
focused? (= shared-element-id message-id)
|
||||
dimensions (utils/get-dimensions
|
||||
(or image-width c/default-dimension)
|
||||
(or image-height c/default-duration)
|
||||
curr-orientation
|
||||
render-data)
|
||||
animations (utils/init-animations)
|
||||
rescale (fn [value exit?]
|
||||
(utils/rescale-image value
|
||||
exit?
|
||||
dimensions
|
||||
animations
|
||||
state))]
|
||||
(rn/use-effect (fn []
|
||||
(js/setTimeout (fn [] (reset! set-full-height? true)) 500)))
|
||||
(when platform/ios?
|
||||
(utils/handle-orientation-change curr-orientation focused? dimensions animations state)
|
||||
(utils/handle-exit-lightbox-signal exit-lightbox-signal
|
||||
index
|
||||
(anim/get-val (:scale animations))
|
||||
rescale
|
||||
set-full-height?))
|
||||
(utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
|
||||
[:f> f-zoomable-image dimensions animations state rescale curr-orientation content focused?
|
||||
index render-data]))))
|
||||
|
||||
@@ -5,31 +5,69 @@
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.messages.drawers.view :as drawers]))
|
||||
|
||||
(defn- on-press
|
||||
[own message-id emoji-id emoji-reaction-id]
|
||||
(if own
|
||||
(rf/dispatch [:models.reactions/send-emoji-reaction-retraction
|
||||
{:message-id message-id
|
||||
:emoji-id emoji-id
|
||||
:emoji-reaction-id emoji-reaction-id}])
|
||||
(rf/dispatch [:models.reactions/send-emoji-reaction
|
||||
{:message-id message-id
|
||||
:emoji-id emoji-id}])))
|
||||
|
||||
(defn- on-long-press
|
||||
[message-id emoji-id]
|
||||
(rf/dispatch [:chat.ui/emoji-reactions-by-message-id
|
||||
{:message-id message-id
|
||||
:long-pressed-emoji emoji-id}]))
|
||||
|
||||
(defn show-authors-sheet
|
||||
[user-message-content reactions]
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:on-close (fn []
|
||||
(rf/dispatch
|
||||
[:chat/clear-emoji-reaction-author-details]))
|
||||
:content (fn []
|
||||
[drawers/reaction-authors
|
||||
(map :emoji-id reactions)])
|
||||
:selected-item (fn []
|
||||
user-message-content)
|
||||
:padding-bottom-override 0}]))
|
||||
|
||||
(defn message-reactions-row
|
||||
[chat-id message-id]
|
||||
[{:keys [message-id chat-id]} user-message-content]
|
||||
(let [reactions (rf/sub [:chats/message-reactions message-id chat-id])]
|
||||
(when (seq reactions)
|
||||
[rn/view {:margin-left 52 :margin-bottom 12 :flex-direction :row}
|
||||
(for [{:keys [own emoji-id quantity emoji-reaction-id] :as emoji-reaction} reactions]
|
||||
^{:key (str emoji-reaction)}
|
||||
[rn/view {:style {:margin-right 6}}
|
||||
[quo/reaction
|
||||
{:emoji (get constants/reactions emoji-id)
|
||||
:neutral? own
|
||||
:clicks quantity
|
||||
:on-press (if own
|
||||
#(rf/dispatch [:models.reactions/send-emoji-reaction-retraction
|
||||
{:message-id message-id
|
||||
:emoji-id emoji-id
|
||||
:emoji-reaction-id emoji-reaction-id}])
|
||||
#(rf/dispatch [:models.reactions/send-emoji-reaction
|
||||
{:message-id message-id
|
||||
:emoji-id emoji-id}]))
|
||||
:accessibility-label (str "emoji-reaction-" emoji-id)}]])
|
||||
[quo/add-reaction
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn [] [drawers/reactions
|
||||
{:chat-id chat-id :message-id message-id}])}]))}]])))
|
||||
[:<>
|
||||
(when (seq reactions)
|
||||
[rn/view
|
||||
{:style {:margin-left 52
|
||||
:margin-bottom 12
|
||||
:flex-direction :row}}
|
||||
(for [{:keys [own emoji-id quantity emoji-reaction-id]
|
||||
:as emoji-reaction} reactions]
|
||||
^{:key emoji-reaction}
|
||||
[rn/view {:style {:margin-right 6}}
|
||||
[quo/reaction
|
||||
{:emoji (get constants/reactions emoji-id)
|
||||
:neutral? own
|
||||
:clicks quantity
|
||||
:on-press #(on-press own message-id emoji-id emoji-reaction-id)
|
||||
:on-long-press (fn []
|
||||
(on-long-press message-id
|
||||
emoji-id)
|
||||
(show-authors-sheet user-message-content
|
||||
reactions))
|
||||
:accessibility-label (str "emoji-reaction-" emoji-id)}]])
|
||||
[quo/add-reaction
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn [] [drawers/reactions
|
||||
{:chat-id chat-id
|
||||
:message-id message-id}])
|
||||
:selected-item (fn []
|
||||
user-message-content)}]))}]])]))
|
||||
|
||||
@@ -23,16 +23,18 @@
|
||||
[status-im2.common.not-implemented :as not-implemented]
|
||||
[utils.datetime :as datetime]
|
||||
[reagent.core :as reagent]
|
||||
[utils.address :as address]))
|
||||
[utils.address :as address]
|
||||
[react-native.gesture :as gesture]))
|
||||
|
||||
(def delivery-state-showing-time-ms 3000)
|
||||
|
||||
(defn avatar-container
|
||||
[{:keys [content last-in-group? pinned-by quoted-message from]}]
|
||||
[{:keys [content last-in-group? pinned-by quoted-message from]} message-reaction-view?]
|
||||
(if (or (and (seq (:response-to content))
|
||||
quoted-message)
|
||||
last-in-group?
|
||||
pinned-by)
|
||||
pinned-by
|
||||
message-reaction-view?)
|
||||
[avatar/avatar from :small]
|
||||
[rn/view {:padding-top 2 :width 32}]))
|
||||
|
||||
@@ -43,8 +45,9 @@
|
||||
pinned-by
|
||||
quoted-message
|
||||
from
|
||||
timestamp]}]
|
||||
(when (or (and (seq response-to) quoted-message) last-in-group? pinned-by)
|
||||
timestamp]}
|
||||
message-reaction-view?]
|
||||
(when (or (and (seq response-to) quoted-message) last-in-group? pinned-by message-reaction-view?)
|
||||
(let [[primary-name secondary-name] (rf/sub [:contacts/contact-two-names-by-identity from])
|
||||
{:keys [ens-verified added?]} (rf/sub [:contacts/contact-by-address from])]
|
||||
[quo/author
|
||||
@@ -82,7 +85,8 @@
|
||||
(let [show-delivery-state? (reagent/atom false)]
|
||||
(fn [{:keys [content-type quoted-message content outgoing outgoing-status] :as message-data}
|
||||
context
|
||||
keyboard-shown]
|
||||
keyboard-shown?
|
||||
message-reaction-view?]
|
||||
(let [first-image (first (:album message-data))
|
||||
outgoing-status (if (= content-type constants/content-type-album)
|
||||
(:outgoing-status first-image)
|
||||
@@ -91,7 +95,8 @@
|
||||
(:outgoing first-image)
|
||||
outgoing)
|
||||
context (assoc context :on-long-press #(on-long-press message-data context))
|
||||
response-to (:response-to content)]
|
||||
response-to (:response-to content)
|
||||
height (rf/sub [:dimensions/window-height])]
|
||||
[rn/touchable-highlight
|
||||
{:accessibility-label (if (and outgoing (= outgoing-status :sending))
|
||||
:message-sending
|
||||
@@ -100,7 +105,7 @@
|
||||
:style {:border-radius 16
|
||||
:opacity (if (and outgoing (= outgoing-status :sending)) 0.5 1)}
|
||||
:on-press (fn []
|
||||
(if (and platform/ios? @keyboard-shown)
|
||||
(if (and platform/ios? keyboard-shown?)
|
||||
(rn/dismiss-keyboard!)
|
||||
(when (and outgoing
|
||||
(not= outgoing-status :sending)
|
||||
@@ -115,37 +120,41 @@
|
||||
[rn/view
|
||||
{:style {:padding-horizontal 12
|
||||
:flex-direction :row}}
|
||||
[avatar-container message-data]
|
||||
[rn/view
|
||||
{:style {:margin-left 8
|
||||
:flex 1}}
|
||||
[author message-data]
|
||||
(case content-type
|
||||
[avatar-container message-data message-reaction-view?]
|
||||
(into
|
||||
(if message-reaction-view?
|
||||
[gesture/scroll-view]
|
||||
[rn/view])
|
||||
[{:style {:margin-left 8
|
||||
:flex 1
|
||||
:max-height (when message-reaction-view? (* 0.4 height))}}
|
||||
[author message-data message-reaction-view?]
|
||||
(case content-type
|
||||
|
||||
constants/content-type-text [content.text/text-content message-data]
|
||||
constants/content-type-text [content.text/text-content message-data context]
|
||||
|
||||
constants/content-type-emoji
|
||||
[not-implemented/not-implemented [old-message/emoji message-data]]
|
||||
constants/content-type-emoji
|
||||
[not-implemented/not-implemented [old-message/emoji message-data]]
|
||||
|
||||
constants/content-type-sticker
|
||||
[not-implemented/not-implemented [old-message/sticker message-data]]
|
||||
constants/content-type-sticker
|
||||
[not-implemented/not-implemented [old-message/sticker message-data]]
|
||||
|
||||
constants/content-type-audio
|
||||
[audio/audio-message message-data context]
|
||||
constants/content-type-audio
|
||||
[audio/audio-message message-data context]
|
||||
|
||||
constants/content-type-image
|
||||
[image/image-message 0 message-data on-long-press]
|
||||
constants/content-type-image
|
||||
[image/image-message 0 message-data context on-long-press]
|
||||
|
||||
constants/content-type-album
|
||||
[album/album-message message-data context on-long-press]
|
||||
constants/content-type-album
|
||||
[album/album-message message-data context on-long-press]
|
||||
|
||||
[not-implemented/not-implemented [content.unknown/unknown-content message-data]])
|
||||
(when @show-delivery-state?
|
||||
[status/status outgoing-status])]]]]))))
|
||||
[not-implemented/not-implemented [content.unknown/unknown-content message-data]])
|
||||
(when @show-delivery-state?
|
||||
[status/status outgoing-status])])]]]))))
|
||||
|
||||
(defn message-with-reactions
|
||||
[{:keys [pinned-by mentioned in-pinned-view? content-type last-in-group? message-id] :as message-data}
|
||||
{:keys [chat-id] :as context}
|
||||
[{:keys [pinned-by mentioned in-pinned-view? content-type last-in-group?] :as message-data}
|
||||
context
|
||||
keyboard-shown]
|
||||
[rn/view
|
||||
{:style (style/message-container in-pinned-view? pinned-by mentioned last-in-group?)
|
||||
@@ -153,9 +162,10 @@
|
||||
(when pinned-by
|
||||
[pin/pinned-by-view pinned-by])
|
||||
(if (#{constants/content-type-system-text constants/content-type-community
|
||||
constants/content-type-system-pinned-message
|
||||
constants/content-type-contact-request}
|
||||
constants/content-type-contact-request
|
||||
constants/content-type-system-pinned-message}
|
||||
content-type)
|
||||
[system-message-content message-data]
|
||||
[user-message-content message-data context keyboard-shown])
|
||||
[reactions/message-reactions-row chat-id message-id]])
|
||||
[user-message-content message-data context keyboard-shown false])
|
||||
[reactions/message-reactions-row message-data
|
||||
[user-message-content message-data context keyboard-shown true]]])
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
(ns status-im2.contexts.chat.messages.drawers.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def tab
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
|
||||
(def tab-icon {:margin-right 4})
|
||||
|
||||
(defn tab-count
|
||||
[active?]
|
||||
{:color (if (or active? (colors/dark?)) colors/white colors/neutral-100)})
|
||||
|
||||
(def tabs-container
|
||||
{:flex 1
|
||||
:align-self :stretch
|
||||
:padding-left 20
|
||||
:padding-right 8
|
||||
:margin-bottom 12})
|
||||
|
||||
(def authors-list
|
||||
{:height 320
|
||||
:flex 1})
|
||||
@@ -3,10 +3,74 @@
|
||||
[react-native.core :as rn]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im2.contexts.chat.composer.reply.view :as reply]
|
||||
[status-im2.common.not-implemented :as not-implemented]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.common.contact-list-item.view :as contact-list-item]
|
||||
[status-im2.contexts.chat.messages.drawers.style :as style]
|
||||
[react-native.gesture :as gesture]))
|
||||
|
||||
(defn contact-list-item-fn
|
||||
[{:keys [from compressed-key]}]
|
||||
(let [[primary-name secondary-name] (rf/sub [:contacts/contact-two-names-by-identity
|
||||
from])
|
||||
{:keys [ens-verified added?]} (rf/sub [:contacts/contact-by-address from])]
|
||||
^{:key compressed-key}
|
||||
[contact-list-item/contact-list-item
|
||||
{:on-press #(rf/dispatch [:chat.ui/show-profile from])}
|
||||
{:primary-name primary-name
|
||||
:secondary-name secondary-name
|
||||
:public-key from
|
||||
:compressed-key compressed-key
|
||||
:ens-verified ens-verified
|
||||
:added? added?}]))
|
||||
|
||||
(defn get-tabs-data
|
||||
[reaction-authors selected-tab reactions-order]
|
||||
(map (fn [reaction-type-int]
|
||||
(let [author-details (get reaction-authors reaction-type-int)]
|
||||
{:id reaction-type-int
|
||||
:accessibility-label (keyword (str "authors-for-reaction-" reaction-type-int))
|
||||
:label [rn/view {:style style/tab}
|
||||
[quo/icon
|
||||
(get constants/reactions reaction-type-int)
|
||||
{:no-color true
|
||||
:container-style style/tab-icon}]
|
||||
[quo/text
|
||||
{:weight :medium
|
||||
:size :paragraph-1
|
||||
:style (style/tab-count (= selected-tab
|
||||
reaction-type-int))}
|
||||
(count author-details)]]}))
|
||||
reactions-order))
|
||||
|
||||
(defn reaction-authors-comp
|
||||
[selected-tab reaction-authors reactions-order]
|
||||
[:<>
|
||||
[rn/view style/tabs-container
|
||||
[quo/tabs
|
||||
{:size 32
|
||||
:scrollable? true
|
||||
:in-scroll-view? true
|
||||
:on-change #(reset! selected-tab %)
|
||||
:default-active @selected-tab
|
||||
:data (get-tabs-data reaction-authors @selected-tab reactions-order)}]]
|
||||
[gesture/flat-list
|
||||
{:data (for [contact (get reaction-authors @selected-tab)]
|
||||
contact)
|
||||
:render-fn contact-list-item-fn
|
||||
:key-fn :from
|
||||
:style style/authors-list}]])
|
||||
|
||||
(defn reaction-authors
|
||||
[reactions-order]
|
||||
(let [{:keys [reaction-authors-list
|
||||
selected-reaction]} (rf/sub [:chat/reactions-authors])
|
||||
selected-tab (reagent/atom (or selected-reaction
|
||||
(first (keys reaction-authors-list))))]
|
||||
(fn []
|
||||
[reaction-authors-comp selected-tab reaction-authors-list reactions-order])))
|
||||
|
||||
(defn pin-message
|
||||
[{:keys [chat-id pinned pinned-by] :as message-data}]
|
||||
@@ -110,29 +174,23 @@
|
||||
:padding-horizontal 30
|
||||
:padding-top 5
|
||||
:padding-bottom 15}}
|
||||
(doall
|
||||
(for [[id icon] constants/reactions]
|
||||
(let [emoji-reaction-id (get own-reactions id)]
|
||||
^{:key id}
|
||||
[not-implemented/not-implemented
|
||||
[quo/button
|
||||
(merge
|
||||
{:size 40
|
||||
:type (if emoji-reaction-id :grey :ghost)
|
||||
:icon true
|
||||
:icon-no-color true
|
||||
:accessibility-label (str "emoji-picker-" id)
|
||||
:on-press (fn []
|
||||
(if emoji-reaction-id
|
||||
(rf/dispatch [:models.reactions/send-emoji-reaction-retraction
|
||||
{:message-id message-id
|
||||
:emoji-id id
|
||||
:emoji-reaction-id emoji-reaction-id}])
|
||||
(rf/dispatch [:models.reactions/send-emoji-reaction
|
||||
{:message-id message-id
|
||||
:emoji-id id}]))
|
||||
(rf/dispatch [:hide-bottom-sheet]))})
|
||||
icon]])))]))
|
||||
(for [[id reaction-name] constants/reactions
|
||||
:let [emoji-reaction-id (get own-reactions id)]]
|
||||
^{:key id}
|
||||
[quo/reactions reaction-name
|
||||
{:start-pressed? (boolean emoji-reaction-id)
|
||||
:accessibility-label (str "reaction-" (name reaction-name))
|
||||
:on-press
|
||||
(fn []
|
||||
(if emoji-reaction-id
|
||||
(rf/dispatch [:models.reactions/send-emoji-reaction-retraction
|
||||
{:message-id message-id
|
||||
:emoji-id id
|
||||
:emoji-reaction-id emoji-reaction-id}])
|
||||
(rf/dispatch [:models.reactions/send-emoji-reaction
|
||||
{:message-id message-id
|
||||
:emoji-id id}]))
|
||||
(rf/dispatch [:hide-bottom-sheet]))}])]))
|
||||
|
||||
(defn reactions-and-actions
|
||||
[message-data
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
(ns status-im2.contexts.chat.messages.list.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defonce ^:const cover-height 168)
|
||||
(defonce ^:const overscroll-cover-height 2000)
|
||||
(defonce ^:const header-avatar-top-offset -36)
|
||||
(defonce ^:const messages-list-bottom-offset 16)
|
||||
|
||||
(defn keyboard-avoiding-container
|
||||
[{:keys [top]}]
|
||||
{:position :relative
|
||||
:flex 1
|
||||
:top (- top)
|
||||
:margin-bottom (- top)})
|
||||
|
||||
(def list-container
|
||||
{:padding-vertical 16})
|
||||
|
||||
(defn header-container
|
||||
[show?]
|
||||
{:display (if show? :flex :none)
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:top (- overscroll-cover-height)
|
||||
:margin-bottom (- overscroll-cover-height)})
|
||||
|
||||
(defn header-cover
|
||||
[cover-bg-color]
|
||||
{:flex 1
|
||||
:height (+ overscroll-cover-height cover-height)
|
||||
:background-color cover-bg-color})
|
||||
|
||||
(defn header-bottom-part
|
||||
[animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:border-top-right-radius animation
|
||||
:border-top-left-radius animation}
|
||||
{:top -16
|
||||
:margin-bottom -16
|
||||
:padding-bottom 24
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-100)}))
|
||||
|
||||
(def header-avatar
|
||||
{:top header-avatar-top-offset
|
||||
:margin-horizontal 20
|
||||
:margin-bottom header-avatar-top-offset})
|
||||
|
||||
(defn header-image
|
||||
[scale-animation top-margin-animation side-margin-animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:transform [{:scale scale-animation}]
|
||||
:margin-top top-margin-animation
|
||||
:margin-left side-margin-animation
|
||||
:margin-bottom side-margin-animation}
|
||||
{:align-items :flex-start}))
|
||||
|
||||
(def name-container
|
||||
{:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def bio
|
||||
{:margin-top 8})
|
||||
@@ -3,8 +3,12 @@
|
||||
[quo2.core :as quo]
|
||||
[react-native.background-timer :as background-timer]
|
||||
[react-native.core :as rn]
|
||||
[react-native.hooks :as hooks]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im.ui.screens.chat.group :as chat.group]
|
||||
[status-im.ui.screens.chat.message.gap :as message.gap]
|
||||
[status-im2.common.not-implemented :as not-implemented]
|
||||
@@ -12,21 +16,35 @@
|
||||
[status-im2.contexts.chat.messages.content.deleted.view :as content.deleted]
|
||||
[status-im2.contexts.chat.messages.content.view :as message]
|
||||
[status-im2.contexts.chat.messages.list.state :as state]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.composer.constants :as composer.constants]))
|
||||
[status-im2.contexts.chat.messages.list.style :as style]
|
||||
[status-im2.contexts.chat.messages.navigation.style :as navigation.style]
|
||||
[status-im2.contexts.chat.composer.constants :as composer.constants]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75)
|
||||
(defonce ^:const loading-indicator-extra-spacing 250)
|
||||
(defonce ^:const loading-indicator-page-loading-height 100)
|
||||
(defonce ^:const scroll-animation-input-range [50 125])
|
||||
(defonce ^:const spacing-between-composer-and-content 64)
|
||||
|
||||
(defonce messages-list-ref (atom nil))
|
||||
(defonce messages-view-height (reagent/atom 0))
|
||||
(defonce messages-view-header-height (reagent/atom 0))
|
||||
(defonce show-floating-scroll-down-button (reagent/atom false))
|
||||
|
||||
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
|
||||
(defn list-ref [ref] (reset! messages-list-ref ref))
|
||||
|
||||
(defn scroll-to-offset
|
||||
[position]
|
||||
(some-> ^js @messages-list-ref
|
||||
(.scrollToOffset #js
|
||||
{:offset position
|
||||
:animated true})))
|
||||
|
||||
(defn scroll-to-bottom
|
||||
[]
|
||||
(some-> ^js @messages-list-ref
|
||||
(.scrollToOffset #js {:y 0 :animated true})))
|
||||
|
||||
(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75)
|
||||
(defonce show-floating-scroll-down-button (reagent/atom false))
|
||||
(scroll-to-offset (- 0 style/messages-list-bottom-offset)))
|
||||
|
||||
(defn on-scroll
|
||||
[evt]
|
||||
@@ -40,11 +58,11 @@
|
||||
(reset! show-floating-scroll-down-button reached-threshold?))))
|
||||
|
||||
(defn on-viewable-items-changed
|
||||
[evt]
|
||||
[e]
|
||||
(when @messages-list-ref
|
||||
(reset! state/first-not-visible-item
|
||||
(when-let [last-visible-element (aget (oops/oget evt "viewableItems")
|
||||
(dec (oops/oget evt "viewableItems.length")))]
|
||||
(when-let [last-visible-element (aget (oops/oget e "viewableItems")
|
||||
(dec (oops/oget e "viewableItems.length")))]
|
||||
(let [index (oops/oget last-visible-element "index")
|
||||
;; Get first not visible element, if it's a datemark/gap
|
||||
;; we might unnecessarely add messages on receiving as
|
||||
@@ -55,43 +73,164 @@
|
||||
(= :message (:type first-not-visible)))
|
||||
first-not-visible))))))
|
||||
|
||||
;;TODO this is not really working in pair with inserting new messages because we stop inserting new
|
||||
;;messages
|
||||
;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because
|
||||
;;we
|
||||
;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will
|
||||
;;work wrong
|
||||
|
||||
(defn list-on-end-reached
|
||||
[]
|
||||
(if @state/scrolling
|
||||
(rf/dispatch [:chat.ui/load-more-messages-for-current-chat])
|
||||
(background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat])
|
||||
(if platform/low-device? 700 200))))
|
||||
[scroll-y]
|
||||
;; FIXME: that's a bit of a hack but we need to update `scroll-y` once the new messages
|
||||
;; are fetched in order for the header to work properly
|
||||
(let [on-loaded (fn [n]
|
||||
(reanimated/set-shared-value scroll-y
|
||||
(+ (reanimated/get-shared-value scroll-y)
|
||||
(* n 200))))]
|
||||
(if @state/scrolling
|
||||
(rf/dispatch [:chat.ui/load-more-messages-for-current-chat on-loaded])
|
||||
(background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat
|
||||
on-loaded])
|
||||
(if platform/low-device? 700 100)))))
|
||||
|
||||
(defonce messages-view-height (reagent/atom 0))
|
||||
(defn contact-icon
|
||||
[{:keys [ens-verified added?]}]
|
||||
(when (or ens-verified added?)
|
||||
[rn/view
|
||||
{:style {:padding-left 10
|
||||
:margin-top 2}}
|
||||
(if ens-verified
|
||||
[quo/icon :i/verified
|
||||
{:no-color true
|
||||
:size 20
|
||||
:color (colors/theme-colors colors/success-50 colors/success-60)}]
|
||||
(when added?
|
||||
[quo/icon :i/contact
|
||||
{:no-color true
|
||||
:size 20
|
||||
:color (colors/theme-colors colors/primary-50 colors/primary-60)}]))]))
|
||||
|
||||
(defn on-messages-view-layout
|
||||
[evt]
|
||||
(reset! messages-view-height (oops/oget evt "nativeEvent.layout.height")))
|
||||
(def header-extrapolation-option
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"})
|
||||
|
||||
(defn list-footer
|
||||
[{:keys [chat-id]}]
|
||||
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
|
||||
all-loaded? (rf/sub [:chats/all-loaded? chat-id])]
|
||||
(when (or loading-messages? (not chat-id) (not all-loaded?))
|
||||
[rn/view {:style (when platform/android? {:scaleY -1})}
|
||||
[quo/skeleton @messages-view-height]])))
|
||||
(defn loading-view
|
||||
[chat-id]
|
||||
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
|
||||
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
|
||||
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
|
||||
loading-first-page? (= (count messages) 0)
|
||||
top-spacing (if loading-first-page? 0 navigation.style/navigation-bar-height)]
|
||||
(when (or loading-messages? (not all-loaded?))
|
||||
[rn/view {:padding-top top-spacing}
|
||||
[quo/skeleton
|
||||
(if loading-first-page?
|
||||
(- @messages-view-height
|
||||
@messages-view-header-height
|
||||
composer.constants/composer-default-height
|
||||
loading-indicator-extra-spacing)
|
||||
loading-indicator-page-loading-height)]])))
|
||||
|
||||
(defn list-header
|
||||
[{:keys [chat-id chat-type invitation-admin]}]
|
||||
(when (= chat-type constants/private-group-chat-type)
|
||||
[rn/view {:style (when platform/android? {:scaleY -1})}
|
||||
[chat.group/group-chat-footer chat-id invitation-admin]]))
|
||||
[insets]
|
||||
[rn/view
|
||||
{:background-color (colors/theme-colors colors/white colors/neutral-95)
|
||||
:margin-bottom (- 0
|
||||
(:top insets)
|
||||
(when platform/ios? style/overscroll-cover-height))
|
||||
:height (+ composer.constants/composer-default-height
|
||||
(:bottom insets)
|
||||
spacing-between-composer-and-content
|
||||
(when platform/ios? style/overscroll-cover-height))}])
|
||||
|
||||
(defn f-list-footer-avatar
|
||||
[{:keys [scroll-y display-name online? photo-path]}]
|
||||
(let [image-scale-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[1 0.5]
|
||||
header-extrapolation-option)
|
||||
image-top-margin-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[0 40]
|
||||
header-extrapolation-option)
|
||||
image-side-margin-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[0 -20]
|
||||
header-extrapolation-option)]
|
||||
[reanimated/view
|
||||
{:style (style/header-image image-scale-animation
|
||||
image-top-margin-animation
|
||||
image-side-margin-animation)}
|
||||
[quo/user-avatar
|
||||
{:full-name display-name
|
||||
:online? online?
|
||||
:profile-picture photo-path
|
||||
:size :big}]]))
|
||||
|
||||
(defn list-footer-avatar
|
||||
[props]
|
||||
[:f> f-list-footer-avatar props])
|
||||
|
||||
(defn f-list-footer
|
||||
[{:keys [chat scroll-y cover-bg-color on-layout]}]
|
||||
(let [{:keys [chat-id chat-name emoji chat-type
|
||||
group-chat]} chat
|
||||
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
|
||||
display-name (if (= chat-type constants/one-to-one-chat-type)
|
||||
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
||||
(str emoji " " chat-name))
|
||||
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
|
||||
online? (rf/sub [:visibility-status-updates/online? chat-id])
|
||||
contact (when-not group-chat
|
||||
(rf/sub [:contacts/contact-by-address chat-id]))
|
||||
photo-path (when-not (empty? (:images contact))
|
||||
(rf/sub [:chats/photo-path chat-id]))
|
||||
border-animation (reanimated/interpolate scroll-y
|
||||
[30 125]
|
||||
[14 0]
|
||||
header-extrapolation-option)]
|
||||
[rn/view {:flex 1}
|
||||
[rn/view
|
||||
{:style (style/header-container all-loaded?)
|
||||
:on-layout on-layout}
|
||||
(when cover-bg-color
|
||||
[rn/view {:style (style/header-cover cover-bg-color)}])
|
||||
[reanimated/view {:style (style/header-bottom-part border-animation)}
|
||||
[rn/view {:style style/header-avatar}
|
||||
[rn/view {:style {:align-items :flex-start}}
|
||||
(when-not group-chat
|
||||
[list-footer-avatar
|
||||
{:scroll-y scroll-y
|
||||
:display-name display-name
|
||||
:online? online?
|
||||
:profile-picture photo-path}])]
|
||||
[rn/view {:style style/name-container}
|
||||
[quo/text
|
||||
{:weight :semi-bold
|
||||
:size :heading-1
|
||||
:style {:margin-top (if group-chat 54 12)}
|
||||
:number-of-lines 1}
|
||||
display-name
|
||||
[contact-icon contact]]]
|
||||
(when bio
|
||||
[quo/text {:style style/bio}
|
||||
bio])]]]
|
||||
[loading-view chat-id]]))
|
||||
|
||||
(defn list-footer
|
||||
[props]
|
||||
[:f> f-list-footer props])
|
||||
|
||||
(defn list-group-chat-header
|
||||
[{:keys [chat-id invitation-admin]}]
|
||||
[rn/view
|
||||
[chat.group/group-chat-footer chat-id invitation-admin]])
|
||||
|
||||
(defn footer-on-layout
|
||||
[e]
|
||||
(let [height (oops/oget e "nativeEvent.layout.height")
|
||||
y (oops/oget e "nativeEvent.layout.y")]
|
||||
(reset! messages-view-header-height (+ height y))))
|
||||
|
||||
(defn render-fn
|
||||
[{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _
|
||||
{:keys [context keyboard-shown]}]
|
||||
[rn/view {:style (when platform/android? {:scaleY -1})}
|
||||
{:keys [context keyboard-shown?]}]
|
||||
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-95)}
|
||||
(if (= type :datemark)
|
||||
[quo/divider-date value]
|
||||
(if (= content-type constants/content-type-gap)
|
||||
@@ -100,71 +239,95 @@
|
||||
[rn/view {:padding-horizontal 8}
|
||||
(if (or deleted? deleted-for-me?)
|
||||
[content.deleted/deleted-message message-data context]
|
||||
[message/message-with-reactions message-data context keyboard-shown])]))])
|
||||
[message/message-with-reactions message-data context keyboard-shown?])]))])
|
||||
|
||||
(defn scroll-handler
|
||||
[event scroll-y]
|
||||
(let [content-size-y (- (oops/oget event "nativeEvent.contentSize.height")
|
||||
(oops/oget event "nativeEvent.layoutMeasurement.height"))
|
||||
current-y (oops/oget event "nativeEvent.contentOffset.y")]
|
||||
(reanimated/set-shared-value scroll-y (- content-size-y current-y))))
|
||||
|
||||
(defn messages-list-content
|
||||
[{:keys [chat-id] :as chat} insets keyboard-shown]
|
||||
(fn []
|
||||
(let [context (rf/sub [:chats/current-chat-message-list-view-context])
|
||||
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
|
||||
recording? (rf/sub [:chats/recording?])]
|
||||
[rn/view
|
||||
{:style {:flex 1}}
|
||||
;; NOTE: DO NOT use anonymous functions for handlers
|
||||
[rn/flat-list
|
||||
{:key-fn list-key-fn
|
||||
:ref list-ref
|
||||
:header [list-header chat]
|
||||
:footer [list-footer chat]
|
||||
:data messages
|
||||
:render-data {:context context
|
||||
:keyboard-shown keyboard-shown}
|
||||
:render-fn render-fn
|
||||
:on-viewable-items-changed on-viewable-items-changed
|
||||
:on-end-reached list-on-end-reached
|
||||
:on-scroll-to-index-failed identity ; don't remove this
|
||||
:content-container-style {:padding-top (+ composer.constants/composer-default-height
|
||||
(:bottom insets)
|
||||
32)
|
||||
:padding-bottom 16}
|
||||
:scroll-indicator-insets {:top (+ composer.constants/composer-default-height
|
||||
(:bottom insets))}
|
||||
:keyboard-dismiss-mode :interactive
|
||||
:keyboard-should-persist-taps :handled
|
||||
:on-momentum-scroll-begin state/start-scrolling
|
||||
:on-momentum-scroll-end state/stop-scrolling
|
||||
:scroll-event-throttle 16
|
||||
:on-scroll on-scroll
|
||||
;; TODO https://github.com/facebook/react-native/issues/30034
|
||||
:inverted (when platform/ios? true)
|
||||
:style (when platform/android? {:scaleY -1})
|
||||
:on-layout on-messages-view-layout
|
||||
:scroll-enabled (not recording?)}]])))
|
||||
[{:keys [chat insets scroll-y cover-bg-color keyboard-shown?]}]
|
||||
(let [context (rf/sub [:chats/current-chat-message-list-view-context])
|
||||
messages (rf/sub [:chats/raw-chat-messages-stream (:chat-id chat)])
|
||||
recording? (rf/sub [:chats/recording?])
|
||||
all-loaded? (rf/sub [:chats/all-loaded? (:chat-id chat)])]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[rn/flat-list
|
||||
{:key-fn list-key-fn
|
||||
:ref list-ref
|
||||
:header [:<>
|
||||
(when (= (:chat-type chat) constants/private-group-chat-type)
|
||||
[list-group-chat-header chat])
|
||||
[list-header insets]]
|
||||
:footer [list-footer
|
||||
{:chat chat
|
||||
:scroll-y scroll-y
|
||||
:cover-bg-color cover-bg-color
|
||||
:on-layout footer-on-layout}]
|
||||
:data messages
|
||||
:render-data {:context context
|
||||
:keyboard-shown? keyboard-shown?}
|
||||
:render-fn render-fn
|
||||
:on-viewable-items-changed on-viewable-items-changed
|
||||
:on-end-reached #(list-on-end-reached scroll-y)
|
||||
:on-scroll-to-index-failed identity
|
||||
:content-container-style {:padding-bottom style/messages-list-bottom-offset}
|
||||
:scroll-indicator-insets {:top (- composer.constants/composer-default-height 16)}
|
||||
:keyboard-dismiss-mode :interactive
|
||||
:keyboard-should-persist-taps :handled
|
||||
:on-momentum-scroll-begin state/start-scrolling
|
||||
:on-momentum-scroll-end state/stop-scrolling
|
||||
:scroll-event-throttle 16
|
||||
:on-scroll (fn [event]
|
||||
(scroll-handler event scroll-y)
|
||||
(when on-scroll
|
||||
(on-scroll event)))
|
||||
:style {:background-color (if all-loaded?
|
||||
cover-bg-color
|
||||
(colors/theme-colors colors/white
|
||||
colors/neutral-95))}
|
||||
:inverted true
|
||||
:on-layout (fn [e]
|
||||
(when platform/android?
|
||||
;; FIXME: this is due to Android not triggering the initial
|
||||
;; scrollTo event
|
||||
(scroll-to-offset 1))
|
||||
(let [layout-height (oops/oget e "nativeEvent.layout.height")]
|
||||
(reset! messages-view-height layout-height)))
|
||||
:scroll-enabled (not recording?)}]]))
|
||||
|
||||
;; This should be replaced with keyboard hook. It has to do with flat-list probably. The keyboard-shown
|
||||
;; value updates in the parent component, but does not get passed to the children.
|
||||
;; When using listeners and resetting the value on an atom it works.
|
||||
(defn use-keyboard-visibility
|
||||
[]
|
||||
(let [show-listener (atom nil)
|
||||
hide-listener (atom nil)
|
||||
shown? (atom nil)]
|
||||
(defn f-messages-list
|
||||
[{:keys [chat cover-bg-color header-comp footer-comp]}]
|
||||
(let [insets (safe-area/get-insets)
|
||||
scroll-y (reanimated/use-shared-value 0)
|
||||
{:keys [keyboard-height keyboard-shown]} (hooks/use-keyboard)]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(reset! show-listener
|
||||
(.addListener rn/keyboard "keyboardWillShow" #(reset! shown? true)))
|
||||
(reset! hide-listener
|
||||
(.addListener rn/keyboard "keyboardWillHide" #(reset! shown? false)))
|
||||
(fn []
|
||||
(.remove ^js @show-listener)
|
||||
(.remove ^js @hide-listener))))
|
||||
{:shown? shown?}))
|
||||
(when keyboard-shown
|
||||
(reanimated/set-shared-value scroll-y
|
||||
(+ (reanimated/get-shared-value scroll-y)
|
||||
keyboard-height))))
|
||||
[keyboard-shown keyboard-height])
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style (style/keyboard-avoiding-container insets)
|
||||
:keyboard-vertical-offset (- (:bottom insets))}
|
||||
|
||||
(defn- f-messages-list
|
||||
[chat insets]
|
||||
(let [{keyboard-shown? :shown?} (use-keyboard-visibility)]
|
||||
[messages-list-content chat insets keyboard-shown?]))
|
||||
(when header-comp
|
||||
[header-comp {:scroll-y scroll-y}])
|
||||
|
||||
[messages-list-content
|
||||
{:chat chat
|
||||
:insets insets
|
||||
:scroll-y scroll-y
|
||||
:cover-bg-color cover-bg-color
|
||||
:keyboard-shown? keyboard-shown}]
|
||||
|
||||
(when footer-comp
|
||||
(footer-comp {:insets insets}))]))
|
||||
|
||||
(defn messages-list
|
||||
[chat insets]
|
||||
[:f> f-messages-list chat insets])
|
||||
[props]
|
||||
[:f> f-messages-list props])
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
(ns status-im2.contexts.chat.messages.navigation.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defonce ^:const navigation-bar-height 100)
|
||||
(defonce ^:const header-offset 56)
|
||||
|
||||
(defn button-container
|
||||
[position]
|
||||
(merge
|
||||
{:width 32
|
||||
:height 32
|
||||
:border-radius 10
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:background-color (colors/theme-colors colors/white-opa-40 colors/neutral-80-opa-40)}
|
||||
position))
|
||||
|
||||
(defn blur-view
|
||||
[status-bar-height]
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:height (- navigation-bar-height
|
||||
(if platform/ios? 0 status-bar-height))
|
||||
:display :flex
|
||||
:flex-direction :row
|
||||
:overflow :hidden})
|
||||
|
||||
(defn animated-blur-view
|
||||
[enabled? animation status-bar-height]
|
||||
(reanimated/apply-animations-to-style
|
||||
(when enabled?
|
||||
{:opacity animation})
|
||||
(blur-view status-bar-height)))
|
||||
|
||||
(def navigation-view
|
||||
{:z-index 4})
|
||||
|
||||
(defn header-container
|
||||
[status-bar-height]
|
||||
{:position :absolute
|
||||
:top (- header-offset
|
||||
(if platform/ios? 0 status-bar-height))
|
||||
:left 0
|
||||
:right 0
|
||||
:padding-bottom 8
|
||||
:display :flex
|
||||
:flex-direction :row
|
||||
:overflow :hidden})
|
||||
|
||||
(def header
|
||||
{:flex 1})
|
||||
|
||||
(defn animated-header
|
||||
[enabled? y-animation opacity-animation]
|
||||
(reanimated/apply-animations-to-style
|
||||
;; here using `top` won't work on Android, so we are using `translateY`
|
||||
(when enabled?
|
||||
{:transform [{:translateY y-animation}]
|
||||
:opacity opacity-animation})
|
||||
header))
|
||||
|
||||
(defn pinned-banner
|
||||
[status-bar-height]
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:right 0
|
||||
:top (- navigation-bar-height
|
||||
(if platform/ios? 0 status-bar-height))})
|
||||
|
||||
(defn animated-pinned-banner
|
||||
[enabled? animation status-bar-height]
|
||||
(reanimated/apply-animations-to-style
|
||||
(when enabled?
|
||||
{:opacity animation})
|
||||
(pinned-banner status-bar-height)))
|
||||
|
||||
(def header-content-container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:margin-left 8
|
||||
:margin-right 8
|
||||
:margin-top -4
|
||||
:height 40})
|
||||
|
||||
(def header-avatar-container
|
||||
{:margin-right 8})
|
||||
|
||||
(def header-text-container
|
||||
{:flex 1})
|
||||
|
||||
(defn header-display-name
|
||||
[]
|
||||
{:color (colors/theme-colors colors/black colors/white)})
|
||||
|
||||
(defn header-online
|
||||
[]
|
||||
{:color (colors/theme-colors colors/neutral-80-opa-50 colors/white-opa-50)})
|
||||
@@ -0,0 +1,112 @@
|
||||
(ns status-im2.contexts.chat.messages.navigation.view
|
||||
(:require [quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[re-frame.db]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im2.contexts.chat.messages.navigation.style :as style]
|
||||
[status-im2.contexts.chat.messages.pin.banner.view :as pin.banner]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im2.common.home.actions.view :as actions]))
|
||||
|
||||
(defn f-navigation-view
|
||||
[{:keys [scroll-y]}]
|
||||
(let [insets (safe-area/get-insets)
|
||||
status-bar-height (:top insets)
|
||||
{:keys [group-chat chat-id chat-name emoji
|
||||
chat-type]
|
||||
:as chat} (rf/sub [:chats/current-chat-chat-view])
|
||||
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
|
||||
display-name (if (= chat-type constants/one-to-one-chat-type)
|
||||
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
||||
(str emoji " " chat-name))
|
||||
online? (rf/sub [:visibility-status-updates/online? chat-id])
|
||||
contact (when-not group-chat
|
||||
(rf/sub [:contacts/contact-by-address chat-id]))
|
||||
photo-path (when-not (empty? (:images contact))
|
||||
(rf/sub [:chats/photo-path chat-id]))
|
||||
opacity-animation (reanimated/interpolate scroll-y
|
||||
[style/navigation-bar-height
|
||||
(+ style/navigation-bar-height 30)]
|
||||
[0 1]
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "extend"})
|
||||
banner-opacity-animation (reanimated/interpolate scroll-y
|
||||
[(+ style/navigation-bar-height 50)
|
||||
(+ style/navigation-bar-height 100)]
|
||||
[0 1]
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"})
|
||||
translate-animation (reanimated/interpolate scroll-y
|
||||
[(+ style/navigation-bar-height 25)
|
||||
(+ style/navigation-bar-height 100)]
|
||||
[50 0]
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"})
|
||||
title-opacity-animation (reanimated/interpolate scroll-y
|
||||
[0 50]
|
||||
[0 1]
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"})]
|
||||
[rn/view {:style style/navigation-view}
|
||||
[reanimated/blur-view
|
||||
{:blurAmount 32
|
||||
:blurType (colors/theme-colors :xlight :dark)
|
||||
:overlayColor :transparent
|
||||
:style (style/animated-blur-view all-loaded? opacity-animation status-bar-height)}]
|
||||
|
||||
[rn/view
|
||||
[rn/view {:style (style/header-container status-bar-height)}
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:on-press #(rf/dispatch [:navigate-back])
|
||||
:accessibility-label :back-button
|
||||
:style (style/button-container {:margin-left 20})}
|
||||
[quo/icon :i/arrow-left
|
||||
{:size 20 :color (colors/theme-colors colors/black colors/white)}]]
|
||||
[reanimated/view
|
||||
{:style (style/animated-header all-loaded? translate-animation title-opacity-animation)}
|
||||
[rn/view {:style style/header-content-container}
|
||||
(when-not group-chat
|
||||
[rn/view {:style style/header-avatar-container}
|
||||
[quo/user-avatar
|
||||
{:full-name display-name
|
||||
:online? online?
|
||||
:profile-picture photo-path
|
||||
:size :small}]])
|
||||
[rn/view {:style style/header-text-container}
|
||||
[rn/view {:style {:flex-direction :row}}
|
||||
[quo/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:number-of-lines 1
|
||||
:style (style/header-display-name)}
|
||||
display-name]]
|
||||
(when online?
|
||||
[quo/text
|
||||
{:number-of-lines 1
|
||||
:weight :regular
|
||||
:size :paragraph-2
|
||||
:style (style/header-online)}
|
||||
(i18n/label :t/online)])]]]
|
||||
(when (not= chat-type constants/community-chat-type)
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:style (style/button-container {:margin-right 20})
|
||||
:accessibility-label :options-button
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [actions/chat-actions chat true])}]))}
|
||||
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/black colors/white)}]])]
|
||||
|
||||
[reanimated/view
|
||||
{:style (style/animated-pinned-banner all-loaded? banner-opacity-animation status-bar-height)}
|
||||
[pin.banner/banner chat-id]]]]))
|
||||
|
||||
(defn navigation-view
|
||||
[props]
|
||||
[:f> f-navigation-view props])
|
||||
@@ -1,17 +1,14 @@
|
||||
(ns status-im2.contexts.chat.messages.view
|
||||
(:require [quo2.core :as quo]
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[re-frame.db]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.chat.composer.view :as composer]
|
||||
[status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as
|
||||
contact-requests.bottom-drawer]
|
||||
[status-im2.contexts.chat.messages.list.view :as messages.list]
|
||||
[status-im2.contexts.chat.messages.pin.banner.view :as pin.banner]
|
||||
[status-im2.contexts.chat.messages.navigation.view :as messages.navigation]
|
||||
[status-im2.navigation.state :as navigation.state]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn navigate-back-handler
|
||||
@@ -24,62 +21,23 @@
|
||||
;; and will call system back button action
|
||||
true))
|
||||
|
||||
(defn page-nav
|
||||
[]
|
||||
(let [{:keys [group-chat chat-id chat-name emoji
|
||||
chat-type]} (rf/sub [:chats/current-chat])
|
||||
display-name (if (= chat-type constants/one-to-one-chat-type)
|
||||
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
||||
(str emoji " " chat-name))
|
||||
online? (rf/sub [:visibility-status-updates/online? chat-id])
|
||||
contact (when-not group-chat
|
||||
(rf/sub [:contacts/contact-by-address chat-id]))
|
||||
photo-path (rf/sub [:chats/photo-path chat-id])
|
||||
avatar-image-key (if (seq (:images contact))
|
||||
:profile-picture
|
||||
:ring-background)]
|
||||
[quo/page-nav
|
||||
{:align-mid? true
|
||||
:mid-section (if group-chat
|
||||
{:type :text-only
|
||||
:main-text display-name}
|
||||
{:type :user-avatar
|
||||
:avatar {:full-name display-name
|
||||
:online? online?
|
||||
:size :medium
|
||||
avatar-image-key photo-path}
|
||||
:main-text display-name
|
||||
:on-press #(debounce/dispatch-and-chill [:chat.ui/show-profile chat-id]
|
||||
1000)})
|
||||
|
||||
:left-section {:on-press #(do
|
||||
(rf/dispatch [:chat/close])
|
||||
(rf/dispatch [:navigate-back]))
|
||||
:icon :i/arrow-left
|
||||
:accessibility-label :back-button}
|
||||
|
||||
:right-section-buttons [{:on-press #()
|
||||
:style {:border-width 1
|
||||
:border-color :red}
|
||||
:icon :i/options
|
||||
:accessibility-label :options-button}]}]))
|
||||
|
||||
(defn chat-render
|
||||
[]
|
||||
(let [;;NOTE: we want to react only on these fields, do not use full chat map here
|
||||
{:keys [chat-id contact-request-state group-chat able-to-send-message?] :as chat}
|
||||
(rf/sub [:chats/current-chat-chat-view])
|
||||
insets (safe-area/get-insets)]
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style {:position :relative :flex 1}
|
||||
:keyboardVerticalOffset (- (:bottom insets))}
|
||||
[page-nav]
|
||||
[pin.banner/banner chat-id]
|
||||
[messages.list/messages-list chat insets]
|
||||
(if-not able-to-send-message?
|
||||
[contact-requests.bottom-drawer/view chat-id contact-request-state group-chat]
|
||||
[:f> composer/composer insets])]))
|
||||
|
||||
(let [{:keys [chat-id
|
||||
contact-request-state
|
||||
group-chat
|
||||
able-to-send-message?]
|
||||
:as chat} (rf/sub [:chats/current-chat-chat-view])]
|
||||
[messages.list/messages-list
|
||||
{:cover-bg-color (colors/custom-color :turquoise 50 20)
|
||||
:chat chat
|
||||
:header-comp (fn [{:keys [scroll-y]}]
|
||||
[messages.navigation/navigation-view {:scroll-y scroll-y}])
|
||||
:footer-comp (fn [{:keys [insets]}]
|
||||
[rn/view
|
||||
(if-not able-to-send-message?
|
||||
[contact-requests.bottom-drawer/view chat-id contact-request-state group-chat]
|
||||
[:f> composer/composer insets])])}]))
|
||||
|
||||
(defn chat
|
||||
[]
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im2.contexts.chat.photo-selector.view :refer [album-title]]
|
||||
[status-im2.contexts.chat.photo-selector.album-selector.style :as style]))
|
||||
|
||||
(defn album
|
||||
[{:keys [title count uri]} index _ selected-album]
|
||||
(defn render-album
|
||||
[{:keys [title count uri]} index _ {:keys [album? selected-album]}]
|
||||
(let [selected? (= selected-album title)]
|
||||
[rn/touchable-opacity
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:chat.ui/camera-roll-select-album title])
|
||||
(rf/dispatch [:navigate-back]))
|
||||
(rf/dispatch [:photo-selector/get-photos-for-selected-album])
|
||||
(reset! album? false))
|
||||
:style (style/album-container selected?)
|
||||
:accessibility-label (str "album-" index)}
|
||||
[rn/image
|
||||
@@ -31,7 +31,7 @@
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
|
||||
(str count " " (i18n/label :t/images))]]
|
||||
(when count (str count " " (i18n/label :t/images)))]]
|
||||
(when selected?
|
||||
[rn/view
|
||||
{:style {:position :absolute
|
||||
@@ -39,9 +39,11 @@
|
||||
[quo/icon :i/check
|
||||
{:size 20 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]])]))
|
||||
|
||||
(def no-title "no-title")
|
||||
|
||||
(defn section-header
|
||||
[{:keys [title]}]
|
||||
(when (not= title "smart-albums")
|
||||
(when-not (= title no-title)
|
||||
[quo/divider-label
|
||||
{:label title
|
||||
:container-style style/divider}]))
|
||||
@@ -51,22 +53,19 @@
|
||||
(str (:title item) index))
|
||||
|
||||
(defn album-selector
|
||||
[{:keys [on-scroll]}]
|
||||
(rf/dispatch [:chat.ui/camera-roll-get-albums])
|
||||
(fn [{:keys [scroll-enabled]}]
|
||||
(let [albums (rf/sub [:camera-roll/albums])
|
||||
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
|
||||
[rn/view {:style {:padding-top 20}}
|
||||
[album-title false]
|
||||
[gesture/section-list
|
||||
{:data albums
|
||||
:render-fn album
|
||||
:render-data selected-album
|
||||
:sections albums
|
||||
:sticky-section-headers-enabled false
|
||||
:render-section-header-fn section-header
|
||||
:style {:margin-top 12}
|
||||
:content-container-style {:padding-bottom 40}
|
||||
:key-fn key-fn
|
||||
:scroll-enabled @scroll-enabled
|
||||
:on-scroll on-scroll}]])))
|
||||
[{:keys [scroll-enabled on-scroll]} album? selected-album]
|
||||
(let [albums (rf/sub [:camera-roll/albums])
|
||||
albums-sections [{:title no-title :data (:smart-albums albums)}
|
||||
{:title (i18n/label :t/my-albums) :data (:my-albums albums)}]]
|
||||
[gesture/section-list
|
||||
{:data albums-sections
|
||||
:sections albums-sections
|
||||
:render-data {:album? album? :selected-album selected-album}
|
||||
:render-fn render-album
|
||||
:sticky-section-headers-enabled false
|
||||
:render-section-header-fn section-header
|
||||
:content-container-style {:padding-top 64
|
||||
:padding-bottom 40}
|
||||
:key-fn key-fn
|
||||
:scroll-enabled @scroll-enabled
|
||||
:on-scroll on-scroll}]))
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
(ns status-im2.contexts.chat.photo-selector.events
|
||||
(:require [react-native.cameraroll :as cameraroll]
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[utils.i18n :as i18n]
|
||||
[react-native.permissions :as permissions]
|
||||
[status-im2.config :as config]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.image-processing :as image-processing]
|
||||
[taoensso.timbre :as log]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(def maximum-image-size-px 2000)
|
||||
|
||||
(defn- resize-photo
|
||||
[uri callback]
|
||||
(rn/image-get-size
|
||||
uri
|
||||
(fn [width height]
|
||||
(let [resize? (> (max width height) maximum-image-size-px)]
|
||||
(image-processing/resize
|
||||
uri
|
||||
(if resize? maximum-image-size-px width)
|
||||
(if resize? maximum-image-size-px height)
|
||||
60
|
||||
(fn [^js resized-image]
|
||||
(let [path (.-path resized-image)
|
||||
path (if (string/starts-with? path "file") path (str "file://" path))]
|
||||
(callback {:resized-uri path
|
||||
:width width
|
||||
:height height})))
|
||||
#(log/error "could not resize image" %))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:camera-roll-request-permissions-and-get-photos
|
||||
(fn [[num end-cursor album]]
|
||||
(permissions/request-permissions
|
||||
{:permissions [:read-external-storage]
|
||||
:on-allowed
|
||||
(fn []
|
||||
(cameraroll/get-photos
|
||||
(merge {:first num
|
||||
:assetType "Photos"
|
||||
:groupTypes (if (= album (i18n/label :t/recent)) "All" "Albums")
|
||||
:groupName (when (not= album (i18n/label :t/recent)) album)
|
||||
:include ["imageSize"]}
|
||||
(when end-cursor
|
||||
{:after end-cursor}))
|
||||
#(re-frame/dispatch [:on-camera-roll-get-photos (:edges %) (:page_info %) end-cursor])))})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:camera-roll-image-selected
|
||||
(fn [[image chat-id]]
|
||||
(resize-photo (:uri image) #(re-frame/dispatch [:photo-selector/image-selected chat-id image %]))))
|
||||
|
||||
(defn get-albums
|
||||
[callback]
|
||||
(let [albums (atom {:smart-albums []
|
||||
:my-albums []})]
|
||||
;; Get the "recent" album first
|
||||
(cameraroll/get-photos
|
||||
{:first 1 :groupTypes "All"}
|
||||
(fn [res-recent]
|
||||
(swap! albums assoc
|
||||
:smart-albums
|
||||
[{:title (i18n/label :t/recent)
|
||||
:uri (get-in (first (:edges res-recent)) [:node :image :uri])}])
|
||||
;; Get albums, then loop over albums and get each one's cover (first photo)
|
||||
(cameraroll/get-albums
|
||||
{:assetType "All"}
|
||||
(fn [res-albums]
|
||||
(let [response-count (count res-albums)]
|
||||
(if (pos? response-count)
|
||||
(doseq [album res-albums]
|
||||
(cameraroll/get-photos
|
||||
{:first 1 :groupTypes "Albums" :groupName (:title album)}
|
||||
(fn [res]
|
||||
(let [uri (get-in (first (:edges res)) [:node :image :uri])]
|
||||
(swap! albums update :my-albums conj (merge album {:uri uri}))
|
||||
(when (= (count (:my-albums @albums)) response-count)
|
||||
(swap! albums update :my-albums #(sort-by :title %))
|
||||
(callback @albums))))))
|
||||
(callback @albums)))))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:camera-roll-get-albums
|
||||
(fn []
|
||||
(get-albums #(re-frame/dispatch [:on-camera-roll-get-albums %]))))
|
||||
|
||||
(rf/defn on-camera-roll-get-albums
|
||||
{:events [:on-camera-roll-get-albums]}
|
||||
[{:keys [db]} albums]
|
||||
{:db (assoc db :camera-roll/albums albums)})
|
||||
|
||||
(rf/defn camera-roll-get-albums
|
||||
{:events [:photo-selector/camera-roll-get-albums]}
|
||||
[_]
|
||||
{:camera-roll-get-albums nil})
|
||||
|
||||
(rf/defn camera-roll-select-album
|
||||
{:events [:chat.ui/camera-roll-select-album]}
|
||||
[{:keys [db]} album]
|
||||
{:db (assoc db :camera-roll/selected-album album)})
|
||||
|
||||
(rf/defn image-selected
|
||||
{:events [:photo-selector/image-selected]}
|
||||
[{:keys [db]} current-chat-id original {:keys [resized-uri width height]}]
|
||||
{:db
|
||||
(update-in db
|
||||
[:chat/inputs current-chat-id :metadata :sending-image (:uri original)]
|
||||
merge
|
||||
original
|
||||
{:resized-uri resized-uri
|
||||
:width width
|
||||
:height height})})
|
||||
|
||||
(rf/defn on-camera-roll-get-photos
|
||||
{:events [:on-camera-roll-get-photos]}
|
||||
[{:keys [db]} photos page-info end-cursor]
|
||||
(let [photos_x (when end-cursor (:camera-roll/photos db))]
|
||||
{:db (-> db
|
||||
(assoc :camera-roll/photos (concat photos_x (map #(get-in % [:node :image]) photos)))
|
||||
(assoc :camera-roll/end-cursor (:end_cursor page-info))
|
||||
(assoc :camera-roll/has-next-page (:has_next_page page-info))
|
||||
(assoc :camera-roll/loading-more false))}))
|
||||
|
||||
(rf/defn get-photos-for-selected-album
|
||||
{:events [:photo-selector/get-photos-for-selected-album]}
|
||||
[{:keys [db]} end-cursor]
|
||||
{:camera-roll-request-permissions-and-get-photos [21 end-cursor
|
||||
(or (:camera-roll/selected-album db)
|
||||
(i18n/label :t/recent))]})
|
||||
|
||||
(rf/defn camera-roll-loading-more
|
||||
{:events [:photo-selector/camera-roll-loading-more]}
|
||||
[{:keys [db]} is-loading]
|
||||
{:db (assoc db :camera-roll/loading-more is-loading)})
|
||||
|
||||
(rf/defn camera-roll-pick
|
||||
{:events [:photo-selector/camera-roll-pick]}
|
||||
[{:keys [db]} image chat-id]
|
||||
(let [current-chat-id (or chat-id (:current-chat-id db))
|
||||
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
|
||||
(when (and (< (count images) config/max-images-batch)
|
||||
(not (some #(= (:uri image) (:uri %)) images)))
|
||||
{:camera-roll-image-selected [image current-chat-id]})))
|
||||
@@ -15,18 +15,13 @@
|
||||
:flex-direction :row
|
||||
:left 0
|
||||
:right 0
|
||||
:top 0
|
||||
:top 20
|
||||
:justify-content :center
|
||||
:z-index 1})
|
||||
|
||||
(defn clear-container
|
||||
[]
|
||||
{:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
|
||||
:padding-horizontal 12
|
||||
:padding-vertical 5
|
||||
:border-radius 10
|
||||
:position :absolute
|
||||
:right 20})
|
||||
(def clear-container
|
||||
{:position :absolute
|
||||
:right 20})
|
||||
|
||||
(defn close-button-container
|
||||
[]
|
||||
|
||||
@@ -1,150 +1,126 @@
|
||||
(ns status-im2.contexts.chat.photo-selector.view
|
||||
(:require
|
||||
[react-native.gesture :as gesture]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[react-native.platform :as platform]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[quo2.components.notifications.info-count :as info-count]
|
||||
[quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.chat.photo-selector.style :as style]
|
||||
[status-im.utils.core :as utils]
|
||||
[quo.react]
|
||||
[utils.re-frame :as rf]))
|
||||
[status-im2.contexts.chat.photo-selector.album-selector.view :as album-selector]
|
||||
utils.collection))
|
||||
|
||||
(defn show-toast
|
||||
[]
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :random-id
|
||||
:icon :info
|
||||
:icon-color colors/danger-50-opa-40
|
||||
:container-style {:top (when platform/ios? 20)}
|
||||
:text (i18n/label :t/only-6-images)}]))
|
||||
|
||||
(defn on-press-confirm-selection
|
||||
[selected]
|
||||
[selected close]
|
||||
(rf/dispatch [:chat.ui/clear-sending-images])
|
||||
(doseq [item @selected]
|
||||
(rf/dispatch [:chat.ui/camera-roll-pick item]))
|
||||
(reset! selected [])
|
||||
(rf/dispatch [:navigate-back]))
|
||||
(doseq [item selected]
|
||||
(rf/dispatch [:photo-selector/camera-roll-pick item]))
|
||||
(close))
|
||||
|
||||
(defn bottom-gradient
|
||||
[selected-images insets selected]
|
||||
(when (or (seq @selected) (seq selected-images))
|
||||
(defn confirm-button
|
||||
[selected-images sending-image close]
|
||||
(when (not= selected-images sending-image)
|
||||
[linear-gradient/linear-gradient
|
||||
{:colors [:black :transparent]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style (style/gradient-container (:bottom insets))}
|
||||
:style (style/gradient-container (safe-area/get-bottom))}
|
||||
[quo/button
|
||||
{:style {:align-self :stretch
|
||||
:margin-horizontal 20
|
||||
:margin-top 12}
|
||||
:on-press #(on-press-confirm-selection selected)
|
||||
:on-press #(on-press-confirm-selection selected-images close)
|
||||
:accessibility-label :confirm-selection}
|
||||
(i18n/label :t/confirm-selection)]]))
|
||||
|
||||
(defn clear-button
|
||||
[selected]
|
||||
(when (seq @selected)
|
||||
[rn/touchable-opacity
|
||||
{:on-press #(reset! selected [])
|
||||
:style (style/clear-container)
|
||||
:accessibility-label :clear}
|
||||
[quo/text {:weight :medium} (i18n/label :t/clear)]]))
|
||||
[album? selected]
|
||||
(when (and (not album?) (seq @selected))
|
||||
[rn/view {:style style/clear-container}
|
||||
[quo/button
|
||||
{:type :grey
|
||||
:size 32
|
||||
:accessibility-label :clear
|
||||
:on-press #(reset! selected [])}
|
||||
(i18n/label :t/clear)]]))
|
||||
|
||||
(defn remove-selected
|
||||
[coll item]
|
||||
(vec (remove #(= (:uri item) (:uri %)) coll)))
|
||||
|
||||
(defn image
|
||||
(defn render-image
|
||||
[item index _ {:keys [window-width selected]}]
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:on-press (fn []
|
||||
(if (some #(= (:uri item) (:uri %)) @selected)
|
||||
(swap! selected remove-selected item)
|
||||
(if (>= (count @selected) constants/max-album-photos)
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :random-id
|
||||
:icon :info
|
||||
:icon-color colors/danger-50-opa-40
|
||||
:container-style {:top (when platform/ios? 20)}
|
||||
:text (i18n/label :t/only-6-images)}])
|
||||
(swap! selected conj item))))
|
||||
:accessibility-label (str "image-" index)}
|
||||
[rn/image
|
||||
{:source {:uri (:uri item)}
|
||||
:style (style/image window-width index)}]
|
||||
(when (some #(= (:uri item) (:uri %)) @selected)
|
||||
[rn/view {:style (style/overlay window-width)}])
|
||||
(when (some #(= (:uri item) (:uri %)) @selected)
|
||||
[info-count/info-count
|
||||
{:style style/image-count
|
||||
:accessibility-label (str "count-" index)}
|
||||
(inc (utils/first-index #(= (:uri item) (:uri %)) @selected))])])
|
||||
|
||||
(defn album-title
|
||||
[photos? selected temporary-selected insets close]
|
||||
(fn []
|
||||
(let [selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
|
||||
[rn/touchable-opacity
|
||||
{:style (style/title-container)
|
||||
:active-opacity 1
|
||||
:accessibility-label :album-title
|
||||
:on-press (fn []
|
||||
;; TODO: album-selector issue:
|
||||
;; https://github.com/status-im/status-mobile/issues/15398
|
||||
(if photos?
|
||||
(do
|
||||
(reset! temporary-selected @selected)
|
||||
(rf/dispatch [:open-modal :album-selector {:insets insets}]))
|
||||
(close)))}
|
||||
[quo/text
|
||||
{:weight :medium
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1
|
||||
:style {:max-width 150}}
|
||||
selected-album]
|
||||
[rn/view {:style (style/chevron-container)}
|
||||
[quo/icon (if photos? :i/chevron-down :i/chevron-up)
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]])))
|
||||
(let [item-selected? (some #(= (:uri item) (:uri %)) @selected)]
|
||||
[rn/touchable-opacity
|
||||
{:on-press (fn []
|
||||
(if item-selected?
|
||||
(swap! selected remove-selected item)
|
||||
(if (>= (count @selected) constants/max-album-photos)
|
||||
(show-toast)
|
||||
(swap! selected conj item))))
|
||||
:accessibility-label (str "image-" index)}
|
||||
[rn/image
|
||||
{:source {:uri (:uri item)}
|
||||
:style (style/image window-width index)}]
|
||||
(when item-selected?
|
||||
[:<>
|
||||
[rn/view {:style (style/overlay window-width)}]
|
||||
[info-count/info-count
|
||||
{:style style/image-count
|
||||
:accessibility-label (str "count-" index)}
|
||||
(inc (utils.collection/first-index #(= (:uri item) (:uri %)) @selected))]])]))
|
||||
|
||||
(defn photo-selector
|
||||
[{:keys [scroll-enabled on-scroll close]}]
|
||||
[:f>
|
||||
(let [temporary-selected (reagent/atom [])
|
||||
{:keys [insets]} (rf/sub [:get-screen-params])] ; used when switching albums
|
||||
(fn []
|
||||
(let [selected (reagent/atom []) ; currently selected
|
||||
selected-images (rf/sub [:chats/sending-image])
|
||||
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(rf/dispatch [:chat.ui/camera-roll-get-photos 20 nil selected-album])
|
||||
(if (seq selected-images)
|
||||
(reset! selected (vec (vals selected-images)))
|
||||
(reset! selected @temporary-selected)))
|
||||
[selected-album])
|
||||
[:f>
|
||||
(fn []
|
||||
(let [window-width (:width (rn/get-window))
|
||||
camera-roll-photos (rf/sub [:camera-roll/photos])
|
||||
end-cursor (rf/sub [:camera-roll/end-cursor])
|
||||
loading? (rf/sub [:camera-roll/loading-more])
|
||||
has-next-page? (rf/sub [:camera-roll/has-next-page])]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[rn/view
|
||||
{:style style/buttons-container}
|
||||
[album-title true selected temporary-selected insets close]
|
||||
[clear-button selected]]
|
||||
[gesture/flat-list
|
||||
{:key-fn identity
|
||||
:render-fn image
|
||||
:render-data {:window-width window-width :selected selected}
|
||||
:data camera-roll-photos
|
||||
:num-columns 3
|
||||
:content-container-style {:width "100%"
|
||||
:padding-bottom (+ (:bottom insets) 100)
|
||||
:padding-top 64}
|
||||
:on-scroll on-scroll
|
||||
:scroll-enabled @scroll-enabled
|
||||
:on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor
|
||||
selected-album loading?
|
||||
has-next-page?])}]
|
||||
[bottom-gradient selected-images insets selected]]))])))])
|
||||
[{:keys [scroll-enabled on-scroll close] :as sheet}]
|
||||
(rf/dispatch [:photo-selector/get-photos-for-selected-album])
|
||||
(rf/dispatch [:photo-selector/camera-roll-get-albums])
|
||||
(let [album? (reagent/atom false)
|
||||
sending-image (into [] (vals (rf/sub [:chats/sending-image])))
|
||||
selected-images (reagent/atom sending-image)
|
||||
window-width (:width (rn/get-window))]
|
||||
(fn []
|
||||
(let [camera-roll-photos (rf/sub [:camera-roll/photos])
|
||||
end-cursor (rf/sub [:camera-roll/end-cursor])
|
||||
loading? (rf/sub [:camera-roll/loading-more])
|
||||
has-next-page? (rf/sub [:camera-roll/has-next-page])
|
||||
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
|
||||
[rn/view {:style {:flex 1 :margin-top -20}}
|
||||
[rn/view {:style style/buttons-container}
|
||||
[quo/dropdown {:type :grey :size 32 :on-change #(swap! album? not) :selected @album?}
|
||||
selected-album]
|
||||
[clear-button @album? selected-images]]
|
||||
(if @album?
|
||||
[album-selector/album-selector sheet album? selected-album]
|
||||
[:<>
|
||||
[gesture/flat-list
|
||||
{:key-fn identity
|
||||
:render-fn render-image
|
||||
:render-data {:window-width window-width :selected selected-images}
|
||||
:data camera-roll-photos
|
||||
:num-columns 3
|
||||
:content-container-style {:width "100%"
|
||||
:padding-bottom (+ (safe-area/get-bottom) 100)
|
||||
:padding-top 64}
|
||||
:on-scroll on-scroll
|
||||
:scroll-enabled @scroll-enabled
|
||||
:on-end-reached (fn []
|
||||
(when (and (not loading?) has-next-page?)
|
||||
(rf/dispatch [:photo-selector/camera-roll-loading-more true])
|
||||
(rf/dispatch [:photo-selector/get-photos-for-selected-album
|
||||
end-cursor])))}]
|
||||
[confirm-button @selected-images sending-image close]])]))))
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
[rn/view
|
||||
{:style style/featured-list-container
|
||||
:on-layout #(swap! view-size
|
||||
(fn []
|
||||
(fn [_]
|
||||
(- (oops/oget % "nativeEvent.layout.width") 40)))}
|
||||
(when-not (= @view-size 0)
|
||||
[rn/flat-list
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
[content-width]
|
||||
[rn/image
|
||||
{:style {:resize-mode :stretch
|
||||
:margin-top 32
|
||||
:width content-width}
|
||||
:source (resources/get-image :onboarding-illustration)}])
|
||||
|
||||
|
||||
@@ -73,11 +73,11 @@
|
||||
(let [will-show-listener (oops/ocall rn/keyboard
|
||||
"addListener"
|
||||
"keyboardWillShow"
|
||||
#(swap! keyboard-shown? (fn [] true)))
|
||||
#(swap! keyboard-shown? (constantly true)))
|
||||
will-hide-listener (oops/ocall rn/keyboard
|
||||
"addListener"
|
||||
"keyboardWillHide"
|
||||
#(swap! keyboard-shown? (fn [] false)))]
|
||||
#(swap! keyboard-shown? (constantly false)))]
|
||||
(fn []
|
||||
(fn []
|
||||
(oops/ocall will-show-listener "remove")
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
(ns status-im2.contexts.onboarding.enter-seed-phrase.style
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.safe-area :as safe-area]))
|
||||
|
||||
(def full-layout {:flex 1})
|
||||
|
||||
(def page-container
|
||||
{:position :absolute
|
||||
@@ -9,6 +12,27 @@
|
||||
:right 0
|
||||
:background-color colors/neutral-80-opa-80-blur})
|
||||
|
||||
(def form-container
|
||||
{:flex 1
|
||||
:padding-horizontal 20
|
||||
:padding-vertical 12})
|
||||
|
||||
(def header-container
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between})
|
||||
|
||||
(def word-count-container
|
||||
{:justify-content :flex-end
|
||||
:margin-bottom 2})
|
||||
|
||||
(def input-container
|
||||
{:height 120
|
||||
:margin-top 12
|
||||
:margin-horizontal -20})
|
||||
|
||||
(defn continue-button
|
||||
[keyboard-shown?]
|
||||
{:margin-top :auto
|
||||
:margin-bottom (when-not keyboard-shown? (safe-area/get-bottom))})
|
||||
|
||||
(def keyboard-container {:margin-top :auto})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns status-im2.contexts.onboarding.enter-seed-phrase.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
@@ -13,57 +14,152 @@
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(def button-disabled?
|
||||
(comp not constants/seed-phrase-valid-length mnemonic/words-count))
|
||||
(def ^:private max-seed-phrase-length
|
||||
(apply max constants/seed-phrase-valid-length))
|
||||
|
||||
(defn clean-seed-phrase
|
||||
[s]
|
||||
(as-> s $
|
||||
(string/lower-case $)
|
||||
(string/split $ #"\s")
|
||||
(filter #(not (string/blank? %)) $)
|
||||
(string/join " " $)))
|
||||
(defn- partial-word-in-dictionary?
|
||||
[partial-word]
|
||||
(some #(string/starts-with? % partial-word) mnemonic/dictionary))
|
||||
|
||||
(defn page
|
||||
[{:keys [navigation-bar-top]}]
|
||||
(let [seed-phrase (reagent/atom "")
|
||||
error-message (reagent/atom "")
|
||||
on-invalid-seed-phrase #(reset! error-message (i18n/label :t/custom-seed-phrase))]
|
||||
(fn []
|
||||
[rn/view {:style style/page-container}
|
||||
[navigation-bar/navigation-bar {:top navigation-bar-top}]
|
||||
[rn/view {:style {:padding-horizontal 20}}
|
||||
[quo/text
|
||||
{:weight :bold
|
||||
:align :center}
|
||||
(i18n/label :t/use-recovery-phrase)]
|
||||
[quo/text
|
||||
(i18n/label-pluralize (mnemonic/words-count @seed-phrase) :t/words-n)]
|
||||
[rn/view {:style style/input-container}
|
||||
[quo/recovery-phrase-input
|
||||
{:on-change-text (fn [t]
|
||||
(reset! seed-phrase (clean-seed-phrase t))
|
||||
(reset! error-message ""))
|
||||
:mark-errors? true
|
||||
:error-pred (constantly false)
|
||||
:word-limit 24
|
||||
:auto-focus true
|
||||
:accessibility-label :passphrase-input
|
||||
:placeholder (i18n/label :t/seed-phrase-placeholder)
|
||||
:auto-correct false}
|
||||
@seed-phrase]]
|
||||
[quo/button
|
||||
{:disabled (button-disabled? @seed-phrase)
|
||||
:on-press #(rf/dispatch [:onboarding-2/seed-phrase-entered
|
||||
(security/mask-data @seed-phrase)
|
||||
on-invalid-seed-phrase])}
|
||||
(i18n/label :t/continue)]
|
||||
(when (seq @error-message)
|
||||
[quo/text @error-message])]])))
|
||||
(defn- word-in-dictionary?
|
||||
[word]
|
||||
(some #(= % word) mnemonic/dictionary))
|
||||
|
||||
(def ^:private partial-word-not-in-dictionary?
|
||||
(comp not partial-word-in-dictionary?))
|
||||
|
||||
(def ^:private word-not-in-dictionary?
|
||||
(comp not word-in-dictionary?))
|
||||
|
||||
(defn- header
|
||||
[seed-phrase-count]
|
||||
[rn/view {:style style/header-container}
|
||||
[quo/text {:weight :semi-bold :size :heading-1}
|
||||
(i18n/label :t/use-recovery-phrase)]
|
||||
[rn/view {:style style/word-count-container}
|
||||
[quo/text
|
||||
{:style {:color colors/white-opa-40}
|
||||
:weight :regular
|
||||
:size :paragraph-2}
|
||||
(i18n/label-pluralize seed-phrase-count :t/words-n)]]])
|
||||
|
||||
(defn- clean-seed-phrase
|
||||
[seed-phrase]
|
||||
(-> seed-phrase
|
||||
(string/lower-case)
|
||||
(string/replace #", " " ")
|
||||
(string/replace #"," " ")
|
||||
(string/replace #"\s+" " ")
|
||||
(string/trim)))
|
||||
|
||||
(defn- recovery-form
|
||||
[{:keys [seed-phrase word-count error-state? all-words-valid? on-change-seed-phrase
|
||||
keyboard-shown? on-submit]}]
|
||||
(let [button-disabled? (or error-state?
|
||||
(not (constants/seed-phrase-valid-length word-count))
|
||||
(not all-words-valid?))]
|
||||
[rn/view {:style style/form-container}
|
||||
[header word-count]
|
||||
[rn/view {:style style/input-container}
|
||||
[quo/recovery-phrase-input
|
||||
{:accessibility-label :passphrase-input
|
||||
:placeholder (i18n/label :t/seed-phrase-placeholder)
|
||||
:auto-capitalize :none
|
||||
:auto-correct false
|
||||
:auto-focus true
|
||||
:mark-errors? true
|
||||
:word-limit max-seed-phrase-length
|
||||
:error-pred-current-word partial-word-not-in-dictionary?
|
||||
:error-pred-written-words word-not-in-dictionary?
|
||||
:on-change-text on-change-seed-phrase}
|
||||
seed-phrase]]
|
||||
[quo/button
|
||||
{:style (style/continue-button keyboard-shown?)
|
||||
:disabled button-disabled?
|
||||
:on-press on-submit}
|
||||
(i18n/label :t/continue)]]))
|
||||
|
||||
(defn keyboard-suggestions
|
||||
[current-word]
|
||||
(->> mnemonic/dictionary
|
||||
(filter #(string/starts-with? % current-word))
|
||||
(take 7)))
|
||||
|
||||
(defn screen
|
||||
[]
|
||||
(reagent/with-let [keyboard-shown? (reagent/atom false)
|
||||
keyboard-show-listener (.addListener rn/keyboard
|
||||
"keyboardDidShow"
|
||||
#(reset! keyboard-shown? true))
|
||||
keyboard-hide-listener (.addListener rn/keyboard
|
||||
"keyboardDidHide"
|
||||
#(reset! keyboard-shown? false))
|
||||
invalid-seed-phrase? (reagent/atom false)
|
||||
set-invalid-seed-phrase #(reset! invalid-seed-phrase? true)
|
||||
seed-phrase (reagent/atom "")
|
||||
on-change-seed-phrase (fn [new-phrase]
|
||||
(when @invalid-seed-phrase?
|
||||
(reset! invalid-seed-phrase? false))
|
||||
(reset! seed-phrase new-phrase))
|
||||
on-submit (fn []
|
||||
(swap! seed-phrase clean-seed-phrase)
|
||||
(rf/dispatch [:onboarding-2/seed-phrase-entered
|
||||
(security/mask-data @seed-phrase)
|
||||
set-invalid-seed-phrase]))]
|
||||
(let [words-coll (mnemonic/passphrase->words @seed-phrase)
|
||||
last-word (peek words-coll)
|
||||
pick-suggested-word (fn [pressed-word]
|
||||
(swap! seed-phrase str (subs pressed-word (count last-word)) " "))
|
||||
;; Last word doesn't exist in dictionary while being written by the user, so
|
||||
;; it's validated checking whether is a substring of a dictionary word or not.
|
||||
last-partial-word-valid? (partial-word-in-dictionary? last-word)
|
||||
last-word-valid? (word-in-dictionary? last-word)
|
||||
butlast-words-valid? (every? word-in-dictionary? (butlast words-coll))
|
||||
all-words-valid? (and butlast-words-valid? last-word-valid?)
|
||||
word-count (mnemonic/words-count @seed-phrase)
|
||||
words-exceeded? (> word-count max-seed-phrase-length)
|
||||
error-in-words? (or (not last-partial-word-valid?)
|
||||
(not butlast-words-valid?))
|
||||
upper-case? (boolean (re-find #"[A-Z]" @seed-phrase))
|
||||
suggestions-state (cond
|
||||
(or error-in-words?
|
||||
words-exceeded?
|
||||
@invalid-seed-phrase?) :error
|
||||
(string/blank? @seed-phrase) :info
|
||||
(string/ends-with? @seed-phrase " ") :empty
|
||||
:else :words)
|
||||
suggestions-text (cond
|
||||
upper-case? (i18n/label :t/seed-phrase-words-uppercase)
|
||||
words-exceeded? (i18n/label :t/seed-phrase-words-exceeded)
|
||||
error-in-words? (i18n/label :t/seed-phrase-error)
|
||||
@invalid-seed-phrase? (i18n/label :t/seed-phrase-invalid)
|
||||
:else (i18n/label :t/seed-phrase-info))]
|
||||
[:<>
|
||||
[recovery-form
|
||||
{:seed-phrase @seed-phrase
|
||||
:error-state? (= suggestions-state :error)
|
||||
:all-words-valid? all-words-valid?
|
||||
:on-change-seed-phrase on-change-seed-phrase
|
||||
:word-count word-count
|
||||
:on-submit on-submit
|
||||
:keyboard-shown? @keyboard-shown?}]
|
||||
(when @keyboard-shown?
|
||||
[rn/view {:style style/keyboard-container}
|
||||
[quo/predictive-keyboard
|
||||
{:type suggestions-state
|
||||
:blur? true
|
||||
:text suggestions-text
|
||||
:words (keyboard-suggestions last-word)
|
||||
:on-press pick-suggested-word}]])])
|
||||
(finally
|
||||
(.remove keyboard-show-listener)
|
||||
(.remove keyboard-hide-listener))))
|
||||
|
||||
(defn enter-seed-phrase
|
||||
[]
|
||||
(let [{:keys [top]} (safe-area/get-insets)]
|
||||
[rn/view {:style {:flex 1}}
|
||||
(let [{navigation-bar-top :top} (safe-area/get-insets)]
|
||||
[rn/view {:style style/full-layout}
|
||||
[background/view true]
|
||||
[page {:navigation-bar-top top}]]))
|
||||
[rn/keyboard-avoiding-view {:style style/page-container}
|
||||
[navigation-bar/navigation-bar {:top navigation-bar-top}]
|
||||
[screen]]]))
|
||||
|
||||
@@ -1,60 +1,23 @@
|
||||
(ns status-im2.contexts.onboarding.intro.style
|
||||
(:require
|
||||
[react-native.platform :as platform]
|
||||
[quo2.foundations.colors :as colors]))
|
||||
|
||||
(def progress-bar-container
|
||||
{:background-color :transparent
|
||||
:flex-direction :row
|
||||
:margin-vertical 16})
|
||||
|
||||
(defn progress-bar-item
|
||||
[index position end?]
|
||||
{:height 2
|
||||
:flex 1
|
||||
:border-width 1
|
||||
:border-color (if (= index position) colors/white colors/white-opa-10)
|
||||
:margin-right (if end? 0 8)})
|
||||
|
||||
(def carousel
|
||||
{:height 92
|
||||
;; (padding-top) This insets issue needs a consistent implementation across all screens.
|
||||
:padding-top (if platform/android? 0 44)
|
||||
:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0
|
||||
:z-index 2
|
||||
:background-color :transparent
|
||||
:padding-vertical 12
|
||||
:padding-horizontal 20})
|
||||
|
||||
(def carousel-text
|
||||
{:background-color :transparent
|
||||
:color colors/white})
|
||||
|
||||
(def page-container
|
||||
{:flex 1
|
||||
:justify-content :flex-end})
|
||||
|
||||
(def page-image
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0
|
||||
:width "100%"
|
||||
:aspect-ratio 1})
|
||||
|
||||
(def text-container
|
||||
{:flex 1
|
||||
:max-width 180
|
||||
:flex-wrap :wrap})
|
||||
|
||||
(def plain-text
|
||||
{:flex 1
|
||||
:color (colors/alpha colors/white 0.7)})
|
||||
{:size :paragraph-2
|
||||
:weight :regular
|
||||
:color colors/white-opa-70})
|
||||
|
||||
(def highlighted-text
|
||||
{:flex 1
|
||||
:color colors/white})
|
||||
{:flex 1
|
||||
:size :paragraph-2
|
||||
:weight :regular
|
||||
:color colors/white})
|
||||
|
||||
@@ -21,17 +21,15 @@
|
||||
(rf/dispatch [:hide-terms-of-services-opt-in-screen]))
|
||||
:heading (i18n/label :t/new-to-status)
|
||||
:accessibility-label :new-to-status-button}}
|
||||
(i18n/label :t/you-already-use-status)
|
||||
[quo/text
|
||||
{:style style/plain-text}
|
||||
(i18n/label :t/you-already-use-status)]
|
||||
[quo/text
|
||||
{:style style/text-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:style style/plain-text
|
||||
:weight :semi-bold}
|
||||
{:style style/plain-text}
|
||||
(i18n/label :t/by-continuing-you-accept)]
|
||||
[quo/text
|
||||
{:on-press #(rf/dispatch [:open-modal :privacy-policy])
|
||||
:size :paragraph-2
|
||||
:style style/highlighted-text
|
||||
:weight :semi-bold}
|
||||
:style style/highlighted-text}
|
||||
(i18n/label :t/terms-of-service)]]]])
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [pairing-status (rf/sub [:pairing/pairing-in-progress])
|
||||
(let [pairing-status (rf/sub [:pairing/pairing-status])
|
||||
profile-color (:color (rf/sub [:onboarding-2/profile]))]
|
||||
[rn/view {:style style/page-container}
|
||||
[background/view true]
|
||||
|
||||