Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: web account deletion #3416

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/app/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 31
compileSdkVersion 33
VaiTon marked this conversation as resolved.
Show resolved Hide resolved

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand Down
28 changes: 28 additions & 0 deletions packages/app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
webview_flutter:
dependency: transitive
description:
name: webview_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.4"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.10.4"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.5"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.5"
win32:
dependency: transitive
description:
Expand Down
18 changes: 11 additions & 7 deletions packages/smooth_app/lib/data_models/user_management_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ class UserManagementProvider with ChangeNotifier {
}

/// Mounts already stored credentials, called at app startup
static Future<void> mountCredentials() async {
String? userId;
String? password;
///
/// We can use optional parameters to mock in tests
static Future<void> mountCredentials(
{String? userId, String? password}) async {
String? effectiveUserId;
String? effectivePassword;

try {
userId = await DaoSecuredString.get(_USER_ID);
password = await DaoSecuredString.get(_PASSWORD);
effectiveUserId = userId ?? await DaoSecuredString.get(_USER_ID);
effectivePassword = password ?? await DaoSecuredString.get(_PASSWORD);
} on PlatformException {
/// Decrypting the values can go wrong if, for example, the app was
/// manually overwritten from an external apk.
Expand All @@ -59,11 +62,12 @@ class UserManagementProvider with ChangeNotifier {
Logs.e('Credentials query failed, you have been logged out');
}

if (userId == null || password == null) {
if (effectiveUserId == null || effectivePassword == null) {
return;
}

final User user = User(userId: userId, password: password);
final User user =
User(userId: effectiveUserId, password: effectivePassword);
OpenFoodAPIConfiguration.globalUser = user;
}

Expand Down
8 changes: 8 additions & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,14 @@
"@account_delete": {
"description": "Delete account button (user profile)"
},
"account_deletion_subject": "Delete my account",
"@account_deletion_subject": {
"description": "Subject of the webview open when the user wants to delete his account"
},
"account_deletion_path_segment": "account-deletion",
"@account_deletion_path_segment": {
"description": "Path segment of the url open in a webview open when the user wants to delete his account"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"description": "Path segment of the url open in a webview open when the user wants to delete his account"
"description": "Path segment of the url open in a webview open when the user wants to delete his account, go to "https://blog.openfoodfacts.org/en/account-deletion" and choose your language on the top right. Use the text behind the last "/"

},
"user_profile": "Account",
"@user_profile": {
"description": "User account (if connected)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/helpers/user_management_helper.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';
import 'package:webview_flutter/webview_flutter.dart';

class AccountDeletionWebview extends StatefulWidget {
@override
AccountDeletionWebviewState createState() => AccountDeletionWebviewState();
}

class AccountDeletionWebviewState extends State<AccountDeletionWebview> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class should be private

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep the english version looks like the only one work, stick to english smarter ^^

@override
void initState() {
super.initState();
// Enable virtual display.
if (Platform.isAndroid) {
WebView.platform = AndroidWebView();
}
}

String _getUrl(UserPreferences userPreferences) {
final String langageCode = userPreferences.appLanguageCode ??
Pierre-Monier marked this conversation as resolved.
Show resolved Hide resolved
Localizations.localeOf(context).toString();

final AppLocalizations appLocalizations = AppLocalizations.of(context);
final String subject = appLocalizations.account_deletion_subject;
final String pathSegment = appLocalizations.account_deletion_path_segment;

final String? userId = OpenFoodAPIConfiguration.globalUser?.userId;

final Uri uri = Uri(
scheme: 'https',
host: 'blog.openfoodfacts.org',
pathSegments: <String>[
langageCode,
Pierre-Monier marked this conversation as resolved.
Show resolved Hide resolved
pathSegment,
],
queryParameters: <String, String>{
'your-subject': subject,
if (userId != null && userId.isEmail)
'your-mail': userId
else if (userId != null)
'your-name': userId
Comment on lines +39 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to check if they need to be localized too, (hopefully not) though it seems the account deletion form is currently only available in english

});

return uri.toString();
}

@override
Widget build(BuildContext context) {
final UserPreferences userPreferences = context.watch<UserPreferences>();

return SmoothScaffold(
appBar: AppBar(),
body: WebView(
initialUrl: _getUrl(userPreferences),
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the user deleted his account. The webview stay open?

Copy link
Contributor Author

@Pierre-Monier Pierre-Monier Dec 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently yes, I think it's hard to detect when the user has delete his account. The page doesn't switch url when deletion is done. So the only way is to watch html content but this is clearly overkill. Note that the form doesn't directly delete the account.

One good question is if the user delete his account, it should be remove from localStorage, I think it's not the case now (but I havn't check deeply)

);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart';
Expand All @@ -11,11 +10,11 @@ import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/buttons/smooth_simple_button.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_text_form_field.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/launch_url_helper.dart';
import 'package:smooth_app/helpers/user_management_helper.dart';
import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart';
import 'package:smooth_app/pages/preferences/account_deletion_webview.dart';
import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart';
import 'package:smooth_app/pages/preferences/user_preferences_page.dart';
import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart';
Expand Down Expand Up @@ -231,7 +230,6 @@ class _UserPreferencesPageState extends State<UserPreferencesSection> {
final ThemeData theme = Theme.of(context);
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final Size size = MediaQuery.of(context).size;
final TextEditingController reasonController = TextEditingController();

final List<Widget> result;

Expand Down Expand Up @@ -306,47 +304,13 @@ class _UserPreferencesPageState extends State<UserPreferencesSection> {
const UserPreferencesListItemDivider(),
_getListTile(
appLocalizations.account_delete,
() async {
final String? reason = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return SmoothAlertDialog(
title: appLocalizations.account_delete,
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(appLocalizations.account_delete_message),
const SizedBox(
height: 5,
),
SmoothTextFormField(
type: TextFieldTypes.PLAIN_TEXT,
textInputType: TextInputType.text,
controller: reasonController,
hintText: appLocalizations.reason),
],
),
positiveAction: SmoothActionButton(
text: appLocalizations.account_delete,
onPressed: () =>
Navigator.pop(context, reasonController.text),
),
negativeAction: SmoothActionButton(
text: appLocalizations.cancel,
onPressed: () => Navigator.pop(context)),
);
});
if (reason != null) {
final Email email = Email(
body:
'${appLocalizations.email_body_account_deletion(userId)} $reason',
subject: appLocalizations.email_subject_account_deletion,
recipients: <String>['[email protected]'],
);

await FlutterEmailSender.send(email);
}
() {
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => AccountDeletionWebview(),
),
);
},
Icons.delete,
),
Expand Down
40 changes: 34 additions & 6 deletions packages/smooth_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -146,26 +146,26 @@ packages:
description:
path: "packages/camera/camera"
ref: smooth_camera
resolved-ref: "3c62b56bb6cc0ae1c9594f417ce6305ea45e214f"
url: "https://github.com/g123k/plugins.git"
resolved-ref: "2fe0f6e0c7043a9cb855b299315566187827b2a2"
url: "https://github.com/openfoodfacts/smooth_app_plugins_fork.git"
source: git
version: "0.9.6"
camera_platform_interface:
dependency: "direct main"
description:
path: "packages/camera/camera_platform_interface"
ref: smooth_camera
resolved-ref: "3c62b56bb6cc0ae1c9594f417ce6305ea45e214f"
url: "https://github.com/g123k/plugins.git"
resolved-ref: "2fe0f6e0c7043a9cb855b299315566187827b2a2"
url: "https://github.com/openfoodfacts/smooth_app_plugins_fork.git"
source: git
version: "2.1.6"
camera_web:
dependency: transitive
description:
path: "packages/camera/camera_web"
ref: smooth_camera
resolved-ref: "3c62b56bb6cc0ae1c9594f417ce6305ea45e214f"
url: "https://github.com/g123k/plugins.git"
resolved-ref: "2fe0f6e0c7043a9cb855b299315566187827b2a2"
url: "https://github.com/openfoodfacts/smooth_app_plugins_fork.git"
source: git
version: "0.2.1+6"
carousel_slider:
Expand Down Expand Up @@ -1379,6 +1379,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.4"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.10.4"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.5"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.5"
win32:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions packages/smooth_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies:
fimber: 0.6.6
shimmer: 2.0.0
lottie: 1.4.2
webview_flutter: ^3.0.4

dev_dependencies:
integration_test:
Expand Down
57 changes: 57 additions & 0 deletions packages/smooth_app/test/pages/user_preferences_page_test.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:openfoodfacts/personalized_search/product_preferences_selection.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:smooth_app/data_models/product_preferences.dart';
import 'package:smooth_app/data_models/user_management_provider.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/pages/preferences/account_deletion_webview.dart';
import 'package:smooth_app/pages/preferences/user_preferences_page.dart';
import 'package:smooth_app/themes/theme_provider.dart';
import 'package:webview_flutter/webview_flutter.dart';

import '../tests_utils/goldens.dart';
import '../tests_utils/local_database_mock.dart';
import '../tests_utils/mocks.dart';

void main() {
Expand Down Expand Up @@ -70,4 +74,57 @@ void main() {
});
}
});

testWidgets('it should open a webview for account deletion',
(WidgetTester tester) async {
// Override & mock out HTTP Requests
final HttpOverrides? priorOverrides = HttpOverrides.current;
HttpOverrides.global = MockHttpOverrides();

late UserPreferences userPreferences;
late ProductPreferences productPreferences;
late ThemeProvider themeProvider;

SharedPreferences.setMockInitialValues(
mockSharedPreferences(),
);

userPreferences = await UserPreferences.getUserPreferences();

productPreferences = ProductPreferences(ProductPreferencesSelection(
setImportance: userPreferences.setImportance,
getImportance: userPreferences.getImportance,
notify: () => productPreferences.notifyListeners(),
));
await productPreferences.init(PlatformAssetBundle());
await userPreferences.init(productPreferences);
themeProvider = ThemeProvider(userPreferences);

UserManagementProvider.mountCredentials(
userId: 'userId',
password: 'password',
);

await tester.pumpWidget(
MockSmoothApp(
userPreferences,
UserManagementProvider(),
productPreferences,
themeProvider,
const UserPreferencesPage(type: PreferencePageType.ACCOUNT),
localDatabase: MockLocalDatabase(),
),
);
await tester.pump();

await tester.tap(find.byIcon(Icons.delete));

await tester.pumpAndSettle();

expect(find.byType(AccountDeletionWebview), findsOneWidget);
expect(find.byType(WebView), findsOneWidget);

// Restore prior overrides
HttpOverrides.global = priorOverrides;
});
}
Loading