Skip to content

Commit

Permalink
feat(onboarding): Present Terms to users upgrading from v1 (#21124)
Browse files Browse the repository at this point in the history
We now show the onboarding intro requesting the user to accept the Terms of Use
& Privacy Policy with the new button "Explore the new Status" if the user had
installed any version of Status older than the one from this PR and had at
least one profile.

Fixes #21113

status-go PR status-im/status-go#5766

In practice, this means:

- Users coming from Status v1 who had at least one profile will see the
  modified onboarding intro screen and will need to accept the terms to proceed.
- Users who already installed v2 and are upgrading to this PR build (devs & QAs
  mostly) and who had at least one profile will also see the modified intro
  screen and will need to accept the terms to proceed.

Areas that may be impacted

- Onboarding

Steps to test:

The criteria used during development:

1. Given that user Alice had installed v1 and had one or more profiles.
2. When she installs v2 and opens it, she sees the new onboarding intro and must
   agree to the terms to enable the button "Explore the new Status".
3. After pressing the button, she can login as usual in any of her profiles.

1. Given that user Alice already upgraded from v1 and accepted the terms.
2. When she reopens the app she does not need to accept terms again and can
   immediately sign-in with any of her profiles.

1. Given that user Alice already upgraded from v1 and accepted the terms.
2. When she deletes all profiles, she sees the onboarding intro for users who
   have not upgraded, i.e. she has to agree to terms and she sees the usual two
   buttons "Create profile" and "Sync or recover profile".

1. Given that user Alice never installed Status.
2. When she installs v2, she sees the normal onboarding intro screen, where she
   has to accept the terms and she sees two buttons "Create profile" and "Sync
   or recover profile".
3. When she reopens the app, she doesn't see anymore the screen to accept terms.
  • Loading branch information
ilmotta authored Aug 26, 2024
1 parent b1c9077 commit d45eb5e
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
8 changes: 8 additions & 0 deletions modules/react-native-status/ios/RCTStatus/AccountManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ -(NSString *) prepareDirAndUpdateConfig:(NSString *)config
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");
Expand Down
21 changes: 21 additions & 0 deletions modules/react-native-status/nodejs/status.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,26 @@ void _Logout(const FunctionCallbackInfo<Value>& args) {

}

void _AcceptTerms(const FunctionCallbackInfo<Value>& 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<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
args.GetReturnValue().Set(ret);
delete c;
}

void _HashMessage(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Expand Down Expand Up @@ -1998,6 +2018,7 @@ void init(Local<Object> 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);
Expand Down
6 changes: 6 additions & 0 deletions src/native_module/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
140 changes: 83 additions & 57 deletions src/status_im/contexts/onboarding/intro/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,92 @@
[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)
from-v1-without-terms-accepted? (rf/sub [:profile/from-status-v1-without-terms-accepted?])]
[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?]}
from-v1-without-terms-accepted?
(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 from-v1-without-terms-accepted?)
(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]]))
5 changes: 5 additions & 0 deletions src/status_im/contexts/profile/data_store.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(ns status-im.contexts.profile.data-store)

(defn accepted-terms?
[accounts]
(some #(:hasAcceptedTerms %) accounts))
14 changes: 14 additions & 0 deletions src/status_im/contexts/profile/effects.cljs
Original file line number Diff line number Diff line change
@@ -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}))))))
48 changes: 28 additions & 20 deletions src/status_im/contexts/profile/events.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand Down Expand Up @@ -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
Expand All @@ -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]}]]}))
13 changes: 13 additions & 0 deletions src/status_im/subs/profile.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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]))

Expand All @@ -18,6 +19,18 @@
(fn [{:keys [customization-color]}]
(or customization-color constants/profile-default-color)))

(re-frame/reg-sub :profile/accepted-terms?
:<- [:profile/profile]
(fn [{:keys [hasAcceptedTerms]}]
hasAcceptedTerms))

;; A profile can only be created without accepting terms in Status v1.
(re-frame/reg-sub :profile/from-status-v1-without-terms-accepted?
:<- [: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
(fn []
Expand Down
3 changes: 3 additions & 0 deletions src/tests/test_utils.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions status-go-version.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"owner": "status-im",
"repo": "status-go",
"version": "release/0.182.x",
"commit-sha1": "4a18c85c3c1d58ea6c8493c46bfd2ed5772b1386",
"src-sha256": "04fgykwk44r7f16bfxlkpl9kgnl7yssfyycqnddwszinnnlnfmpl"
"commit-sha1": "14c996158cf1d651d44808d51686fdbfb2eb3b39",
"src-sha256": "02dnz3327kz8cnhqp6cgcmvqvhcdc941iic7jz2v35hck221vbkg"
}
1 change: 1 addition & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,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",
Expand Down

0 comments on commit d45eb5e

Please sign in to comment.