diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist index ee14d5bb6af..19ddb96668f 100644 --- a/ios/GoogleService-Info.plist +++ b/ios/GoogleService-Info.plist @@ -2,8 +2,12 @@ + CLIENT_ID + 854811651919-30s20e3l0me0ins0vc4185jbnj7ja49o.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.854811651919-30s20e3l0me0ins0vc4185jbnj7ja49o API_KEY - AIzaSyAm5D6giymR_2llh-8SbCRL3oPxX_v3EZs + AIzaSyB_ZCi76uSX1RBqGiLIllUnJ8D6_cKrRGQ GCM_SENDER_ID 854811651919 PLIST_VERSION diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 64db899de29..a5d3982dfc8 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -168,7 +168,24 @@ :fn-invoke-direct true :optimizations :advanced :js-options {:js-provider :closure} - :reader-features #{:mobile}}}} + :reader-features #{:mobile + ;; NOTE(@seanstrom): Here we are allowing for an + ;; environment variable to configure the intended store + ;; where a user could download the app. For example, we + ;; support the FDroid and Google Play stores, so we can + ;; configure the app to build for FDroid by setting the + ;; `ANDROID_STORE` environment variable to `fdroid`. + ;; + ;; This value is used for configuring Shadow-CLJS to + ;; only require namespaces that will be compatible with + ;; some restrictions imposed by the Android stores. For + ;; example, FDroid does not allow for Google Play + ;; services like Firebase, so we use the + ;; reader-conditional of `:fdroid` or `:google` to + ;; clearly avoid importing Firebase dependencies in the + ;; FDroid builds. + #shadow/env ["ANDROID_STORE" :as :keyword :default + :google]}}}} ;; the tests are ran with node, react-native dependencies are mocked ;; by using node --require override.js, which uses the node-library ;; produced by the target :mocks below and redefines node require diff --git a/src/legacy/status_im/events.cljs b/src/legacy/status_im/events.cljs index 154c6f6dc12..3bb65ab17cf 100644 --- a/src/legacy/status_im/events.cljs +++ b/src/legacy/status_im/events.cljs @@ -107,6 +107,7 @@ (let [new-account? (get db :onboarding/new-account?) app-in-background-since (get db :app-in-background-since) signed-up? (get-in db [:profile/profile :signed-up?]) + notifications-settings? (= (:view-id db) :screen/settings.notifications) requires-bio-auth (and signed-up? (= (:auth-method db) "biometric") @@ -121,7 +122,9 @@ #(when-let [chat-id (:current-chat-id db)] {:dispatch [:chat/mark-all-as-read chat-id]}) #(when requires-bio-auth - {:dispatch [:biometric/authenticate {:on-fail on-biometric-auth-fail}]})))) + {:dispatch [:biometric/authenticate {:on-fail on-biometric-auth-fail}]}) + #(when notifications-settings? + {:dispatch [:notifications/check-notifications-blocked]})))) (rf/defn on-going-in-background [{:keys [db now]}] diff --git a/src/react_native/permissions.cljs b/src/react_native/permissions.cljs index 74e0a64deee..f7f03b60790 100644 --- a/src/react_native/permissions.cljs +++ b/src/react_native/permissions.cljs @@ -1,7 +1,7 @@ (ns react-native.permissions (:require ["react-native-permissions" :refer - [check checkNotifications PERMISSIONS requestMultiple + [check checkNotifications openSettings PERMISSIONS requestMultiple requestNotifications RESULTS]] [clojure.string :as string] [promesa.core :as promesa] @@ -95,3 +95,7 @@ [] (-> (checkNotifications) (promesa/then notification-permissions->notification-permission-statuses))) + +(defn open-notification-settings + [] + (openSettings "notifications")) diff --git a/src/status_im/common/kv_storage/effects.cljs b/src/status_im/common/kv_storage/effects.cljs new file mode 100644 index 00000000000..2cded5e1423 --- /dev/null +++ b/src/status_im/common/kv_storage/effects.cljs @@ -0,0 +1,19 @@ +(ns status-im.common.kv-storage.effects + (:require + [react-native.mmkv :as mmkv] + [utils.re-frame :as rf])) + +(rf/reg-fx :effects.kv/set-object + (fn [{:keys [id value]}] + (mmkv/set-object id value))) + +(rf/reg-fx :effects.kv/merge-object + (fn [{key-id :key + :keys [value]}] + (let [kv-object (mmkv/get-object key-id {})] + (mmkv/set-object key-id + (merge kv-object value))))) + +(rf/reg-fx :effects.kv/delete-key + (fn [key-id] + (mmkv/delete-key key-id))) diff --git a/src/status_im/contexts/onboarding/enable_biometrics/view.cljs b/src/status_im/contexts/onboarding/enable_biometrics/view.cljs index cf25fa01d2e..a27943b7c87 100644 --- a/src/status_im/contexts/onboarding/enable_biometrics/view.cljs +++ b/src/status_im/contexts/onboarding/enable_biometrics/view.cljs @@ -22,9 +22,10 @@ [insets] (let [supported-biometric-type (rf/sub [:biometrics/supported-type]) bio-type-label (biometric/get-label-by-type supported-biometric-type) - profile-color (or (:color (rf/sub [:onboarding/profile])) + onboarding-profile (rf/sub [:onboarding/profile]) + profile-color (or (:color onboarding-profile) (rf/sub [:profile/customization-color])) - syncing? (= (rf/sub [:view-id]) :screen/onboarding.syncing-biometric) + syncing? (:syncing? onboarding-profile) biometric-type (rf/sub [:biometrics/supported-type])] [rn/view {:style (style/buttons insets)} [quo/button @@ -32,15 +33,17 @@ :accessibility-label :enable-biometrics-button :icon-left (biometric/get-icon-by-type biometric-type) :customization-color profile-color - :on-press #(rf/dispatch [:onboarding/enable-biometrics])} + :on-press #(rf/dispatch [:onboarding/biometrics-setup-start + {:enable-biometrics? true + :syncing? syncing?}])} (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})] [quo/button {:accessibility-label :maybe-later-button :background :blur :type :grey - :on-press #(rf/dispatch (if syncing? - [:onboarding/finish-onboarding false] - [:onboarding/create-account-and-login])) + :on-press #(rf/dispatch [:onboarding/biometrics-setup-start + {:enable-biometrics? false + :syncing? syncing?}]) :container-style {:margin-top 12}} (i18n/label :t/maybe-later)]])) diff --git a/src/status_im/contexts/onboarding/enable_notifications/style.cljs b/src/status_im/contexts/onboarding/enable_notifications/style.cljs index 1c22b88e9d7..d8a3e13c030 100644 --- a/src/status_im/contexts/onboarding/enable_notifications/style.cljs +++ b/src/status_im/contexts/onboarding/enable_notifications/style.cljs @@ -24,3 +24,13 @@ [insets] {:margin default-margin :margin-bottom (+ 14 (:bottom insets))}) + +(def news-notifications-checkbox-container + {:flex-direction :row + :gap 8 + :padding-top 8 + :padding-bottom 12 + :padding-horizontal 20}) + +(def news-notifications-checkbox-text + {:flex 1}) diff --git a/src/status_im/contexts/onboarding/enable_notifications/view.cljs b/src/status_im/contexts/onboarding/enable_notifications/view.cljs index 2c72cdd1d94..c8be269397b 100644 --- a/src/status_im/contexts/onboarding/enable_notifications/view.cljs +++ b/src/status_im/contexts/onboarding/enable_notifications/view.cljs @@ -1,10 +1,14 @@ (ns status-im.contexts.onboarding.enable-notifications.view (:require + [quo.context] [quo.core :as quo] [react-native.core :as rn] + [react-native.platform :as platform] [react-native.safe-area :as safe-area] [status-im.common.resources :as resources] + [status-im.contexts.onboarding.common.background.view :as background] [status-im.contexts.onboarding.enable-notifications.style :as style] + [status-im.feature-flags :as ff] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -17,38 +21,82 @@ :description (i18n/label :t/enable-notifications-sub-title) :description-accessibility-label :notifications-sub-title}]) -(defn enable-notification-buttons - [{:keys [insets]}] - (let [profile-color (rf/sub [:onboarding/customization-color]) - ask-permission (fn [] - (rf/dispatch - [:request-notifications - {:on-allowed (fn [] - (js/setTimeout - #(rf/dispatch [:onboarding/finish-onboarding true]) - 300)) - :on-denied (fn [] - (js/setTimeout - #(rf/dispatch [:onboarding/finish-onboarding false]) - 300))}])) - skip-permission #(rf/dispatch [:onboarding/finish-onboarding false])] - [rn/view {:style (style/buttons insets)} - [quo/button - {:on-press ask-permission - :type :primary - :icon-left :i/notifications - :accessibility-label :enable-notifications-button - :customization-color profile-color} - (i18n/label :t/intro-wizard-title6)] - [quo/button - {:on-press skip-permission - :accessibility-label :enable-notifications-later-button - :type :grey - :background :blur - :container-style {:margin-top 12}} - (i18n/label :t/maybe-later)]])) +(defn on-notifications-setup-start + [params] + (rf/dispatch [:onboarding/notifications-setup-start params])) -(defn enable-notifications-simple +(defn notifications-info-view + [{:keys [blur?]}] + [quo/documentation-drawers + {:title (i18n/label :t/enable-notifications) + :show-button? true + :shell? blur? + :button-label (i18n/label :t/read-more) + :button-icon :i/info} + [quo/text (i18n/label :t/enable-notifications-info-description)]]) + +(defn on-open-info + [{:keys [blur? theme] + :or {blur? true}}] + (rf/dispatch [:show-bottom-sheet + {:content (fn [] + [notifications-info-view {:blur? blur?}]) + :theme theme + :shell? blur?}])) + +(defn enable-notification-form + [{:keys [insets params]}] + (let [profile-color (rf/sub [:onboarding/customization-color + {:onboarding? (:onboarding? params)}]) + [third-party-checked? + set-third-party-checked] (rn/use-state + (boolean? (ff/enabled? ::ff/settings.news-notifications))) + on-enable-notifications (rn/use-callback + (fn [] + (on-notifications-setup-start + (assoc params + :enable-notifications? true + :enable-news-notifications? third-party-checked?))) + [params third-party-checked?]) + on-skip-notifications (rn/use-callback + (fn [] + (on-notifications-setup-start + (assoc params + :enable-notifications? false + :enable-news-notifications? false))) + [params])] + [rn/view + (when (and platform/android? + (ff/enabled? ::ff/settings.news-notifications)) + [rn/view + {:style style/news-notifications-checkbox-container} + [quo/selectors + {:type :checkbox + :blur? true + :customization-color profile-color + :checked? third-party-checked? + :on-change set-third-party-checked}] + [quo/text + {:size :paragraph-2 + :style style/news-notifications-checkbox-text} + (i18n/label :t/enable-news-notifications-third-party)]]) + [rn/view {:style (style/buttons insets)} + [quo/button + {:on-press on-enable-notifications + :type :primary + :icon-left :i/notifications + :accessibility-label :enable-notifications-button + :customization-color profile-color} + (i18n/label :t/intro-wizard-title6)] + [quo/button + {:on-press on-skip-notifications + :accessibility-label :enable-notifications-later-button + :type :grey + :background :blur + :container-style {:margin-top 12}} + (i18n/label :t/maybe-later)]]])) + +(defn enable-notifications-illustration [] (let [width (:width (rn/get-window))] [rn/image @@ -56,12 +104,28 @@ :style (style/page-illustration width) :source (resources/get-image :notifications)}])) +(defn background-image + [] + [rn/view {:style rn/stylesheet-absolute-fill} + [background/view true]]) + (defn view [] - (let [insets safe-area/insets] - [rn/view {:style (style/page-container insets)} - [rn/view {:style style/page-heading} - [quo/page-nav {:type :no-title :background :blur}] - [page-title]] - [enable-notifications-simple] - [enable-notification-buttons {:insets insets}]])) + (let [insets safe-area/insets + params (quo.context/use-screen-params)] + [:<> + (when-not (:onboarding? params) + [background-image]) + [rn/view {:style (style/page-container insets)} + [rn/view {:style style/page-heading} + [quo/page-nav + {:type :no-title + :background :blur + :right-side [{:icon-name :i/info + :on-press on-open-info + :accessibility-label :notifications-info-button}]}] + [page-title]] + [enable-notifications-illustration] + [enable-notification-form + {:insets insets + :params params}]]])) diff --git a/src/status_im/contexts/onboarding/events.cljs b/src/status_im/contexts/onboarding/events.cljs index caec8d48b8c..5450b18cc90 100644 --- a/src/status_im/contexts/onboarding/events.cljs +++ b/src/status_im/contexts/onboarding/events.cljs @@ -3,30 +3,164 @@ [quo.foundations.colors :as colors] status-im.common.biometric.events [status-im.constants :as constants] + [status-im.contexts.onboarding.interceptors :as onboarding.interceptors] [status-im.contexts.shell.constants :as shell.constants] + [status-im.feature-flags :as ff] [taoensso.timbre :as log] [utils.i18n :as i18n] [utils.re-frame :as rf] [utils.security.core :as security])) +(defn notifications-setup-start + [{:keys [db]} + [{:keys [enable-notifications? + enable-news-notifications? + biometrics? + onboarding? + syncing?]}]] + {:db (if-not onboarding? + db + (update-in db + [:onboarding/profile] + assoc + :enable-notifications? enable-notifications? + :enable-news-notifications? enable-news-notifications?)) + :fx (cond-> [] + (and (not onboarding?) enable-notifications? enable-news-notifications?) + (conj [:dispatch [:notifications/news-notifications-switch true]]) + + (and (not onboarding?) enable-notifications?) + (conj [:dispatch [:push-notifications/switch true]]) + + :always + (conj [:dispatch + [:onboarding/notifications-setup-done + {:onboarding? onboarding? + :syncing? syncing? + :biometrics? biometrics?}]]))}) + +(rf/reg-event-fx :onboarding/notifications-setup-start notifications-setup-start) + +(defn notifications-setup-done + [{:keys [_db]} [{:keys [biometrics? onboarding? syncing?]}]] + {:fx (cond-> [] + (and onboarding? syncing?) + (conj [:dispatch [:onboarding/finalize-setup]]) + + (and onboarding? syncing? (not biometrics?)) + (conj [:dispatch [:onboarding/finish-onboarding]]) + + (and onboarding? (not syncing?)) + (conj [:dispatch [:onboarding/create-account-and-login]]) + + (and (not onboarding?) (not syncing?)) + (conj [:dispatch [:shell/show-root-view]]))}) + + +(rf/reg-event-fx :onboarding/notifications-setup-done notifications-setup-done) + +(defn notifications-setup + [{:keys [db]} + [{:keys [biometrics-supported? biometrics? syncing? onboarding?]}]] + (let [db-view-id (get-in db [:view-id]) + current-view-id (if (= db-view-id :screen/onboarding.syncing-biometric) + :screen/onboarding.enable-biometrics + db-view-id)] + {:db (assoc-in db [:onboarding/profile :notifications-prompted?] true) + :fx [[:dispatch + (if (ff/enabled? ::ff/settings.news-notifications) + [:navigate-to-within-stack + [:screen/onboarding.enable-notifications current-view-id] + {:syncing? syncing? + :onboarding? onboarding? + :biometrics? (and biometrics-supported? biometrics?)}] + [:onboarding/notifications-setup-done + {:onboarding? onboarding? + :syncing? syncing? + :biometrics? (and biometrics-supported? biometrics?)}])]]})) + +(rf/reg-event-fx :onboarding/notifications-setup notifications-setup) + +(defn biometrics-setup-start + [_ [{:keys [enable-biometrics? syncing?]}]] + {:fx (cond-> [] + enable-biometrics? + (conj [:dispatch + [:onboarding/enable-biometrics + {:on-done [:onboarding/biometrics-setup-done + {:on-success [:onboarding/notifications-setup + {:biometrics? true + :biometrics-supported? true + :onboarding? true + :syncing? syncing?}] + :on-fail [:onboarding/biometrics-fail]}]}]]) + + (not enable-biometrics?) + (conj [:dispatch + [:onboarding/notifications-setup + {:biometrics? false + :biometrics-supported? true + :onboarding? true + :syncing? syncing?}]]))}) + +(rf/reg-event-fx :onboarding/biometrics-setup-start biometrics-setup-start) + +(defn biometrics-setup-done + [{:keys [db]} [{:keys [on-success on-fail]} {:keys [error]}]] + {:db (assoc-in db [:onboarding/profile :auth-method] constants/auth-method-biometric) + :fx [(if (some? error) + [:dispatch (conj on-fail error)] + [:dispatch on-success])]}) + +(rf/reg-event-fx :onboarding/biometrics-setup-done biometrics-setup-done) + +(rf/reg-event-fx + :onboarding/biometrics-fail + (fn [_ [error]] + {:dispatch [:biometric/show-message (ex-cause error)]})) + +(rf/reg-event-fx :onboarding/enable-biometrics + (fn [_ [{:keys [on-done]}]] + {:fx [[:dispatch + [:biometric/authenticate + {:on-success #(rf/dispatch on-done) + :on-fail #(rf/dispatch (conj on-done %))}]]]})) + +(rf/reg-event-fx :shell/show-root-view + [onboarding.interceptors/local-profile-storage-interceptor] + (fn [{:keys [db local-profile-storage]} [{:keys [notifications-prompt-skip?]}]] + (let [{:keys [key-uid]} (get-in db [:profile/profile]) + onboarding-profile (get-in db [:onboarding/profile])] + (if (ff/enabled? ::ff/settings.news-notifications) + (if (or (:notifications-prompted? local-profile-storage) + (:notifications-prompted? onboarding-profile) + notifications-prompt-skip?) + {:fx (cond-> [] + (not (:notifications-prompted? local-profile-storage)) + (conj [:dispatch + [:profile/save-notifications-prompted + {:key-uid key-uid}]]) + :else + (conj [:dispatch [:update-theme-and-init-root :screen/shell-stack]] + [:dispatch [:profile/toggle-testnet-mode-banner]]))} + {:fx [[:dispatch + [:profile/save-notifications-prompted + {:key-uid key-uid}]] + [:dispatch + [:onboarding/notifications-setup + {:biometrics? false + :syncing? false + :onboarding? false}]]]}) + {:fx [[:dispatch [:update-theme-and-init-root :screen/shell-stack]] + [:dispatch [:profile/toggle-testnet-mode-banner]]]})))) + (rf/reg-event-fx :onboarding/finish-onboarding - (fn [_ [notifications-enabled?]] - {:fx [(when notifications-enabled? - [:dispatch [:push-notifications/switch {:notifications-enabled true}]]) - [:dispatch [:shell/change-tab shell.constants/default-selected-stack]] - [:dispatch [:update-theme-and-init-root :screen/shell-stack]] - [:dispatch [:profile/toggle-testnet-mode-banner]] + (fn [_ []] + {:fx [[:dispatch [:shell/change-tab shell.constants/default-selected-stack]] + [:dispatch [:shell/show-root-view]] [:dispatch [:universal-links/process-stored-event]]]})) -(rf/defn enable-biometrics - {:events [:onboarding/enable-biometrics]} - [_] - {:fx [[:dispatch - [:biometric/authenticate - {:on-success #(rf/dispatch [:onboarding/biometrics-done]) - :on-fail #(rf/dispatch [:onboarding/biometrics-fail %])}]]]}) - (rf/reg-event-fx :onboarding/navigate-to-sign-in-by-seed-phrase (fn [{:keys [db]} [from-screen]] {:db (assoc db :onboarding/navigated-to-enter-seed-phrase-from-screen from-screen) @@ -48,20 +182,6 @@ :onboarding/navigated-to-enter-seed-phrase-from-screen :screen/onboarding.create-profile)]]})) -(rf/defn biometrics-done - {:events [:onboarding/biometrics-done]} - [{:keys [db]}] - (let [syncing? (get-in db [:onboarding/profile :syncing?])] - {:db (assoc-in db [:onboarding/profile :auth-method] constants/auth-method-biometric) - :dispatch (if syncing? - [:onboarding/finalize-setup] - [:onboarding/create-account-and-login])})) - -(rf/reg-event-fx - :onboarding/biometrics-fail - (fn [_ [error]] - {:dispatch [:biometric/show-message (ex-cause error)]})) - (rf/reg-event-fx :onboarding/create-account-and-login (fn [{:keys [db]}] (let [{:keys [seed-phrase] @@ -89,15 +209,21 @@ {:events [:onboarding/on-delete-profile-success]} [{:keys [db]} key-uid] (let [multiaccounts (dissoc (:profile/profiles-overview db) key-uid)] - (merge - {:db (assoc db :profile/profiles-overview multiaccounts)} - (when-not (seq multiaccounts) - {:dispatch [:update-theme-and-init-root :screen/onboarding.intro]})))) + {:db (assoc db :profile/profiles-overview multiaccounts) + :fx (cond-> [] + (ff/enabled? ::ff/settings.news-notifications) + (conj [:dispatch + [:profile/remove-local-profile-storage + {:key-uid key-uid}]]) + (not (seq multiaccounts)) + (conj [:dispatch + [:update-theme-and-init-root :screen/onboarding.intro]]))})) (rf/reg-event-fx :onboarding/password-set (fn [{:keys [db]} [masked-password]] (let [biometric-supported-type (get-in db [:biometrics :supported-type]) + syncing? (get-in db [:onboarding/profile :syncing?]) from-screen (get db :onboarding/navigated-to-enter-seed-phrase-from-screen :screen/onboarding.create-profile)] @@ -105,8 +231,17 @@ (assoc-in [:onboarding/profile :password] masked-password) (assoc-in [:onboarding/profile :auth-method] constants/auth-method-password)) :fx [[:dispatch - (if biometric-supported-type + (cond + biometric-supported-type [:navigate-to-within-stack [:screen/onboarding.enable-biometrics from-screen]] + + (ff/enabled? ::ff/settings.news-notifications) + [:onboarding/notifications-setup + {:onboarding? true + :biometrics? false + :syncing? syncing?}] + + :else [:onboarding/create-account-and-login])]]}))) (rf/reg-event-fx @@ -165,23 +300,36 @@ :onboarding/finalize-setup (fn [{db :db}] (let [{:keys [password syncing? auth-method - temporary-display-name?]} (:onboarding/profile db) - {:keys [key-uid] :as profile} (:profile/profile db) - biometric-enabled? (= auth-method constants/auth-method-biometric)] + temporary-display-name? + enable-news-notifications? + enable-notifications?]} (:onboarding/profile db) + {:keys [key-uid] :as profile} (:profile/profile db) + biometric-enabled? (= auth-method constants/auth-method-biometric)] {:db (assoc db :onboarding/generated-keys? true) - :fx [(when temporary-display-name? - [:dispatch [:profile/set-default-profile-name profile]]) - (when biometric-enabled? - [:keychain/save-password-and-auth-method - {:key-uid key-uid - :masked-password (if syncing? - password - (security/hash-masked-password password)) - :on-success (fn [] - (rf/dispatch [:onboarding/set-auth-method auth-method]) - (when syncing? - (rf/dispatch - [:onboarding/finish-onboarding false]))) - :on-error #(log/error "failed to save biometrics" - {:key-uid key-uid - :error %})}])]}))) + :fx (cond-> [] + temporary-display-name? + (conj [:dispatch [:profile/set-default-profile-name profile]]) + + biometric-enabled? + (conj [:keychain/save-password-and-auth-method + {:key-uid key-uid + :masked-password (if syncing? + password + (security/hash-masked-password password)) + :on-success (fn [] + (rf/dispatch [:onboarding/set-auth-method auth-method]) + (when syncing? + (rf/dispatch + [:onboarding/finish-onboarding]))) + :on-error #(log/error "failed to save biometrics" + {:key-uid key-uid + :error %})}]) + + (and enable-notifications? + enable-news-notifications?) + (conj [:dispatch + [:notifications/news-notifications-switch true]]) + + enable-notifications? + (conj [:dispatch + [:push-notifications/switch true]]))}))) diff --git a/src/status_im/contexts/onboarding/interceptors.cljs b/src/status_im/contexts/onboarding/interceptors.cljs new file mode 100644 index 00000000000..0fd418d284e --- /dev/null +++ b/src/status_im/contexts/onboarding/interceptors.cljs @@ -0,0 +1,18 @@ +(ns status-im.contexts.onboarding.interceptors + (:require + [re-frame.interceptor :as interceptor] + [react-native.mmkv :as mmkv])) + +(defn inject-local-profile-storage + [context] + (let [db (interceptor/get-coeffect context :db) + key-uid (get-in db [:profile/profile :key-uid]) + local-profile (mmkv/get-object key-uid)] + (assoc-in context + [:coeffects :local-profile-storage] + local-profile))) + +(def local-profile-storage-interceptor + (interceptor/->interceptor + :id :local-profile-storage-interceptor + :before inject-local-profile-storage)) diff --git a/src/status_im/contexts/profile/events.cljs b/src/status_im/contexts/profile/events.cljs index 35948d36ff4..f643dab058d 100644 --- a/src/status_im/contexts/profile/events.cljs +++ b/src/status_im/contexts/profile/events.cljs @@ -129,3 +129,13 @@ (fn [] {:fx [[:effects.profile/accept-terms {:on-success [:navigate-to :screen/profile.profiles]}]]})) + +(rf/reg-event-fx :profile/save-notifications-prompted + (fn [{:keys [_db]} [{:keys [key-uid]}]] + {:fx [[:effects.kv/merge-object + {:key key-uid + :value {:notifications-prompted? true}}]]})) + +(rf/reg-event-fx :profile/remove-local-profile-storage + (fn [{:keys [_db]} [{:keys [key-uid]}]] + {:fx [[:effects.kv/delete-key key-uid]]})) diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index 6802e49fca7..ebf7589d665 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -97,11 +97,10 @@ (or pairing-completed? (get db :onboarding/new-account?)) [[:dispatch [:onboarding/finalize-setup]] - [:dispatch [:onboarding/finish-onboarding false]]] + [:dispatch [:onboarding/finish-onboarding]]] :else - [[:dispatch [:update-theme-and-init-root :screen/shell-stack]] - [:dispatch [:profile/toggle-testnet-mode-banner]]]))}))) + [[:dispatch [:shell/show-root-view]]]))}))) ;; login phase 2: we want to load and show chats faster, so we split login into 2 phases (rf/reg-event-fx :profile.login/get-chats-callback diff --git a/src/status_im/contexts/profile/push_notifications/effects.cljs b/src/status_im/contexts/profile/push_notifications/effects.cljs index db018d6b9b2..95769affc2f 100644 --- a/src/status_im/contexts/profile/push_notifications/effects.cljs +++ b/src/status_im/contexts/profile/push_notifications/effects.cljs @@ -129,3 +129,13 @@ (doseq [chat-id chat-ids] (native-module.pn/clear-message-notifications chat-id)) (pn-notifications/clear-received-notifications)))) + +(rf/reg-fx :effects/check-notifications-permissions + (fn [{:keys [on-success on-error]}] + (-> (pn-permissions/check-notification-permissions) + (promesa/then (partial rf/call-continuation on-success)) + (promesa/catch (partial rf/call-continuation on-error))))) + +(rf/reg-fx :effects/open-notifications-settings + (fn [] + (pn-permissions/open-notifications-settings))) diff --git a/src/status_im/contexts/profile/push_notifications/events.cljs b/src/status_im/contexts/profile/push_notifications/events.cljs index b1d985a35ca..4195b78977d 100644 --- a/src/status_im/contexts/profile/push_notifications/events.cljs +++ b/src/status_im/contexts/profile/push_notifications/events.cljs @@ -145,8 +145,7 @@ news-notifications-enabled? (conj :disable-news-notifications?) messenger-notifications-enabled? (conj :disable-chat-notifications?))] :else nil) - (when enabled? - (save-profile-setting-fx permission-setting)) + (save-profile-setting-fx permission-setting) (when should-enable-messenger-notifications? (save-profile-setting-fx messenger-setting))]})) @@ -193,3 +192,38 @@ (save-profile-setting-fx setting))]})) (rf/reg-event-fx :notifications/news-notifications-switch news-notifications-switch) + +(defn check-notifications-blocked + [_] + {:fx [[:effects/check-notifications-permissions + {:on-success [:notifications/determine-notifications-blocked] + :on-error [:log/debug "failed to check notification permissions"]}]]}) + +(rf/reg-event-fx :notifications/check-notifications-blocked check-notifications-blocked) + +(defn determine-notifications-blocked + [{:keys [db]} [{:keys [denied? undetermined? authorized?]}]] + (let [{:keys [notifications-enabled? + messenger-notifications-enabled? + news-notifications-enabled? + notifications-blocked?]} (get-in db [:profile/profile]) + blocked? (if platform/ios? + denied? + (and notifications-enabled? undetermined?))] + {:db (assoc-in db [:profile/profile :notifications-blocked?] (boolean blocked?)) + :fx [(when (and notifications-blocked? + authorized? + notifications-enabled? + messenger-notifications-enabled?) + [:effects/push-notifications-enable #{:enable-chat-notifications?}]) + (when (and notifications-blocked? + authorized? + notifications-enabled? + news-notifications-enabled?) + [:effects/push-notifications-enable #{:enable-news-notifications?}])]})) + +(rf/reg-event-fx :notifications/determine-notifications-blocked determine-notifications-blocked) + +(rf/reg-event-fx :notifications/open-notifications-settings + (fn [_ _] + {:fx [[:effects/open-notifications-settings]]})) diff --git a/src/status_im/contexts/profile/settings/screens/notifications/styles.cljs b/src/status_im/contexts/profile/settings/screens/notifications/styles.cljs new file mode 100644 index 00000000000..f9bccb8030f --- /dev/null +++ b/src/status_im/contexts/profile/settings/screens/notifications/styles.cljs @@ -0,0 +1,31 @@ +(ns status-im.contexts.profile.settings.screens.notifications.styles + (:require + [quo.foundations.colors :as colors])) + +(def information-box + {:margin-horizontal 20 + :margin-vertical 12}) + +(def information-box-button-label + {:gap 4 + :align-items :center + :flex-direction :row}) + +(def settings-group-container + {:margin-horizontal 20 + :margin-vertical 8 + :border-width 1 + :border-radius 20 + :border-color colors/white-opa-5}) + +(def settings-group-header + {:flex-direction :row + :padding-left 16 + :padding-right 12 + :padding-vertical 13 + :gap 12}) + +(def settings-group-item-container + {:padding-horizontal 8 + :padding-top 0 + :paddong-bottom 0}) diff --git a/src/status_im/contexts/profile/settings/screens/notifications/view.cljs b/src/status_im/contexts/profile/settings/screens/notifications/view.cljs index 2aebc422636..cbad467257f 100644 --- a/src/status_im/contexts/profile/settings/screens/notifications/view.cljs +++ b/src/status_im/contexts/profile/settings/screens/notifications/view.cljs @@ -5,6 +5,7 @@ [react-native.platform :as platform] [status-im.common.events-helper :as events-helper] [status-im.config :as config] + [status-im.contexts.profile.settings.screens.notifications.styles :as styles] [status-im.feature-flags :as ff] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -30,7 +31,7 @@ (rf/dispatch [:push-notifications/switch-block-mentions community-mentions-notifications-enabled?])) (defn notifications-enabled-setting - [{:keys [notifications-enabled?]}] + [{:keys [notifications-blocked? notifications-enabled?]}] (let [on-change (rn/use-callback #(toggle-notifications-enabled notifications-enabled?) [notifications-enabled?])] @@ -38,7 +39,8 @@ :title (i18n/label :t/show-notifications) :action :selector :action-props {:on-change on-change - :checked? notifications-enabled?}})) + :checked? (and (not notifications-blocked?) + notifications-enabled?)}})) (defn chat-non-contacts-notifications-setting [{:keys [notifications-enabled? @@ -49,13 +51,17 @@ on-change (rn/use-callback #(toggle-non-contact-notifications non-contact-notifications-enabled?) [non-contact-notifications-enabled?])] - {:blur? true - :title (i18n/label :t/notifications-non-contacts) - :action :selector - :action-props {:on-change (when-not disabled? on-change) - :disabled? disabled? - :checked? (and (not disabled?) - non-contact-notifications-enabled?)}})) + {:blur? true + :title (i18n/label :t/notifications-non-contacts) + :description :text + :description-props {:text (i18n/label :t/notifications-non-contacts-description)} + :action :selector + :action-props {:on-change (when-not disabled? on-change) + :disabled? disabled? + :checked? (if (ff/enabled? ::ff/settings.news-notifications) + non-contact-notifications-enabled? + (and (not disabled?) + non-contact-notifications-enabled?))}})) (defn chat-community-mentions-notifications-setting [{:keys [notifications-enabled? @@ -66,13 +72,17 @@ on-change (rn/use-callback #(toggle-community-mentions-notifications community-mentions-notifications-enabled?) [community-mentions-notifications-enabled?])] - {:blur? true - :title (i18n/label :t/allow-mention-notifications) - :action :selector - :action-props {:on-change (when-not disabled? on-change) - :disabled? disabled? - :checked? (and (not disabled?) - community-mentions-notifications-enabled?)}})) + {:blur? true + :title (i18n/label :t/communities) + :description :text + :description-props {:text (i18n/label :t/allow-community-mentions-notifications-description)} + :action :selector + :action-props {:on-change (when-not disabled? on-change) + :disabled? disabled? + :checked? (if (ff/enabled? ::ff/settings.news-notifications) + community-mentions-notifications-enabled? + (and (not disabled?) + community-mentions-notifications-enabled?))}})) (defn messenger-notifications-setting [{:keys [notifications-enabled? messenger-notifications-enabled?]}] @@ -80,12 +90,17 @@ on-change (rn/use-callback #(toggle-messenger-notifications messenger-notifications-enabled?) [messenger-notifications-enabled?])] - {:blur? true - :title (i18n/label :t/allow-messenger-notifications) - :action :selector - :action-props {:on-change (when-not disabled? on-change) - :disabled? disabled? - :checked? messenger-notifications-enabled?}})) + (cond-> {:blur? true + :title (i18n/label :t/allow-messenger-notifications) + :action :selector + :action-props {:on-change (when-not disabled? on-change) + :disabled? disabled? + :checked? messenger-notifications-enabled?}} + platform/android? + (assoc :title (i18n/label :t/allow-messages-and-communities-notifications) + :description :text + :description-props {:text (i18n/label + :t/allow-messages-and-communities-notifications-description)})))) (defn news-notifications-setting [{:keys [notifications-enabled? news-notifications-enabled?]}] @@ -93,49 +108,91 @@ on-change (rn/use-callback #(toggle-news-notifications news-notifications-enabled?) [news-notifications-enabled?])] - {:blur? true - :title (i18n/label :t/allow-news-notifications) - :action :selector - :action-props {:on-change (when-not disabled? on-change) - :disabled? disabled? - :checked? news-notifications-enabled?}})) + (cond-> {:blur? true + :title (i18n/label :t/allow-news-notifications) + :action :selector + :action-props {:on-change (when-not disabled? on-change) + :disabled? disabled? + :checked? news-notifications-enabled?}} + platform/android? + (assoc :description :text + :description-props {:text (i18n/label :t/allow-news-notifications-description)})))) + +(defn- settings-group-item + [item & _rest] + [quo/category + {:blur? true + :list-type :settings + :container-style styles/settings-group-item-container + :data [item]}]) + +(defn- messenger-notifications-settings + [notifications-settings] + (let [{:keys [action-props title]} (messenger-notifications-setting notifications-settings)] + [rn/pressable + {:on-press (:on-change action-props) + :style styles/settings-group-container} + [rn/view + {:style styles/settings-group-header} + [quo/text {:style {:flex 1}} title] + [quo/selectors + {:type :toggle + :checked? (:checked? action-props) + :disabled? (:disabled? action-props) + :on-change (:on-change action-props)}]] + [rn/flat-list + {:data [(chat-non-contacts-notifications-setting notifications-settings) + (chat-community-mentions-notifications-setting notifications-settings)] + :render-fn settings-group-item + :separator [rn/view {:style {:height 0}}]}]])) (defn view [] (let [notifications-settings (rf/sub [:profile/notifications-settings])] + (rn/use-mount #(rf/dispatch [:notifications/check-notifications-blocked])) [quo/overlay {:type :shell :top-inset? true} [quo/page-nav {:background :blur :icon-name :i/arrow-left :on-press events-helper/navigate-back}] [quo/page-top {:title (i18n/label :t/notifications)}] + (when (:notifications-blocked? notifications-settings) + [quo/information-box + {:type :error + :style styles/information-box + :blur? false + :on-button-press #(rf/dispatch [:notifications/open-notifications-settings]) + :button-label [rn/view {:style styles/information-box-button-label} + [quo/text {:size :paragraph-2} + (i18n/label :t/enabled-push-notifications)] + [quo/icon :i/external {:size 12}]]} + (i18n/label (if platform/ios? + :t/push-notifications-blocked-ios + :t/push-notifications-blocked-android))]) [quo/category {:blur? true :list-type :settings :data [(notifications-enabled-setting notifications-settings)]}] - ;; NOTE(@seanstrom): temporarily hide the messenger notification - ;; toggle until we update the design system - (if (and (not config/fdroid?) - (ff/enabled? ::ff/settings.news-notifications)) + (if (ff/enabled? ::ff/settings.news-notifications) [:<> (cond platform/ios? - [quo/category - {:blur? true - :list-type :settings - :data [(messenger-notifications-setting notifications-settings) - (chat-non-contacts-notifications-setting notifications-settings) - (chat-community-mentions-notifications-setting notifications-settings)]}] + [messenger-notifications-settings notifications-settings] + platform/android? [quo/category {:blur? true :list-type :settings :data [(messenger-notifications-setting notifications-settings)]}] + :else nil) - [quo/category - {:blur? true - :list-type :settings - :data [(news-notifications-setting notifications-settings)]}]] + + (when-not config/fdroid? + [quo/category + {:blur? true + :list-type :settings + :data [(news-notifications-setting notifications-settings)]}])] + (when platform/ios? [quo/category {:blur? true diff --git a/src/status_im/contexts/push_notifications/permissions.cljs b/src/status_im/contexts/push_notifications/permissions.cljs index b87d3c972d2..e5b34ae188a 100644 --- a/src/status_im/contexts/push_notifications/permissions.cljs +++ b/src/status_im/contexts/push_notifications/permissions.cljs @@ -6,6 +6,10 @@ (def request-notification-permissions permissions/request-notification-permissions) +(def check-notification-permissions permissions/check-notification-permissions) + +(def open-notifications-settings permissions/open-notification-settings) + (defn release-notification-permissions [] (when platform/ios? diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c036a34950a..4d0dd09e4e9 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -9,6 +9,7 @@ status-im.common.font.events status-im.common.image-crop-picker.events [status-im.common.json-rpc.events] + status-im.common.kv-storage.effects status-im.common.log status-im.common.peer-stats.events status-im.common.shared-urls.events diff --git a/src/status_im/subs/onboarding.cljs b/src/status_im/subs/onboarding.cljs index 42264ae4e5e..f3bc3ec471c 100644 --- a/src/status_im/subs/onboarding.cljs +++ b/src/status_im/subs/onboarding.cljs @@ -6,8 +6,8 @@ :onboarding/customization-color :<- [:onboarding/profile] :<- [:profile/customization-color] - :<- [:onboarding/new-account?] - (fn [[{:keys [color]} customization-color new-account?]] - (if new-account? + (fn [[{:keys [color]} customization-color] + [_sub-id {:keys [onboarding?]}]] + (if (and onboarding? (some? color)) color customization-color))) diff --git a/src/status_im/subs/profile.cljs b/src/status_im/subs/profile.cljs index aa741f1ec01..ac7beec1f8f 100644 --- a/src/status_im/subs/profile.cljs +++ b/src/status_im/subs/profile.cljs @@ -311,13 +311,17 @@ (re-frame/reg-sub :profile/notifications-settings :<- [:profile/profile] - (fn [{:keys [notifications-enabled? + (fn [{:keys [notifications-blocked? + notifications-enabled? news-notifications-enabled? messenger-notifications-enabled? push-notifications-block-mentions? push-notifications-from-contacts-only?]}] - {:notifications-enabled? (boolean notifications-enabled?) - :news-notifications-enabled? (boolean news-notifications-enabled?) - :messenger-notifications-enabled? (boolean messenger-notifications-enabled?) - :non-contact-notifications-enabled? (not (boolean push-notifications-from-contacts-only?)) - :community-mentions-notifications-enabled? (not (boolean push-notifications-block-mentions?))})) + (let [blocked? (boolean notifications-blocked?)] + {:notifications-blocked? blocked? + :notifications-enabled? (and (boolean notifications-enabled?) + (not blocked?)) + :news-notifications-enabled? (boolean news-notifications-enabled?) + :messenger-notifications-enabled? (boolean messenger-notifications-enabled?) + :non-contact-notifications-enabled? (not (boolean push-notifications-from-contacts-only?)) + :community-mentions-notifications-enabled? (not (boolean push-notifications-block-mentions?))}))) diff --git a/translations/en.json b/translations/en.json index b7b926a3608..352d8137d00 100644 --- a/translations/en.json +++ b/translations/en.json @@ -132,10 +132,14 @@ "all-time": "All time", "allow": "Allow", "allow-and-send": "Allow and send", + "allow-community-mentions-notifications-description": "Mentions and replies", "allow-mention-notifications": "Show @ mentions", + "allow-messages-and-communities-notifications": "Messages and communities", + "allow-messages-and-communities-notifications-description": "1-1 and group messages, replies and mentions in communities. Possible high battery usage.", "allow-messenger-notifications": "Show messenger notifications", "allow-new-contact-requests": "Allow new contact requests", - "allow-news-notifications": "Show news notifications", + "allow-news-notifications": "News from Status", + "allow-news-notifications-description": "Uses Google services, and works even when the app is closed.", "allowing-authorizes-this-dapp": "Allowing authorizes this DApp to retrieve your wallet address and enable Web3", "alphabetically": "Alphabetically", "already-have-asset": "You already have this asset", @@ -919,8 +923,12 @@ "enable-link-previews": "Enable link previews in chat?", "enable-network-access": "Enable network access", "enable-network-to-sign-requests": "to sign requests from", - "enable-notifications-sub-title": "Receive notifications about your new messages or wallet transactions", + "enable-news-notifications-third-party": "Include notifications delivered through third-party providers", + "enable-notifications": "Enable notifications", + "enable-notifications-info-description": "Describe how using Firebase affects privacy.", + "enable-notifications-sub-title": "Receive notifications about your new messages, wallet transactions and news", "enable-store-confirmations": "Enable Store confirmations?\n\nYou’ll need to log in again.", + "enabled-push-notifications": "Enable push notifications", "encrypt-with-password": "Encrypt with password", "encrypted-key-pairs": "Encrypted key pairs", "encrypted-key-pairs-code": "Encrypted key pairs code", @@ -1902,7 +1910,8 @@ "notification-settings": "Notification settings", "notifications": "Notifications", "notifications-marked-as-read": "{{count}} notifications marked as read", - "notifications-non-contacts": "Notifications from non-contacts", + "notifications-non-contacts": "From non-contacts", + "notifications-non-contacts-description": "Contact requests and group messages", "notifications-preferences": "Notification preferences", "notifications-servers": "Notification servers", "notifications-switch": "Show notifications", @@ -2160,6 +2169,8 @@ "push-failed-transaction-body": "{{value}} {{currency}} to {{to}}", "push-inbound-transaction": "You received {{value}} {{currency}}", "push-inbound-transaction-body": "From {{from}} to {{to}}", + "push-notifications-blocked-android": "Android push notifications are disabled.", + "push-notifications-blocked-ios": "iOS push notifications are disabled.", "push-notifications-server-enabled": "Server enabled", "push-notifications-servers": "Push notification servers", "push-outbound-transaction": "You sent {{value}} {{currency}}",