From e1abe5c6e20420f7a0f1ba4d4123a454a92756b5 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Wed, 23 Oct 2024 11:26:29 -0300 Subject: [PATCH] feat(onboarding): Present Terms to users upgrading from v1 or those who need to accept updated Terms (#21487) Cherry-pick https://github.com/status-im/status-mobile/commit/d45eb5ec20ae7757cd99554920e466eabac0bfb1. Fixes https://github.com/status-im/status-mobile/issues/21113 Related status-go PR: https://github.com/status-im/status-go/pull/5977 --- .../status/ethereum/module/AccountManager.kt | 5 + .../ios/RCTStatus/AccountManager.m | 8 + modules/react-native-status/nodejs/status.cpp | 21 +++ src/native_module/core.cljs | 6 + .../contexts/onboarding/intro/view.cljs | 141 +++++++++++------- .../contexts/profile/data_store.cljs | 5 + src/status_im/contexts/profile/effects.cljs | 14 ++ src/status_im/contexts/profile/events.cljs | 48 +++--- src/status_im/subs/profile.cljs | 10 ++ src/tests/test_utils.cljs | 3 + status-go-version.json | 6 +- translations/en.json | 1 + 12 files changed, 188 insertions(+), 80 deletions(-) create mode 100644 src/status_im/contexts/profile/data_store.cljs create mode 100644 src/status_im/contexts/profile/effects.cljs diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt index a3a64d5ef4..4ad0aee3b6 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt @@ -253,6 +253,11 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC utils.executeRunnableStatusGoMethod({ Statusgo.initializeApplication(request) }, callback) } + @ReactMethod + private fun acceptTerms(callback: Callback) { + Log.d(TAG, "acceptTerms") + utils.executeRunnableStatusGoMethod({ Statusgo.acceptTerms() }, callback) + } @ReactMethod fun logout() { diff --git a/modules/react-native-status/ios/RCTStatus/AccountManager.m b/modules/react-native-status/ios/RCTStatus/AccountManager.m index d8dd8aa556..da7ab6d5f4 100644 --- a/modules/react-native-status/ios/RCTStatus/AccountManager.m +++ b/modules/react-native-status/ios/RCTStatus/AccountManager.m @@ -204,6 +204,14 @@ RCT_EXPORT_METHOD(initializeApplication:(NSString *)request callback(@[result]); } +RCT_EXPORT_METHOD(acceptTerms:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"acceptTerms() method called"); +#endif + NSString *result = StatusgoAcceptTerms(); + callback(@[result]); +} + RCT_EXPORT_METHOD(openAccounts:(RCTResponseSenderBlock)callback) { #if DEBUG NSLog(@"OpenAccounts() method called"); diff --git a/modules/react-native-status/nodejs/status.cpp b/modules/react-native-status/nodejs/status.cpp index d236e5edda..7d9f371aca 100644 --- a/modules/react-native-status/nodejs/status.cpp +++ b/modules/react-native-status/nodejs/status.cpp @@ -761,6 +761,26 @@ void _Logout(const FunctionCallbackInfo& args) { } +void _AcceptTerms(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.Length() != 0) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong number of arguments for AcceptTerms"))); + return; + } + + // Check the argument types + + // Call exported Go function, which returns a C string + char *c = AcceptTerms(); + + Local ret = String::NewFromUtf8(isolate, c).ToLocalChecked(); + args.GetReturnValue().Set(ret); + delete c; +} + void _HashMessage(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); @@ -1998,6 +2018,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "multiAccountStoreAccount", _MultiAccountStoreAccount); NODE_SET_METHOD(exports, "initKeystore", _InitKeystore); NODE_SET_METHOD(exports, "initializeApplication", _InitializeApplication); + NODE_SET_METHOD(exports, "acceptTerms", _AcceptTerms); NODE_SET_METHOD(exports, "fleets", _Fleets); NODE_SET_METHOD(exports, "stopCPUProfiling", _StopCPUProfiling); NODE_SET_METHOD(exports, "encodeTransfer", _EncodeTransfer); diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 52ac713b8d..a279d1bcee 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -79,6 +79,12 @@ (types/clj->json request) #(callback (types/json->clj %)))) +(defn accept-terms + ([] + (native-utils/promisify-native-module-call accept-terms)) + ([callback] + (.acceptTerms ^js (account-manager) callback))) + (defn prepare-dir-and-update-config [key-uid config callback] (log/debug "[native-module] prepare-dir-and-update-config") diff --git a/src/status_im/contexts/onboarding/intro/view.cljs b/src/status_im/contexts/onboarding/intro/view.cljs index 7c4d8fd8a3..80d4957195 100644 --- a/src/status_im/contexts/onboarding/intro/view.cljs +++ b/src/status_im/contexts/onboarding/intro/view.cljs @@ -11,66 +11,93 @@ [utils.i18n :as i18n] [utils.re-frame :as rf])) +(defn- show-terms-of-use + [] + (rf/dispatch [:show-bottom-sheet {:content terms/terms-of-use :shell? true}])) + +(defn- show-privacy-policy + [] + (rf/dispatch [:show-bottom-sheet {:content privacy/privacy-statement :shell? true}])) + +(defn- terms + [terms-accepted? set-terms-accepted?] + [rn/view {:style style/terms-privacy-container} + [rn/view + {:accessibility-label :terms-privacy-checkbox-container} + [quo/selectors + {:type :checkbox + :blur? true + :checked? terms-accepted? + :on-change #(set-terms-accepted? not)}]] + [rn/view {:style style/text-container} + [quo/text + {:style style/plain-text + :size :paragraph-2} + (str (i18n/label :t/accept-status-tos-prefix) " ")] + [quo/text + {:on-press show-terms-of-use + :style style/highlighted-text + :size :paragraph-2 + :weight :medium} + (i18n/label :t/terms-of-service)] + [quo/text + {:style style/plain-text + :size :paragraph-2} + " " (i18n/label :t/and) " "] + [quo/text + {:on-press show-privacy-policy + :style style/highlighted-text + :size :paragraph-2 + :weight :medium} + (i18n/label :t/intro-privacy-policy)]]]) + +(defn- explore-new-status + [] + (rf/dispatch [:profile/explore-new-status])) + +(defn- sync-or-recover-profile + [] + (when-let [blur-show-fn @overlay/blur-show-fn-atom] + (blur-show-fn)) + (rf/dispatch [:open-modal :screen/onboarding.sync-or-recover-profile])) + +(defn- create-profile + [] + (when-let [blur-show-fn @overlay/blur-show-fn-atom] + (blur-show-fn)) + (rf/dispatch [:open-modal :screen/onboarding.new-to-status])) + (defn view [] - (let [[terms-accepted? set-terms-accepted?] (rn/use-state false)] + (let [[terms-accepted? set-terms-accepted?] (rn/use-state false) + has-profiles-and-unaccepted-terms? (rf/sub [:profile/has-profiles-and-unaccepted-terms?])] [rn/view {:style style/page-container} [background/view false] [quo/bottom-actions - {:container-style (style/bottom-actions-container (safe-area/get-bottom)) - :actions :two-vertical-actions - :description :top - :description-top-text [rn/view - {:style style/terms-privacy-container} - [rn/view - {:accessibility-label :terms-privacy-checkbox-container} - [quo/selectors - {:type :checkbox - :blur? true - :checked? terms-accepted? - :on-change #(set-terms-accepted? not)}]] - [rn/view {:style style/text-container} - [quo/text - {:style style/plain-text - :size :paragraph-2} - (str (i18n/label :t/accept-status-tos-prefix) " ")] - [quo/text - {:on-press #(rf/dispatch [:show-bottom-sheet - {:content terms/terms-of-use - :shell? true}]) - :style style/highlighted-text - :size :paragraph-2 - :weight :medium} - (i18n/label :t/terms-of-service)] - [quo/text - {:style style/plain-text - :size :paragraph-2} - " " (i18n/label :t/and) " "] - [quo/text - {:on-press #(rf/dispatch [:show-bottom-sheet - {:content privacy/privacy-statement - :shell? true}]) - :style style/highlighted-text - :size :paragraph-2 - :weight :medium} - (i18n/label :t/intro-privacy-policy)]]] - :button-one-label (i18n/label :t/sync-or-recover-profile) - :button-one-props {:type :dark-grey - :disabled? (not terms-accepted?) - :accessibility-label :already-use-status-button - :on-press (fn [] - (when-let [blur-show-fn @overlay/blur-show-fn-atom] - (blur-show-fn)) - (rf/dispatch - [:open-modal - :screen/onboarding.sync-or-recover-profile]))} - :button-two-label (i18n/label :t/create-profile) - :button-two-props {:accessibility-label :new-to-status-button - :disabled? (not terms-accepted?) - :on-press - (fn [] - (when-let [blur-show-fn @overlay/blur-show-fn-atom] - (blur-show-fn)) - (rf/dispatch - [:open-modal :screen/onboarding.new-to-status]))}}] + (cond-> + {:container-style (style/bottom-actions-container (safe-area/get-bottom)) + :actions :two-vertical-actions + :description :top + :description-top-text [terms terms-accepted? set-terms-accepted?]} + + has-profiles-and-unaccepted-terms? + (assoc + :actions :one-action + :button-one-label (i18n/label :t/explore-the-new-status) + :button-one-props {:disabled? (not terms-accepted?) + :accessibility-label :explore-new-status + :on-press explore-new-status}) + + (not has-profiles-and-unaccepted-terms?) + (assoc + :actions :two-vertical-actions + :button-one-label (i18n/label :t/sync-or-recover-profile) + :button-one-props {:type :dark-grey + :disabled? (not terms-accepted?) + :accessibility-label :already-use-status-button + :on-press sync-or-recover-profile} + :button-two-label (i18n/label :t/create-profile) + :button-two-props {:accessibility-label :new-to-status-button + :disabled? (not terms-accepted?) + :on-press create-profile}))] [overlay/view]])) diff --git a/src/status_im/contexts/profile/data_store.cljs b/src/status_im/contexts/profile/data_store.cljs new file mode 100644 index 0000000000..8080e4ef50 --- /dev/null +++ b/src/status_im/contexts/profile/data_store.cljs @@ -0,0 +1,5 @@ +(ns status-im.contexts.profile.data-store) + +(defn accepted-terms? + [accounts] + (some :hasAcceptedTerms accounts)) diff --git a/src/status_im/contexts/profile/effects.cljs b/src/status_im/contexts/profile/effects.cljs new file mode 100644 index 0000000000..f6e9314c5b --- /dev/null +++ b/src/status_im/contexts/profile/effects.cljs @@ -0,0 +1,14 @@ +(ns status-im.contexts.profile.effects + (:require + [native-module.core :as native-module] + [promesa.core :as promesa] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(rf/reg-fx :effects.profile/accept-terms + (fn [{:keys [on-success]}] + (-> (native-module/accept-terms) + (promesa/then (fn [] + (rf/call-continuation on-success))) + (promesa/catch (fn [error] + (log/error "Failed to accept terms" {:error error})))))) diff --git a/src/status_im/contexts/profile/events.cljs b/src/status_im/contexts/profile/events.cljs index ae9b58f1a6..b35e224f28 100644 --- a/src/status_im/contexts/profile/events.cljs +++ b/src/status_im/contexts/profile/events.cljs @@ -4,10 +4,12 @@ [legacy.status-im.multiaccounts.update.core :as multiaccounts.update] [native-module.core :as native-module] [status-im.config :as config] + [status-im.contexts.profile.data-store :as profile.data-store] [status-im.contexts.profile.edit.accent-colour.events] [status-im.contexts.profile.edit.bio.events] [status-im.contexts.profile.edit.header.events] [status-im.contexts.profile.edit.name.events] + status-im.contexts.profile.effects status-im.contexts.profile.login.events [status-im.contexts.profile.rpc :as profile.rpc] [utils.re-frame :as rf])) @@ -42,26 +44,27 @@ (rf/reg-event-fx :profile/get-profiles-overview-success (fn [{:keys [db]} [{:keys [accounts] {:keys [userConfirmed enabled]} :centralizedMetricsInfo}]] - (let [db-with-settings (assoc db - :centralized-metrics/user-confirmed? userConfirmed - :centralized-metrics/enabled? enabled)] - (if (seq accounts) - (let [profiles (reduce-profiles accounts) - {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] - {:db (if key-uid - (-> db-with-settings - (assoc :profile/profiles-overview profiles) - (update :profile/login #(select-profile % key-uid))) - db-with-settings) - :fx [[:dispatch [:update-theme-and-init-root :screen/profile.profiles]] - (when (and key-uid userConfirmed) - [:effects.biometric/check-if-available - {:key-uid key-uid - :on-success (fn [auth-method] - (rf/dispatch [:profile.login/check-biometric-success key-uid - auth-method]))}])]}) - {:db db-with-settings - :fx [[:dispatch [:update-theme-and-init-root :screen/onboarding.intro]]]})))) + (let [db-with-settings (assoc db + :centralized-metrics/user-confirmed? userConfirmed + :centralized-metrics/enabled? enabled) + profiles (reduce-profiles accounts) + {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles))) + new-db (cond-> db-with-settings + (seq profiles) + (assoc :profile/profiles-overview profiles) + + key-uid + (update :profile/login #(select-profile % key-uid)))] + {:db new-db + :fx (if (profile.data-store/accepted-terms? accounts) + [[:dispatch [:update-theme-and-init-root :screen/profile.profiles]] + (when (and key-uid userConfirmed) + [:effects.biometric/check-if-available + {:key-uid key-uid + :on-success (fn [auth-method] + (rf/dispatch [:profile.login/check-biometric-success key-uid + auth-method]))}])] + [[:dispatch [:update-theme-and-init-root :screen/onboarding.intro]]])}))) (rf/reg-event-fx :profile/update-setting-from-backup @@ -82,3 +85,8 @@ :messages-from-contacts-only (not (get-in db [:profile/profile :messages-from-contacts-only])) {}))) + +(rf/reg-event-fx :profile/explore-new-status + (fn [] + {:fx [[:effects.profile/accept-terms + {:on-success [:navigate-to :screen/profile.profiles]}]]})) diff --git a/src/status_im/subs/profile.cljs b/src/status_im/subs/profile.cljs index b2f5443bf8..56ba9f19b9 100644 --- a/src/status_im/subs/profile.cljs +++ b/src/status_im/subs/profile.cljs @@ -8,6 +8,7 @@ [re-frame.core :as re-frame] [status-im.common.pixel-ratio :as pixel-ratio] [status-im.constants :as constants] + [status-im.contexts.profile.data-store :as profile.data-store] [status-im.contexts.profile.utils :as profile.utils] [utils.security.core :as security])) @@ -17,6 +18,15 @@ (fn [{:keys [customization-color]}] (or customization-color constants/profile-default-color))) +;; A profile can only be created without accepting terms in Status v1. In Status +;; v2, the terms may be unaccepted because we intentionally created a migration +;; resetting the flag in case the privacy policy changed. +(re-frame/reg-sub :profile/has-profiles-and-unaccepted-terms? + :<- [:profile/profiles-overview] + (fn [profiles-overview] + (and (seq profiles-overview) + (not (profile.data-store/accepted-terms? (vals profiles-overview)))))) + (re-frame/reg-sub :profile/currency :<- [:profile/profile] diff --git a/src/tests/test_utils.cljs b/src/tests/test_utils.cljs index 069e492276..833d2e2c1d 100644 --- a/src/tests/test_utils.cljs +++ b/src/tests/test_utils.cljs @@ -70,6 +70,9 @@ {:initializeApplication (fn [request callback] (callback (.initializeApplication native-status request))) + :acceptTerms + (fn [callback] + (callback (.acceptTerms native-status))) :createAccountAndLogin (fn [request] (.createAccountAndLogin native-status request)) :restoreAccountAndLogin diff --git a/status-go-version.json b/status-go-version.json index 7379584df2..89f1eb645e 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v3.3.0", - "commit-sha1": "43659f0c7e1a64102e7f8e38bd532b72714c5819", - "src-sha256": "01fpviw1g22f4ni3li2wnhyz0l517jfipvnyfysckds6yi94l0hf" + "version": "v3.5.0", + "commit-sha1": "39511298cdc8f50e7659ac8079d5bc8a4763ddc7", + "src-sha256": "1nn8gq9d91ix5mzndr306rq3n68rzj4drym3pfaxzaa73k4v9r07" } diff --git a/translations/en.json b/translations/en.json index d77ad3f856..caa55062ec 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1002,6 +1002,7 @@ "expand-all": "Expand all", "experienced-web3": "Experienced in Web3?", "explore-the-decentralized-web": "Explore and interact with the decentralized web", + "explore-the-new-status": "Explore the new Status", "export-account": "Export account", "export-key": "Export private key", "external-link": "External link",