mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 10:12:12 +00:00
redesigning register view
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
32231d11c2
commit
ea41158872
9 changed files with 485 additions and 253 deletions
|
|
@ -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/mediafiles/mediafile.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/setup.notifications.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
|
|
|||
|
|
@ -98,16 +98,10 @@ abstract class AppLocalizations {
|
|||
Locale('en'),
|
||||
];
|
||||
|
||||
/// No description provided for @registerTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Welcome to twonly!'**
|
||||
String get registerTitle;
|
||||
|
||||
/// No description provided for @registerSlogan.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
/// No description provided for @onboardingWelcomeTitle.
|
||||
|
|
@ -179,7 +173,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @registerUsernameSlogan.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please select a username so others can find you!'**
|
||||
/// **'Your public username'**
|
||||
String get registerUsernameSlogan;
|
||||
|
||||
/// No description provided for @registerUsernameDecoration.
|
||||
|
|
@ -191,7 +185,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @registerUsernameLimits.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your username must be at least 3 characters long.'**
|
||||
/// **'At least 3 characters.'**
|
||||
String get registerUsernameLimits;
|
||||
|
||||
/// No description provided for @registerProofOfWorkFailed.
|
||||
|
|
@ -1583,7 +1577,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @twonlySafeRecoverTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recovery'**
|
||||
/// **'Restore backup'**
|
||||
String get twonlySafeRecoverTitle;
|
||||
|
||||
/// No description provided for @twonlySafeRecoverDesc.
|
||||
|
|
|
|||
|
|
@ -8,12 +8,9 @@ import 'app_localizations.dart';
|
|||
class AppLocalizationsDe extends AppLocalizations {
|
||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||
|
||||
@override
|
||||
String get registerTitle => 'Willkommen bei twonly!';
|
||||
|
||||
@override
|
||||
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
|
||||
String get onboardingWelcomeTitle => 'Willkommen bei twonly!';
|
||||
|
|
@ -55,15 +52,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get onboardingGetStartedTitle => 'Auf geht\'s';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan =>
|
||||
'Bitte wähle einen Benutzernamen, damit dich andere finden können!';
|
||||
String get registerUsernameSlogan => 'Dein öffentlicher Benutzername';
|
||||
|
||||
@override
|
||||
String get registerUsernameDecoration => 'Benutzername';
|
||||
|
||||
@override
|
||||
String get registerUsernameLimits =>
|
||||
'Der Benutzername muss mindestens 3 Zeichen lang sein.';
|
||||
String get registerUsernameLimits => 'Mindestens 3 Zeichen.';
|
||||
|
||||
@override
|
||||
String get registerProofOfWorkFailed =>
|
||||
|
|
@ -816,7 +811,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupChangePassword => 'Password ändern';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Recovery';
|
||||
String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverDesc =>
|
||||
|
|
|
|||
|
|
@ -8,12 +8,9 @@ import 'app_localizations.dart';
|
|||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get registerTitle => 'Welcome to twonly!';
|
||||
|
||||
@override
|
||||
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
|
||||
String get onboardingWelcomeTitle => 'Welcome to twonly!';
|
||||
|
|
@ -54,15 +51,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get onboardingGetStartedTitle => 'Let\'s go!';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan =>
|
||||
'Please select a username so others can find you!';
|
||||
String get registerUsernameSlogan => 'Your public username';
|
||||
|
||||
@override
|
||||
String get registerUsernameDecoration => 'Username';
|
||||
|
||||
@override
|
||||
String get registerUsernameLimits =>
|
||||
'Your username must be at least 3 characters long.';
|
||||
String get registerUsernameLimits => 'At least 3 characters.';
|
||||
|
||||
@override
|
||||
String get registerProofOfWorkFailed =>
|
||||
|
|
@ -810,7 +805,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get backupChangePassword => 'Change password';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Recovery';
|
||||
String get twonlySafeRecoverTitle => 'Restore backup';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverDesc =>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit c41aaeff47007ddd5484fe1124bb15b905c3b247
|
||||
Subproject commit 8660b9b11721adda59e387a129f9017bb9e72c67
|
||||
|
|
@ -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)}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ import 'package:twonly/src/services/backup.service.dart';
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.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 {
|
||||
const BackupRecoveryView({super.key});
|
||||
|
|
@ -64,66 +66,112 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('twonly Backup ${context.lang.twonlySafeRecoverTitle}'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await showAlertDialog(
|
||||
context,
|
||||
'twonly Backup',
|
||||
context.lang.backupTwonlySafeLongDesc,
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.circleInfo),
|
||||
iconSize: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(
|
||||
vertical: 40,
|
||||
horizontal: 40,
|
||||
),
|
||||
child: ListView(
|
||||
return OnboardingWrapper(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
context.lang.twonlySafeRecoverDesc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
TextField(
|
||||
controller: usernameCtrl,
|
||||
onChanged: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
style: const TextStyle(fontSize: 17),
|
||||
decoration: getInputDecoration(
|
||||
context,
|
||||
context.lang.registerUsernameDecoration,
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
),
|
||||
color: Colors.white,
|
||||
iconSize: 20,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Stack(
|
||||
children: [
|
||||
TextField(
|
||||
controller: passwordCtrl,
|
||||
onChanged: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
style: const TextStyle(fontSize: 17),
|
||||
obscureText: obscureText,
|
||||
decoration: getInputDecoration(
|
||||
context,
|
||||
context.lang.password,
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await showAlertDialog(
|
||||
context,
|
||||
'twonly Backup',
|
||||
context.lang.backupTwonlySafeLongDesc,
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.circleInfo),
|
||||
color: Colors.white,
|
||||
iconSize: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: LinkLogoAnimation(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
context.lang.twonlySafeRecoverTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
controller: usernameCtrl,
|
||||
onChanged: (value) => setState(() {}),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
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,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: IconButton(
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: passwordCtrl,
|
||||
onChanged: (value) => setState(() {}),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
hintText: 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,
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
obscureText = !obscureText;
|
||||
|
|
@ -137,25 +185,42 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: FilledButton.icon(
|
||||
onPressed: (!isLoading) ? _recoverTwonlySafe : null,
|
||||
icon: isLoading
|
||||
? const SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator(strokeWidth: 1),
|
||||
)
|
||||
: const Icon(Icons.lock_clock_rounded),
|
||||
label: Text(context.lang.twonlySafeRecoverBtn),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 32),
|
||||
FilledButton(
|
||||
onPressed: (!isLoading) ? _recoverTwonlySafe : null,
|
||||
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(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
context.lang.twonlySafeRecoverBtn,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// ignore_for_file: avoid_dynamic_calls
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/storage.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/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';
|
||||
|
||||
class RegisterView extends StatefulWidget {
|
||||
|
|
@ -134,7 +138,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
userId: userId,
|
||||
username: username,
|
||||
displayName: username,
|
||||
subscriptionPlan: 'Preview',
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: SetupPages.profile.name,
|
||||
)..appVersion = AppState.latestAppVersionId;
|
||||
|
||||
|
|
@ -146,174 +150,184 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_registrationDisabled) {
|
||||
return Scaffold(
|
||||
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(
|
||||
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: 130),
|
||||
return OnboardingWrapper(
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: const LinkLogoAnimation(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
context.lang.registerSlogan,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
context.lang.registrationClosed,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
context.lang.registerTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 30),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: Text(
|
||||
context.lang.registerSlogan,
|
||||
const SizedBox(height: 48),
|
||||
] else ...[
|
||||
Text(
|
||||
context.lang.registerUsernameSlogan,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[800],
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 60),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: Text(
|
||||
context.lang.registerUsernameSlogan,
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
controller: usernameController,
|
||||
onChanged: (value) {
|
||||
usernameController.text = value.toLowerCase();
|
||||
usernameController.selection = TextSelection.fromPosition(
|
||||
TextPosition(
|
||||
offset: usernameController.text.length,
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_isValidUserName = usernameController.text.length >= 3;
|
||||
});
|
||||
},
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(12),
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp('[a-z0-9A-Z._]'),
|
||||
),
|
||||
],
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_showUserNameError &&
|
||||
usernameController.text.length < 3) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.lang.registerUsernameLimits,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
TextField(
|
||||
controller: usernameController,
|
||||
onChanged: (value) {
|
||||
usernameController.text = value.toLowerCase();
|
||||
usernameController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: usernameController.text.length),
|
||||
);
|
||||
setState(() {
|
||||
_isValidUserName = usernameController.text.length >= 3;
|
||||
});
|
||||
},
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(12),
|
||||
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')),
|
||||
],
|
||||
style: const TextStyle(fontSize: 17),
|
||||
decoration: getInputDecoration(
|
||||
context.lang.registerUsernameDecoration,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
context.lang.registerUsernameLimits,
|
||||
style: TextStyle(
|
||||
color: _showUserNameError ? Colors.red : Colors.transparent,
|
||||
fontSize: 12,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
context.lang.registerProofOfWorkFailed,
|
||||
style: TextStyle(
|
||||
color: _showProofOfWorkError
|
||||
? Colors.red
|
||||
: Colors.transparent,
|
||||
fontSize: 12,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Column(
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
icon: _isTryingToRegister
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.black,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.group),
|
||||
onPressed: createNewUser,
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 30,
|
||||
if (_showProofOfWorkError) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.lang.registerProofOfWorkFailed,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
FilledButton(
|
||||
onPressed: _isTryingToRegister ? null : createNewUser,
|
||||
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(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
context.lang.registerSubmitButton,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: _isTryingToRegister
|
||||
? WidgetStateProperty.all<MaterialColor>(
|
||||
Colors.grey,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
label: Text(
|
||||
context.lang.registerSubmitButton,
|
||||
style: const TextStyle(fontSize: 17),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () =>
|
||||
context.push(Routes.settingsBackupRecovery),
|
||||
label: Text(context.lang.twonlySafeRecoverBtn),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
context.lang.twonlySafeRecoverBtn,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue