This commit is contained in:
Vandad Nahavandipoor 2022-01-23 07:36:59 +01:00
parent 0a663378f9
commit 2690957ef0
19 changed files with 282 additions and 72 deletions

View File

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>sv</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>

3
l10n.yaml Normal file
View File

@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: intl_en.arb
output-localization-file: app_localizations.dart

View File

@ -0,0 +1,7 @@
import 'package:flutter/material.dart' show BuildContext;
import 'package:flutter_gen/gen_l10n/app_localizations.dart'
show AppLocalizations;
extension Localization on BuildContext {
AppLocalizations get loc => AppLocalizations.of(this)!;
}

62
lib/l10n/intl_en.arb Normal file
View File

@ -0,0 +1,62 @@
{
"logout_button": "Log out",
"note": "Note",
"cancel": "Cancel",
"yes": "Yes",
"delete": "Delete",
"sharing": "Sharing",
"ok": "OK",
"login": "Login",
"verify_email": "Verify email",
"register": "Register",
"restart": "Restart",
"start_typing_your_note": "Start typing your note",
"delete_note_prompt": "Are you sure you want to delete this note?",
"cannot_share_empty_note_prompt": "Cannot share an empty note!",
"generic_error_prompt": "An error occurred",
"logout_dialog_prompt": "Are you sure you want to log out?",
"password_reset": "Password reset",
"password_reset_dialog_prompt": "We have now sent you a password reset link. Please check your email for more information.",
"login_error_cannot_find_user": "Cannot find a user with the entered credentials!",
"login_error_wrong_credentials": "Wrong credentials",
"login_error_auth_error": "Authentication error",
"login_view_prompt": "Please log in to your account in order to interact with and create notes!",
"login_view_forgot_password": "I forgot my password",
"login_view_not_registered_yet": "Not registered yet? Register here!",
"email_text_field_placeholder": "Enter your email here",
"password_text_field_placeholder": "Enter your password here",
"forgot_password": "Forgot Password",
"forgot_password_view_generic_error": "We could not process your request. Please make sure that you are a registered user, or if not, register a user now by going back one step.",
"forgot_password_view_prompt": "If you forgot your password, simply enter your email and we will send you a password reset link.",
"forgot_password_view_send_me_link": "Send me password reset link",
"forgot_password_view_back_to_login": "Back to login page",
"register_error_weak_password": "This password is not secure enough. Please choose another password!",
"register_error_email_already_in_use": "This email is already registered to another user. Please choose another email!",
"register_error_generic": "Failed to register. Please try again later!",
"register_error_invalid_email": "The email address you entered appears to be invalid. Please try another email address!",
"register_view_prompt": "Enter your email and password to see your notes!",
"register_view_already_registered": "Already registered? Login here!",
"verify_email_view_prompt": "We've sent you an email verification. Please open it to verify your account. If you haven't received a verification email yet, press the button below!",
"verify_email_send_email_verification": "Send email verification",
"notes_title": "{count, plural, =0{No notes yet} =1{1 note} other{{count} notes}}",
"@notes_title": {
"placeholders": {
"count": {
"type": "int",
"example": "3 notes"
}
}
}
}

62
lib/l10n/intl_sv.arb Normal file
View File

@ -0,0 +1,62 @@
{
"logout_button": "Logga ut",
"note": "Antäckning",
"cancel": "Avbryt",
"yes": "Ja",
"delete": "Radera",
"sharing": "Delning",
"ok": "OK",
"login": "Logga in",
"verify_email": "Verifera",
"register": "Registrera",
"restart": "Börja om",
"start_typing_your_note": "Skriv din antäckning här",
"delete_note_prompt": "Are du säker på att du vill radera den här antäckningen?",
"cannot_share_empty_note_prompt": "Kan inte dela en tom antäckning!",
"generic_error_prompt": "Ett fel har inträffats",
"logout_dialog_prompt": "Är du säker på att du vill logga ut?",
"password_reset": "Nollställ ditt lösenord",
"password_reset_dialog_prompt": "Vi har nu skickat ett mejl till dig. Mejlet innehåller instruktioner på hur du kan nollställa ditt lösenord!",
"login_error_cannot_find_user": "Kunde inte hitta en användare med de angivna uppgifter!",
"login_error_wrong_credentials": "Fel inloggnings uppgifter!",
"login_error_auth_error": "Ett autentiseringsfel har inträffats!",
"login_view_prompt": "Logga in för att kunna se dina antäckningar!",
"login_view_forgot_password": "Glömt lösenord?",
"login_view_not_registered_yet": "Har inte registrerar än? Tryck här!",
"email_text_field_placeholder": "Skriv ditt mejladdress här",
"password_text_field_placeholder": "Skriv ditt lösenord här",
"forgot_password": "Glömt Lösenord",
"forgot_password_view_generic_error": "Vi kunde inte hantera din begäran. Var vänlig och skriv ditt inloggningsuppgifter och prova igen!",
"forgot_password_view_prompt": "Om du har glömt ditt lösenord, mata in ditt mejladdress så kan vi skicka ett mejl till dig där du kan nollställa ditt lösenord!",
"forgot_password_view_send_me_link": "Skicka mejlet",
"forgot_password_view_back_to_login": "Tillbaka till loginskärmen",
"register_error_weak_password": "Ditt valda lösenord är inte säkert nog. Var vänlig och välj ett annat lösenord!",
"register_error_email_already_in_use": "Mejladdresset som du angav är redan taget. Var vänlig och välj ett annat mejladdress!",
"register_error_generic": "Kunde tyvärr inte hantera din begäran. Var vänlig försök igen senare.",
"register_error_invalid_email": "Ditt valda mejladdress verkar inte vara giltigt. Var vänlig försök med ett annat mejladdress.",
"register_view_prompt": "Mata in ditt mejladdress och ditt lösenord för att se dina antäckningar!",
"register_view_already_registered": "Redan registrerat? Logga in här!",
"verify_email_view_prompt": "Vi har skickat ett mejl till ditt mejladdress. Du behöver trycka på länken i mejlet för att verifiera ditt mejladdress. Om du inte har redan fått mejlet efter en stund, tryck på knappen nedan för att skicka ett till mejl!",
"verify_email_send_email_verification": "Skicka verifieringsmejlet igen",
"notes_title": "{count, plural, =0{Inga antäckningar än} =1{1 antäckning} other{{count} antäckningar}}",
"@notes_title": {
"placeholders": {
"count": {
"type": "int",
"example": "3 antäckningar"
}
}
}
}

View File

@ -12,11 +12,14 @@ import 'package:mynotes/views/notes/create_update_note_view.dart';
import 'package:mynotes/views/notes/notes_view.dart';
import 'package:mynotes/views/register_view.dart';
import 'package:mynotes/views/verify_email_view.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(

View File

@ -1,13 +1,14 @@
import 'package:flutter/material.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/utilities/dialogs/generic_dialog.dart';
Future<void> showCannotShareEmptyNoteDialog(BuildContext context) {
return showGenericDialog<void>(
context: context,
title: 'Sharing',
content: 'You cannot share an empty note!',
title: context.loc.sharing,
content: context.loc.cannot_share_empty_note_prompt,
optionsBuilder: () => {
'OK': null,
context.loc.ok: null,
},
);
}

View File

@ -1,14 +1,15 @@
import 'package:flutter/material.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/utilities/dialogs/generic_dialog.dart';
Future<bool> showDeleteDialog(BuildContext context) {
return showGenericDialog<bool>(
context: context,
title: 'Delete',
content: 'Are you sure you want to delete this item?',
title: context.loc.delete,
content: context.loc.delete_note_prompt,
optionsBuilder: () => {
'Cancel': false,
'Yes': true,
context.loc.cancel: false,
context.loc.yes: true,
},
).then(
(value) => value ?? false,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/utilities/dialogs/generic_dialog.dart';
Future<void> showErrorDialog(
@ -7,10 +8,10 @@ Future<void> showErrorDialog(
) {
return showGenericDialog<void>(
context: context,
title: 'An error occurred',
title: context.loc.generic_error_prompt,
content: text,
optionsBuilder: () => {
'OK': null,
context.loc.ok: null,
},
);
}

View File

@ -1,14 +1,15 @@
import 'package:flutter/material.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/utilities/dialogs/generic_dialog.dart';
Future<bool> showLogOutDialog(BuildContext context) {
return showGenericDialog<bool>(
context: context,
title: 'Log out',
content: 'Are you sure you want to log out?',
title: context.loc.logout_button,
content: context.loc.logout_dialog_prompt,
optionsBuilder: () => {
'Cancel': false,
'Log out': true,
context.loc.cancel: false,
context.loc.logout_button: true,
},
).then(
(value) => value ?? false,

View File

@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/utilities/dialogs/generic_dialog.dart';
Future<void> showPasswordResetSentDialog(BuildContext context) {
return showGenericDialog<void>(
context: context,
title: 'Password Reset',
content:
'We have now sent you a password reset link. Please check your email for more information.',
title: context.loc.password_reset,
content: context.loc.password_reset_dialog_prompt,
optionsBuilder: () => {
'OK': null,
context.loc.ok: null,
},
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
import 'package:mynotes/services/auth/bloc/auth_event.dart';
import 'package:mynotes/services/auth/bloc/auth_state.dart';
@ -38,29 +39,32 @@ class _ForgotPasswordViewState extends State<ForgotPasswordView> {
await showPasswordResetSentDialog(context);
}
if (state.exception != null) {
await showErrorDialog(context,
'We could not process your request. Please make sure that you are a registered user, or if not, register a user now by going back one step.');
await showErrorDialog(
context,
context.loc.forgot_password_view_generic_error,
);
}
}
},
child: Scaffold(
appBar: AppBar(
title: const Text('Forgot Password'),
title: Text(context.loc.forgot_password),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: [
const Text(
'If you forgot your password, simply enter your email and we will send you a password reset link.'),
Text(
context.loc.forgot_password_view_prompt,
),
TextField(
keyboardType: TextInputType.emailAddress,
autocorrect: false,
autofocus: true,
controller: _controller,
decoration: const InputDecoration(
hintText: 'Your email address....',
decoration: InputDecoration(
hintText: context.loc.email_text_field_placeholder,
),
),
TextButton(
@ -70,7 +74,9 @@ class _ForgotPasswordViewState extends State<ForgotPasswordView> {
.read<AuthBloc>()
.add(AuthEventForgotPassword(email: email));
},
child: const Text('Send me password reset link'),
child: Text(
context.loc.forgot_password_view_send_me_link,
),
),
TextButton(
onPressed: () {
@ -78,7 +84,9 @@ class _ForgotPasswordViewState extends State<ForgotPasswordView> {
const AuthEventLogOut(),
);
},
child: const Text('Back to login page'),
child: Text(
context.loc.forgot_password_view_back_to_login,
),
),
],
),

View File

@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:mynotes/constants/routes.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/services/auth/auth_exceptions.dart';
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
import 'package:mynotes/services/auth/bloc/auth_event.dart';
import 'package:mynotes/services/auth/bloc/auth_state.dart';
import 'package:mynotes/utilities/dialogs/error_dialog.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mynotes/utilities/dialogs/loading_dialog.dart';
class LoginView extends StatefulWidget {
const LoginView({Key? key}) : super(key: key);
@ -41,33 +40,38 @@ class _LoginViewState extends State<LoginView> {
if (state.exception is UserNotFoundAuthException) {
await showErrorDialog(
context,
'Cannot find a user with the entered credentials!',
context.loc.login_error_cannot_find_user,
);
} else if (state.exception is WrongPasswordAuthException) {
await showErrorDialog(context, 'Wrong credentials');
await showErrorDialog(
context,
context.loc.login_error_wrong_credentials,
);
} else if (state.exception is GenericAuthException) {
await showErrorDialog(context, 'Authentication error');
await showErrorDialog(
context,
context.loc.login_error_auth_error,
);
}
}
},
child: Scaffold(
appBar: AppBar(
title: const Text('Login'),
title: Text(context.loc.login),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: [
const Text(
'Please log in to your account in order to interact with and create notes!'),
Text(context.loc.login_view_prompt),
TextField(
controller: _email,
enableSuggestions: false,
autocorrect: false,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
hintText: 'Enter your email here',
decoration: InputDecoration(
hintText: context.loc.email_text_field_placeholder,
),
),
TextField(
@ -75,8 +79,8 @@ class _LoginViewState extends State<LoginView> {
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: const InputDecoration(
hintText: 'Enter your password here',
decoration: InputDecoration(
hintText: context.loc.password_text_field_placeholder,
),
),
TextButton(
@ -90,7 +94,7 @@ class _LoginViewState extends State<LoginView> {
),
);
},
child: const Text('Login'),
child: Text(context.loc.login),
),
TextButton(
onPressed: () {
@ -98,7 +102,9 @@ class _LoginViewState extends State<LoginView> {
const AuthEventForgotPassword(),
);
},
child: const Text('I forgot my password'),
child: Text(
context.loc.login_view_forgot_password,
),
),
TextButton(
onPressed: () {
@ -106,7 +112,9 @@ class _LoginViewState extends State<LoginView> {
const AuthEventShouldRegister(),
);
},
child: const Text('Not registered yet? Register here!'),
child: Text(
context.loc.login_view_not_registered_yet,
),
)
],
),

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/services/auth/auth_service.dart';
import 'package:mynotes/utilities/dialogs/cannot_share_empty_note_dialog.dart';
import 'package:mynotes/utilities/generics/get_arguments.dart';
import 'package:mynotes/services/cloud/cloud_note.dart';
import 'package:mynotes/services/cloud/cloud_storage_exceptions.dart';
import 'package:mynotes/services/cloud/firebase_cloud_storage.dart';
import 'package:share_plus/share_plus.dart';
@ -93,7 +93,9 @@ class _CreateUpdateNoteViewState extends State<CreateUpdateNoteView> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('New Note'),
title: Text(
context.loc.note,
),
actions: [
IconButton(
onPressed: () async {
@ -118,8 +120,8 @@ class _CreateUpdateNoteViewState extends State<CreateUpdateNoteView> {
controller: _textController,
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: const InputDecoration(
hintText: 'Start typing your note...',
decoration: InputDecoration(
hintText: context.loc.start_typing_your_note,
),
);
default:

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:mynotes/constants/routes.dart';
import 'package:mynotes/enums/menu_action.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/services/auth/auth_service.dart';
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
import 'package:mynotes/services/auth/bloc/auth_event.dart';
@ -10,6 +11,10 @@ import 'package:mynotes/utilities/dialogs/logout_dialog.dart';
import 'package:mynotes/views/notes/notes_list_view.dart';
import 'package:flutter_bloc/flutter_bloc.dart' show ReadContext;
extension Count<T extends Iterable> on Stream<T> {
Stream<int> get getLength => map((event) => event.length);
}
class NotesView extends StatefulWidget {
const NotesView({Key? key}) : super(key: key);
@ -31,7 +36,18 @@ class _NotesViewState extends State<NotesView> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Your Notes'),
title: StreamBuilder(
stream: _notesService.allNotes(ownerUserId: userId).getLength,
builder: (context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
final noteCount = snapshot.data ?? 0;
final text = context.loc.notes_title(noteCount);
return Text(text);
} else {
return const Text('');
}
},
),
actions: [
IconButton(
onPressed: () {
@ -52,10 +68,10 @@ class _NotesViewState extends State<NotesView> {
}
},
itemBuilder: (context) {
return const [
return [
PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Log out'),
child: Text(context.loc.logout_button),
),
];
},

View File

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mynotes/constants/routes.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/services/auth/auth_exceptions.dart';
import 'package:mynotes/services/auth/auth_service.dart';
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
import 'package:mynotes/services/auth/bloc/auth_event.dart';
import 'package:mynotes/services/auth/bloc/auth_state.dart';
@ -39,19 +38,31 @@ class _RegisterViewState extends State<RegisterView> {
listener: (context, state) async {
if (state is AuthStateRegistering) {
if (state.exception is WeakPasswordAuthException) {
await showErrorDialog(context, 'Weak password');
await showErrorDialog(
context,
context.loc.register_error_weak_password,
);
} else if (state.exception is EmailAlreadyInUseAuthException) {
await showErrorDialog(context, 'Email is already in use');
await showErrorDialog(
context,
context.loc.register_error_email_already_in_use,
);
} else if (state.exception is GenericAuthException) {
await showErrorDialog(context, 'Failed to register');
await showErrorDialog(
context,
context.loc.register_error_generic,
);
} else if (state.exception is InvalidEmailAuthException) {
await showErrorDialog(context, 'Invalid email');
await showErrorDialog(
context,
context.loc.register_error_invalid_email,
);
}
}
},
child: Scaffold(
appBar: AppBar(
title: const Text('Register'),
title: Text(context.loc.register),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
@ -59,15 +70,15 @@ class _RegisterViewState extends State<RegisterView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Enter your email and password to see your notes!'),
Text(context.loc.register_view_prompt),
TextField(
controller: _email,
enableSuggestions: false,
autocorrect: false,
autofocus: true,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
hintText: 'Enter your email here',
decoration: InputDecoration(
hintText: context.loc.email_text_field_placeholder,
),
),
TextField(
@ -75,8 +86,8 @@ class _RegisterViewState extends State<RegisterView> {
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: const InputDecoration(
hintText: 'Enter your password here',
decoration: InputDecoration(
hintText: context.loc.password_text_field_placeholder,
),
),
Center(
@ -93,7 +104,9 @@ class _RegisterViewState extends State<RegisterView> {
),
);
},
child: const Text('Register'),
child: Text(
context.loc.register,
),
),
TextButton(
onPressed: () {
@ -101,7 +114,9 @@ class _RegisterViewState extends State<RegisterView> {
const AuthEventLogOut(),
);
},
child: const Text('Already registered? Login here!'),
child: Text(
context.loc.register_view_already_registered,
),
),
],
),

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mynotes/constants/routes.dart';
import 'package:mynotes/services/auth/auth_service.dart';
import 'package:mynotes/extensions/buildcontext/loc.dart';
import 'package:mynotes/services/auth/bloc/auth_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mynotes/services/auth/bloc/auth_event.dart';
@ -17,22 +16,26 @@ class _VerifyEmailViewState extends State<VerifyEmailView> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Verify email'),
title: Text(context.loc.verify_email),
),
body: SingleChildScrollView(
child: Column(
children: [
const Text(
"We've sent you an email verification. Please open it to verify your account."),
const Text(
"If you haven't received a verification email yet, press the button below"),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
context.loc.verify_email_view_prompt,
),
),
TextButton(
onPressed: () {
context.read<AuthBloc>().add(
const AuthEventSendEmailVerification(),
);
},
child: const Text('Send email verification'),
child: Text(
context.loc.verify_email_send_email_verification,
),
),
TextButton(
onPressed: () async {
@ -40,7 +43,9 @@ class _VerifyEmailViewState extends State<VerifyEmailView> {
const AuthEventLogOut(),
);
},
child: const Text('Restart'),
child: Text(
context.loc.restart,
),
)
],
),

View File

@ -251,6 +251,11 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -297,7 +302,7 @@ packages:
source: hosted
version: "3.1.0"
intl:
dependency: transitive
dependency: "direct main"
description:
name: intl
url: "https://pub.dartlang.org"

View File

@ -29,6 +29,8 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
@ -46,6 +48,7 @@ dependencies:
flutter_bloc: ^8.0.1
equatable: ^2.0.3
flutter_launcher_icons: ^0.9.2
intl: ^0.17.0
dev_dependencies:
flutter_test:
@ -65,6 +68,8 @@ dev_dependencies:
# The following section is specific to Flutter.
flutter:
generate: true
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.