Skip to content

Commit

Permalink
feat: web account deletion (#3416)
Browse files Browse the repository at this point in the history
* feat: add webview for account deletion

* feat: add webview for account deletion

* clean(preferences): remove useless reasonController

* clean: fix variable naming

* refactor: add deletionwebview state private and remove l10n from path segments
  • Loading branch information
Pierre-Monier authored Dec 9, 2022
1 parent fbab382 commit 61d9f39
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 54 deletions.
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

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
4 changes: 4 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,10 @@
"@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"
},
"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,58 @@
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: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
State<AccountDeletionWebview> createState() => _AccountDeletionWebviewState();
}

class _AccountDeletionWebviewState extends State<AccountDeletionWebview> {
@override
void initState() {
super.initState();
// Enable virtual display.
if (Platform.isAndroid) {
WebView.platform = AndroidWebView();
}
}

String _getUrl() {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final String subject = appLocalizations.account_deletion_subject;

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

final Uri uri = Uri(
scheme: 'https',
host: 'blog.openfoodfacts.org',
pathSegments: <String>[
'en',
'account-deletion',
],
queryParameters: <String, String>{
'your-subject': subject,
if (userId != null && userId.isEmail)
'your-mail': userId
else if (userId != null)
'your-name': userId
});

return uri.toString();
}

@override
Widget build(BuildContext context) {
return SmoothScaffold(
appBar: AppBar(),
body: WebView(
initialUrl: _getUrl(),
),
);
}
}
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
28 changes: 28 additions & 0 deletions packages/smooth_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,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 @@ -80,6 +80,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;
});
}
4 changes: 4 additions & 0 deletions packages/smooth_app/test/tests_utils/local_database_mock.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import 'package:mockito/mockito.dart';
import 'package:smooth_app/database/local_database.dart';

class MockLocalDatabase extends Mock implements LocalDatabase {}
9 changes: 7 additions & 2 deletions packages/smooth_app/test/tests_utils/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:provider/provider.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/database/local_database.dart';
import 'package:smooth_app/themes/smooth_theme.dart';
import 'package:smooth_app/themes/theme_provider.dart';

Expand All @@ -20,13 +21,15 @@ class MockSmoothApp extends StatelessWidget {
this.userManagementProvider,
this.productPreferences,
this.themeProvider,
this.child,
);
this.child, {
this.localDatabase,
});

final UserPreferences userPreferences;
final UserManagementProvider userManagementProvider;
final ProductPreferences productPreferences;
final ThemeProvider themeProvider;
final LocalDatabase? localDatabase;
final Widget child;

@override
Expand All @@ -39,6 +42,8 @@ class MockSmoothApp extends StatelessWidget {
ChangeNotifierProvider<ThemeProvider>.value(value: themeProvider),
ChangeNotifierProvider<UserManagementProvider>.value(
value: userManagementProvider),
if (localDatabase != null)
ChangeNotifierProvider<LocalDatabase>.value(value: localDatabase!),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
Expand Down

0 comments on commit 61d9f39

Please sign in to comment.