Skip to content

Commit

Permalink
Improve how we manager consent in the app (#4118)
Browse files Browse the repository at this point in the history
  • Loading branch information
g123k authored Jun 11, 2023
1 parent 882c7fb commit abab934
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 27 deletions.
47 changes: 44 additions & 3 deletions packages/smooth_app/lib/data_models/user_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ enum UserPictureSource {

class UserPreferences extends ChangeNotifier {
UserPreferences._shared(final SharedPreferences sharedPreferences)
: _sharedPreferences = sharedPreferences;
: _sharedPreferences = sharedPreferences {
onCrashReportingChanged = ValueNotifier<bool>(crashReports);
onAnalyticsChanged = ValueNotifier<bool>(userTracking);
}

/// Singleton
static UserPreferences? _instance;
Expand All @@ -46,8 +49,16 @@ class UserPreferences extends ChangeNotifier {
return _instance!;
}

static const String _TAG_PREFIX_IMPORTANCE = 'IMPORTANCE_AS_STRING';
late ValueNotifier<bool> onCrashReportingChanged;
late ValueNotifier<bool> onAnalyticsChanged;

/// Whether the preferences are empty or not
static const String _TAG_INIT = 'init';

/// The current version of preferences (1)
static const String _TAG_VERSION = 'prefs_version';
static const int _PREFS_CURRENT_VERSION = 1;
static const String _TAG_PREFIX_IMPORTANCE = 'IMPORTANCE_AS_STRING';
static const String _TAG_CURRENT_THEME_MODE = 'currentThemeMode';
static const String _TAG_CURRENT_COLOR_SCHEME = 'currentColorScheme';
static const String _TAG_CURRENT_CONTRAST_MODE = 'contrastMode';
Expand All @@ -56,6 +67,7 @@ class UserPreferences extends ChangeNotifier {
'lastVisitedOnboardingPage';
static const String _TAG_PREFIX_FLAG = 'FLAG_PREFIX_';
static const String _TAG_DEV_MODE = 'devMode';
static const String _TAG_USER_TRACKING = 'user_tracking';
static const String _TAG_CRASH_REPORTS = 'crash_reports';
static const String _TAG_EXCLUDED_ATTRIBUTE_IDS = 'excluded_attributes';

Expand Down Expand Up @@ -84,13 +96,32 @@ class UserPreferences extends ChangeNotifier {
'inAppReviewAlreadyAsked';

Future<void> init(final ProductPreferences productPreferences) async {
await _onMigrate();

if (_sharedPreferences.getBool(_TAG_INIT) != null) {
return;
}
await productPreferences.resetImportances();
await _sharedPreferences.setBool(_TAG_INIT, true);
}

/// Allow to migrate between versions
Future<void> _onMigrate() async {
final int? currentVersion = _sharedPreferences.getInt(_TAG_VERSION);

/// With version == null (or 0), [_TAG_USER_TRACKING] didn't exist
if (currentVersion == null) {
final bool? crashReporting =
_sharedPreferences.getBool(_TAG_CRASH_REPORTS);
if (crashReporting != null) {
await setUserTracking(crashReporting);
}

await _sharedPreferences.setInt(
_TAG_VERSION, UserPreferences._PREFS_CURRENT_VERSION);
}
}

String _getImportanceTag(final String variable) =>
_TAG_PREFIX_IMPORTANCE + variable;

Expand Down Expand Up @@ -123,13 +154,23 @@ class UserPreferences extends ChangeNotifier {
notifyListeners();
}

Future<void> setUserTracking(final bool state) async {
await _sharedPreferences.setBool(_TAG_USER_TRACKING, state);
onAnalyticsChanged.value = state;
notifyListeners();
}

bool get userTracking =>
_sharedPreferences.getBool(_TAG_USER_TRACKING) ?? false;

Future<void> setCrashReports(final bool state) async {
await _sharedPreferences.setBool(_TAG_CRASH_REPORTS, state);
onCrashReportingChanged.value = state;
notifyListeners();
}

bool get crashReports =>
_sharedPreferences.getBool(_TAG_CRASH_REPORTS) ?? true;
_sharedPreferences.getBool(_TAG_CRASH_REPORTS) ?? false;

String get currentTheme =>
_sharedPreferences.getString(_TAG_CURRENT_THEME_MODE) ?? 'System Default';
Expand Down
69 changes: 54 additions & 15 deletions packages/smooth_app/lib/helpers/analytics_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:matomo_tracker/matomo_tracker.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/helpers/entry_points_helper.dart';
import 'package:smooth_app/helpers/global_vars.dart';

Expand Down Expand Up @@ -90,11 +91,25 @@ class AnalyticsHelper {
AnalyticsHelper._();

static bool _crashReports = false;
static _AnalyticsTrackingMode _analyticsReporting =
_AnalyticsTrackingMode.disabled;

static String latestSearch = '';

/// Did the user allow the analytic reports?
static bool _allow = false;
static void linkPreferences(UserPreferences userPreferences) {
// Init the value
_setAnalyticsReports(userPreferences.onAnalyticsChanged.value);
_setCrashReports(userPreferences.onCrashReportingChanged.value);

// Listen to changes
userPreferences.onAnalyticsChanged.addListener(() {
_setAnalyticsReports(userPreferences.onAnalyticsChanged.value);
});

userPreferences.onCrashReportingChanged.addListener(() {
_setCrashReports(userPreferences.onCrashReportingChanged.value);
});
}

static Future<void> initSentry({
required Function()? appRunner,
Expand All @@ -117,17 +132,26 @@ class AnalyticsHelper {
);
}

static void setCrashReports(final bool crashReports) =>
/// Don't call this method directly, it is automatically updated via the
/// [UserPreferences]
static void _setCrashReports(final bool crashReports) =>
_crashReports = crashReports;

static Future<void> setAnalyticsReports(final bool allow) async {
_allow = allow;

// F-Droid special case
if (GlobalVars.storeLabel == StoreLabel.FDroid && !allow) {
/// Don't call this method directly, it is automatically updated via the
/// [UserPreferences]
static Future<void> _setAnalyticsReports(final bool allow) async {
if (GlobalVars.storeLabel == StoreLabel.FDroid) {
_analyticsReporting = _AnalyticsTrackingMode.disabled;
await MatomoTracker.instance.setOptOut(optout: true);
} else {
if (allow) {
_analyticsReporting = _AnalyticsTrackingMode.enabled;
} else {
_analyticsReporting = _AnalyticsTrackingMode.anonymous;
}

await MatomoTracker.instance.setOptOut(optout: false);
MatomoTracker.instance.setVisitorUserId(_uuid);
}
}

Expand All @@ -143,31 +167,37 @@ class AnalyticsHelper {
final bool screenshotMode,
) async {
if (screenshotMode) {
setCrashReports(false);
setAnalyticsReports(false);
_setCrashReports(false);
_setAnalyticsReports(false);
return;
}
try {
await MatomoTracker.instance.initialize(
url: 'https://analytics.openfoodfacts.org/matomo.php',
siteId: 2,
visitorId: uuid,
visitorId: _uuid,
);
} catch (err) {
// With Hot Reload, this may trigger a late field already initialized
}
}

/// A UUID must be at least one 16 characters
static String? get uuid {
static String? get _uuid {
// if user opts out then track anonymously with userId containg zeros
if (kDebugMode) {
return 'smoothie_debug--';
}
if (!_allow) {
return '0' * 16;

switch (_analyticsReporting) {
case _AnalyticsTrackingMode.anonymous:
return '0' * 16;
case _AnalyticsTrackingMode.disabled:
return '';
case _AnalyticsTrackingMode.enabled:
default:
return OpenFoodAPIConfiguration.uuid;
}
return OpenFoodAPIConfiguration.uuid;
}

static void trackEvent(
Expand Down Expand Up @@ -245,3 +275,12 @@ class AnalyticsHelper {

static String? get matomoVisitorId => MatomoTracker.instance.visitor.id;
}

enum _AnalyticsTrackingMode {
// With the user consent
enabled,
// Without the user consent
anonymous,
// On F-Droid builds
disabled,
}
3 changes: 2 additions & 1 deletion packages/smooth_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ Future<bool> _init1() async {
);
UserManagementProvider().checkUserLoginValidity();

AnalyticsHelper.setCrashReports(_userPreferences.crashReports);
AnalyticsHelper.linkPreferences(_userPreferences);

await ProductQuery.setCountry(_userPreferences);
_themeProvider = ThemeProvider(_userPreferences);
_colorProvider = ColorProvider(_userPreferences);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:smooth_app/data_models/onboarding_loader.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/app_helper.dart';
import 'package:smooth_app/pages/onboarding/onboarding_bottom_bar.dart';
import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart';
Expand Down Expand Up @@ -104,7 +103,8 @@ class ConsentAnalyticsPage extends StatelessWidget {
final ThemeProvider themeProvider,
) async {
await userPreferences.setCrashReports(accept);
AnalyticsHelper.setAnalyticsReports(accept);
await userPreferences.setUserTracking(accept);

themeProvider.finishOnboarding();
//ignore: use_build_context_synchronously
await OnboardingLoader(localDatabase).runAtNextTime(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:matomo_tracker/matomo_tracker.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/widgets/language_selector.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/camera_helper.dart';
import 'package:smooth_app/helpers/entry_points_helper.dart';
import 'package:smooth_app/helpers/global_vars.dart';
Expand Down Expand Up @@ -548,14 +546,14 @@ class _SendAnonymousDataSettingState extends State<_SendAnonymousDataSetting> {
@override
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final UserPreferences userPreferences = context.watch<UserPreferences>();

return UserPreferencesSwitchItem(
title: appLocalizations.send_anonymous_data_toggle_title,
subtitle: appLocalizations.send_anonymous_data_toggle_subtitle,
value: !MatomoTracker.instance.getOptOut(),
value: userPreferences.userTracking,
onChanged: (final bool allow) async {
await AnalyticsHelper.setAnalyticsReports(allow);
setState(() {});
await userPreferences.setUserTracking(allow);
},
);
}
Expand All @@ -575,7 +573,6 @@ class _CrashReportingSetting extends StatelessWidget {
value: userPreferences.crashReports,
onChanged: (final bool value) async {
await userPreferences.setCrashReports(value);
AnalyticsHelper.setCrashReports(value);
},
);
}
Expand Down

0 comments on commit abab934

Please sign in to comment.