2022-11-08 18:30:17 +01:00
|
|
|
(ns react-native.core
|
2023-01-16 17:20:10 +01:00
|
|
|
(:require ["react" :as react]
|
2022-11-08 18:30:17 +01:00
|
|
|
["react-native" :as react-native]
|
2022-12-20 21:52:28 +08:00
|
|
|
[cljs-bean.core :as bean]
|
|
|
|
[oops.core :as oops]
|
2022-11-21 16:03:49 +04:00
|
|
|
[react-native.flat-list :as flat-list]
|
2022-12-20 22:45:37 +08:00
|
|
|
[react-native.platform :as platform]
|
2022-12-05 14:22:06 +01:00
|
|
|
[react-native.section-list :as section-list]
|
2022-12-20 22:45:37 +08:00
|
|
|
[reagent.core :as reagent]))
|
2022-11-08 18:30:17 +01:00
|
|
|
|
2022-11-14 19:16:55 +01:00
|
|
|
(def app-state ^js (.-AppState ^js react-native))
|
|
|
|
|
Allow users to swipe to delete or swipe to toggle unread notification status (#15106)
Adds support for swiping left/right on some types of notifications. Swiping left
(from left to right) shows a blue button allowing the user to mark the
notification as read/unread. Swiping right (from right to left) shows a red
button, allowing the user to delete the notification for good.
Related PR in status-go https://github.com/status-im/status-go/pull/3201.
Fixes https://github.com/status-im/status-mobile/issues/14901
Fixes https://github.com/status-im/status-mobile/issues/14900
Technical notes
===============
How's the performance? It feels near native performance in a production release
in a mid-range smartphone. So I'd say it's pretty good, but let me know if you
find any issue.
- I refrained from trying to eliminate all code duplication in this PR. Some
notifications will behave differently, especially the ones with call to
action, so I ask you to please take that in consideration when reviewing. See
https://github.com/status-im/status-mobile/issues/15118
- React Native Gesture Handler has a component named
[Swipeable](https://docs.swmansion.com/react-native-gesture-handler/docs/api/components/swipeable/).
I used it instead of writing a monstrosity :japanese_ogre: of code in
Reanimated to achieve the same results.
- RN Gesture Handler touchables are the only ones that work with the Swipeable
component, so I used them and added vars to `react-native.gesture`.
- I had to manually interpolate the translation X of the buttons behind
notifications because notifications are transparent. To make interpolation
work with `Swipeable` it's mandatory to use RN `Animated.View` and not
`Reanimated.View` (see next point).
- `Swipeable` expects us to pass functions that will receive RN
`AnimatedInterpolation` instances and the rendering lifecycle does not work as
usual. Hooks didn't trigger as expected, functional Reagent components didn't
behave as expected, etc. This means `Reanimated.View` and its interpolation
function is out of question. I did try for almost two days, nothing works.
Testing notes
=============
These are some of the manual tests I ran. There are more scenarios to cover
obviously. Assuming no unread notifications before each flow:
Contact request notification
============================
From the perspective of an user A:
1. Receive a contact request from a non-mutual contact B.
2. Verify the unread count is displayed over the bell icon.
3. Verify the unread count is displayed on the `Messages > Contacts` tab, as
well as on the AC `Contact requests` tab.
4. Open the AC and before accepting/declining the contact request, check that
you CAN'T swipe left or right.
5. Accept or decline the contact request.
6. Check the unread indicator disappears in all necessary places.
7. Press on the notification and see if you're redirected to the chat.
8. Go back to the AC and swipe left to mark as `Unread`. Notice that opening the
chat marks the notification as `Read`. Also very important, notice that the
`Messages > Contacts` tab will NOT show the *pending contact requests*
section at the top. This is on purpose, given the notification is unread, but
the user has already accepted/declined the contact request, hence it's not
pending.
9. Swipe left againg to mark as `Read`. Check all unread indicators are updated.
10. Swipe right to delete the notification (it won't be displayed ever again).
Admin notification
==================
1. Generate an admin notification, e.g. a community owner receiving a request
notification to join.
2. Verify the unread count is displayed over the bell icon, as well as the AC
Admin tab.
3. Verify the community unread indicator is correctly displayed.
4. As an admin, open the AC and before accepting/declining the request, check
that you CAN'T swipe left or right.
5. Accept or decline the membership request.
6. Check the unread indicator disappears accordingly.
7. Swipe left to mark as `Read`.
8. Swipe left to mark as `Unread`.
9. Swipe right to delete the notification (it won't be displayed ever again).
Mentions & replies
==================
Similar steps outlined for `Admin` notifications, but there's one important
difference. Mention and reply notifications don't require a call to action from
the user, so the user can swipe left/right **without** first having to do
anything on the notification (such as pressing on it). See issue
https://github.com/status-im/status-mobile/issues/15118
What about other types of notifications?
========================================
Swipe gestures for other notification types will be implemented in a separate
PR.
2023-02-24 21:22:31 -03:00
|
|
|
;; Only use this component for exceptional cases, otherwise use Reanimated, e.g.
|
|
|
|
;; when using interpolated values exposed by RN Gesture Handler > Swipeable.
|
|
|
|
(def animated-view (reagent/adapt-react-class react-native/Animated.View))
|
|
|
|
|
2022-11-08 18:30:17 +01:00
|
|
|
(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)))
|
|
|
|
(def text (reagent/adapt-react-class (.-Text ^js react-native)))
|
2022-11-21 16:03:49 +04:00
|
|
|
(def text-input (reagent/adapt-react-class (.-TextInput ^js react-native)))
|
2022-11-08 18:30:17 +01:00
|
|
|
|
|
|
|
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js react-native)))
|
|
|
|
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js react-native)))
|
2022-12-20 22:45:37 +08:00
|
|
|
(def touchable-without-feedback
|
|
|
|
(reagent/adapt-react-class (.-TouchableWithoutFeedback ^js react-native)))
|
2022-11-08 18:30:17 +01:00
|
|
|
|
|
|
|
(def flat-list flat-list/flat-list)
|
|
|
|
|
2022-11-21 16:03:49 +04:00
|
|
|
(def section-list section-list/section-list)
|
|
|
|
|
2022-11-10 09:10:43 +01:00
|
|
|
(def activity-indicator (reagent/adapt-react-class (.-ActivityIndicator ^js react-native)))
|
|
|
|
|
2022-11-08 18:30:17 +01:00
|
|
|
(def modal (reagent/adapt-react-class (.-Modal ^js react-native)))
|
|
|
|
|
|
|
|
(def keyboard ^js (.-Keyboard ^js react-native))
|
|
|
|
|
|
|
|
(def dismiss-keyboard! #(.dismiss keyboard))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn use-window-dimensions
|
|
|
|
[]
|
2022-11-08 18:30:17 +01:00
|
|
|
(let [window ^js (react-native/useWindowDimensions)]
|
|
|
|
{:font-scale (.-fontScale window)
|
|
|
|
:height (.-height window)
|
|
|
|
:scale (.-scale window)
|
2022-11-14 19:16:55 +01:00
|
|
|
:width (.-width window)}))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn hide-splash-screen
|
|
|
|
[]
|
2022-11-14 19:16:55 +01:00
|
|
|
(.hide ^js (-> react-native .-NativeModules .-SplashScreen)))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn alert
|
|
|
|
[title message buttons options]
|
2022-11-14 19:16:55 +01:00
|
|
|
(.alert (.-Alert ^js react-native) title message (clj->js buttons) (clj->js options)))
|
|
|
|
|
|
|
|
(def appearance ^js (.-Appearance ^js react-native))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn get-color-scheme
|
|
|
|
[]
|
2022-11-14 19:16:55 +01:00
|
|
|
(.getColorScheme appearance))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn appearance-add-change-listener
|
|
|
|
[handler]
|
2022-11-14 19:16:55 +01:00
|
|
|
(.addChangeListener appearance handler))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn get-window
|
|
|
|
[]
|
2022-11-14 19:16:55 +01:00
|
|
|
(js->clj (.get (.-Dimensions ^js react-native) "window") :keywordize-keys true))
|
2022-11-16 12:00:16 +01:00
|
|
|
|
|
|
|
(def status-bar (.-StatusBar ^js react-native))
|
2022-12-05 14:22:06 +01:00
|
|
|
|
2022-12-28 15:23:58 +01:00
|
|
|
(def style-sheet (.-StyleSheet ^js react-native))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn status-bar-height
|
|
|
|
[]
|
2022-12-19 14:51:53 +05:30
|
|
|
(.-currentHeight ^js status-bar))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn hw-back-add-listener
|
|
|
|
[callback]
|
2022-12-05 14:22:06 +01:00
|
|
|
(.addEventListener (.-BackHandler ^js react-native) "hardwareBackPress" callback))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn hw-back-remove-listener
|
|
|
|
[callback]
|
2022-12-05 14:22:06 +01:00
|
|
|
(.removeEventListener (.-BackHandler ^js react-native) "hardwareBackPress" callback))
|
|
|
|
|
|
|
|
(def keyboard-avoiding-view-class (reagent/adapt-react-class (.-KeyboardAvoidingView react-native)))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn keyboard-avoiding-view
|
|
|
|
[props & children]
|
2022-12-05 14:22:06 +01:00
|
|
|
(into [keyboard-avoiding-view-class
|
|
|
|
(merge (when platform/ios? {:behavior :padding})
|
|
|
|
props)]
|
|
|
|
children))
|
2022-12-20 21:52:28 +08:00
|
|
|
|
2023-01-27 16:40:04 +01:00
|
|
|
(def memo react/memo)
|
|
|
|
|
2023-01-16 17:20:10 +01:00
|
|
|
(def create-ref react/createRef)
|
2022-12-20 21:52:28 +08:00
|
|
|
|
|
|
|
(def use-ref react/useRef)
|
2023-01-16 17:20:10 +01:00
|
|
|
|
|
|
|
(defn use-effect
|
|
|
|
([effect-fn]
|
2022-12-23 14:23:48 -03:00
|
|
|
(use-effect effect-fn []))
|
|
|
|
([effect-fn deps]
|
2023-01-16 17:20:10 +01:00
|
|
|
(react/useEffect
|
|
|
|
#(let [ret (effect-fn)]
|
2022-12-23 14:23:48 -03:00
|
|
|
(if (fn? ret) ret js/undefined))
|
|
|
|
(bean/->js deps))))
|
2023-01-16 17:20:10 +01:00
|
|
|
|
2023-02-02 15:03:59 +04:00
|
|
|
(def use-callback react/useCallback)
|
|
|
|
|
2023-01-16 17:20:10 +01:00
|
|
|
(defn use-effect-once
|
|
|
|
[effect-fn]
|
|
|
|
(use-effect effect-fn))
|
|
|
|
|
2022-12-20 22:45:37 +08:00
|
|
|
(defn use-unmount
|
|
|
|
[f]
|
2022-12-20 21:52:28 +08:00
|
|
|
(let [fn-ref (use-ref f)]
|
|
|
|
(oops/oset! fn-ref "current" f)
|
2023-01-10 10:02:23 +08:00
|
|
|
(use-effect-once (fn [] (fn [] (oops/ocall! fn-ref "current"))))))
|
2023-01-18 12:16:33 +01:00
|
|
|
|
|
|
|
(def layout-animation (.-LayoutAnimation ^js react-native))
|
|
|
|
(def configure-next (.-configureNext ^js layout-animation))
|
|
|
|
|
|
|
|
(def layout-animation-presets
|
|
|
|
{:ease-in-ease-out (-> ^js layout-animation .-Presets .-easeInEaseOut)
|
|
|
|
:linear (-> ^js layout-animation .-Presets .-linear)
|
|
|
|
:spring (-> ^js layout-animation .-Presets .-spring)})
|
2023-01-23 14:41:55 +01:00
|
|
|
|
|
|
|
(def find-node-handle (.-findNodeHandle ^js react-native))
|
|
|
|
|
|
|
|
(defn selectable-text-input-manager
|
|
|
|
[]
|
|
|
|
(when (exists? (.-NativeModules ^js react-native))
|
|
|
|
(.-RNSelectableTextInputManager ^js (.-NativeModules ^js react-native))))
|
|
|
|
|
|
|
|
(defonce selectable-text-input
|
2023-02-17 13:10:00 +01:00
|
|
|
(reagent/adapt-react-class
|
|
|
|
(.requireNativeComponent ^js react-native "RNSelectableTextInput")))
|