Skip to content

Commit

Permalink
Associate email
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed May 19, 2024
1 parent 498f3f7 commit 001d9a8
Show file tree
Hide file tree
Showing 13 changed files with 526 additions and 239 deletions.
41 changes: 41 additions & 0 deletions lib/components/accounts/account_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';

class AccountCard extends StatelessWidget {
final String label;
final String value;

const AccountCard({
super.key,
required this.label,
required this.value,
});

@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: const TextStyle(color: Colors.white),
),
],
),
),
);
}
}
156 changes: 156 additions & 0 deletions lib/components/accounts/associate_email.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import 'package:application/controllers/member_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:vibration/vibration.dart';

class AssociateEmail extends StatefulWidget {
const AssociateEmail({super.key});

@override
State<StatefulWidget> createState() => _AssociateEmailState();
}

class _AssociateEmailState extends State<AssociateEmail> {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _codeController = TextEditingController();
bool _isEmailInError = false;
bool _isCodeInError = false;
bool _isLoading = false;
String? _actionUuid;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.associateEmail),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.email,
errorText: _isEmailInError
? AppLocalizations.of(context)!.invalidEmail
: null,
),
controller: _emailController,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.code,
errorText: _isCodeInError
? AppLocalizations.of(context)!.invalidCode
: null,
),
controller: _codeController,
),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: _isLoading
? null
: () {
saveEmail(context);
},
child: Text(AppLocalizations.of(context)!.sendCode),
),
],
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: _isLoading
? null
: () {
validateAction(context);
},
child: Text(AppLocalizations.of(context)!.save),
),
],
),
),
);
}

Future<void> saveEmail(BuildContext context) async {
if (_emailController.text.isEmpty) {
Vibration.vibrate(duration: 200, amplitude: 255);
return;
}

try {
// Verify if the email is valid (^[A-Za-z0-9+_.-]+@(.+)$)
if (!RegExp(r'^[A-Za-z0-9+_.-]+@(.+)$').hasMatch(_emailController.text)) {
Vibration.vibrate(duration: 200, amplitude: 255);

if (context.mounted) {
setState(() {
_isEmailInError = true;
});
}

return;
}

setState(() {
_isEmailInError = false;
_isLoading = true;
});

_actionUuid =
await MemberController.instance.associateEmail(_emailController.text);
} catch (e) {
Vibration.vibrate(duration: 200, amplitude: 255);

if (context.mounted) {
setState(() {
_isEmailInError = true;
});
}
} finally {
if (context.mounted) {
setState(() {
_isLoading = false;
});
}
}
}

Future<void> validateAction(BuildContext context) async {
if (_codeController.text.isEmpty || _actionUuid == null) {
Vibration.vibrate(duration: 200, amplitude: 255);
return;
}

try {
await MemberController.instance
.validateAction(_actionUuid!, _codeController.text);

if (context.mounted) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'OK',
textAlign: TextAlign.center,
),
),
);
}
} catch (e) {
Vibration.vibrate(duration: 200, amplitude: 255);

if (context.mounted) {
setState(() {
_isCodeInError = true;
});
}
}
}
}
81 changes: 81 additions & 0 deletions lib/components/accounts/edit_identifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import 'package:application/controllers/member_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:vibration/vibration.dart';

class EditIdentifier extends StatelessWidget {
final TextEditingController _controller = TextEditingController();

EditIdentifier({super.key});

@override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context)!.enterNewIdentifier,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.identifier,
),
controller: _controller,
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(context)!.cancel),
),
ElevatedButton(
onPressed: () {
saveIdentifier(context);
},
child: Text(AppLocalizations.of(context)!.save),
),
],
);
}

Future<void> saveIdentifier(BuildContext context) async {
if (_controller.text.isEmpty) {
Vibration.vibrate(duration: 200, amplitude: 255);
return;
}

final oldIdentifier = MemberController.instance.identifier;

try {
await MemberController.instance.testLogin(_controller.text);
await MemberController.instance.login(identifier: _controller.text);
} catch (e) {
Vibration.vibrate(duration: 200, amplitude: 255);

if (MemberController.instance.identifier != oldIdentifier) {
await MemberController.instance.login(identifier: oldIdentifier);
}

if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.invalidIdentifier,
textAlign: TextAlign.center,
),
),
);
}
} finally {
if (context.mounted) {
Navigator.of(context).pop();
}
}
}
}
34 changes: 32 additions & 2 deletions lib/controllers/member_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class MemberController {
}

Future<String> register() async {
final response = await HttpRequest().post('/v1/members/private-register');
final response = await HttpRequest().post('/v1/members/register');

if (response.statusCode != 201) {
throw const HttpException('Failed to register');
Expand All @@ -60,7 +60,7 @@ class MemberController {

Future<Response> testLogin(String identifier) async {
final response =
await HttpRequest().post('/v1/members/private-login', body: identifier);
await HttpRequest().post('/v1/members/login', body: identifier);

if (response.statusCode == 404) {
throw const HttpException('Failed to login, identifier not found');
Expand All @@ -85,6 +85,36 @@ class MemberController {
member = MemberDto.fromJson(json);
}

Future<String> associateEmail(String email) async {
final response = await HttpRequest().post(
'/v1/members/associate-email',
token: member!.token,
body: email,
timeout: false,
);

if (response.statusCode != 201) {
throw const HttpException('Failed to associate email');
}

final json = jsonDecode(utf8.decode(response.bodyBytes));
return json['uuid'] as String;
}

Future<void> validateAction(String uuid, String code) async {
final response = await HttpRequest().post(
'/v1/member-actions/validate?uuid=$uuid',
token: member!.token,
body: code,
);

if (response.statusCode != 200) {
throw const HttpException('Failed to validate action');
}

await login();
}

Future<void> followAnime(AnimeDto anime) async {
final response = await HttpRequest().put(
'/v1/members/animes',
Expand Down
1 change: 1 addition & 0 deletions lib/dtos/member_dto.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class MemberDto with _$MemberDto {
required String creationDateTime,
required String lastUpdateDateTime,
required bool isPrivate,
required String? email,
required List<String> followedAnimes,
required List<String> followedEpisodes,
required int totalDuration,
Expand Down
Loading

0 comments on commit 001d9a8

Please sign in to comment.