Skip to content

Commit 8757037

Browse files
authored
feat(ui_auth): show confirmation dialog when trying to unlink a provider (#116)
* wip * localizations * add license header
1 parent 109eb89 commit 8757037

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1241
-209
lines changed

packages/firebase_ui_auth/example/lib/main.dart

+1
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ class FirebaseAuthUIExample extends StatelessWidget {
267267
showMFATile: kIsWeb ||
268268
platform == TargetPlatform.iOS ||
269269
platform == TargetPlatform.android,
270+
showUnlinkConfirmationDialog: true,
270271
);
271272
},
272273
},

packages/firebase_ui_auth/lib/src/navigation/authentication.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Future<bool> showReauthenticateDialog({
2828
final reauthenticated = await showGeneralDialog<bool>(
2929
context: context,
3030
barrierDismissible: true,
31-
barrierLabel: l.cancelLabel,
31+
barrierLabel: l.cancelButtonLabel,
3232
pageBuilder: (_, __, ___) => FirebaseUIActions.inherit(
3333
from: context,
3434
child: ReauthenticateDialog(

packages/firebase_ui_auth/lib/src/screens/profile_screen.dart

+36
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,13 @@ class _LinkedProvidersRow extends StatefulWidget {
176176
final FirebaseAuth? auth;
177177
final List<AuthProvider> providers;
178178
final VoidCallback onProviderUnlinked;
179+
final bool showUnlinkConfirmationDialog;
179180

180181
const _LinkedProvidersRow({
181182
this.auth,
182183
required this.providers,
183184
required this.onProviderUnlinked,
185+
required this.showUnlinkConfirmationDialog,
184186
});
185187

186188
@override
@@ -201,13 +203,41 @@ class _LinkedProvidersRowState extends State<_LinkedProvidersRow> {
201203
});
202204
}
203205

206+
void Function() pop(bool value) {
207+
return () {
208+
Navigator.of(context).pop(value);
209+
};
210+
}
211+
204212
Future<void> _unlinkProvider(BuildContext context, String providerId) async {
205213
setState(() {
206214
unlinkingProvider = providerId;
207215
error = null;
208216
});
209217

218+
bool? confirmed = !widget.showUnlinkConfirmationDialog;
219+
220+
if (!confirmed) {
221+
final l = FirebaseUILocalizations.labelsOf(context);
222+
223+
confirmed = await showAdaptiveDialog<bool?>(
224+
context: context,
225+
builder: (context) {
226+
return UniversalAlert(
227+
onConfirm: pop(true),
228+
onCancel: pop(false),
229+
title: l.ulinkProviderAlertTitle,
230+
confirmButtonText: l.confirmUnlinkButtonLabel,
231+
cancelButtonText: l.cancelButtonLabel,
232+
message: l.unlinkProviderAlertMessage,
233+
);
234+
},
235+
);
236+
}
237+
210238
try {
239+
if (!(confirmed ?? false)) return;
240+
211241
final user = widget.auth!.currentUser!;
212242
await user.unlink(providerId);
213243
await user.reload();
@@ -712,6 +742,10 @@ class ProfileScreen extends MultiProviderScreen {
712742
/// are ignored.
713743
final Widget? avatar;
714744

745+
/// Indicates wether a confirmation dialog should be shown when the user
746+
/// tries to unlink a provider.
747+
final bool showUnlinkConfirmationDialog;
748+
715749
const ProfileScreen({
716750
super.key,
717751
super.auth,
@@ -726,6 +760,7 @@ class ProfileScreen extends MultiProviderScreen {
726760
this.cupertinoNavigationBar,
727761
this.actionCodeSettings,
728762
this.showMFATile = false,
763+
this.showUnlinkConfirmationDialog = false,
729764
});
730765

731766
Future<bool> _reauthenticate(BuildContext context) {
@@ -819,6 +854,7 @@ class ProfileScreen extends MultiProviderScreen {
819854
auth: auth,
820855
providers: linkedProviders,
821856
onProviderUnlinked: providersScopeKey.rebuild,
857+
showUnlinkConfirmationDialog: showUnlinkConfirmationDialog,
822858
),
823859
);
824860
},

packages/firebase_ui_auth/lib/src/widgets/different_method_sign_in_dialog.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class DifferentMethodSignInDialog extends StatelessWidget {
6060
onSignedIn: onSignedIn,
6161
),
6262
UniversalButton(
63-
text: l.cancelLabel,
63+
text: l.cancelButtonLabel,
6464
onPressed: () => Navigator.of(context).pop(),
6565
),
6666
],

packages/firebase_ui_localizations/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,30 @@ and make sure that your custom delegate extends `LocalizationsDelegate<FirebaseU
6262

6363
## Contributing
6464

65+
### Adding a new language
66+
6567
If you want to add a new language, make sure to add a relevant `.arb` file into `lib/i10n`.
6668

6769
- copy `lib/i10n/firebase_ui_en.arb` to `lib/i10n/firebase_ui_<your-language-code>.arb`
6870
- translate labels
6971
- run `dart run firebase_ui_localizations:gen_l10n`
7072
- commit the `.arb` and generated `.dart` file
7173
- submit a PR
74+
75+
### Adding a new label to existing languages
76+
77+
If you want to add new labels to existing languages,
78+
79+
- Execute `dart run firebase_ui_localizations:add_label`:
80+
81+
```bash
82+
dart run firebase_ui_localizations:add_label
83+
Label name?: someNewLabel
84+
Label description?: This will go to the doc comment of the label
85+
English translation?: Some new label
86+
Done!
87+
```
88+
89+
- Execute `dart run firebase_ui_localizations:gen_l10n`
90+
- Commit the changes
91+
- Submit a PR

packages/firebase_ui_localizations/bin/add_label.dart

+13-2
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,22 @@ String prompt(String tag) {
2020

2121
Future<void> main(List<String> args) async {
2222
final name = prompt('Label name');
23-
final description = prompt('Label description');
24-
final englishTranslation = prompt('English translation');
2523

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

27+
final enArb = File(path.join(l10nSrc.path, 'firebase_ui_en.arb'));
28+
final enContent =
29+
jsonDecode(await enArb.readAsString()) as Map<String, dynamic>;
30+
31+
if (enContent.containsKey(name)) {
32+
stderr.writeln('Label "$name" already exists');
33+
exit(1);
34+
}
35+
36+
final description = prompt('Label description');
37+
final englishTranslation = prompt('English translation');
38+
2939
final files = l10nSrc.listSync().whereType<File>().toList();
3040
final futures = files.map((e) async {
3141
final newContent = await addLabel(e, name, description, englishTranslation);
@@ -48,6 +58,7 @@ Future<Map<String, dynamic>> addLabel(
4858
String englishTranslation,
4959
) async {
5060
final content = jsonDecode(await file.readAsString()) as Map<String, dynamic>;
61+
5162
return {
5263
...content,
5364
"@@last_modified": DateTime.now().toIso8601String(),

packages/firebase_ui_localizations/bin/gen_l10n.dart

+133
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ void main() async {
7474
}).expand((element) => element);
7575

7676
await Future.wait([...genOps.cast<Future>()]);
77+
78+
await generateDefaultLocalizations(
79+
labelsByLocale['en']['default'].cast<String, dynamic>(),
80+
licenseHeader,
81+
);
82+
7783
await generateLanguagesList(labelsByLocale, licenseHeader);
7884
Process.runSync('dart', ['format', outDir.path]);
7985
}
@@ -226,3 +232,130 @@ Future<String> getLicenseHeader() async {
226232
return '// $e';
227233
}).join('\n');
228234
}
235+
236+
Future<void> generateDefaultLocalizations(
237+
Map<String, dynamic> arb,
238+
String licenseHeader,
239+
) async {
240+
final labels = arb.entries.where(isLabelEntry).map((e) {
241+
final meta = arb['@${e.key}'] ?? {};
242+
243+
return Label(
244+
key: e.key,
245+
translation: e.value,
246+
description: meta['description'],
247+
);
248+
}).toList()
249+
..sort((a, b) => a.key.compareTo(b.key));
250+
251+
final content = await getDefaultLocalizationsContent(labels, licenseHeader);
252+
final outFile = File(path.join(outDir.path, 'default_localizations.dart'));
253+
254+
if (!outFile.existsSync()) {
255+
outFile.createSync(recursive: true);
256+
}
257+
258+
final out = outFile.openWrite();
259+
out.write(content);
260+
await out.flush();
261+
await out.close();
262+
}
263+
264+
Future<String> getDefaultLocalizationsContent(
265+
List<Label> labels,
266+
String licenseHeader,
267+
) async {
268+
final sb = StringBuffer();
269+
270+
sb.writeln(licenseHeader);
271+
sb.writeln(defaultLocalizationsHeader);
272+
273+
sb.writeln('abstract class FirebaseUILocalizationLabels {');
274+
sb.writeln(' const FirebaseUILocalizationLabels();');
275+
276+
for (var label in labels) {
277+
sb.writeln();
278+
if (label.description != null && label.description!.isNotEmpty) {
279+
const prefix = ' /// ';
280+
for (var line in breakIntoLines(label.description!, 80 - prefix.length)) {
281+
sb.writeln('$prefix$line');
282+
}
283+
}
284+
sb.writeln(' String get ${label.key};');
285+
}
286+
287+
sb.writeln('}');
288+
sb.writeln();
289+
290+
sb.writeln(defaultLocalizationsFooter);
291+
292+
return sb.toString();
293+
}
294+
295+
const defaultLocalizationsHeader = '''
296+
297+
/*
298+
* THIS FILE IS GENERATED.
299+
* DO NOT MODIFY IT BY HAND UNLESS YOU KNOW WHAT YOU ARE DOING.
300+
*
301+
* See README.md for instructions on how to generate this file.
302+
*/
303+
304+
import 'package:flutter/material.dart';
305+
306+
import 'lang/en.dart';
307+
308+
/// An abstract class containing all labels that concrete languages should
309+
/// provide.
310+
///
311+
/// The easiest way to override some of these labels is to provide
312+
/// an object that extends [DefaultLocalizations] and pass it to the
313+
/// [MaterialApp.localizationsDelegates].
314+
///
315+
/// ```dart
316+
/// import 'package:firebase_ui_localizations/firebase_ui_localizations.dart';
317+
///
318+
/// class LabelOverrides extends DefaultLocalizations {
319+
/// const LabelOverrides();
320+
///
321+
/// @override
322+
/// String get emailInputLabel => 'Enter your email';
323+
/// }
324+
///
325+
/// MaterialApp(
326+
/// // ...
327+
/// localizationsDelegates: [
328+
/// FirebaseUILocalizations.withDefaultOverrides(const LabelOverrides()),
329+
/// GlobalMaterialLocalizations.delegate,
330+
/// GlobalWidgetsLocalizations.delegate,
331+
/// FirebaseUILocalizations.delegate,
332+
/// ],
333+
/// )
334+
/// ```''';
335+
336+
const defaultLocalizationsFooter = '''
337+
class DefaultLocalizations extends EnLocalizations {
338+
const DefaultLocalizations();
339+
}
340+
''';
341+
342+
List<String> breakIntoLines(String string, int lineLength) {
343+
final lines = <String>[];
344+
final words = string.split(' ');
345+
346+
var currentLine = StringBuffer();
347+
for (var word in words) {
348+
if (currentLine.length + word.length > lineLength) {
349+
lines.add(currentLine.toString());
350+
currentLine = StringBuffer();
351+
}
352+
353+
currentLine.write('$word ');
354+
}
355+
356+
if (currentLine.isNotEmpty) {
357+
lines.add(currentLine.toString());
358+
}
359+
360+
return lines;
361+
}

packages/firebase_ui_localizations/lib/l10n/firebase_ui_ar.arb

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"@@locale": "ar",
3-
"@@last_modified": "2023-09-06T14:33:29.121119",
3+
"@@last_modified": "2023-09-22T14:51:00.799791",
44
"accessDisabledErrorText": "تم إيقاف إذن الوصول إلى هذا الحساب مؤقتًا.",
55
"@accessDisabledErrorText": {
66
"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).",
@@ -488,5 +488,25 @@
488488
"@invalidVerificationCodeErrorText": {
489489
"description": "Error text indicating that entered SMS code is invalid",
490490
"placeholders": {}
491+
},
492+
"ulinkProviderAlertTitle": "Unlink provider",
493+
"@ulinkProviderAlertTitle": {
494+
"description": "Title that is shown in AlertDialog asking for provider unlink confirmation",
495+
"placeholders": {}
496+
},
497+
"confirmUnlinkButtonLabel": "Unlink",
498+
"@confirmUnlinkButtonLabel": {
499+
"description": "Unlink confirmation button label",
500+
"placeholders": {}
501+
},
502+
"cancelButtonLabel": "Cancel",
503+
"@cancelButtonLabel": {
504+
"description": "Cancel button label",
505+
"placeholders": {}
506+
},
507+
"unlinkProviderAlertMessage": "Are you sure you want to unlink this provider?",
508+
"@unlinkProviderAlertMessage": {
509+
"description": "Text that is shown as a message of the AlertDialog confirming provider unlink",
510+
"placeholders": {}
491511
}
492512
}

packages/firebase_ui_localizations/lib/l10n/firebase_ui_de.arb

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"@@locale": "de",
3-
"@@last_modified": "2023-09-06T14:33:29.117252",
3+
"@@last_modified": "2023-09-22T14:51:00.791691",
44
"accessDisabledErrorText": "Der Zugriff auf dieses Konto wurde vorübergehend gesperrt",
55
"@accessDisabledErrorText": {
66
"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).",
@@ -488,5 +488,25 @@
488488
"@invalidVerificationCodeErrorText": {
489489
"description": "Error text indicating that entered SMS code is invalid",
490490
"placeholders": {}
491+
},
492+
"ulinkProviderAlertTitle": "Unlink provider",
493+
"@ulinkProviderAlertTitle": {
494+
"description": "Title that is shown in AlertDialog asking for provider unlink confirmation",
495+
"placeholders": {}
496+
},
497+
"confirmUnlinkButtonLabel": "Unlink",
498+
"@confirmUnlinkButtonLabel": {
499+
"description": "Unlink confirmation button label",
500+
"placeholders": {}
501+
},
502+
"cancelButtonLabel": "Cancel",
503+
"@cancelButtonLabel": {
504+
"description": "Cancel button label",
505+
"placeholders": {}
506+
},
507+
"unlinkProviderAlertMessage": "Are you sure you want to unlink this provider?",
508+
"@unlinkProviderAlertMessage": {
509+
"description": "Text that is shown as a message of the AlertDialog confirming provider unlink",
510+
"placeholders": {}
491511
}
492512
}

0 commit comments

Comments
 (0)