diff --git a/Makefile b/Makefile index ed713e1493..9ed252d9e6 100644 --- a/Makefile +++ b/Makefile @@ -285,7 +285,7 @@ endif lint: export TARGET := clojure lint: ##@test Run code style checks - yarn clj-kondo --confg .clj-kondo/config.edn --lint src && \ + yarn clj-kondo --config .clj-kondo/config.edn --cache false --lint src && \ TARGETS=$$(git diff --diff-filter=d --cached --name-only src && echo src) && \ clojure -Scp "$$CLASS_PATH" -m cljfmt.main check --indents indentation.edn $$TARGETS diff --git a/package.json b/package.json index c4ec0a78b6..6e6f460c35 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@babel/preset-env": "7.1.0", "@babel/register": "7.0.0", "@mapbox/node-pre-gyp": "^1.0.9", - "clj-kondo": "^2020.1.13", + "clj-kondo": "^2022.9.8", "jest": "^25.1.0", "nodemon": "^2.0.16", "nyc": "^14.1.1", diff --git a/resources/images/icons/add_user16@2x.png b/resources/images/icons/add_user16@2x.png new file mode 100644 index 0000000000..59097c2790 Binary files /dev/null and b/resources/images/icons/add_user16@2x.png differ diff --git a/resources/images/icons/add_user16@3x.png b/resources/images/icons/add_user16@3x.png new file mode 100644 index 0000000000..fae387cf85 Binary files /dev/null and b/resources/images/icons/add_user16@3x.png differ diff --git a/resources/images/icons/add_user20@2x.png b/resources/images/icons/add_user20@2x.png new file mode 100644 index 0000000000..cb949acc55 Binary files /dev/null and b/resources/images/icons/add_user20@2x.png differ diff --git a/resources/images/icons/add_user20@3x.png b/resources/images/icons/add_user20@3x.png new file mode 100644 index 0000000000..9776e14262 Binary files /dev/null and b/resources/images/icons/add_user20@3x.png differ diff --git a/src/quo2/components/tags/status_tags.cljs b/src/quo2/components/tags/status_tags.cljs index 853b6d815e..8b369b9816 100644 --- a/src/quo2/components/tags/status_tags.cljs +++ b/src/quo2/components/tags/status_tags.cljs @@ -1,10 +1,10 @@ (ns quo2.components.tags.status-tags - (:require [status-im.i18n.i18n :as i18n] - [quo2.foundations.colors :as colors] + (:require [quo.react-native :as rn] [quo.theme :as quo.theme] [quo2.components.icon :as icon] [quo2.components.markdown.text :as text] - [quo.react-native :as rn])) + [quo2.foundations.colors :as colors] + [status-im.i18n.i18n :as i18n])) (def default-container-style {:border-radius 20 @@ -50,42 +50,48 @@ :style {:padding-left 5 :color text-color}} label]]]))) -(defn positive [size theme label] - [base-tag {:size size +(defn- positive + [size theme label] + [base-tag {:size size :background-color colors/success-50-opa-10 - :icon :verified - :border-color colors/success-50-opa-20 - :text-color (if (= theme :light) colors/success-50 - colors/success-60) - :label (or label (i18n/label :positive))}]) + :icon :verified + :border-color colors/success-50-opa-20 + :label (or label (i18n/label :positive)) + :text-color (if (= theme :light) colors/success-50 + colors/success-60)}]) -(defn negative [size theme label] - [base-tag {:size size - :icon :untrustworthy +(defn- negative + [size theme label] + [base-tag {:size size + :icon :untrustworthy :background-color colors/danger-50-opa-10 - :border-color colors/danger-50-opa-20 - :text-color (if (= theme :light) - colors/danger-50 - colors/danger-60) - :label (or label (i18n/label :negative))}]) + :border-color colors/danger-50-opa-20 + :label (or label (i18n/label :negative)) + :text-color (if (= theme :light) + colors/danger-50 + colors/danger-60)}]) -(defn pending [size theme label] - [base-tag {:size size - :icon :pending +(defn- pending + [size theme label] + [base-tag {:size size + :icon :pending + :label (or label (i18n/label :pending)) :background-color (if (= theme :light) colors/neutral-10 colors/neutral-80) - :border-color (if (= theme :light) - colors/neutral-20 - colors/neutral-70) - :text-color colors/neutral-50 - :label (or label (i18n/label :pending))}]) + :border-color (if (= theme :light) + colors/neutral-20 + colors/neutral-70) + :text-color colors/neutral-50}]) -(defn status-tag [_] - (fn [{:keys [status size override-theme label]}] - (let [theme (or override-theme (quo.theme/get-theme))] - [(case status - :positive positive - :negative negative - :pending pending - nil) size theme label]))) +(defn status-tag [{:keys [status size override-theme label]}] + (when status + (when-let [status-component (case (:type status) + :positive positive + :negative negative + :pending pending + nil)] + [status-component + size + (or override-theme (quo.theme/get-theme)) + label]))) diff --git a/src/quo2/screens/tags/status_tags.cljs b/src/quo2/screens/tags/status_tags.cljs index a624d2ea74..5fd2ae54e3 100644 --- a/src/quo2/screens/tags/status_tags.cljs +++ b/src/quo2/screens/tags/status_tags.cljs @@ -6,15 +6,15 @@ [quo2.components.tags.status-tags :as quo2])) (def status-tags-options - {:label "Status" - :key :status - :type :select + {:label "Status" + :key :status + :type :select :options [{:value "Positive" - :key :positive} + :key :positive} {:value "Negative" - :key :negative} + :key :negative} {:value "Pending" - :key :pending}]}) + :key :pending}]}) (def descriptor [status-tags-options {:label "Size" @@ -27,16 +27,20 @@ (defn cool-preview [] (let [state (reagent/atom {:status :positive - :size :small})] + :size :small})] (fn [] - [rn/view {:margin-bottom 50 - :padding 16} - [rn/view {:flex 1} - [preview/customizer state descriptor]] - [rn/view {:padding-vertical 60 - :flex-direction :row - :justify-content :center} - [quo2/status-tag @state]]]))) + (let [props (cond-> @state + (= :positive (:status @state)) (assoc :status {:label "Positive" :type :positive}) + (= :negative (:status @state)) (assoc :status {:label "Negative" :type :negative}) + (= :pending (:status @state)) (assoc :status {:label "Pending" :type :pending}))] + [rn/view {:margin-bottom 50 + :padding 16} + [rn/view {:flex 1} + [preview/customizer state descriptor]] + [rn/view {:padding-vertical 60 + :flex-direction :row + :justify-content :center} + [quo2/status-tag props]]])))) (defn preview-status-tags [] [rn/view {:background-color (colors/theme-colors colors/white diff --git a/src/status_im/ui/screens/activity_center/views.cljs b/src/status_im/ui/screens/activity_center/views.cljs index 5505e58ea1..76f6299025 100644 --- a/src/status_im/ui/screens/activity_center/views.cljs +++ b/src/status_im/ui/screens/activity_center/views.cljs @@ -5,26 +5,88 @@ [quo2.components.tags.context-tags :as context-tags] [quo2.foundations.colors :as colors] [reagent.core :as reagent] + [status-im.constants :as constants] [status-im.i18n.i18n :as i18n] + [status-im.multiaccounts.core :as multiaccounts] [status-im.ui.components.topbar :as topbar] + [status-im.utils.datetime :as datetime] [status-im.utils.handlers :refer [evt]])) +(defn activity-title + [{:keys [type]}] + (case type + constants/activity-center-notification-type-contact-request + (i18n/label :t/contact-request) + + constants/activity-center-notification-type-one-to-one-chat + "Dummy 1:1 chat title" + + "Dummy default title")) + +(defn activity-icon + [{:keys [type]}] + (case type + constants/activity-center-notification-type-contact-request + :add-user + :placeholder)) + +(defn activity-context + [{:keys [message last-message type]}] + (case type + constants/activity-center-notification-type-contact-request + (let [message (or message last-message) + contact (relative (:timestamp notification)) + :title (activity-title notification) + :unread? (not (:read notification))} + (activity-buttons notification))]]) (defn notifications-list [] diff --git a/src/status_im/utils/datetime.cljs b/src/status_im/utils/datetime.cljs index 9aed6d3b36..2fafbe78d5 100644 --- a/src/status_im/utils/datetime.cljs +++ b/src/status_im/utils/datetime.cljs @@ -1,7 +1,7 @@ (ns status-im.utils.datetime (:require [re-frame.core :as re-frame] [cljs-time.core :as t :refer [plus minus days hours before?]] - [cljs-time.coerce :refer [from-long from-date]] + [cljs-time.coerce :refer [from-long]] [cljs-time.format :refer [formatters formatter unparse]] @@ -10,6 +10,8 @@ [clojure.string :as s] [status-im.goog.i18n :as goog.18n])) +;;;; Datetime constants + (defn now [] (t/now)) @@ -29,28 +31,54 @@ (def time-zone-offset (hours (- (/ (.getTimezoneOffset ^js (js/Date.)) 60)))) -;; detects if given locale symbols timeformat generates AM/PM ("a") -(defn- is24Hour-locsym [^js locsym] +;;;; Utilities + +(defn- is24Hour-locsym + "Detects if given locale symbols timeformat generates AM/PM ('a')." + [^js locsym] (not (s/includes? (nth (.-TIMEFORMATS locsym) 2) "a"))) -;; returns is24Hour from device or from given locale symbols -;; whenever we get non-nil value use it, else calculate it from the given locale symbol -(defn- is24Hour [locsym] +(defn- is24Hour + "Returns is24Hour from device or from given locale symbols. Whenever we get + non-nil value use it, else calculate it from the given locale symbol." + [^js locsym] (if-some [fromdev (status/is24Hour)] fromdev (is24Hour-locsym locsym))) -;; time formats -(defn- short-time-format [locsym] (if (is24Hour locsym) "HH:mm" "h:mm a")) -(defn- time-format [locsym] (if (is24Hour locsym) "HH:mm:ss" "h:mm:ss a")) +;;;; Time formats + +(defn- short-time-format + [^js locsym] + (if (is24Hour locsym) + "HH:mm" + "h:mm a")) + +(defn- time-format + [^js locsym] + (if (is24Hour locsym) + "HH:mm:ss" + "h:mm:ss a")) + +;;;; Date formats -;; date formats (defn- short-date-format [_] "dd MMM") -(defn- medium-date-format [^js locsym] (nth (.-DATEFORMATS locsym) 2)) ; get medium format from current locale symbols -;; date-time formats +(defn- datetime-within-one-week-format + [^js locsym] + (if (is24Hour locsym) + "E HH:mm" + "E h:mm a")) + +(defn- medium-date-format + "Get medium format from current locale symbols." + [^js locsym] + (nth (.-DATEFORMATS locsym) 2)) + +;;;; Datetime formats + (defn- medium-date-time-format [locsym] (str (medium-date-format locsym) ", " (time-format locsym))) @@ -61,19 +89,38 @@ (reset! formatter (goog.18n/mk-fmt status-im.i18n.i18n/locale format)))))) -;; generate formatters for different formats -(def date-time-fmt - (get-formatter-fn medium-date-time-format)) -(def date-fmt - (get-formatter-fn medium-date-format)) -(def time-fmt - (get-formatter-fn short-time-format)) -(def short-date-fmt - (get-formatter-fn short-date-format)) +(def date-time-fmt (get-formatter-fn medium-date-time-format)) +(def date-fmt (get-formatter-fn medium-date-format)) +(def time-fmt (get-formatter-fn short-time-format)) +(def short-date-fmt (get-formatter-fn short-date-format)) +(def datetime-within-one-week-fmt (get-formatter-fn datetime-within-one-week-format)) -;; -;; functions which apply formats for the given timestamp -;; +;;;; Utilities + +(defn previous-years? + [datetime] + (< (t/year datetime) (t/year (t/now)))) + +(defn current-year? + [datetime] + (= (t/year datetime) (t/year (t/now)))) + +(defn today? + [datetime] + (let [now (t/now)] + (and (= (t/year now) (t/year datetime)) + (= (t/month now) (t/month datetime)) + (= (t/day now) (t/day datetime))))) + +(defn within-last-n-days? + "Returns true if `datetime` is within last `n` days (inclusive on both ends)." + [datetime n] + (let [now (t/now) + start (t/at-midnight (t/minus now (t/days n))) + end (t/plus now (t/millis 1))] + (t/within? start end datetime))) + +;;;; Timestamp formatters (defn- to-str [ms old-fmt-fn yesterday-fmt-fn today-fmt-fn] (let [date (from-long ms) @@ -97,6 +144,26 @@ #(label :t/datetime-yesterday) #(label :t/datetime-today))) +(defn timestamp->relative [ms] + (let [datetime (from-long ms)] + (cond + (today? datetime) + (.format ^js (time-fmt) datetime) + + (within-last-n-days? datetime 1) + (str (s/capitalize (label :t/datetime-yesterday)) + " " + (.format ^js (time-fmt) datetime)) + + (within-last-n-days? datetime 6) + (.format ^js (datetime-within-one-week-fmt) datetime) + + (current-year? datetime) + (.format ^js (short-date-fmt) datetime) + + (previous-years? datetime) + (.format ^js (date-fmt) datetime)))) + (defn timestamp->mini-date [ms] (.format ^js (short-date-fmt) (-> ms from-long @@ -171,21 +238,5 @@ (fn [coeffects _] (assoc coeffects :now (timestamp)))) -(defn format-date [format date] - (let [local (plus (from-date date) time-zone-offset)] - (unparse (formatter format) local))) - -(defn get-ordinal-date [date] - (let [local (plus (from-date date) time-zone-offset) - day (js/parseInt (unparse (formatter "d") local)) - s {0 "th" - 1 "st" - 2 "nd" - 3 "rd"} - m (mod day 100)] - (str day (or (s (mod (- m 20) 10)) - (s m) - (s 0))))) - (defn to-ms [sec] (* 1000 sec)) diff --git a/src/status_im/utils/datetime_test.cljs b/src/status_im/utils/datetime_test.cljs index ac814f93f7..79eed016a1 100644 --- a/src/status_im/utils/datetime_test.cljs +++ b/src/status_im/utils/datetime_test.cljs @@ -1,8 +1,9 @@ (ns status-im.utils.datetime-test - (:require [cljs.test :refer-macros [deftest is]] - [status-im.utils.datetime :as d] + (:require [cljs-time.coerce :as time-coerce] + [cljs-time.core :as t] + [cljs.test :refer-macros [deftest testing is]] [status-im.goog.i18n :as i18n] - [cljs-time.core :as t])) + [status-im.utils.datetime :as d])) (defn match [name symbols] (is (identical? (.-dateTimeSymbols_ (i18n/mk-fmt name #'status-im.utils.datetime/medium-date-format)) @@ -75,6 +76,93 @@ d/date-fmt (fn [] (i18n/mk-fmt "nb-NO" #'status-im.utils.datetime/medium-date-time-format))] (is (= (d/day-relative epoch) "1. jan. 1970, 00:00:00")))) +(deftest current-year?-test + ;; Today is Monday, 1975-03-10 15:15:45Z + (with-redefs [t/*ms-fn* (constantly 163696545000) + d/time-zone-offset (t/period :hours 0)] + (is (d/current-year? (t/now))) + + (testing "returns false for future years" + (is (not (d/current-year? (t/plus (t/now) (t/years 1)))))) + + (testing "returns true at 1975-01-01 00:00:00" + (is (d/current-year? (time-coerce/from-long 157766400000)))) + + (testing "returns false at 1974-12-31 23:59:59" + (is (not (d/current-year? (time-coerce/from-long 157766399000))))))) + +(deftest previous-years?-test + ;; Today is Monday, 1975-03-10 15:15:45Z + (with-redefs [t/*ms-fn* (constantly 163696545000) + d/time-zone-offset (t/period :hours 0)] + (is (not (d/previous-years? (t/now)))) + + (testing "returns false for future years" + (is (not (d/current-year? (t/plus (t/now) (t/years 1)))))) + + (testing "returns false at 1975-01-01 00:00:00" + (is (not (d/previous-years? (time-coerce/from-long 1640995200000))))) + + (testing "returns true at 1974-12-31 23:59:59" + (is (not (d/previous-years? (time-coerce/from-long 1640995199000))))))) + +(deftest within-last-n-days?-test + ;; Today is Monday, 1975-03-10 15:15:45Z + (let [now 163696545000] + (with-redefs [t/*ms-fn* (constantly now) + d/time-zone-offset (t/period :hours 0)] + (testing "start of the period, 6 days ago (inclusive)" + ;; Tuesday, 1975-03-03 23:59:59Z + (is (not (d/within-last-n-days? (time-coerce/from-long 163123199000) 6))) + + ;; Tuesday, 1975-03-04 00:00:00Z + (is (d/within-last-n-days? (time-coerce/from-long 163123200000) 6)) + + ;; Tuesday, 1975-03-04 00:00:01Z + (is (d/within-last-n-days? (time-coerce/from-long 163123201000) 6))) + + (testing "end of the period (inclusive)" + ;; Monday, 1975-03-10 15:15:44Z + (is (d/within-last-n-days? (time-coerce/from-long 163696544000) 6)) + + ;; Monday, 1975-03-10 15:15:45Z + (is (d/within-last-n-days? (time-coerce/from-long now) 6)) + + ;; Monday, 1975-03-10 15:15:46Z + (is (not (d/within-last-n-days? (time-coerce/from-long 163696546000) 6))))))) + +(deftest timestamp->relative-test + ;; Today is Monday, 1975-03-10 15:15:45Z + (with-redefs [t/*ms-fn* (constantly 163696545000) + d/time-zone-offset (t/period :hours 0) + d/is24Hour (constantly false)] + (testing "formats previous years" + ;; 1974-12-31 23:59:59Z + (is (= "Dec 31, 1974" (d/timestamp->relative 157766399000))) + ;; 1973-01-01 00:00:00Z + (is (= "Jan 1, 1973" (d/timestamp->relative 94694400000)))) + + (testing "formats 7 days ago or older, but in the current year" + (is (= "03 Mar" (d/timestamp->relative 163091745000))) + (is (= "02 Mar" (d/timestamp->relative 163004400000))) + (is (= "01 Jan" (d/timestamp->relative 157820400000)))) + + (testing "formats dates within the last 6 days" + (is (= "Sat 3:15 PM" (d/timestamp->relative 163523745000))) + (is (= "Fri 3:15 PM" (d/timestamp->relative 163437345000))) + (is (= "Thu 3:15 PM" (d/timestamp->relative 163350945000))) + (is (= "Wed 3:15 PM" (d/timestamp->relative 163264545000))) + (is (= "Tue 3:15 PM" (d/timestamp->relative 163178145000)))) + + (testing "formats within yesterday window" + (is (= "Yesterday 3:15 PM" (d/timestamp->relative 163610145000))) + (is (= "Yesterday 11:59 PM" (d/timestamp->relative 163641599000)))) + + (testing "formats today, at various timestamps" + (is (= "3:15 PM" (d/timestamp->relative 163696545000))) + (is (= "12:00 PM" (d/timestamp->relative 163684800000))) + (is (= "12:00 AM" (d/timestamp->relative 163641600000)))))) + #_((deftest day-relative-before-yesterday-force-24H-test (with-redefs [t/*ms-fn* (constantly epoch-plus-3d) d/is24Hour (constantly true) diff --git a/translations/en.json b/translations/en.json index 02b3ef6870..5f80766fc9 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1766,6 +1766,7 @@ "opened" : "Opened", "accepted": "Accepted", "declined": "Declined", + "contact-request-sent": "sent contact request", "contact-request-header": "👋 Contact requests", "contact-request-declined": "Declined ⓧ", "contact-request-accepted": "Accepted ✓", diff --git a/yarn.lock b/yarn.lock index c01ac766af..5eb94e766e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3147,10 +3147,10 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -clj-kondo@^2020.1.13: - version "2020.1.13" - resolved "https://registry.yarnpkg.com/clj-kondo/-/clj-kondo-2020.1.13.tgz#bffe0dde83169f3b7a605f459f835cbf90ef9347" - integrity sha512-hLKi4toY3UFe1WnuX/HGr2e3PWEt4++7286Jiv/nyBoy1zcEopz7k+e0XnjX5GkxFUgXv1KrIyHQ3eM4+iW2dw== +clj-kondo@^2022.9.8: + version "2022.9.8" + resolved "https://registry.yarnpkg.com/clj-kondo/-/clj-kondo-2022.9.8.tgz#50bd2ca712d92876226d2fbe8083adbfd57e1af8" + integrity sha512-YAJivlvKxdGrnE/RhYh+ggHJKzF7wCnKQn9UVrQNrxJAE2wCirWbxjypt4S5FzlTGlzDaIDxLZSuECxI4iuXdA== dependencies: binwrap "^0.2.2" request "^2.88.0"