diff --git a/flutter/assets/GitHub.svg b/flutter/assets/GitHub.svg deleted file mode 100644 index ef0bb12a7..000000000 --- a/flutter/assets/GitHub.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/Google.svg b/flutter/assets/Google.svg deleted file mode 100644 index df394a84f..000000000 --- a/flutter/assets/Google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-apple.svg b/flutter/assets/auth-apple.svg new file mode 100644 index 000000000..a3c2e871d --- /dev/null +++ b/flutter/assets/auth-apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-auth0.svg b/flutter/assets/auth-auth0.svg new file mode 100644 index 000000000..e8c7557e7 --- /dev/null +++ b/flutter/assets/auth-auth0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-azure.svg b/flutter/assets/auth-azure.svg new file mode 100644 index 000000000..0482b22e6 --- /dev/null +++ b/flutter/assets/auth-azure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-default.svg b/flutter/assets/auth-default.svg new file mode 100644 index 000000000..905c9ca9b --- /dev/null +++ b/flutter/assets/auth-default.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/flutter/assets/auth-facebook.svg b/flutter/assets/auth-facebook.svg new file mode 100644 index 000000000..d921a6716 --- /dev/null +++ b/flutter/assets/auth-facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-github.svg b/flutter/assets/auth-github.svg new file mode 100644 index 000000000..1ba71c98b --- /dev/null +++ b/flutter/assets/auth-github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-google.svg b/flutter/assets/auth-google.svg new file mode 100644 index 000000000..f9ab170e6 --- /dev/null +++ b/flutter/assets/auth-google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/Okta.svg b/flutter/assets/auth-okta.svg similarity index 100% rename from flutter/assets/Okta.svg rename to flutter/assets/auth-okta.svg diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index e96f02772..71fbfcdd9 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2314,3 +2314,10 @@ Widget unreadTopRightBuilder(RxInt? count, {Widget? icon}) { ], ); } + +String toCapitalized(String s) { + if (s.isEmpty) { + return s; + } + return s.substring(0, 1).toUpperCase() + s.substring(1); +} diff --git a/flutter/lib/common/widgets/login.dart b/flutter/lib/common/widgets/login.dart index 17cc7090c..d7037e58f 100644 --- a/flutter/lib/common/widgets/login.dart +++ b/flutter/lib/common/widgets/login.dart @@ -12,25 +12,33 @@ import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; import './dialog.dart'; +const kOpSvgList = ['github', 'google', 'apple', 'okta', 'facebook', 'azure', 'auth0']; + class _IconOP extends StatelessWidget { - final String icon; - final double iconWidth; + final String op; + final String? icon; final EdgeInsets margin; const _IconOP( {Key? key, + required this.op, required this.icon, - required this.iconWidth, this.margin = const EdgeInsets.symmetric(horizontal: 4.0)}) : super(key: key); @override Widget build(BuildContext context) { + final svgFile = kOpSvgList.contains(op.toLowerCase()) ? op.toLowerCase() : 'default'; return Container( margin: margin, - child: SvgPicture.asset( - 'assets/$icon.svg', - width: iconWidth, - ), + child: icon == null + ? SvgPicture.asset( + 'assets/auth-$svgFile.svg', + width: 20, + ) + : SvgPicture.string( + icon!, + width: 20, + ), ); } } @@ -38,7 +46,7 @@ class _IconOP extends StatelessWidget { class ButtonOP extends StatelessWidget { final String op; final RxString curOP; - final double iconWidth; + final String? icon; final Color primaryColor; final double height; final Function() onTap; @@ -47,7 +55,7 @@ class ButtonOP extends StatelessWidget { Key? key, required this.op, required this.curOP, - required this.iconWidth, + required this.icon, required this.primaryColor, required this.height, required this.onTap, @@ -61,7 +69,7 @@ class ButtonOP extends StatelessWidget { width: 200, child: Obx(() => ElevatedButton( style: ElevatedButton.styleFrom( - primary: curOP.value.isEmpty || curOP.value == op + backgroundColor: curOP.value.isEmpty || curOP.value == op ? primaryColor : Colors.grey, ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), @@ -69,17 +77,21 @@ class ButtonOP extends StatelessWidget { child: Row( children: [ SizedBox( - width: 30, - child: _IconOP( - icon: op, - iconWidth: iconWidth, - margin: EdgeInsets.only(right: 5), - )), + width: 30, + child: _IconOP( + op: op, + icon: icon, + margin: EdgeInsets.only(right: 5), + ), + ), Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Center( - child: Text('${translate("Continue with")} $op')))), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Center( + child: Text( + '${translate("Continue with")} ${op.toLowerCase() == "github" ? "GitHub" : toCapitalized(op)}')), + ), + ), ], ))), ), @@ -89,8 +101,8 @@ class ButtonOP extends StatelessWidget { class ConfigOP { final String op; - final double iconWidth; - ConfigOP({required this.op, required this.iconWidth}); + final String? icon; + ConfigOP({required this.op, required this.icon}); } class WidgetOP extends StatefulWidget { @@ -182,7 +194,7 @@ class _WidgetOPState extends State { ButtonOP( op: widget.config.op, curOP: widget.curOP, - iconWidth: widget.config.iconWidth, + icon: widget.config.icon, primaryColor: str2color(widget.config.op, 0x7f), height: 36, onTap: () async { @@ -380,7 +392,7 @@ Future loginDialog() async { final loginOptions = [].obs; Future.delayed(Duration.zero, () async { - loginOptions.value = await UserModel.queryLoginOptions(); + loginOptions.value = await UserModel.queryOidcLoginOptions(); }); final res = await gFFI.dialogManager.show((setState, close, context) { @@ -460,12 +472,8 @@ Future loginDialog() async { } thirdAuthWidget() => Obx(() { - final oidcOptions = loginOptions - .where((opt) => opt.startsWith(kAuthReqTypeOidc)) - .map((opt) => opt.substring(kAuthReqTypeOidc.length)) - .toList(); return Offstage( - offstage: oidcOptions.isEmpty, + offstage: loginOptions.isEmpty, child: Column( children: [ const SizedBox( @@ -480,12 +488,8 @@ Future loginDialog() async { height: 8.0, ), LoginWidgetOP( - ops: [ - ConfigOP(op: 'GitHub', iconWidth: 20), - ConfigOP(op: 'Google', iconWidth: 20), - ConfigOP(op: 'Okta', iconWidth: 38), - ] - .where((op) => oidcOptions.contains(op.op.toLowerCase())) + ops: loginOptions + .map((e) => ConfigOP(op: e['name'], icon: e['icon'])) .toList(), curOP: curOP, cbLogin: (Map authBody) { diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index 83df2e632..ebed47587 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -163,15 +163,27 @@ class UserModel { return loginResponse; } - static Future> queryLoginOptions() async { + static Future> queryOidcLoginOptions() async { try { final url = await bind.mainGetApiServer(); if (url.trim().isEmpty) return []; final resp = await http.get(Uri.parse('$url/api/login-options')); - return jsonDecode(resp.body); + final List ops = []; + for (final item in jsonDecode(resp.body)) { + ops.add(item as String); + } + for (final item in ops) { + if (item.startsWith('common-oidc/')) { + return jsonDecode(item.substring('common-oidc/'.length)); + } + } + return ops + .where((item) => item.startsWith('oidc/')) + .map((item) => {'name': item.substring('oidc/'.length)}) + .toList(); } catch (e) { debugPrint( - "queryLoginOptions: jsonDecode resp body failed: ${e.toString()}"); + "queryOidcLoginOptions: jsonDecode resp body failed: ${e.toString()}"); return []; } }