redesigning register view
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2026-05-16 23:02:12 +02:00
parent 32231d11c2
commit ea41158872
9 changed files with 485 additions and 253 deletions

View file

@ -32,7 +32,6 @@ import 'package:twonly/src/services/background/callback_dispatcher.background.da
import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/services/backup.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/memories/memories.service.dart'; import 'package:twonly/src/services/memories/memories.service.dart';
import 'package:twonly/src/services/notifications/fcm.notifications.dart'; import 'package:twonly/src/services/notifications/fcm.notifications.dart';
import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart';
import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/services/user.service.dart';

View file

@ -98,16 +98,10 @@ abstract class AppLocalizations {
Locale('en'), Locale('en'),
]; ];
/// No description provided for @registerTitle.
///
/// In en, this message translates to:
/// **'Welcome to twonly!'**
String get registerTitle;
/// No description provided for @registerSlogan. /// No description provided for @registerSlogan.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing'** /// **'Stay in touch with friends privately and securely.'**
String get registerSlogan; String get registerSlogan;
/// No description provided for @onboardingWelcomeTitle. /// No description provided for @onboardingWelcomeTitle.
@ -179,7 +173,7 @@ abstract class AppLocalizations {
/// No description provided for @registerUsernameSlogan. /// No description provided for @registerUsernameSlogan.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Please select a username so others can find you!'** /// **'Your public username'**
String get registerUsernameSlogan; String get registerUsernameSlogan;
/// No description provided for @registerUsernameDecoration. /// No description provided for @registerUsernameDecoration.
@ -191,7 +185,7 @@ abstract class AppLocalizations {
/// No description provided for @registerUsernameLimits. /// No description provided for @registerUsernameLimits.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Your username must be at least 3 characters long.'** /// **'At least 3 characters.'**
String get registerUsernameLimits; String get registerUsernameLimits;
/// No description provided for @registerProofOfWorkFailed. /// No description provided for @registerProofOfWorkFailed.
@ -1583,7 +1577,7 @@ abstract class AppLocalizations {
/// No description provided for @twonlySafeRecoverTitle. /// No description provided for @twonlySafeRecoverTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Recovery'** /// **'Restore backup'**
String get twonlySafeRecoverTitle; String get twonlySafeRecoverTitle;
/// No description provided for @twonlySafeRecoverDesc. /// No description provided for @twonlySafeRecoverDesc.

View file

@ -8,12 +8,9 @@ import 'app_localizations.dart';
class AppLocalizationsDe extends AppLocalizations { class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale); AppLocalizationsDe([String locale = 'de']) : super(locale);
@override
String get registerTitle => 'Willkommen bei twonly!';
@override @override
String get registerSlogan => String get registerSlogan =>
'twonly, eine private und sichere Möglichkeit um mit Freunden in Kontakt zu bleiben.'; 'Privat und sicher mit Freunden in Kontakt bleiben.';
@override @override
String get onboardingWelcomeTitle => 'Willkommen bei twonly!'; String get onboardingWelcomeTitle => 'Willkommen bei twonly!';
@ -55,15 +52,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get onboardingGetStartedTitle => 'Auf geht\'s'; String get onboardingGetStartedTitle => 'Auf geht\'s';
@override @override
String get registerUsernameSlogan => String get registerUsernameSlogan => 'Dein öffentlicher Benutzername';
'Bitte wähle einen Benutzernamen, damit dich andere finden können!';
@override @override
String get registerUsernameDecoration => 'Benutzername'; String get registerUsernameDecoration => 'Benutzername';
@override @override
String get registerUsernameLimits => String get registerUsernameLimits => 'Mindestens 3 Zeichen.';
'Der Benutzername muss mindestens 3 Zeichen lang sein.';
@override @override
String get registerProofOfWorkFailed => String get registerProofOfWorkFailed =>
@ -816,7 +811,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get backupChangePassword => 'Password ändern'; String get backupChangePassword => 'Password ändern';
@override @override
String get twonlySafeRecoverTitle => 'Recovery'; String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
@override @override
String get twonlySafeRecoverDesc => String get twonlySafeRecoverDesc =>

View file

@ -8,12 +8,9 @@ import 'app_localizations.dart';
class AppLocalizationsEn extends AppLocalizations { class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale); AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get registerTitle => 'Welcome to twonly!';
@override @override
String get registerSlogan => String get registerSlogan =>
'twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing'; 'Stay in touch with friends privately and securely.';
@override @override
String get onboardingWelcomeTitle => 'Welcome to twonly!'; String get onboardingWelcomeTitle => 'Welcome to twonly!';
@ -54,15 +51,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get onboardingGetStartedTitle => 'Let\'s go!'; String get onboardingGetStartedTitle => 'Let\'s go!';
@override @override
String get registerUsernameSlogan => String get registerUsernameSlogan => 'Your public username';
'Please select a username so others can find you!';
@override @override
String get registerUsernameDecoration => 'Username'; String get registerUsernameDecoration => 'Username';
@override @override
String get registerUsernameLimits => String get registerUsernameLimits => 'At least 3 characters.';
'Your username must be at least 3 characters long.';
@override @override
String get registerProofOfWorkFailed => String get registerProofOfWorkFailed =>
@ -810,7 +805,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get backupChangePassword => 'Change password'; String get backupChangePassword => 'Change password';
@override @override
String get twonlySafeRecoverTitle => 'Recovery'; String get twonlySafeRecoverTitle => 'Restore backup';
@override @override
String get twonlySafeRecoverDesc => String get twonlySafeRecoverDesc =>

@ -1 +1 @@
Subproject commit c41aaeff47007ddd5484fe1124bb15b905c3b247 Subproject commit 8660b9b11721adda59e387a129f9017bb9e72c67

View file

@ -0,0 +1,101 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class LinkLogoAnimation extends StatefulWidget {
const LinkLogoAnimation({
super.key,
this.size = 130,
this.color = Colors.white,
});
final double size;
final Color color;
@override
State<LinkLogoAnimation> createState() => _LinkLogoAnimationState();
}
class _LinkLogoAnimationState extends State<LinkLogoAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _rotation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
)..repeat(reverse: true);
_rotation =
Tween<double>(
begin: -2.0 * (math.pi / 180.0),
end: 2.0 * (math.pi / 180.0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
const originalViewportSize = 640.0;
const path1 =
'M451.5 160C434.9 160 418.8 164.5 404.7 172.7C388.9 156.7 370.5 143.3 350.2 133.2C378.4 109.2 414.3 96 451.5 96C537.9 96 608 166 608 252.5C608 294 591.5 333.8 562.2 363.1L491.1 434.2C461.8 463.5 422 480 380.5 480C294.1 480 224 410 224 323.5C224 322 224 320.5 224.1 319C224.6 301.3 239.3 287.4 257 287.9C274.7 288.4 288.6 303.1 288.1 320.8C288.1 321.7 288.1 322.6 288.1 323.4C288.1 374.5 329.5 415.9 380.6 415.9C405.1 415.9 428.6 406.2 446 388.8L517.1 317.7C534.4 300.4 544.2 276.8 544.2 252.3C544.2 201.2 502.8 159.8 451.7 159.8z';
const path2 =
'M307.2 237.3C305.3 236.5 303.4 235.4 301.7 234.2C289.1 227.7 274.7 224 259.6 224C235.1 224 211.6 233.7 194.2 251.1L123.1 322.2C105.8 339.5 96 363.1 96 387.6C96 438.7 137.4 480.1 188.5 480.1C205 480.1 221.1 475.7 235.2 467.5C251 483.5 269.4 496.9 289.8 507C261.6 530.9 225.8 544.2 188.5 544.2C102.1 544.2 32 474.2 32 387.7C32 346.2 48.5 306.4 77.8 277.1L148.9 206C178.2 176.7 218 160.2 259.5 160.2C346.1 160.2 416 230.8 416 317.1C416 318.4 416 319.7 416 321C415.6 338.7 400.9 352.6 383.2 352.2C365.5 351.8 351.6 337.1 352 319.4C352 318.6 352 317.9 352 317.1C352 283.4 334 253.8 307.2 237.5z';
return SizedBox(
width: widget.size,
height: widget.size,
child: AnimatedBuilder(
animation: _rotation,
builder: (context, child) {
return Stack(
children: [
Positioned.fill(
child: Transform(
alignment: const Alignment(
(416 * 2 / originalViewportSize) - 1,
(288 * 2 / originalViewportSize) - 1,
),
transform: Matrix4.identity()..rotateZ(_rotation.value),
child: SvgPicture.string(
'<svg viewBox="0 0 640 640"><path d="$path1" fill="${_toHex(widget.color)}"/></svg>',
),
),
),
Positioned.fill(
child: Transform(
alignment: const Alignment(
(224 * 2 / originalViewportSize) - 1,
(352 * 2 / originalViewportSize) - 1,
),
transform: Matrix4.identity()..rotateZ(-_rotation.value),
child: SvgPicture.string(
'<svg viewBox="0 0 640 640"><path d="$path2" fill="${_toHex(widget.color)}"/></svg>',
),
),
),
],
);
},
),
);
}
String _toHex(Color color) {
return '#${color.toARGB32().toRadixString(16).substring(2)}';
}
}

View file

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/visual/themes/light.dart';
class OnboardingWrapper extends StatelessWidget {
const OnboardingWrapper({
required this.children,
super.key,
});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
behavior: HitTestBehavior.opaque,
child: Scaffold(
backgroundColor: primaryColor,
body: Stack(
children: [
Positioned(
top: -100,
right: -100,
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.1),
),
),
),
Positioned(
bottom: -50,
left: -50,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withValues(alpha: 0.05),
),
),
),
SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
),
),
);
},
),
),
],
),
),
);
}
}

View file

@ -5,7 +5,9 @@ import 'package:twonly/src/services/backup.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart';
import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/components/snackbar.dart';
import 'package:twonly/src/visual/decorations/input_text.decoration.dart'; import 'package:twonly/src/visual/themes/light.dart';
import 'package:twonly/src/visual/views/onboarding/components/link_logo_animation.dart';
import 'package:twonly/src/visual/views/onboarding/components/onboarding_wrapper.dart';
class BackupRecoveryView extends StatefulWidget { class BackupRecoveryView extends StatefulWidget {
const BackupRecoveryView({super.key}); const BackupRecoveryView({super.key});
@ -64,10 +66,19 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return OnboardingWrapper(
appBar: AppBar( children: [
title: Text('twonly Backup ${context.lang.twonlySafeRecoverTitle}'), Row(
actions: [ children: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(
Icons.arrow_back_ios_new_rounded,
),
color: Colors.white,
iconSize: 20,
),
const Spacer(),
IconButton( IconButton(
onPressed: () async { onPressed: () async {
await showAlertDialog( await showAlertDialog(
@ -77,53 +88,90 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
); );
}, },
icon: const FaIcon(FontAwesomeIcons.circleInfo), icon: const FaIcon(FontAwesomeIcons.circleInfo),
iconSize: 18, color: Colors.white,
iconSize: 20,
), ),
], ],
), ),
body: Padding( const SizedBox(height: 20),
padding: const EdgeInsetsGeometry.symmetric( const Center(
vertical: 40, child: Padding(
horizontal: 40, padding: EdgeInsets.all(20),
child: LinkLogoAnimation(),
), ),
child: ListView( ),
children: [ const SizedBox(height: 16),
Text( Padding(
context.lang.twonlySafeRecoverDesc, padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
context.lang.twonlySafeRecoverTitle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w800,
color: Colors.white,
letterSpacing: -0.5,
), ),
const SizedBox(height: 30), ),
),
const SizedBox(height: 48),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(32),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField( TextField(
controller: usernameCtrl, controller: usernameCtrl,
onChanged: (value) { onChanged: (value) => setState(() {}),
setState(() {}); style: const TextStyle(
}, fontSize: 18,
style: const TextStyle(fontSize: 17), fontWeight: FontWeight.w500,
decoration: getInputDecoration( ),
context, decoration: InputDecoration(
context.lang.registerUsernameDecoration, hintText: context.lang.registerUsernameDecoration,
filled: true,
fillColor: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
prefixIcon: const Icon(
Icons.alternate_email,
), ),
), ),
const SizedBox(height: 10), ),
Stack( const SizedBox(height: 16),
children: [
TextField( TextField(
controller: passwordCtrl, controller: passwordCtrl,
onChanged: (value) { onChanged: (value) => setState(() {}),
setState(() {}); style: const TextStyle(
}, fontSize: 18,
style: const TextStyle(fontSize: 17), fontWeight: FontWeight.w500,
),
obscureText: obscureText, obscureText: obscureText,
decoration: getInputDecoration( decoration: InputDecoration(
context, hintText: context.lang.password,
context.lang.password, filled: true,
fillColor: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
), ),
prefixIcon: const Icon(
Icons.lock_outline_rounded,
), ),
Positioned( suffixIcon: IconButton(
right: 0,
top: 0,
bottom: 0,
child: IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
obscureText = !obscureText; obscureText = !obscureText;
@ -137,25 +185,42 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
), ),
), ),
), ),
],
), ),
const SizedBox(height: 10), const SizedBox(height: 32),
Center( FilledButton(
child: FilledButton.icon(
onPressed: (!isLoading) ? _recoverTwonlySafe : null, onPressed: (!isLoading) ? _recoverTwonlySafe : null,
icon: isLoading style: FilledButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(60),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
elevation: 0,
),
child: isLoading
? const SizedBox( ? const SizedBox(
height: 12, height: 24,
width: 12, width: 24,
child: CircularProgressIndicator(strokeWidth: 1), child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 3,
),
) )
: const Icon(Icons.lock_clock_rounded), : Text(
label: Text(context.lang.twonlySafeRecoverBtn), context.lang.twonlySafeRecoverBtn,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
), ),
), ),
], ],
), ),
), ),
const Spacer(),
const SizedBox(height: 40),
],
); );
} }
} }

View file

@ -1,6 +1,7 @@
// ignore_for_file: avoid_dynamic_calls // ignore_for_file: avoid_dynamic_calls
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -16,7 +17,10 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/utils/pow.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart';
import 'package:twonly/src/visual/themes/light.dart';
import 'package:twonly/src/visual/views/groups/group.view.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart';
import 'package:twonly/src/visual/views/onboarding/components/link_logo_animation.dart';
import 'package:twonly/src/visual/views/onboarding/components/onboarding_wrapper.dart';
import 'package:twonly/src/visual/views/onboarding/setup.view.dart'; import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
class RegisterView extends StatefulWidget { class RegisterView extends StatefulWidget {
@ -134,7 +138,7 @@ class _RegisterViewState extends State<RegisterView> {
userId: userId, userId: userId,
username: username, username: username,
displayName: username, displayName: username,
subscriptionPlan: 'Preview', subscriptionPlan: 'Free',
currentSetupPage: SetupPages.profile.name, currentSetupPage: SetupPages.profile.name,
)..appVersion = AppState.latestAppVersionId; )..appVersion = AppState.latestAppVersionId;
@ -146,89 +150,75 @@ class _RegisterViewState extends State<RegisterView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_registrationDisabled) { return OnboardingWrapper(
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: ListView(
children: [ children: [
const SizedBox(height: 50), const SizedBox(height: 40),
Text( Center(
context.lang.registerTitle, child: Container(
textAlign: TextAlign.center, padding: const EdgeInsets.all(20),
style: const TextStyle(fontSize: 30), child: const LinkLogoAnimation(),
), ),
),
const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 30), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text( child: Text(
context.lang.registerSlogan, context.lang.registerSlogan,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12), style: TextStyle(
fontSize: 16,
color: Colors.white.withValues(alpha: 0.9),
fontWeight: FontWeight.w500,
), ),
), ),
const SizedBox(height: 130), ),
const SizedBox(height: 48),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(32),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_registrationDisabled) ...[
const SizedBox(height: 24),
Text( Text(
context.lang.registrationClosed, context.lang.registrationClosed,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 16,
color: Colors.red, color: Colors.red,
), ),
), ),
], const SizedBox(height: 48),
), ] else ...[
),
),
);
}
InputDecoration getInputDecoration(String hintText) {
return InputDecoration(hintText: hintText, fillColor: Colors.grey[400]);
}
return Scaffold(
appBar: AppBar(
title: const Text(''),
),
body: Padding(
padding: const EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: ListView(
children: [
const SizedBox(height: 50),
Text( Text(
context.lang.registerTitle,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 30),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Text(
context.lang.registerSlogan,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12),
),
),
const SizedBox(height: 60),
Center(
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Text(
context.lang.registerUsernameSlogan, context.lang.registerUsernameSlogan,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontSize: 15), style: TextStyle(
fontSize: 16,
color: Colors.grey[800],
fontWeight: FontWeight.w600,
), ),
), ),
), const SizedBox(height: 20),
const SizedBox(height: 15),
TextField( TextField(
controller: usernameController, controller: usernameController,
onChanged: (value) { onChanged: (value) {
usernameController.text = value.toLowerCase(); usernameController.text = value.toLowerCase();
usernameController.selection = TextSelection.fromPosition( usernameController.selection = TextSelection.fromPosition(
TextPosition(offset: usernameController.text.length), TextPosition(
offset: usernameController.text.length,
),
); );
setState(() { setState(() {
_isValidUserName = usernameController.text.length >= 3; _isValidUserName = usernameController.text.length >= 3;
@ -236,84 +226,108 @@ class _RegisterViewState extends State<RegisterView> {
}, },
inputFormatters: [ inputFormatters: [
LengthLimitingTextInputFormatter(12), LengthLimitingTextInputFormatter(12),
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')), FilteringTextInputFormatter.allow(
RegExp('[a-z0-9A-Z._]'),
),
], ],
style: const TextStyle(fontSize: 17), style: const TextStyle(
decoration: getInputDecoration( fontSize: 18,
context.lang.registerUsernameDecoration, fontWeight: FontWeight.w500,
),
decoration: InputDecoration(
hintText: context.lang.registerUsernameDecoration,
filled: true,
fillColor: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
prefixIcon: const Icon(
Icons.alternate_email,
), ),
), ),
const SizedBox(height: 10), ),
if (_showUserNameError &&
usernameController.text.length < 3) ...[
const SizedBox(height: 8),
Text( Text(
context.lang.registerUsernameLimits, context.lang.registerUsernameLimits,
style: TextStyle( style: const TextStyle(
color: _showUserNameError ? Colors.red : Colors.transparent, color: Colors.red,
fontSize: 12, fontSize: 13,
fontWeight: FontWeight.w500,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 10), ],
if (_showProofOfWorkError) ...[
const SizedBox(height: 8),
Text( Text(
context.lang.registerProofOfWorkFailed, context.lang.registerProofOfWorkFailed,
style: TextStyle( style: const TextStyle(
color: _showProofOfWorkError color: Colors.red,
? Colors.red fontSize: 13,
: Colors.transparent, fontWeight: FontWeight.w500,
fontSize: 12,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 10), ],
Column( const SizedBox(height: 24),
children: [ FilledButton(
FilledButton.icon( onPressed: _isTryingToRegister ? null : createNewUser,
icon: _isTryingToRegister style: FilledButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(60),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
elevation: 0,
),
child: _isTryingToRegister
? const SizedBox( ? const SizedBox(
width: 18, width: 24,
height: 18, height: 24,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.black, color: Colors.white,
strokeWidth: 2, strokeWidth: 3,
), ),
) )
: const Icon(Icons.group), : Text(
onPressed: createNewUser,
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(
const EdgeInsets.symmetric(
vertical: 10,
horizontal: 30,
),
),
backgroundColor: _isTryingToRegister
? WidgetStateProperty.all<MaterialColor>(
Colors.grey,
)
: null,
),
label: Text(
context.lang.registerSubmitButton, context.lang.registerSubmitButton,
style: const TextStyle(fontSize: 17), style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 16),
],
TextButton(
onPressed: () => context.push(
Routes.settingsBackupRecovery,
),
style: TextButton.styleFrom(
minimumSize: const Size.fromHeight(50),
foregroundColor: Colors.grey[600],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
),
child: Text(
context.lang.twonlySafeRecoverBtn,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
), ),
), ),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
OutlinedButton.icon(
onPressed: () =>
context.push(Routes.settingsBackupRecovery),
label: Text(context.lang.twonlySafeRecoverBtn),
), ),
], ],
), ),
),
const Spacer(),
const SizedBox(height: 40),
], ],
),
// ),
],
),
),
),
); );
} }
} }