initial design updates to signup/login process (#1268)
* initial design updates to signup/login process * feat: added signup/login assets, better button styling and animations * fix: signup / login updated based on mobile testing
This commit is contained in:
parent
d6d6875882
commit
16bdce9bd2
32 changed files with 1520 additions and 991 deletions
|
|
@ -4631,5 +4631,21 @@
|
|||
"couldNotFindTTS": "We couldn't find a text-to-speech engine for your current target language. ",
|
||||
"ttsInstructionsHyperlink": "Click here to view instructions for downloading a new voice on your device.",
|
||||
"currentVersion": "Current Version",
|
||||
"latestVersion": "Latest Version"
|
||||
"latestVersion": "Latest Version",
|
||||
"createAnAccount": "Create an account",
|
||||
"signIn": "Sign in",
|
||||
"signUpWithEmail": "Sign up with Email",
|
||||
"signUpWithGoogle": "Sign up with Google",
|
||||
"signUpWithApple": "Sign up with Apple",
|
||||
"yourUsername": "Your username",
|
||||
"yourEmail": "Your email",
|
||||
"pleaseEnterAnEmail": "Please enter an email address",
|
||||
"signInWithGoogle": "Sign in with Google",
|
||||
"signInWithApple": "Sign in with Apple",
|
||||
"chooseYourAvatar": "Choose your avatar",
|
||||
"iWantToLearn": "I want to learn",
|
||||
"letsStart": "Let's start",
|
||||
"pleaseAgreeToTOS": "Please agree to the Terms and Conditions",
|
||||
"pleaseEnterEmail": "Please enter a valid email address.",
|
||||
"pleaseSelectALanguage": "Please select a language"
|
||||
}
|
||||
|
|
|
|||
BIN
assets/pangea/Avatar_1.png
Normal file
BIN
assets/pangea/Avatar_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
BIN
assets/pangea/Avatar_2.png
Normal file
BIN
assets/pangea/Avatar_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
BIN
assets/pangea/Avatar_3.png
Normal file
BIN
assets/pangea/Avatar_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 KiB |
BIN
assets/pangea/Avatar_4.png
Normal file
BIN
assets/pangea/Avatar_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 348 KiB |
BIN
assets/pangea/Avatar_5.png
Normal file
BIN
assets/pangea/Avatar_5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 641 KiB |
BIN
assets/pangea/PangeaChat_Glow_Logo.png
Normal file
BIN
assets/pangea/PangeaChat_Glow_Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 285 KiB |
|
|
@ -10,7 +10,6 @@ import 'package:fluffychat/pages/chat_members/chat_members.dart';
|
|||
import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings.dart';
|
||||
import 'package:fluffychat/pages/chat_search/chat_search_page.dart';
|
||||
import 'package:fluffychat/pages/device_settings/device_settings.dart';
|
||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
||||
import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart';
|
||||
import 'package:fluffychat/pages/login/login.dart';
|
||||
import 'package:fluffychat/pages/new_group/new_group.dart';
|
||||
|
|
@ -28,9 +27,10 @@ import 'package:fluffychat/pages/settings_security/settings_security.dart';
|
|||
import 'package:fluffychat/pages/settings_style/settings_style.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
import 'package:fluffychat/pangea/pages/find_partner/find_partner.dart';
|
||||
import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_subscription/settings_subscription.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/login_or_signup_view.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/signup.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/user_settings.dart';
|
||||
import 'package:fluffychat/pangea/widgets/class/join_with_link.dart';
|
||||
import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
||||
import 'package:fluffychat/widgets/layouts/two_column_layout.dart';
|
||||
|
|
@ -73,7 +73,10 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const HomeserverPicker(addMultiAccount: false),
|
||||
// #Pangea
|
||||
// const HomeserverPicker(addMultiAccount: false),
|
||||
const LoginOrSignupView(),
|
||||
// Pangea#
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
routes: [
|
||||
|
|
@ -95,6 +98,17 @@ abstract class AppRoutes {
|
|||
const SignupPage(),
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'email',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SignupPage(withEmail: true),
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
),
|
||||
],
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
|
|
@ -121,7 +135,7 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const PUserAge(),
|
||||
const UserSettingsPage(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/tor_stub.dart'
|
||||
|
|
@ -13,7 +11,6 @@ import 'package:fluffychat/utils/tor_stub.dart'
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -36,11 +33,9 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
bool isLoading = false;
|
||||
bool isLoggingIn = false;
|
||||
|
||||
// #Pangea
|
||||
// final TextEditingController homeserverController = TextEditingController(
|
||||
// text: AppConfig.defaultHomeserver,
|
||||
// );
|
||||
// Pangea#
|
||||
final TextEditingController homeserverController = TextEditingController(
|
||||
text: AppConfig.defaultHomeserver,
|
||||
);
|
||||
|
||||
String? error;
|
||||
|
||||
|
|
@ -82,71 +77,48 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
checkHomeserverAction();
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
Map<String, dynamic>? _rawLoginTypes;
|
||||
// Pangea#
|
||||
|
||||
// #Pangea
|
||||
// void onSubmitted([_]) {
|
||||
// if (isLoading || _checkHomeserverCooldown?.isActive == true) {
|
||||
// return tryCheckHomeserverActionWithoutCooldown();
|
||||
// }
|
||||
// if (supportsSso) return ssoLoginAction();
|
||||
// if (supportsPasswordLogin) return login();
|
||||
// return tryCheckHomeserverActionWithoutCooldown();
|
||||
// }
|
||||
// Pangea#
|
||||
void onSubmitted([_]) {
|
||||
if (isLoading || _checkHomeserverCooldown?.isActive == true) {
|
||||
return tryCheckHomeserverActionWithoutCooldown();
|
||||
}
|
||||
if (supportsSso) return ssoLoginAction();
|
||||
if (supportsPasswordLogin) return login();
|
||||
return tryCheckHomeserverActionWithoutCooldown();
|
||||
}
|
||||
|
||||
/// Starts an analysis of the given homeserver. It uses the current domain and
|
||||
/// makes sure that it is prefixed with https. Then it searches for the
|
||||
/// well-known information and forwards to the login page depending on the
|
||||
/// login type.
|
||||
Future<void> checkHomeserverAction([_]) async {
|
||||
// #Pangea
|
||||
// final homeserverInput =
|
||||
// homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
||||
final homeserverInput =
|
||||
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
||||
|
||||
// if (homeserverInput.isEmpty || !homeserverInput.contains('.')) {
|
||||
// setState(() {
|
||||
// error = loginFlows = null;
|
||||
// isLoading = false;
|
||||
// Matrix.of(context).getLoginClient().homeserver = null;
|
||||
// _lastCheckedUrl = null;
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// if (_lastCheckedUrl == homeserverInput) return;
|
||||
if (homeserverInput.isEmpty || !homeserverInput.contains('.')) {
|
||||
setState(() {
|
||||
error = loginFlows = null;
|
||||
isLoading = false;
|
||||
Matrix.of(context).getLoginClient().homeserver = null;
|
||||
_lastCheckedUrl = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (_lastCheckedUrl == homeserverInput) return;
|
||||
|
||||
// _lastCheckedUrl = homeserverInput;
|
||||
_lastCheckedUrl = AppConfig.defaultHomeserver;
|
||||
// Pangea#
|
||||
_lastCheckedUrl = homeserverInput;
|
||||
setState(() {
|
||||
error = loginFlows = null;
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// #Pangea
|
||||
// var homeserver = Uri.parse(homeserverInput);
|
||||
// if (homeserver.scheme.isEmpty) {
|
||||
// homeserver = Uri.https(homeserverInput, '');
|
||||
// }
|
||||
var homeserver = Uri.parse(AppConfig.defaultHomeserver);
|
||||
var homeserver = Uri.parse(homeserverInput);
|
||||
if (homeserver.scheme.isEmpty) {
|
||||
homeserver = Uri.https(AppConfig.defaultHomeserver, '');
|
||||
homeserver = Uri.https(homeserverInput, '');
|
||||
}
|
||||
// Pangea#
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
|
||||
this.loginFlows = loginFlows;
|
||||
// #Pangea
|
||||
if (supportsSso) {
|
||||
_rawLoginTypes = await client.request(
|
||||
RequestType.GET,
|
||||
'/client/v3/login',
|
||||
);
|
||||
}
|
||||
// Pangea#
|
||||
} catch (e) {
|
||||
setState(
|
||||
() => error = (e).toLocalizedString(
|
||||
|
|
@ -173,11 +145,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
|
||||
bool get supportsPasswordLogin => _supportsFlow('m.login.password');
|
||||
|
||||
void ssoLoginAction(
|
||||
// #Pangea
|
||||
IdentityProvider provider,
|
||||
// Pangea#
|
||||
) async {
|
||||
void ssoLoginAction() async {
|
||||
final redirectUrl = kIsWeb
|
||||
? Uri.parse(html.window.location.href)
|
||||
.resolveUri(
|
||||
|
|
@ -189,42 +157,18 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
: 'http://localhost:3001//login';
|
||||
|
||||
final url = Matrix.of(context).getLoginClient().homeserver!.replace(
|
||||
// #Pangea
|
||||
// path: '/_matrix/client/v3/login/sso/redirect',
|
||||
path:
|
||||
'/_matrix/client/v3/login/sso/redirect${provider.id == null ? '' : '/${provider.id}'}',
|
||||
// Pangea#
|
||||
path: '/_matrix/client/v3/login/sso/redirect',
|
||||
queryParameters: {'redirectUrl': redirectUrl},
|
||||
);
|
||||
|
||||
final urlScheme = isDefaultPlatform
|
||||
? Uri.parse(redirectUrl).scheme
|
||||
: "http://localhost:3001";
|
||||
// #Pangea
|
||||
// final result = await FlutterWebAuth2.authenticate(
|
||||
// url: url.toString(),
|
||||
// callbackUrlScheme: urlScheme,
|
||||
// options: const FlutterWebAuth2Options(),
|
||||
// );
|
||||
String result;
|
||||
try {
|
||||
result = await FlutterWebAuth2.authenticate(
|
||||
url: url.toString(),
|
||||
callbackUrlScheme: urlScheme,
|
||||
options: const FlutterWebAuth2Options(),
|
||||
);
|
||||
} catch (err) {
|
||||
if (err is PlatformException && err.code == 'CANCELED') {
|
||||
debugPrint("user cancelled SSO login");
|
||||
return;
|
||||
}
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
final result = await FlutterWebAuth2.authenticate(
|
||||
url: url.toString(),
|
||||
callbackUrlScheme: urlScheme,
|
||||
options: const FlutterWebAuth2Options(),
|
||||
);
|
||||
final token = Uri.parse(result).queryParameters['loginToken'];
|
||||
if (token?.isEmpty ?? false) return;
|
||||
|
||||
|
|
@ -233,17 +177,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
isLoading = isLoggingIn = true;
|
||||
});
|
||||
try {
|
||||
// #Pangea
|
||||
final loginRes = await Matrix.of(context).getLoginClient().login(
|
||||
// await Matrix.of(context).getLoginClient().login(
|
||||
// Pangea#
|
||||
await Matrix.of(context).getLoginClient().login(
|
||||
LoginType.mLoginToken,
|
||||
token: token,
|
||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
);
|
||||
// #Pangea
|
||||
GoogleAnalytics.login(provider.name!, loginRes.userId);
|
||||
// Pangea#
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
error = e.toLocalizedString(context);
|
||||
|
|
@ -258,12 +196,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
}
|
||||
|
||||
void login() async {
|
||||
// #Pangea
|
||||
// if (!supportsPasswordLogin) {
|
||||
// homeserverController.text = AppConfig.defaultHomeserver;
|
||||
// await checkHomeserverAction();
|
||||
// }
|
||||
// Pangea#
|
||||
if (!supportsPasswordLogin) {
|
||||
homeserverController.text = AppConfig.defaultHomeserver;
|
||||
await checkHomeserverAction();
|
||||
}
|
||||
context.push(
|
||||
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
|
||||
);
|
||||
|
|
@ -291,13 +227,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
final client = Matrix.of(context).getLoginClient();
|
||||
await client.importDump(String.fromCharCodes(await file.readAsBytes()));
|
||||
Matrix.of(context).initMatrix();
|
||||
} catch (e, s) {
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
error = e.toLocalizedString(context);
|
||||
});
|
||||
// #Pangea
|
||||
ErrorHandler.logError(e: e, s: s);
|
||||
// Pangea#
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
|
@ -307,27 +240,6 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
List<IdentityProvider>? get identityProviders {
|
||||
final loginTypes = _rawLoginTypes;
|
||||
if (loginTypes == null) return null;
|
||||
final List? rawProviders =
|
||||
loginTypes.tryGetList('flows')?.singleWhereOrNull(
|
||||
(flow) => flow['type'] == AuthenticationTypes.sso,
|
||||
)['identity_providers'] ??
|
||||
[
|
||||
{'id': null},
|
||||
];
|
||||
if (rawProviders == null) return null;
|
||||
final list =
|
||||
rawProviders.map((json) => IdentityProvider.fromJson(json)).toList();
|
||||
if (PlatformInfos.isCupertinoStyle) {
|
||||
list.sort((a, b) => a.brand == 'apple' ? -1 : 1);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void onMoreAction(MoreLoginActions action) {
|
||||
switch (action) {
|
||||
case MoreLoginActions.passwordLogin:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/pages/connect/p_sso_button.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart';
|
||||
import 'package:fluffychat/pangea/widgets/signup/signup_buttons.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../../config/themes.dart';
|
||||
import 'homeserver_picker.dart';
|
||||
|
||||
class HomeserverPickerView extends StatelessWidget {
|
||||
|
|
@ -23,306 +25,225 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
|
||||
return LoginScaffold(
|
||||
enforceMobileMode: Matrix.of(context).client.isLogged(),
|
||||
// #Pangea
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
AppConfig.applicationName,
|
||||
controller.widget.addMultiAccount
|
||||
? L10n.of(context).addAccount
|
||||
: L10n.of(context).login,
|
||||
),
|
||||
actions: [
|
||||
PopupMenuButton<MoreLoginActions>(
|
||||
onSelected: controller.onMoreAction,
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(
|
||||
value: MoreLoginActions.passwordLogin,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.login_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).loginWithMatrixId),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: MoreLoginActions.privacy,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.privacy_tip_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).privacy),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: MoreLoginActions.about,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.info_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).about),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// appBar: AppBar(
|
||||
// centerTitle: true,
|
||||
// title: Text(
|
||||
// controller.widget.addMultiAccount
|
||||
// ? L10n.of(context).addAccount
|
||||
// : L10n.of(context).login,
|
||||
// ),
|
||||
// actions: [
|
||||
// PopupMenuButton<MoreLoginActions>(
|
||||
// onSelected: controller.onMoreAction,
|
||||
// itemBuilder: (_) => [
|
||||
// PopupMenuItem(
|
||||
// value: MoreLoginActions.passwordLogin,
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const Icon(Icons.login_outlined),
|
||||
// const SizedBox(width: 12),
|
||||
// Text(L10n.of(context).loginWithMatrixId),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// PopupMenuItem(
|
||||
// value: MoreLoginActions.privacy,
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const Icon(Icons.privacy_tip_outlined),
|
||||
// const SizedBox(width: 12),
|
||||
// Text(L10n.of(context).privacy),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// PopupMenuItem(
|
||||
// value: MoreLoginActions.about,
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const Icon(Icons.info_outlined),
|
||||
// const SizedBox(width: 12),
|
||||
// Text(L10n.of(context).about),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// Pangea#
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: controller.isLoading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: Column(
|
||||
child: Column(
|
||||
children: [
|
||||
// display a prominent banner to import session for TOR browser
|
||||
// users. This feature is just some UX sugar as TOR users are
|
||||
// usually forced to logout as TOR browser is non-persistent
|
||||
AnimatedContainer(
|
||||
height: controller.isTorBrowser ? 64 : 0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
bottom: Radius.circular(8),
|
||||
),
|
||||
color: theme.colorScheme.surface,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.vpn_key),
|
||||
title: Text(L10n.of(context).hydrateTor),
|
||||
subtitle: Text(L10n.of(context).hydrateTorLong),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
onTap: controller.restoreBackup,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Hero(
|
||||
tag: 'info-logo',
|
||||
child: Image.asset(
|
||||
'./assets/banner_transparent.png',
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: SelectableLinkify(
|
||||
text: L10n.of(context).welcomeText,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
linkStyle: TextStyle(
|
||||
color: theme.colorScheme.secondary,
|
||||
decorationColor: theme.colorScheme.secondary,
|
||||
),
|
||||
onOpen: (link) => launchUrlString(link.url),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (controller.error != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
const Center(
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 48,
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Center(
|
||||
child: Text(
|
||||
controller.error!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 18,
|
||||
TextField(
|
||||
onChanged:
|
||||
controller.tryCheckHomeserverActionWithCooldown,
|
||||
onSubmitted: controller.onSubmitted,
|
||||
onTap:
|
||||
controller.tryCheckHomeserverActionWithCooldown,
|
||||
controller: controller.homeserverController,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: controller.isLoading
|
||||
? Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
alignment: Alignment.center,
|
||||
child: const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child:
|
||||
CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.search_outlined),
|
||||
filled: false,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
] else
|
||||
const SignupButtons(),
|
||||
if (controller.identityProviders != null) ...[
|
||||
...controller.identityProviders!.map(
|
||||
(provider) => Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Hero(
|
||||
tag:
|
||||
"ssobutton ${provider.id ?? provider.name}",
|
||||
child: PangeaSsoButton(
|
||||
identityProvider: provider,
|
||||
onPressed: () =>
|
||||
controller.ssoLoginAction(provider),
|
||||
),
|
||||
),
|
||||
hintText: AppConfig.defaultHomeserver,
|
||||
hintStyle: TextStyle(
|
||||
color: theme.colorScheme.surfaceTint,
|
||||
),
|
||||
),
|
||||
if (controller.supportsPasswordLogin)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Hero(
|
||||
tag: 'signinButton',
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.login,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
const PangeaLogoSvg(width: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
labelText: 'Sign in with:',
|
||||
errorText: controller.error,
|
||||
errorMaxLines: 4,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog.adaptive(
|
||||
title: Text(
|
||||
L10n.of(context).whatIsAHomeserver,
|
||||
),
|
||||
content: Linkify(
|
||||
text: L10n.of(context)
|
||||
.homeserverDescription,
|
||||
),
|
||||
actions: [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () => launchUrl(
|
||||
Uri.https('servers.joinmatrix.org'),
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).signInWithUsername,
|
||||
L10n.of(context)
|
||||
.discoverHomeservers,
|
||||
),
|
||||
),
|
||||
AdaptiveDialogAction(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(L10n.of(context).close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.info_outlined),
|
||||
),
|
||||
],
|
||||
// display a prominent banner to import session for TOR browser
|
||||
// users. This feature is just some UX sugar as TOR users are
|
||||
// usually forced to logout as TOR browser is non-persistent
|
||||
// #Pangea
|
||||
// AnimatedContainer(
|
||||
// height: controller.isTorBrowser ? 64 : 0,
|
||||
// duration: FluffyThemes.animationDuration,
|
||||
// curve: FluffyThemes.animationCurve,
|
||||
// clipBehavior: Clip.hardEdge,
|
||||
// decoration: const BoxDecoration(),
|
||||
// child: Material(
|
||||
// clipBehavior: Clip.hardEdge,
|
||||
// borderRadius: const BorderRadius.vertical(
|
||||
// bottom: Radius.circular(8),
|
||||
// ),
|
||||
// color: theme.colorScheme.surface,
|
||||
// child: ListTile(
|
||||
// leading: const Icon(Icons.vpn_key),
|
||||
// title: Text(L10n.of(context).hydrateTor),
|
||||
// subtitle: Text(L10n.of(context).hydrateTorLong),
|
||||
// trailing: const Icon(Icons.chevron_right_outlined),
|
||||
// onTap: controller.restoreBackup,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// alignment: Alignment.center,
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
// child: Hero(
|
||||
// tag: 'info-logo',
|
||||
// child: Image.asset(
|
||||
// './assets/banner_transparent.png',
|
||||
// fit: BoxFit.fitWidth,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 32),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
// child: SelectableLinkify(
|
||||
// text: L10n.of(context).welcomeText,
|
||||
// style: TextStyle(
|
||||
// color: theme.colorScheme.onSecondaryContainer,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// ),
|
||||
// textAlign: TextAlign.center,
|
||||
// linkStyle: TextStyle(
|
||||
// color: theme.colorScheme.secondary,
|
||||
// decorationColor: theme.colorScheme.secondary,
|
||||
// ),
|
||||
// onOpen: (link) => launchUrlString(link.url),
|
||||
// ),
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// const Padding(
|
||||
// padding: EdgeInsets.all(32.0),
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
// children: [
|
||||
// TextField(
|
||||
// onChanged:
|
||||
// controller.tryCheckHomeserverActionWithCooldown,
|
||||
// onEditingComplete: controller
|
||||
// .tryCheckHomeserverActionWithoutCooldown,
|
||||
// onSubmitted: controller
|
||||
// .tryCheckHomeserverActionWithoutCooldown,
|
||||
// onTap:
|
||||
// controller.tryCheckHomeserverActionWithCooldown,
|
||||
// controller: controller.homeserverController,
|
||||
// autocorrect: false,
|
||||
// keyboardType: TextInputType.url,
|
||||
// decoration: InputDecoration(
|
||||
// prefixIcon: controller.isLoading
|
||||
// ? Container(
|
||||
// width: 16,
|
||||
// height: 16,
|
||||
// alignment: Alignment.center,
|
||||
// child: const SizedBox(
|
||||
// width: 16,
|
||||
// height: 16,
|
||||
// child:
|
||||
// CircularProgressIndicator.adaptive(
|
||||
// strokeWidth: 2,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : const Icon(Icons.search_outlined),
|
||||
// filled: false,
|
||||
// border: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.circular(
|
||||
// AppConfig.borderRadius,
|
||||
// ),
|
||||
// ),
|
||||
// hintText: AppConfig.defaultHomeserver,
|
||||
// hintStyle: TextStyle(
|
||||
// color: theme.colorScheme.surfaceTint,
|
||||
// ),
|
||||
// labelText: 'Sign in with:',
|
||||
// errorText: controller.error,
|
||||
// errorMaxLines: 4,
|
||||
// suffixIcon: IconButton(
|
||||
// onPressed: () {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (context) => AlertDialog.adaptive(
|
||||
// title: Text(
|
||||
// L10n.of(context).whatIsAHomeserver,
|
||||
// ),
|
||||
// content: Linkify(
|
||||
// text: L10n.of(context)
|
||||
// .homeserverDescription,
|
||||
// ),
|
||||
// actions: [
|
||||
// AdaptiveDialogAction(
|
||||
// onPressed: () => launchUrl(
|
||||
// Uri.https('servers.joinmatrix.org'),
|
||||
// ),
|
||||
// child: Text(
|
||||
// L10n.of(context)
|
||||
// .discoverHomeservers,
|
||||
// ),
|
||||
// ),
|
||||
// AdaptiveDialogAction(
|
||||
// onPressed: Navigator.of(context).pop,
|
||||
// child: Text(L10n.of(context).close),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// icon: const Icon(Icons.info_outlined),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 32),
|
||||
// ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// backgroundColor: theme.colorScheme.primary,
|
||||
// foregroundColor: theme.colorScheme.onPrimary,
|
||||
// ),
|
||||
// onPressed:
|
||||
// controller.isLoggingIn || controller.isLoading
|
||||
// ? null
|
||||
// : () => controller.supportsSso
|
||||
// ? controller.ssoLoginAction
|
||||
// : controller.supportsPasswordLogin
|
||||
// ? controller.login
|
||||
// : null,
|
||||
// child: Text(L10n.of(context).continueText),
|
||||
// ),
|
||||
// TextButton(
|
||||
// style: TextButton.styleFrom(
|
||||
// foregroundColor: theme.colorScheme.secondary,
|
||||
// textStyle: theme.textTheme.labelMedium,
|
||||
// ),
|
||||
// onPressed:
|
||||
// controller.isLoggingIn || controller.isLoading
|
||||
// ? null
|
||||
// : controller.restoreBackup,
|
||||
// child: Text(L10n.of(context).hydrate),
|
||||
// ),
|
||||
// Pangea#
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
onPressed:
|
||||
controller.isLoggingIn || controller.isLoading
|
||||
? null
|
||||
: controller.supportsSso
|
||||
? controller.ssoLoginAction
|
||||
: controller.supportsPasswordLogin
|
||||
? controller.login
|
||||
: null,
|
||||
child: Text(L10n.of(context).continueText),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.secondary,
|
||||
textStyle: theme.textTheme.labelMedium,
|
||||
),
|
||||
onPressed:
|
||||
controller.isLoggingIn || controller.isLoading
|
||||
? null
|
||||
: controller.restoreBackup,
|
||||
child: Text(L10n.of(context).hydrate),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_view.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -11,7 +12,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../utils/platform_infos.dart';
|
||||
import 'login_view.dart';
|
||||
|
||||
class Login extends StatefulWidget {
|
||||
const Login({super.key});
|
||||
|
|
@ -23,13 +23,26 @@ class Login extends StatefulWidget {
|
|||
class LoginController extends State<Login> {
|
||||
final TextEditingController usernameController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
String? usernameText;
|
||||
String? passwordText;
|
||||
|
||||
String? usernameError;
|
||||
String? passwordError;
|
||||
|
||||
bool loading = false;
|
||||
bool showPassword = false;
|
||||
|
||||
// #Pangea
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
bool get enabledSignIn =>
|
||||
!loading &&
|
||||
usernameText != null &&
|
||||
usernameText!.isNotEmpty &&
|
||||
passwordText != null &&
|
||||
passwordText!.isNotEmpty;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
|
|
@ -46,6 +59,25 @@ class LoginController extends State<Login> {
|
|||
passwordError = err.toLocalizedString(context);
|
||||
});
|
||||
});
|
||||
|
||||
usernameController.addListener(() {
|
||||
_setStateOnTextChange(usernameText, usernameController.text);
|
||||
usernameText = usernameController.text;
|
||||
});
|
||||
|
||||
passwordController.addListener(() {
|
||||
_setStateOnTextChange(passwordText, passwordController.text);
|
||||
passwordText = passwordController.text;
|
||||
});
|
||||
}
|
||||
|
||||
void _setStateOnTextChange(String? oldText, String newText) {
|
||||
if ((oldText == null || oldText.isEmpty) && (newText.isNotEmpty)) {
|
||||
setState(() {});
|
||||
}
|
||||
if ((oldText != null && oldText.isNotEmpty) && (newText.isEmpty)) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
|
|
@ -53,6 +85,11 @@ class LoginController extends State<Login> {
|
|||
setState(() => showPassword = !loading && !showPassword);
|
||||
|
||||
void login() async {
|
||||
// #Pangea
|
||||
final valid = formKey.currentState!.validate();
|
||||
if (!valid) return;
|
||||
// Pangea#
|
||||
|
||||
final matrix = Matrix.of(context);
|
||||
if (usernameController.text.isEmpty) {
|
||||
setState(() => usernameError = L10n.of(context).pleaseEnterYourUsername);
|
||||
|
|
@ -113,10 +150,22 @@ class LoginController extends State<Login> {
|
|||
GoogleAnalytics.login("pangea", loginRes.userId);
|
||||
// Pangea#
|
||||
} on MatrixException catch (exception) {
|
||||
setState(() => passwordError = exception.errorMessage);
|
||||
// #Pangea
|
||||
// setState(() => passwordError = exception.errorMessage);
|
||||
setState(() {
|
||||
passwordError = exception.errorMessage;
|
||||
usernameError = exception.errorMessage;
|
||||
});
|
||||
// Pangea#
|
||||
return setState(() => loading = false);
|
||||
} catch (exception) {
|
||||
setState(() => passwordError = exception.toString());
|
||||
// #Pangea
|
||||
// setState(() => passwordError = exception.toString());
|
||||
setState(() {
|
||||
passwordError = exception.toString();
|
||||
usernameError = exception.toString();
|
||||
});
|
||||
// Pangea#
|
||||
return setState(() => loading = false);
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +328,10 @@ class LoginController extends State<Login> {
|
|||
static int sendAttempt = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => LoginView(this);
|
||||
// #Pangea
|
||||
// Widget build(BuildContext context) => LoginView(this);
|
||||
Widget build(BuildContext context) => PangeaLoginView(this);
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
extension on String {
|
||||
|
|
|
|||
|
|
@ -76,12 +76,12 @@ class PermissionsController extends BaseController {
|
|||
// _getRoomRules(roomID)?.isShareVideo ?? isUser18();
|
||||
|
||||
/// works for both roomID of chat and class
|
||||
bool canSharePhoto(String? roomID) => isUser18();
|
||||
bool canSharePhoto(String? roomID) => true;
|
||||
// Rules can't be edited; default to true
|
||||
// _getRoomRules(roomID)?.isSharePhoto ?? isUser18();
|
||||
|
||||
/// works for both roomID of chat and class
|
||||
bool canShareFile(String? roomID) => isUser18();
|
||||
bool canShareFile(String? roomID) => true;
|
||||
// Rules can't be edited; default to true
|
||||
// _getRoomRules(roomID)?.isShareFiles ?? isUser18();
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class UserController extends BaseController {
|
|||
}
|
||||
|
||||
/// Creates a new profile for the user with the given date of birth.
|
||||
Future<void> createProfile({required DateTime dob}) async {
|
||||
Future<void> createProfile({DateTime? dob}) async {
|
||||
final userSettings = UserSettings(
|
||||
dateOfBirth: dob,
|
||||
createdAt: DateTime.now(),
|
||||
|
|
@ -165,15 +165,13 @@ class UserController extends BaseController {
|
|||
return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", "");
|
||||
}
|
||||
|
||||
/// Checks if user data is available and the date of birth is set.
|
||||
/// Returns a [Future] that completes with a [bool] value indicating
|
||||
/// whether the user data is available and the date of birth is set.
|
||||
Future<bool> get isUserDataAvailableAndDateOfBirthSet async {
|
||||
/// Checks if user data is available and the user's l2 is set.
|
||||
Future<bool> get isUserDataAvailableAndL2Set async {
|
||||
try {
|
||||
// the function fetchUserModel() uses a completer, so it shouldn't
|
||||
// re-call the endpoint if it has already been called
|
||||
await initialize();
|
||||
return profile.userSettings.dateOfBirth != null;
|
||||
return profile.userSettings.targetLanguage != null;
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ class PAuthGaurd {
|
|||
) async {
|
||||
if (pController != null) {
|
||||
if (Matrix.of(context).client.isLogged()) {
|
||||
final bool dobIsSet = await pController!
|
||||
.userController.isUserDataAvailableAndDateOfBirthSet;
|
||||
final bool dobIsSet =
|
||||
await pController!.userController.isUserDataAvailableAndL2Set;
|
||||
return dobIsSet ? '/rooms' : '/user_age';
|
||||
}
|
||||
return null;
|
||||
|
|
@ -36,8 +36,8 @@ class PAuthGaurd {
|
|||
if (!Matrix.of(context).client.isLogged()) {
|
||||
return '/home';
|
||||
}
|
||||
final bool dobIsSet = await pController!
|
||||
.userController.isUserDataAvailableAndDateOfBirthSet;
|
||||
final bool dobIsSet =
|
||||
await pController!.userController.isUserDataAvailableAndL2Set;
|
||||
return dobIsSet ? null : '/user_age';
|
||||
} else {
|
||||
debugPrint("controller is null in pguard check");
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class UserSettings {
|
|||
});
|
||||
|
||||
factory UserSettings.fromJson(Map<String, dynamic> json) => UserSettings(
|
||||
dateOfBirth: DateTime.parse(json[ModelKey.userDateOfBirth]),
|
||||
dateOfBirth: json[ModelKey.userDateOfBirth] != null
|
||||
? DateTime.parse(json[ModelKey.userDateOfBirth])
|
||||
: null,
|
||||
createdAt: json[ModelKey.userCreatedAt] != null
|
||||
? DateTime.parse(json[ModelKey.userCreatedAt])
|
||||
: null,
|
||||
|
|
|
|||
|
|
@ -1,84 +1,111 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/sso_login_action.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:matrix/matrix_api_lite/model/matrix_exception.dart';
|
||||
|
||||
class ButtonInfo {
|
||||
String iconPath;
|
||||
String text;
|
||||
enum SSOProvider { google, apple }
|
||||
|
||||
ButtonInfo(this.iconPath, this.text);
|
||||
extension on SSOProvider {
|
||||
String get id {
|
||||
switch (this) {
|
||||
case SSOProvider.google:
|
||||
return "oidc-google";
|
||||
case SSOProvider.apple:
|
||||
return "oidc-apple";
|
||||
}
|
||||
}
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
case SSOProvider.google:
|
||||
return "Google";
|
||||
case SSOProvider.apple:
|
||||
return "Apple";
|
||||
}
|
||||
}
|
||||
|
||||
String get asset {
|
||||
switch (this) {
|
||||
case SSOProvider.google:
|
||||
return "assets/pangea/google.svg";
|
||||
case SSOProvider.apple:
|
||||
return "assets/pangea/apple.svg";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PangeaSsoButton extends StatelessWidget {
|
||||
final IdentityProvider identityProvider;
|
||||
final void Function()? onPressed;
|
||||
class PangeaSsoButton extends StatefulWidget {
|
||||
final String title;
|
||||
final SSOProvider provider;
|
||||
const PangeaSsoButton({
|
||||
required this.title,
|
||||
required this.provider,
|
||||
super.key,
|
||||
required this.identityProvider,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
ButtonInfo getButtonInfo(BuildContext context) {
|
||||
switch (identityProvider.id) {
|
||||
case "oidc-google":
|
||||
return ButtonInfo(
|
||||
"assets/pangea/google.svg",
|
||||
"${L10n.of(context).loginOrSignup} Google",
|
||||
);
|
||||
case "oidc-apple":
|
||||
return ButtonInfo(
|
||||
"assets/pangea/apple.svg",
|
||||
"${L10n.of(context).loginOrSignup} Apple",
|
||||
);
|
||||
default:
|
||||
return ButtonInfo(
|
||||
"assets/pangea/pangea.svg",
|
||||
"${L10n.of(context).loginOrSignup} Pangea Chat",
|
||||
);
|
||||
@override
|
||||
PangeaSsoButtonState createState() => PangeaSsoButtonState();
|
||||
}
|
||||
|
||||
class PangeaSsoButtonState extends State<PangeaSsoButton> {
|
||||
bool _loading = false;
|
||||
String? _error;
|
||||
|
||||
Future<void> _runSSOLogin() async {
|
||||
try {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
await pangeaSSOLoginAction(
|
||||
IdentityProvider(
|
||||
id: widget.provider.id,
|
||||
name: widget.provider.name,
|
||||
),
|
||||
Matrix.of(context).getLoginClient(),
|
||||
context,
|
||||
);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
if (err is MatrixException) {
|
||||
_error = err.errorMessage;
|
||||
} else {
|
||||
_error = L10n.of(context).oopsSomethingWentWrong;
|
||||
}
|
||||
_error = err.toString();
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ButtonInfo buttonInfo = getButtonInfo(context);
|
||||
return ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Row(
|
||||
return FullWidthButton(
|
||||
depressed: _loading,
|
||||
error: _error,
|
||||
loading: _loading,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
identityProvider.icon == null
|
||||
? SvgPicture.asset(
|
||||
buttonInfo.iconPath,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? AppConfig.primaryColor
|
||||
: AppConfig.primaryColorLight,
|
||||
)
|
||||
: Image.network(
|
||||
Uri.parse(identityProvider.icon!)
|
||||
.getDownloadLink(Matrix.of(context).getLoginClient())
|
||||
.toString(),
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
// #Pangea
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||
child: Text(
|
||||
identityProvider.name != null
|
||||
? buttonInfo.text
|
||||
: (identityProvider.brand != null
|
||||
? L10n.of(context).loginOrSignup
|
||||
: L10n.of(context).loginOrSignup),
|
||||
SvgPicture.asset(
|
||||
widget.provider.asset,
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(widget.title),
|
||||
],
|
||||
),
|
||||
onPressed: _runSSOLogin,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/age_limits.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/pages/p_user_age/p_user_age_view.dart';
|
||||
import 'package:fluffychat/pangea/utils/p_extension.dart';
|
||||
import 'package:fluffychat/widgets/fluffy_chat_app.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../utils/error_handler.dart';
|
||||
|
||||
class PUserAge extends StatefulWidget {
|
||||
const PUserAge({super.key});
|
||||
|
||||
@override
|
||||
PUserAgeController createState() => PUserAgeController();
|
||||
}
|
||||
|
||||
class PUserAgeController extends State<PUserAge> {
|
||||
bool loading = false;
|
||||
int? selectedAge;
|
||||
TextEditingController dobController = TextEditingController();
|
||||
|
||||
String? error;
|
||||
bool unknownErrorState = false;
|
||||
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
pangeaController.startChatWithBotIfNotPresent();
|
||||
}
|
||||
|
||||
String? dobValidator() {
|
||||
try {
|
||||
if (selectedDate == null) {
|
||||
return L10n.of(context).yourBirthdayPleaseShort;
|
||||
}
|
||||
if (!selectedDate!.isAtLeastYearsOld(AgeLimits.toUseTheApp)) {
|
||||
return L10n.of(context).mustBe13;
|
||||
}
|
||||
return null;
|
||||
} catch (err, stack) {
|
||||
ErrorHandler.logError(e: err, s: stack);
|
||||
return L10n.of(context).invalidDob;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? get selectedDate {
|
||||
if (selectedAge == null) return null;
|
||||
final now = DateTime.now();
|
||||
return DateTime(now.year - selectedAge!, now.month, now.day);
|
||||
}
|
||||
|
||||
//Note: used linear progress bar (also used in fluffychat signup button) for consistency
|
||||
Future<void> createUserInPangea() async {
|
||||
try {
|
||||
setState(() => error = dobValidator());
|
||||
if (error?.isNotEmpty == true) return;
|
||||
setState(() => loading = true);
|
||||
|
||||
final DateTime? dob =
|
||||
pangeaController.userController.profile.userSettings.dateOfBirth;
|
||||
|
||||
if (dob == null) {
|
||||
await pangeaController.userController.createProfile(
|
||||
dob: selectedDate!,
|
||||
);
|
||||
} else {
|
||||
pangeaController.userController.updateProfile((profile) {
|
||||
profile.userSettings.dateOfBirth = selectedDate!;
|
||||
return profile;
|
||||
});
|
||||
}
|
||||
pangeaController.subscriptionController.reinitialize();
|
||||
FluffyChatApp.router.go('/rooms');
|
||||
} catch (err, s) {
|
||||
setState(() {
|
||||
unknownErrorState = true;
|
||||
});
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
void setSelectedAge(int? value) {
|
||||
setState(() {
|
||||
selectedAge = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return !unknownErrorState
|
||||
? PUserAgeView(this)
|
||||
: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(50),
|
||||
child: Text(
|
||||
"${L10n.of(context).oopsSomethingWentWrong} \n ${L10n.of(context).errorPleaseRefresh}",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../../widgets/layouts/login_scaffold.dart';
|
||||
|
||||
class PUserAgeView extends StatelessWidget {
|
||||
final PUserAgeController controller;
|
||||
const PUserAgeView(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LoginScaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: !controller.loading,
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withAlpha(50),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Text(
|
||||
L10n.of(context).yourBirthdayPlease,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).certifyAge(13),
|
||||
style: const TextStyle(color: Colors.black, fontSize: 14),
|
||||
),
|
||||
leading: Radio<int>(
|
||||
value: 13,
|
||||
groupValue: controller.selectedAge,
|
||||
onChanged: controller.setSelectedAge,
|
||||
activeColor: AppConfig.primaryColor,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).certifyAge(18),
|
||||
style: const TextStyle(color: Colors.black, fontSize: 14),
|
||||
),
|
||||
leading: Radio<int>(
|
||||
value: 18,
|
||||
groupValue: controller.selectedAge,
|
||||
onChanged: controller.setSelectedAge,
|
||||
activeColor: AppConfig.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Hero(
|
||||
tag: 'loginButton',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.createUserInPangea,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
),
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context).getStarted),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
146
lib/pangea/pages/sign_up/full_width_button.dart
Normal file
146
lib/pangea/pages/sign_up/full_width_button.dart
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/widgets/pressable_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FullWidthButton extends StatefulWidget {
|
||||
final Widget title;
|
||||
final void Function()? onPressed;
|
||||
final bool depressed;
|
||||
final String? error;
|
||||
final bool loading;
|
||||
final bool enabled;
|
||||
|
||||
const FullWidthButton({
|
||||
required this.title,
|
||||
required this.onPressed,
|
||||
this.depressed = false,
|
||||
this.error,
|
||||
this.loading = false,
|
||||
this.enabled = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
FullWidthButtonState createState() => FullWidthButtonState();
|
||||
}
|
||||
|
||||
class FullWidthButtonState extends State<FullWidthButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(4, 4, 4, widget.error == null ? 4 : 0),
|
||||
child: AnimatedOpacity(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
opacity: widget.enabled ? 1 : 0.5,
|
||||
child: PressableButton(
|
||||
depressed: widget.depressed || !widget.enabled,
|
||||
onPressed: widget.onPressed,
|
||||
borderRadius: BorderRadius.circular(36),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.enabled
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).disabledColor,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
disabledForegroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
textStyle: const TextStyle(fontSize: 18),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(36),
|
||||
),
|
||||
),
|
||||
onPressed: widget.enabled
|
||||
? () => ButtonPressedNotification().dispatch(context)
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
widget.loading
|
||||
? const Expanded(child: LinearProgressIndicator())
|
||||
: widget.title,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: widget.error == null
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 30,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Text(
|
||||
widget.error!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FullWidthTextField extends StatelessWidget {
|
||||
final String hintText;
|
||||
final bool autocorrect;
|
||||
final bool autofocus;
|
||||
final bool obscureText;
|
||||
final TextInputAction? textInputAction;
|
||||
final TextInputType? keyboardType;
|
||||
final String? Function(String?)? validator;
|
||||
final TextEditingController? controller;
|
||||
final String? errorText;
|
||||
|
||||
const FullWidthTextField({
|
||||
required this.hintText,
|
||||
this.autocorrect = false,
|
||||
this.autofocus = false,
|
||||
this.obscureText = false,
|
||||
this.textInputAction,
|
||||
this.keyboardType,
|
||||
this.validator,
|
||||
this.controller,
|
||||
this.errorText,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: TextFormField(
|
||||
obscureText: obscureText,
|
||||
autocorrect: autocorrect,
|
||||
autofocus: autofocus,
|
||||
textInputAction: textInputAction,
|
||||
keyboardType: keyboardType,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(36.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
errorText: errorText,
|
||||
),
|
||||
validator: validator,
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
25
lib/pangea/pages/sign_up/login_or_signup_view.dart
Normal file
25
lib/pangea/pages/sign_up/login_or_signup_view.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:fluffychat/pangea/pages/sign_up/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class LoginOrSignupView extends StatelessWidget {
|
||||
const LoginOrSignupView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PangeaLoginScaffold(
|
||||
children: [
|
||||
FullWidthButton(
|
||||
title: Text(L10n.of(context).createAnAccount),
|
||||
onPressed: () => context.go('/home/signup'),
|
||||
),
|
||||
FullWidthButton(
|
||||
title: Text(L10n.of(context).signIn),
|
||||
onPressed: () => context.go('/home/login'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
78
lib/pangea/pages/sign_up/pangea_login_scaffold.dart
Normal file
78
lib/pangea/pages/sign_up/pangea_login_scaffold.dart
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PangeaLoginScaffold extends StatelessWidget {
|
||||
final String mainAssetPath;
|
||||
final Uint8List? mainAssetBytes;
|
||||
final List<Widget> children;
|
||||
final bool showAppName;
|
||||
|
||||
const PangeaLoginScaffold({
|
||||
required this.children,
|
||||
this.mainAssetPath = "assets/pangea/PangeaChat_Glow_Logo.png",
|
||||
this.mainAssetBytes,
|
||||
this.showAppName = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 450,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
child: ClipOval(
|
||||
child: mainAssetBytes != null
|
||||
? Image.memory(
|
||||
mainAssetBytes!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.asset(
|
||||
mainAssetPath,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (showAppName)
|
||||
Text(
|
||||
AppConfig.applicationName,
|
||||
style: Theme.of(context).textTheme.displaySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
87
lib/pangea/pages/sign_up/pangea_login_view.dart
Normal file
87
lib/pangea/pages/sign_up/pangea_login_view.dart
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import 'package:fluffychat/pages/login/login.dart';
|
||||
import 'package:fluffychat/pangea/pages/connect/p_sso_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class PangeaLoginView extends StatelessWidget {
|
||||
final LoginController controller;
|
||||
|
||||
const PangeaLoginView(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: controller.formKey,
|
||||
child: PangeaLoginScaffold(
|
||||
children: [
|
||||
FullWidthTextField(
|
||||
hintText: L10n.of(context).username,
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return L10n.of(context).pleaseEnterYourUsername;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: controller.usernameController,
|
||||
errorText: controller.usernameError,
|
||||
),
|
||||
FullWidthTextField(
|
||||
hintText: L10n.of(context).password,
|
||||
obscureText: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return L10n.of(context).pleaseEnterYourPassword;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: controller.passwordController,
|
||||
errorText: controller.passwordError,
|
||||
),
|
||||
FullWidthButton(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
PangeaLogoSvg(
|
||||
width: 20,
|
||||
forceColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(L10n.of(context).signIn),
|
||||
],
|
||||
),
|
||||
onPressed: controller.enabledSignIn ? controller.login : null,
|
||||
loading: controller.loading,
|
||||
enabled: controller.enabledSignIn,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
const Expanded(child: Divider()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(L10n.of(context).or),
|
||||
),
|
||||
const Expanded(child: Divider()),
|
||||
],
|
||||
),
|
||||
),
|
||||
PangeaSsoButton(
|
||||
provider: SSOProvider.google,
|
||||
title: L10n.of(context).signInWithGoogle,
|
||||
),
|
||||
PangeaSsoButton(
|
||||
provider: SSOProvider.apple,
|
||||
title: L10n.of(context).signInWithApple,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/pangea/pages/sign_up/signup_view.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/signup_with_email_view.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
|
|
@ -9,22 +10,67 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
import 'package:matrix/matrix_api_lite/model/matrix_exception.dart';
|
||||
|
||||
class SignupPage extends StatefulWidget {
|
||||
const SignupPage({super.key});
|
||||
final bool withEmail;
|
||||
const SignupPage({
|
||||
this.withEmail = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
SignupPageController createState() => SignupPageController();
|
||||
}
|
||||
|
||||
class SignupPageController extends State<SignupPage> {
|
||||
final TextEditingController usernameController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final TextEditingController password2Controller = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
|
||||
String? usernameText;
|
||||
String? passwordText;
|
||||
String? emailText;
|
||||
|
||||
String? error;
|
||||
bool loading = false;
|
||||
bool showPassword = false;
|
||||
bool noEmailWarningConfirmed = false;
|
||||
bool displaySecondPasswordField = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
usernameController.addListener(() {
|
||||
_setStateOnTextChange(usernameText, usernameController.text);
|
||||
usernameText = usernameController.text;
|
||||
});
|
||||
|
||||
passwordController.addListener(() {
|
||||
_setStateOnTextChange(passwordText, passwordController.text);
|
||||
passwordText = passwordController.text;
|
||||
});
|
||||
|
||||
emailController.addListener(() {
|
||||
_setStateOnTextChange(emailText, emailController.text);
|
||||
emailText = emailController.text;
|
||||
});
|
||||
}
|
||||
|
||||
bool get enableSignUp =>
|
||||
!loading &&
|
||||
isTnCChecked &&
|
||||
emailController.text.isNotEmpty &&
|
||||
usernameController.text.isNotEmpty &&
|
||||
passwordController.text.isNotEmpty;
|
||||
|
||||
void _setStateOnTextChange(String? oldText, String newText) {
|
||||
if ((oldText == null || oldText.isEmpty) && (newText.isNotEmpty)) {
|
||||
setState(() {});
|
||||
}
|
||||
if ((oldText != null && oldText.isNotEmpty) && (newText.isEmpty)) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
static const int minPassLength = 8;
|
||||
|
||||
void toggleShowPassword() => setState(() => showPassword = !showPassword);
|
||||
|
|
@ -63,9 +109,13 @@ class SignupPageController extends State<SignupPage> {
|
|||
}
|
||||
|
||||
String? emailTextFieldValidator(String? value) {
|
||||
if (value!.isEmpty && !noEmailWarningConfirmed) {
|
||||
noEmailWarningConfirmed = true;
|
||||
return L10n.of(context).noEmailWarning;
|
||||
// #Pangea
|
||||
if (value == null || value.isEmpty) {
|
||||
// if (value!.isEmpty && !noEmailWarningConfirmed) {
|
||||
// noEmailWarningConfirmed = true;
|
||||
// return L10n.of(context).noEmailWarning;
|
||||
return L10n.of(context).pleaseEnterEmail;
|
||||
// Pangea#
|
||||
}
|
||||
if (value.isNotEmpty && !value.contains('@')) {
|
||||
return L10n.of(context).pleaseEnterValidEmail;
|
||||
|
|
@ -73,7 +123,6 @@ class SignupPageController extends State<SignupPage> {
|
|||
return null;
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
bool isTnCChecked = false;
|
||||
String? signupError;
|
||||
void onTncChange(bool? value) {
|
||||
|
|
@ -81,21 +130,21 @@ class SignupPageController extends State<SignupPage> {
|
|||
signupError = null;
|
||||
setState(() {});
|
||||
}
|
||||
// #Pangea
|
||||
|
||||
void signup([_]) async {
|
||||
setState(() {
|
||||
error = null;
|
||||
});
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
// #Pangea
|
||||
final valid = formKey.currentState!.validate();
|
||||
if (!isTnCChecked) {
|
||||
setState(() {
|
||||
signupError = 'Please agree to the Terms and Conditions';
|
||||
signupError = L10n.of(context).pleaseAgreeToTOS;
|
||||
});
|
||||
}
|
||||
if (!valid || !isTnCChecked) {
|
||||
return;
|
||||
}
|
||||
// #Pangea
|
||||
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
|
|
@ -114,7 +163,7 @@ class SignupPageController extends State<SignupPage> {
|
|||
);
|
||||
}
|
||||
|
||||
final displayname = Matrix.of(context).loginUsername!;
|
||||
final displayname = usernameController.text;
|
||||
final localPart = displayname.toLowerCase().replaceAll(' ', '_');
|
||||
|
||||
final registerRes = await client.uiaRequestBackground(
|
||||
|
|
@ -126,30 +175,25 @@ class SignupPageController extends State<SignupPage> {
|
|||
),
|
||||
);
|
||||
|
||||
//@brord is this right??
|
||||
//#Pangea
|
||||
GoogleAnalytics.login("pangea", registerRes.userId);
|
||||
//Pangea#
|
||||
|
||||
// Set displayname
|
||||
if (displayname != localPart && client.userID != null) {
|
||||
await client.setDisplayName(
|
||||
client.userID!,
|
||||
displayname,
|
||||
);
|
||||
}
|
||||
} on MatrixException catch (e) {
|
||||
} on MatrixException catch (e, s) {
|
||||
if (e.error != MatrixError.M_THREEPID_IN_USE) {
|
||||
rethrow;
|
||||
ErrorHandler.logError(e: e, s: s);
|
||||
}
|
||||
error = e.errorMessage;
|
||||
} catch (e, s) {
|
||||
//#Pangea
|
||||
const cancelledString = "Exception: Request has been canceled";
|
||||
if (e.toString() != cancelledString) {
|
||||
ErrorHandler.logError(e: e, s: s);
|
||||
error = (e).toLocalizedString(context);
|
||||
}
|
||||
// Pangea#
|
||||
error = (e).toLocalizedString(context);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loading = false);
|
||||
|
|
@ -158,5 +202,6 @@ class SignupPageController extends State<SignupPage> {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SignupPageView(this);
|
||||
Widget build(BuildContext context) =>
|
||||
widget.withEmail ? SignupWithEmailView(this) : SignupPageView(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
// Flutter imports:
|
||||
|
||||
import 'package:fluffychat/pangea/widgets/signup/tos_checkbox.dart';
|
||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/pages/connect/p_sso_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'signup.dart';
|
||||
|
||||
|
|
@ -13,153 +16,31 @@ class SignupPageView extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LoginScaffold(
|
||||
appBar: AppBar(
|
||||
leading: controller.loading ? null : const BackButton(),
|
||||
automaticallyImplyLeading: !controller.loading,
|
||||
title: Text(
|
||||
L10n.of(context).signUp,
|
||||
// #Pangea
|
||||
style: const TextStyle(color: Colors.white),
|
||||
// #Pangea
|
||||
return PangeaLoginScaffold(
|
||||
children: [
|
||||
FullWidthButton(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
PangeaLogoSvg(
|
||||
width: 20,
|
||||
forceColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(L10n.of(context).signUpWithEmail),
|
||||
],
|
||||
),
|
||||
onPressed: () => context.go('/home/signup/email'),
|
||||
),
|
||||
),
|
||||
body: Form(
|
||||
key: controller.formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
onChanged: controller.onPasswordType,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.newPassword],
|
||||
controller: controller.passwordController,
|
||||
obscureText: !controller.showPassword,
|
||||
validator: controller.password1TextFieldValidator,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.vpn_key_outlined),
|
||||
suffixIcon: IconButton(
|
||||
tooltip: L10n.of(context).showPassword,
|
||||
icon: Icon(
|
||||
controller.showPassword
|
||||
? Icons.visibility_off_outlined
|
||||
: Icons.visibility_outlined,
|
||||
color: Colors.black,
|
||||
),
|
||||
onPressed: controller.toggleShowPassword,
|
||||
),
|
||||
// #Pangea
|
||||
// errorStyle: const TextStyle(color: Colors.orange),
|
||||
errorStyle: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
fontSize: 14,
|
||||
),
|
||||
// Pangea#
|
||||
hintText: L10n.of(context).chooseAStrongPassword,
|
||||
// #Pangea
|
||||
fillColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.75),
|
||||
// #Pangea
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.displaySecondPasswordField)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.newPassword],
|
||||
controller: controller.password2Controller,
|
||||
obscureText: !controller.showPassword,
|
||||
validator: controller.password2TextFieldValidator,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.repeat_outlined),
|
||||
hintText: L10n.of(context).repeatPassword,
|
||||
// #Pangea
|
||||
// errorStyle: const TextStyle(color: Colors.orange),
|
||||
errorStyle: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
fontSize: 14,
|
||||
),
|
||||
fillColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.75),
|
||||
// #Pangea
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextFormField(
|
||||
readOnly: controller.loading,
|
||||
autocorrect: false,
|
||||
controller: controller.emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
autofillHints:
|
||||
controller.loading ? null : [AutofillHints.username],
|
||||
validator: controller.emailTextFieldValidator,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.mail_outlined),
|
||||
hintText: L10n.of(context).enterAnEmailAddress,
|
||||
errorText: controller.error,
|
||||
errorMaxLines: 4,
|
||||
// #Pangea
|
||||
fillColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.75),
|
||||
// errorStyle: TextStyle(
|
||||
// color: controller.emailController.text.isEmpty
|
||||
// ? Colors.orangeAccent
|
||||
// : Colors.orange,
|
||||
// ),
|
||||
errorStyle: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
fontSize: 14,
|
||||
),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
TosCheckbox(controller),
|
||||
// #Pangea
|
||||
Hero(
|
||||
tag: 'loginButton',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
// #Pangea
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.loading ? () {} : controller.signup,
|
||||
child: controller.loading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context).signUp),
|
||||
),
|
||||
// child: ElevatedButton.icon(
|
||||
// icon: const Icon(Icons.person_add_outlined),
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// onPressed: controller.loading ? () {} : controller.signup,
|
||||
// label: controller.loading
|
||||
// ? const LinearProgressIndicator()
|
||||
// : Text(L10n.of(context).signUp),
|
||||
// ),
|
||||
// #Pangea
|
||||
),
|
||||
),
|
||||
],
|
||||
PangeaSsoButton(
|
||||
provider: SSOProvider.google,
|
||||
title: L10n.of(context).signUpWithGoogle,
|
||||
),
|
||||
),
|
||||
PangeaSsoButton(
|
||||
provider: SSOProvider.apple,
|
||||
title: L10n.of(context).signUpWithApple,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
70
lib/pangea/pages/sign_up/signup_with_email_view.dart
Normal file
70
lib/pangea/pages/sign_up/signup_with_email_view.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Flutter imports:
|
||||
|
||||
import 'package:fluffychat/pangea/pages/sign_up/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart';
|
||||
import 'package:fluffychat/pangea/widgets/signup/tos_checkbox.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'signup.dart';
|
||||
|
||||
class SignupWithEmailView extends StatelessWidget {
|
||||
final SignupPageController controller;
|
||||
const SignupWithEmailView(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: controller.formKey,
|
||||
child: PangeaLoginScaffold(
|
||||
children: [
|
||||
FullWidthTextField(
|
||||
hintText: L10n.of(context).yourUsername,
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (text) {
|
||||
if (text == null || text.isEmpty) {
|
||||
return L10n.of(context).pleaseChooseAUsername;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: controller.usernameController,
|
||||
),
|
||||
FullWidthTextField(
|
||||
hintText: L10n.of(context).yourEmail,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: controller.emailTextFieldValidator,
|
||||
controller: controller.emailController,
|
||||
),
|
||||
FullWidthTextField(
|
||||
hintText: L10n.of(context).password,
|
||||
textInputAction: TextInputAction.done,
|
||||
obscureText: true,
|
||||
validator: controller.password1TextFieldValidator,
|
||||
controller: controller.passwordController,
|
||||
),
|
||||
TosCheckbox(controller),
|
||||
FullWidthButton(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
PangeaLogoSvg(
|
||||
width: 20,
|
||||
forceColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(L10n.of(context).signUp),
|
||||
],
|
||||
),
|
||||
onPressed: controller.enableSignUp ? controller.signup : null,
|
||||
error: controller.error,
|
||||
loading: controller.loading,
|
||||
enabled: controller.enableSignUp,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
169
lib/pangea/pages/sign_up/user_settings.dart
Normal file
169
lib/pangea/pages/sign_up/user_settings.dart
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/user_settings_view.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class UserSettingsPage extends StatefulWidget {
|
||||
const UserSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
UserSettingsState createState() => UserSettingsState();
|
||||
}
|
||||
|
||||
class UserSettingsState extends State<UserSettingsPage> {
|
||||
PangeaController get _pangeaController => MatrixState.pangeaController;
|
||||
|
||||
LanguageModel? selectedTargetLanguage;
|
||||
|
||||
String? selectedLanguageError;
|
||||
String? profileCreationError;
|
||||
|
||||
bool loading = false;
|
||||
|
||||
Uint8List? avatar;
|
||||
String? _selectedFilePath;
|
||||
|
||||
List<String> avatarPaths = const [
|
||||
"assets/pangea/Avatar_1.png",
|
||||
"assets/pangea/Avatar_2.png",
|
||||
"assets/pangea/Avatar_3.png",
|
||||
"assets/pangea/Avatar_4.png",
|
||||
"assets/pangea/Avatar_5.png",
|
||||
];
|
||||
String? selectedAvatarPath;
|
||||
|
||||
LanguageModel? get _systemLanguage {
|
||||
final systemLangCode =
|
||||
_pangeaController.languageController.systemLanguage?.langCode;
|
||||
return systemLangCode == null
|
||||
? null
|
||||
: PangeaLanguage.byLangCode(systemLangCode);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedTargetLanguage = _pangeaController.languageController.userL2;
|
||||
selectedAvatarPath = avatarPaths.first;
|
||||
}
|
||||
|
||||
void setSelectedTargetLanguage(LanguageModel? language) {
|
||||
setState(() {
|
||||
selectedTargetLanguage = language;
|
||||
selectedLanguageError = null;
|
||||
});
|
||||
}
|
||||
|
||||
void setSelectedAvatarPath(int index) {
|
||||
if (index < 0 || index >= avatarPaths.length) return;
|
||||
setState(() {
|
||||
avatar = null;
|
||||
selectedAvatarPath = avatarPaths[index];
|
||||
});
|
||||
}
|
||||
|
||||
int get selectedAvatarIndex {
|
||||
if (selectedAvatarPath == null) return -1;
|
||||
return avatarPaths.indexOf(selectedAvatarPath!);
|
||||
}
|
||||
|
||||
void uploadAvatar() async {
|
||||
final photo = await selectFiles(
|
||||
context,
|
||||
type: FileSelectorType.images,
|
||||
allowMultiple: false,
|
||||
);
|
||||
final selectedFile = photo.singleOrNull;
|
||||
final bytes = await selectedFile?.readAsBytes();
|
||||
final path = selectedFile?.path;
|
||||
|
||||
setState(() {
|
||||
selectedAvatarPath = null;
|
||||
avatar = bytes;
|
||||
_selectedFilePath = path;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _setAvatar() async {
|
||||
final client = Matrix.of(context).client;
|
||||
try {
|
||||
MatrixFile? file;
|
||||
if (avatar != null && _selectedFilePath != null) {
|
||||
file = MatrixFile(
|
||||
bytes: avatar!,
|
||||
name: _selectedFilePath!,
|
||||
);
|
||||
} else if (selectedAvatarPath != null) {
|
||||
final ByteData byteData = await rootBundle.load(selectedAvatarPath!);
|
||||
final Uint8List bytes = byteData.buffer.asUint8List();
|
||||
file = MatrixFile(
|
||||
bytes: bytes,
|
||||
name: selectedAvatarPath!,
|
||||
);
|
||||
}
|
||||
if (file != null) await client.setAvatar(file);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createUserInPangea() async {
|
||||
setState(() => profileCreationError = null);
|
||||
|
||||
if (selectedTargetLanguage == null) {
|
||||
setState(() {
|
||||
selectedLanguageError = L10n.of(context).pleaseSelectALanguage;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => loading = true);
|
||||
|
||||
try {
|
||||
final updateFuture = [
|
||||
_setAvatar(),
|
||||
_pangeaController.subscriptionController.reinitialize(),
|
||||
_pangeaController.userController.updateProfile(
|
||||
(profile) {
|
||||
if (_systemLanguage != null) {
|
||||
profile.userSettings.sourceLanguage = _systemLanguage!.langCode;
|
||||
}
|
||||
profile.userSettings.targetLanguage =
|
||||
selectedTargetLanguage!.langCode;
|
||||
profile.userSettings.createdAt = DateTime.now();
|
||||
return profile;
|
||||
},
|
||||
waitForDataInSync: true,
|
||||
),
|
||||
];
|
||||
await Future.wait(updateFuture);
|
||||
context.go('/rooms');
|
||||
} catch (err) {
|
||||
if (err is MatrixException) {
|
||||
profileCreationError = err.errorMessage;
|
||||
} else {
|
||||
profileCreationError = err.toLocalizedString(context);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<LanguageModel> get targetOptions =>
|
||||
_pangeaController.pLanguageStore.targetOptions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => UserSettingsView(controller: this);
|
||||
}
|
||||
148
lib/pangea/pages/sign_up/user_settings_view.dart
Normal file
148
lib/pangea/pages/sign_up/user_settings_view.dart
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/user_settings.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dropdown.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class UserSettingsView extends StatelessWidget {
|
||||
final UserSettingsState controller;
|
||||
|
||||
const UserSettingsView({
|
||||
required this.controller,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> avatarOptions = controller.avatarPaths
|
||||
.mapIndexed((index, path) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: AvatarOption(
|
||||
onTap: () => controller.setSelectedAvatarPath(index),
|
||||
path: path,
|
||||
selected: controller.selectedAvatarIndex == index,
|
||||
),
|
||||
);
|
||||
})
|
||||
.cast<Widget>()
|
||||
.toList();
|
||||
|
||||
avatarOptions.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: InkWell(
|
||||
onTap: controller.uploadAvatar,
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: controller.avatar != null
|
||||
? AppConfig.activeToggleColor
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.upload,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return PangeaLoginScaffold(
|
||||
showAppName: false,
|
||||
mainAssetPath: controller.selectedAvatarPath ?? "",
|
||||
mainAssetBytes: controller.avatar,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0.9,
|
||||
child: Text(
|
||||
L10n.of(context).chooseYourAvatar,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w100,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: avatarOptions,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: PLanguageDropdown(
|
||||
languages: controller.targetOptions,
|
||||
onChange: controller.setSelectedTargetLanguage,
|
||||
initialLanguage: controller.selectedTargetLanguage,
|
||||
isL2List: true,
|
||||
error: controller.selectedLanguageError,
|
||||
),
|
||||
),
|
||||
FullWidthButton(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [Text(L10n.of(context).letsStart)],
|
||||
),
|
||||
onPressed: controller.selectedTargetLanguage != null
|
||||
? controller.createUserInPangea
|
||||
: null,
|
||||
error: controller.profileCreationError,
|
||||
loading: controller.loading,
|
||||
enabled: controller.selectedTargetLanguage != null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AvatarOption extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final String path; // Path or URL of the SVG file
|
||||
final double size; // Diameter of the circle
|
||||
final bool selected;
|
||||
|
||||
const AvatarOption({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
required this.path,
|
||||
this.size = 50.0,
|
||||
this.selected = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? AppConfig.activeToggleColor
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
path,
|
||||
fit: BoxFit.cover, // scale properly without warping
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
65
lib/pangea/utils/sso_login_action.dart
Normal file
65
lib/pangea/utils/sso_login_action.dart
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
||||
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
Future<void> pangeaSSOLoginAction(
|
||||
IdentityProvider provider,
|
||||
Client client,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final bool isDefaultPlatform =
|
||||
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS);
|
||||
|
||||
final redirectUrl = kIsWeb
|
||||
? Uri.parse(html.window.location.href)
|
||||
.resolveUri(
|
||||
Uri(pathSegments: ['auth.html']),
|
||||
)
|
||||
.toString()
|
||||
: isDefaultPlatform
|
||||
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
||||
: 'http://localhost:3001//login';
|
||||
|
||||
final url = Uri.parse(
|
||||
"${AppConfig.defaultHomeserver}/_matrix/client/v3/login/sso/redirect${provider.id == null ? '' : '/${provider.id}'}",
|
||||
).replace(
|
||||
scheme: "https",
|
||||
queryParameters: {'redirectUrl': redirectUrl},
|
||||
);
|
||||
|
||||
final urlScheme = isDefaultPlatform
|
||||
? Uri.parse(redirectUrl).scheme
|
||||
: "http://localhost:3001";
|
||||
|
||||
String result;
|
||||
try {
|
||||
result = await FlutterWebAuth2.authenticate(
|
||||
url: url.toString(),
|
||||
callbackUrlScheme: urlScheme,
|
||||
options: const FlutterWebAuth2Options(),
|
||||
);
|
||||
} catch (err) {
|
||||
if (err is PlatformException && err.code == 'CANCELED') {
|
||||
debugPrint("user cancelled SSO login");
|
||||
return;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final token = Uri.parse(result).queryParameters['loginToken'];
|
||||
if (token?.isEmpty ?? false) return;
|
||||
|
||||
final loginRes = await client.login(
|
||||
LoginType.mLoginToken,
|
||||
token: token,
|
||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
);
|
||||
GoogleAnalytics.login(provider.name!, loginRes.userId);
|
||||
}
|
||||
|
|
@ -39,16 +39,21 @@ class PressableButtonState extends State<PressableButton>
|
|||
Completer<void>? _animationCompleter;
|
||||
StreamSubscription? _triggerAnimationSubscription;
|
||||
|
||||
// seperate the widget's depressed state from the internal
|
||||
// state to enable animations when this changes
|
||||
bool _depressed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_depressed = widget.depressed;
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
vsync: this,
|
||||
);
|
||||
_tweenAnimation =
|
||||
Tween<double>(begin: 0, end: widget.buttonHeight).animate(_controller);
|
||||
if (!widget.depressed) {
|
||||
if (!_depressed) {
|
||||
_triggerAnimationSubscription = widget.triggerAnimation?.listen((_) {
|
||||
_animationCompleter = Completer<void>();
|
||||
_animateUp();
|
||||
|
|
@ -57,15 +62,30 @@ class PressableButtonState extends State<PressableButton>
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PressableButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (_depressed && !widget.depressed) {
|
||||
_controller.forward().then((_) {
|
||||
_depressed = widget.depressed;
|
||||
_controller.reverse();
|
||||
});
|
||||
} else if (!_depressed && widget.depressed) {
|
||||
_controller.forward().then((_) {
|
||||
_depressed = widget.depressed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapDown(TapDownDetails? details) {
|
||||
if (widget.depressed) return;
|
||||
if (_depressed) return;
|
||||
_animationCompleter = Completer<void>();
|
||||
if (!mounted) return;
|
||||
_animateUp();
|
||||
}
|
||||
|
||||
void _animateUp() {
|
||||
if (widget.depressed || !mounted) return;
|
||||
if (_depressed || !mounted) return;
|
||||
_controller.forward().then((_) {
|
||||
_animationCompleter?.complete();
|
||||
_animationCompleter = null;
|
||||
|
|
@ -73,8 +93,11 @@ class PressableButtonState extends State<PressableButton>
|
|||
}
|
||||
|
||||
Future<void> _onTapUp(TapUpDetails? details) async {
|
||||
if (_animationCompleter != null) {
|
||||
await _animationCompleter!.future;
|
||||
}
|
||||
widget.onPressed?.call();
|
||||
if (widget.depressed) return;
|
||||
if (_depressed) return;
|
||||
await _animateDown();
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +113,7 @@ class PressableButtonState extends State<PressableButton>
|
|||
}
|
||||
|
||||
void _onTapCancel() {
|
||||
if (widget.depressed) return;
|
||||
if (_depressed) return;
|
||||
if (mounted) _controller.reverse();
|
||||
}
|
||||
|
||||
|
|
@ -103,37 +126,46 @@ class PressableButtonState extends State<PressableButton>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
child: AnimatedBuilder(
|
||||
animation: _tweenAnimation,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(
|
||||
Colors.black.withOpacity(0.25),
|
||||
widget.color,
|
||||
return NotificationListener<ButtonPressedNotification>(
|
||||
onNotification: (notification) {
|
||||
_onTapDown(null);
|
||||
_onTapUp(null);
|
||||
return true; // Stop the notification from bubbling further
|
||||
},
|
||||
child: GestureDetector(
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
child: AnimatedBuilder(
|
||||
animation: _tweenAnimation,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(
|
||||
Colors.black.withOpacity(0.25),
|
||||
widget.color,
|
||||
),
|
||||
borderRadius: widget.borderRadius,
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: !_depressed
|
||||
? widget.buttonHeight - _tweenAnimation.value
|
||||
: 0,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: widget.color,
|
||||
borderRadius: widget.borderRadius,
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: !widget.depressed
|
||||
? widget.buttonHeight - _tweenAnimation.value
|
||||
: 0,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: widget.color,
|
||||
borderRadius: widget.borderRadius,
|
||||
child: widget.child,
|
||||
),
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonPressedNotification extends Notification {}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,87 @@
|
|||
// Flutter imports:
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/pages/sign_up/signup.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class TosCheckbox extends StatelessWidget {
|
||||
class TosCheckbox extends StatefulWidget {
|
||||
final SignupPageController controller;
|
||||
const TosCheckbox(this.controller, {super.key});
|
||||
|
||||
const TosCheckbox(
|
||||
this.controller, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
TosCheckboxState createState() => TosCheckboxState();
|
||||
}
|
||||
|
||||
class TosCheckboxState extends State<TosCheckbox>
|
||||
with SingleTickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
value: controller.isTnCChecked,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
onChanged: controller.onTncChange,
|
||||
title: InkWell(
|
||||
onTap: () =>
|
||||
UrlLauncher(context, AppConfig.termsOfServiceUrl).launchUrl(),
|
||||
child: RichText(
|
||||
maxLines: 2,
|
||||
text: TextSpan(
|
||||
text: L10n.of(context).iAgreeToThe,
|
||||
children: [
|
||||
//PTODO - make sure this is actually a link
|
||||
TextSpan(
|
||||
text: L10n.of(context).termsAndConditions,
|
||||
style: const TextStyle(color: Colors.blue),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => UrlLauncher(context, AppConfig.termsOfServiceUrl)
|
||||
.launchUrl(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: L10n.of(context).iAgreeToThe,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: L10n.of(context).termsAndConditions,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: L10n.of(context).andCertifyIAmAtLeast13YearsOfAge,
|
||||
),
|
||||
],
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: widget.controller.signupError == null
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 4, left: 30),
|
||||
child: Text(
|
||||
widget.controller.signupError!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
margin: const EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
controller.signupError ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: widget.controller.isTnCChecked,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
onChanged: widget.controller.onTncChange,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
// Flutter imports:
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/enum/l2_support_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../widgets/flag.dart';
|
||||
|
||||
class PLanguageDropdown extends StatefulWidget {
|
||||
final List<LanguageModel> languages;
|
||||
final LanguageModel initialLanguage;
|
||||
final LanguageModel? initialLanguage;
|
||||
final Function(LanguageModel) onChange;
|
||||
final bool showMultilingual;
|
||||
final bool isL2List;
|
||||
final String? error;
|
||||
|
||||
const PLanguageDropdown({
|
||||
super.key,
|
||||
|
|
@ -20,6 +23,7 @@ class PLanguageDropdown extends StatefulWidget {
|
|||
required this.initialLanguage,
|
||||
this.showMultilingual = false,
|
||||
required this.isL2List,
|
||||
this.error,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -55,68 +59,71 @@ class _PLanguageDropdownState extends State<PLanguageDropdown> {
|
|||
|
||||
sortedLanguages.sort((a, b) => sortLanguages(a, b));
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
width: 0.5,
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(36)),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
child: DropdownButton<LanguageModel>(
|
||||
// Initial Value
|
||||
hint: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: LanguageFlag(
|
||||
language: widget.initialLanguage,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: DropdownButton<LanguageModel>(
|
||||
hint: Row(
|
||||
children: [
|
||||
const Icon(Icons.language_outlined),
|
||||
const SizedBox(width: 10),
|
||||
Text(L10n.of(context).iWantToLearn),
|
||||
],
|
||||
),
|
||||
isExpanded: true,
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
underline: Container(),
|
||||
items: [
|
||||
if (widget.showMultilingual)
|
||||
DropdownMenuItem(
|
||||
value: LanguageModel.multiLingual(context),
|
||||
child: LanguageDropDownEntry(
|
||||
languageModel: LanguageModel.multiLingual(context),
|
||||
isL2List: widget.isL2List,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
widget.initialLanguage.getDisplayName(context) ?? "",
|
||||
style: const TextStyle().copyWith(
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
fontSize: 14,
|
||||
...sortedLanguages.map(
|
||||
(languageModel) => DropdownMenuItem(
|
||||
value: languageModel,
|
||||
child: LanguageDropDownEntry(
|
||||
languageModel: languageModel,
|
||||
isL2List: widget.isL2List,
|
||||
),
|
||||
),
|
||||
overflow: TextOverflow.clip,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
onChanged: (value) => widget.onChange(value!),
|
||||
value: widget.initialLanguage,
|
||||
),
|
||||
|
||||
isExpanded: true,
|
||||
// Down Arrow Icon
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
underline: Container(),
|
||||
// Array list of items
|
||||
items: [
|
||||
if (widget.showMultilingual)
|
||||
DropdownMenuItem(
|
||||
value: LanguageModel.multiLingual(context),
|
||||
child: LanguageDropDownEntry(
|
||||
languageModel: LanguageModel.multiLingual(context),
|
||||
isL2List: widget.isL2List,
|
||||
),
|
||||
),
|
||||
...sortedLanguages.map(
|
||||
(languageModel) => DropdownMenuItem(
|
||||
value: languageModel,
|
||||
child: LanguageDropDownEntry(
|
||||
languageModel: languageModel,
|
||||
isL2List: widget.isL2List,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) => widget.onChange(value!),
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: widget.error == null
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 30,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Text(
|
||||
widget.error!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,8 +82,17 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
widget.clients.add(getLoginClient());
|
||||
}
|
||||
if (_activeClient < 0 || _activeClient >= widget.clients.length) {
|
||||
// #Pangea
|
||||
currentBundle!.first!.homeserver =
|
||||
Uri.parse("https://${AppConfig.defaultHomeserver}");
|
||||
// Pangea#
|
||||
return currentBundle!.first!;
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
widget.clients[_activeClient].homeserver =
|
||||
Uri.parse("https://${AppConfig.defaultHomeserver}");
|
||||
// Pangea#
|
||||
return widget.clients[_activeClient];
|
||||
}
|
||||
|
||||
|
|
@ -175,6 +184,9 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
_loginClientCandidate = null;
|
||||
FluffyChatApp.router.go('/rooms');
|
||||
});
|
||||
// #Pangea
|
||||
candidate.homeserver = Uri.parse("https://${AppConfig.defaultHomeserver}");
|
||||
// Pangea#
|
||||
return candidate;
|
||||
}
|
||||
|
||||
|
|
@ -338,10 +350,10 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
}
|
||||
String routeDestination;
|
||||
if (state == LoginState.loggedIn) {
|
||||
routeDestination = await pangeaController
|
||||
.userController.isUserDataAvailableAndDateOfBirthSet
|
||||
? '/rooms'
|
||||
: "/user_age";
|
||||
routeDestination =
|
||||
await pangeaController.userController.isUserDataAvailableAndL2Set
|
||||
? '/rooms'
|
||||
: "/user_age";
|
||||
} else {
|
||||
routeDestination = '/home';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue