Skip to content

Commit

Permalink
feat(ui_auth): show confirmation dialog when trying to unlink a provi…
Browse files Browse the repository at this point in the history
…der (#116)

* wip

* localizations

* add license header
  • Loading branch information
lesnitsky authored Oct 3, 2023
1 parent 109eb89 commit 8757037
Show file tree
Hide file tree
Showing 54 changed files with 1,241 additions and 209 deletions.
1 change: 1 addition & 0 deletions packages/firebase_ui_auth/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ class FirebaseAuthUIExample extends StatelessWidget {
showMFATile: kIsWeb ||
platform == TargetPlatform.iOS ||
platform == TargetPlatform.android,
showUnlinkConfirmationDialog: true,
);
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Future<bool> showReauthenticateDialog({
final reauthenticated = await showGeneralDialog<bool>(
context: context,
barrierDismissible: true,
barrierLabel: l.cancelLabel,
barrierLabel: l.cancelButtonLabel,
pageBuilder: (_, __, ___) => FirebaseUIActions.inherit(
from: context,
child: ReauthenticateDialog(
Expand Down
36 changes: 36 additions & 0 deletions packages/firebase_ui_auth/lib/src/screens/profile_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,13 @@ class _LinkedProvidersRow extends StatefulWidget {
final FirebaseAuth? auth;
final List<AuthProvider> providers;
final VoidCallback onProviderUnlinked;
final bool showUnlinkConfirmationDialog;

const _LinkedProvidersRow({
this.auth,
required this.providers,
required this.onProviderUnlinked,
required this.showUnlinkConfirmationDialog,
});

@override
Expand All @@ -201,13 +203,41 @@ class _LinkedProvidersRowState extends State<_LinkedProvidersRow> {
});
}

void Function() pop(bool value) {
return () {
Navigator.of(context).pop(value);
};
}

Future<void> _unlinkProvider(BuildContext context, String providerId) async {
setState(() {
unlinkingProvider = providerId;
error = null;
});

bool? confirmed = !widget.showUnlinkConfirmationDialog;

if (!confirmed) {
final l = FirebaseUILocalizations.labelsOf(context);

confirmed = await showAdaptiveDialog<bool?>(
context: context,
builder: (context) {
return UniversalAlert(
onConfirm: pop(true),
onCancel: pop(false),
title: l.ulinkProviderAlertTitle,
confirmButtonText: l.confirmUnlinkButtonLabel,
cancelButtonText: l.cancelButtonLabel,
message: l.unlinkProviderAlertMessage,
);
},
);
}

try {
if (!(confirmed ?? false)) return;

final user = widget.auth!.currentUser!;
await user.unlink(providerId);
await user.reload();
Expand Down Expand Up @@ -712,6 +742,10 @@ class ProfileScreen extends MultiProviderScreen {
/// are ignored.
final Widget? avatar;

/// Indicates wether a confirmation dialog should be shown when the user
/// tries to unlink a provider.
final bool showUnlinkConfirmationDialog;

const ProfileScreen({
super.key,
super.auth,
Expand All @@ -726,6 +760,7 @@ class ProfileScreen extends MultiProviderScreen {
this.cupertinoNavigationBar,
this.actionCodeSettings,
this.showMFATile = false,
this.showUnlinkConfirmationDialog = false,
});

Future<bool> _reauthenticate(BuildContext context) {
Expand Down Expand Up @@ -819,6 +854,7 @@ class ProfileScreen extends MultiProviderScreen {
auth: auth,
providers: linkedProviders,
onProviderUnlinked: providersScopeKey.rebuild,
showUnlinkConfirmationDialog: showUnlinkConfirmationDialog,
),
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class DifferentMethodSignInDialog extends StatelessWidget {
onSignedIn: onSignedIn,
),
UniversalButton(
text: l.cancelLabel,
text: l.cancelButtonLabel,
onPressed: () => Navigator.of(context).pop(),
),
],
Expand Down
20 changes: 20 additions & 0 deletions packages/firebase_ui_localizations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,30 @@ and make sure that your custom delegate extends `LocalizationsDelegate<FirebaseU

## Contributing

### Adding a new language

If you want to add a new language, make sure to add a relevant `.arb` file into `lib/i10n`.

- copy `lib/i10n/firebase_ui_en.arb` to `lib/i10n/firebase_ui_<your-language-code>.arb`
- translate labels
- run `dart run firebase_ui_localizations:gen_l10n`
- commit the `.arb` and generated `.dart` file
- submit a PR

### Adding a new label to existing languages

If you want to add new labels to existing languages,

- Execute `dart run firebase_ui_localizations:add_label`:

```bash
dart run firebase_ui_localizations:add_label
Label name?: someNewLabel
Label description?: This will go to the doc comment of the label
English translation?: Some new label
Done!
```

- Execute `dart run firebase_ui_localizations:gen_l10n`
- Commit the changes
- Submit a PR
15 changes: 13 additions & 2 deletions packages/firebase_ui_localizations/bin/add_label.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ String prompt(String tag) {

Future<void> main(List<String> args) async {
final name = prompt('Label name');
final description = prompt('Label description');
final englishTranslation = prompt('English translation');

final cwd = Directory.current.path;
final l10nSrc = Directory(path.join(cwd, 'lib', 'l10n'));

final enArb = File(path.join(l10nSrc.path, 'firebase_ui_en.arb'));
final enContent =
jsonDecode(await enArb.readAsString()) as Map<String, dynamic>;

if (enContent.containsKey(name)) {
stderr.writeln('Label "$name" already exists');
exit(1);
}

final description = prompt('Label description');
final englishTranslation = prompt('English translation');

final files = l10nSrc.listSync().whereType<File>().toList();
final futures = files.map((e) async {
final newContent = await addLabel(e, name, description, englishTranslation);
Expand All @@ -48,6 +58,7 @@ Future<Map<String, dynamic>> addLabel(
String englishTranslation,
) async {
final content = jsonDecode(await file.readAsString()) as Map<String, dynamic>;

return {
...content,
"@@last_modified": DateTime.now().toIso8601String(),
Expand Down
133 changes: 133 additions & 0 deletions packages/firebase_ui_localizations/bin/gen_l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ void main() async {
}).expand((element) => element);

await Future.wait([...genOps.cast<Future>()]);

await generateDefaultLocalizations(
labelsByLocale['en']['default'].cast<String, dynamic>(),
licenseHeader,
);

await generateLanguagesList(labelsByLocale, licenseHeader);
Process.runSync('dart', ['format', outDir.path]);
}
Expand Down Expand Up @@ -226,3 +232,130 @@ Future<String> getLicenseHeader() async {
return '// $e';
}).join('\n');
}

Future<void> generateDefaultLocalizations(
Map<String, dynamic> arb,
String licenseHeader,
) async {
final labels = arb.entries.where(isLabelEntry).map((e) {
final meta = arb['@${e.key}'] ?? {};

return Label(
key: e.key,
translation: e.value,
description: meta['description'],
);
}).toList()
..sort((a, b) => a.key.compareTo(b.key));

final content = await getDefaultLocalizationsContent(labels, licenseHeader);
final outFile = File(path.join(outDir.path, 'default_localizations.dart'));

if (!outFile.existsSync()) {
outFile.createSync(recursive: true);
}

final out = outFile.openWrite();
out.write(content);
await out.flush();
await out.close();
}

Future<String> getDefaultLocalizationsContent(
List<Label> labels,
String licenseHeader,
) async {
final sb = StringBuffer();

sb.writeln(licenseHeader);
sb.writeln(defaultLocalizationsHeader);

sb.writeln('abstract class FirebaseUILocalizationLabels {');
sb.writeln(' const FirebaseUILocalizationLabels();');

for (var label in labels) {
sb.writeln();
if (label.description != null && label.description!.isNotEmpty) {
const prefix = ' /// ';
for (var line in breakIntoLines(label.description!, 80 - prefix.length)) {
sb.writeln('$prefix$line');
}
}
sb.writeln(' String get ${label.key};');
}

sb.writeln('}');
sb.writeln();

sb.writeln(defaultLocalizationsFooter);

return sb.toString();
}

const defaultLocalizationsHeader = '''
/*
* THIS FILE IS GENERATED.
* DO NOT MODIFY IT BY HAND UNLESS YOU KNOW WHAT YOU ARE DOING.
*
* See README.md for instructions on how to generate this file.
*/
import 'package:flutter/material.dart';
import 'lang/en.dart';
/// An abstract class containing all labels that concrete languages should
/// provide.
///
/// The easiest way to override some of these labels is to provide
/// an object that extends [DefaultLocalizations] and pass it to the
/// [MaterialApp.localizationsDelegates].
///
/// ```dart
/// import 'package:firebase_ui_localizations/firebase_ui_localizations.dart';
///
/// class LabelOverrides extends DefaultLocalizations {
/// const LabelOverrides();
///
/// @override
/// String get emailInputLabel => 'Enter your email';
/// }
///
/// MaterialApp(
/// // ...
/// localizationsDelegates: [
/// FirebaseUILocalizations.withDefaultOverrides(const LabelOverrides()),
/// GlobalMaterialLocalizations.delegate,
/// GlobalWidgetsLocalizations.delegate,
/// FirebaseUILocalizations.delegate,
/// ],
/// )
/// ```''';

const defaultLocalizationsFooter = '''
class DefaultLocalizations extends EnLocalizations {
const DefaultLocalizations();
}
''';

List<String> breakIntoLines(String string, int lineLength) {
final lines = <String>[];
final words = string.split(' ');

var currentLine = StringBuffer();
for (var word in words) {
if (currentLine.length + word.length > lineLength) {
lines.add(currentLine.toString());
currentLine = StringBuffer();
}

currentLine.write('$word ');
}

if (currentLine.isNotEmpty) {
lines.add(currentLine.toString());
}

return lines;
}
22 changes: 21 additions & 1 deletion packages/firebase_ui_localizations/lib/l10n/firebase_ui_ar.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"@@locale": "ar",
"@@last_modified": "2023-09-06T14:33:29.121119",
"@@last_modified": "2023-09-22T14:51:00.799791",
"accessDisabledErrorText": "تم إيقاف إذن الوصول إلى هذا الحساب مؤقتًا.",
"@accessDisabledErrorText": {
"description": "Used as an error message when account is blocked and user tries to perform some actions with the account (e.g. unlinking a credential).",
Expand Down Expand Up @@ -488,5 +488,25 @@
"@invalidVerificationCodeErrorText": {
"description": "Error text indicating that entered SMS code is invalid",
"placeholders": {}
},
"ulinkProviderAlertTitle": "Unlink provider",
"@ulinkProviderAlertTitle": {
"description": "Title that is shown in AlertDialog asking for provider unlink confirmation",
"placeholders": {}
},
"confirmUnlinkButtonLabel": "Unlink",
"@confirmUnlinkButtonLabel": {
"description": "Unlink confirmation button label",
"placeholders": {}
},
"cancelButtonLabel": "Cancel",
"@cancelButtonLabel": {
"description": "Cancel button label",
"placeholders": {}
},
"unlinkProviderAlertMessage": "Are you sure you want to unlink this provider?",
"@unlinkProviderAlertMessage": {
"description": "Text that is shown as a message of the AlertDialog confirming provider unlink",
"placeholders": {}
}
}
22 changes: 21 additions & 1 deletion packages/firebase_ui_localizations/lib/l10n/firebase_ui_de.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"@@locale": "de",
"@@last_modified": "2023-09-06T14:33:29.117252",
"@@last_modified": "2023-09-22T14:51:00.791691",
"accessDisabledErrorText": "Der Zugriff auf dieses Konto wurde vorübergehend gesperrt",
"@accessDisabledErrorText": {
"description": "Used as an error message when account is blocked and user tries to perform some actions with the account (e.g. unlinking a credential).",
Expand Down Expand Up @@ -488,5 +488,25 @@
"@invalidVerificationCodeErrorText": {
"description": "Error text indicating that entered SMS code is invalid",
"placeholders": {}
},
"ulinkProviderAlertTitle": "Unlink provider",
"@ulinkProviderAlertTitle": {
"description": "Title that is shown in AlertDialog asking for provider unlink confirmation",
"placeholders": {}
},
"confirmUnlinkButtonLabel": "Unlink",
"@confirmUnlinkButtonLabel": {
"description": "Unlink confirmation button label",
"placeholders": {}
},
"cancelButtonLabel": "Cancel",
"@cancelButtonLabel": {
"description": "Cancel button label",
"placeholders": {}
},
"unlinkProviderAlertMessage": "Are you sure you want to unlink this provider?",
"@unlinkProviderAlertMessage": {
"description": "Text that is shown as a message of the AlertDialog confirming provider unlink",
"placeholders": {}
}
}
Loading

0 comments on commit 8757037

Please sign in to comment.