diff options
Diffstat (limited to 'lib/screens')
-rw-r--r-- | lib/screens/dashboard_screen.dart | 100 | ||||
-rw-r--r-- | lib/screens/login_screen.dart | 171 | ||||
-rw-r--r-- | lib/screens/onboarding_screen.dart | 103 | ||||
-rw-r--r-- | lib/screens/register_screen.dart | 178 |
4 files changed, 552 insertions, 0 deletions
diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..cb31a58 --- /dev/null +++ b/lib/screens/dashboard_screen.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../firebase/auth.dart'; +import '../providers/theme_provider.dart'; +import '../settings/themes.dart'; + +class DashboardScreen extends StatefulWidget { + const DashboardScreen({super.key}); + + @override + State<DashboardScreen> createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State<DashboardScreen> { + late Auth _auth; + + @override + void initState() { + super.initState(); + _auth = Auth(); + } + + @override + Widget build(BuildContext context) { + final ThemeProvider themeProvider = context.watch<ThemeProvider>(); + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Inicio'), + ), + const SliverFillRemaining( + child: Placeholder(), + ), + ], + ), + drawer: Drawer( + child: ListView( + children: [ + UserAccountsDrawerHeader( + currentAccountPicture: CircleAvatar( + backgroundImage: _auth.currentUser?.photoURL != null + ? NetworkImage(_auth.currentUser!.photoURL!) + : null, + ), + accountName: Text(_auth.currentUser?.displayName ?? "Lincite"), + accountEmail: _auth.currentUser?.email != null + ? Text(_auth.currentUser!.email!) + : null, + ), + ListTile( + title: const Text('Tema'), + trailing: SegmentedButton<ThemeData?>( + segments: [ + const ButtonSegment<ThemeData?>( + value: null, + icon: Icon(Icons.brightness_auto), + ), + ButtonSegment<ThemeData?>( + value: ThemeSettings.lightTheme, + icon: const Icon(Icons.light_mode), + ), + ButtonSegment<ThemeData?>( + value: ThemeSettings.darkTheme, + icon: const Icon(Icons.dark_mode)), + ], + selected: <ThemeData?>{themeProvider.theme}, + onSelectionChanged: ((Set<ThemeData?> newSelection) { + themeProvider.theme = newSelection.first; + }), + ), + ), + const Divider(), + ListTile( + title: const Text('Cerrar sesión'), + leading: const Icon(Icons.logout), + onTap: () => signOut(context), + ) + ], + ), + ), + floatingActionButton: FloatingActionButton.extended( + label: const Text('Nuevo'), + icon: const Icon(Icons.create), + onPressed: () { + Navigator.of(context).pushNamed('/new'); + }, + ), + ); + } + + void signOut(BuildContext context) { + _auth.signOut().then((success) { + if (success) { + Navigator.of(context).popUntil(ModalRoute.withName('/login')); + } + }); + } +} diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..44cef6f --- /dev/null +++ b/lib/screens/login_screen.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:social_login_buttons/social_login_buttons.dart'; + +import '../firebase/auth.dart'; +import '../widgets/loading_modal_widget.dart'; +import '../widgets/responsive.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State<LoginScreen> createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State<LoginScreen> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + final Auth _auth = Auth(); + + bool isLoading = false; + + final padding = 16.0; + final spacer = const SizedBox(height: 16.0); + + // TextField controllers + late TextEditingController _emailController; + late TextEditingController _passwordController; + + @override + void initState() { + super.initState(); + _emailController = TextEditingController(); + _passwordController = TextEditingController(); + _controller = AnimationController(vsync: this); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Widget logoItc() => Padding( + padding: EdgeInsets.all(padding * 2), + child: Image.asset('assets/logo.png', height: 120.0), + ); + + Widget loginForm() => Column( + children: [ + spacer, + Container( + width: double.infinity, + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(horizontal: padding), + child: Text( + 'Iniciar sesión', + style: Theme.of(context).textTheme.displaySmall, + textAlign: TextAlign.left, + ), + ), + Card( + margin: EdgeInsets.all(padding), + child: Padding( + padding: EdgeInsets.all(padding), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: _emailController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Correo electrónico', + hintText: 'test@example.com', + ), + keyboardType: TextInputType.emailAddress, + ), + spacer, + TextField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Contraseña', + ), + ), + spacer, + SocialLoginButton( + buttonType: SocialLoginButtonType.generalLogin, + text: 'Iniciar sesión', + backgroundColor: Theme.of(context).colorScheme.primary, + onPressed: () => onLoginClicked(context), + ), + spacer, + TextButton( + onPressed: () { + Navigator.of(context).pushNamed('/register'); + }, + child: const Text('Crear cuenta'), + ), + ], + ), + ), + ), + ], + ); + + void onLoginClicked(BuildContext context) { + _auth + .signInWithEmailAndPassword( + email: _emailController.text, + password: _passwordController.text, + ) + .then((success) { + setState(() { + isLoading = false; + }); + // TODO: checar si el resultado es true + Navigator.of(context).pushNamed('/dash'); + }); + } + + void onGoogleLoginClicked(BuildContext context) { + _auth.signInWithGoogle().then((success) { + setState(() { + isLoading = false; + }); + if (success) { + Navigator.of(context).pushNamed('/dash'); + } + }); + } + + void onGithubLoginClicked(BuildContext context) { + _auth.signInWithGithub().then((success) { + setState(() { + isLoading = false; + }); + if (success) { + Navigator.of(context).pushNamed('/dash'); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + SingleChildScrollView( + child: SafeArea( + child: Responsive( + mobile: Column( + children: [logoItc(), loginForm()], + ), + desktop: Row( + children: [ + Expanded(child: logoItc()), + Expanded(child: loginForm()), + ], + ), + ), + ), + ), + isLoading ? const LoadingModal() : const SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/screens/onboarding_screen.dart b/lib/screens/onboarding_screen.dart new file mode 100644 index 0000000..e1755c4 --- /dev/null +++ b/lib/screens/onboarding_screen.dart @@ -0,0 +1,103 @@ +import 'package:concentric_transition/concentric_transition.dart'; +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; + +import '../widgets/responsive.dart'; + +class PageData { + final String title; + final String icon; + final MaterialColor bgColor; + final Color textColor; + + const PageData({ + required this.title, + required this.icon, + required this.bgColor, + required this.textColor, + }); +} + +class OnboardingScreen extends StatelessWidget { + const OnboardingScreen({super.key}); + + static const pages = [ + PageData( + title: '¡Bienvenidx a LinkChat!', + icon: 'assets/star.json', + bgColor: Colors.deepPurple, + textColor: Colors.white, + ), + PageData( + title: + 'Una nueva experiencia de chat donde solo puedes compartir enlaces', + icon: 'assets/share.json', + bgColor: Colors.red, + textColor: Colors.white, + ), + PageData( + title: 'Guarda tus enlaces favoritos para verlos más tarde', + icon: 'assets/heart.json', + bgColor: Colors.green, + textColor: Colors.black, + ), + ]; + + Widget icon(PageData data) => CircleAvatar( + radius: 100.0, + backgroundColor: data.bgColor.shade200, + child: Lottie.asset( + data.icon, + height: 100.0, + fit: BoxFit.fill, + ), + ); + + Widget text(BuildContext context, PageData data) => Text( + data.title, + textAlign: TextAlign.center, + style: Theme.of(context) + .typography + .englishLike + .headlineMedium! + .copyWith(color: data.textColor), + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ConcentricPageView( + colors: pages.map((PageData p) => p.bgColor).toList(), + itemCount: pages.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (int index) { + PageData data = pages[index]; + return Center( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Responsive( + mobile: Column( + mainAxisSize: MainAxisSize.min, + children: [ + icon(data), + const SizedBox(height: 50.0), + text(context, data), + ], + ), + desktop: Row( + children: [ + Expanded(child: icon(data)), + Expanded(child: text(context, data)), + ], + ), + ), + ), + ); + }, + onFinish: () { + Navigator.of(context).pushNamed('/dash'); + }, + ), + ); + } +} diff --git a/lib/screens/register_screen.dart b/lib/screens/register_screen.dart new file mode 100644 index 0000000..349cac0 --- /dev/null +++ b/lib/screens/register_screen.dart @@ -0,0 +1,178 @@ +import 'dart:io'; + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:social_login_buttons/social_login_buttons.dart'; + +import '../firebase/auth.dart'; +import '../widgets/avatar_picker.dart'; +import '../widgets/loading_modal_widget.dart'; + +class RegisterScreen extends StatefulWidget { + const RegisterScreen({super.key}); + + @override + State<RegisterScreen> createState() => _RegisterScreenState(); +} + +class _RegisterScreenState extends State<RegisterScreen> { + bool isLoading = false; + + final padding = 16.0; + final spacer = const SizedBox(height: 16.0); + + XFile? _avatar; + + // TextField controllers + late TextEditingController _nameController; + late TextEditingController _emailController; + late TextEditingController _passwordController; + + final _formKey = GlobalKey<FormState>(); + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(); + _emailController = TextEditingController(); + _passwordController = TextEditingController(); + } + + bool validateForm() { + if (_formKey.currentState!.validate() && _avatar != null) { + return true; + } + return false; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Crear cuenta'), + ), + SliverFillRemaining( + child: Stack( + children: [ + SingleChildScrollView( + child: Column( + children: [ + spacer, + Card( + margin: + EdgeInsets.fromLTRB(padding, 0, padding, padding), + child: Padding( + padding: EdgeInsets.all(padding), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AvatarPicker( + avatar: _avatar, + onAvatarPicked: (avatar) { + setState(() { + _avatar = avatar; + }); + }, + ), + spacer, + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Nombre', + hintText: 'Juan Pérez', + ), + keyboardType: TextInputType.name, + validator: (value) { + if (value == null || value.isEmpty) { + return 'El nombre no debe estar vacío'; + } + return null; + }, + ), + spacer, + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Correo electrónico', + hintText: 'test@example.com', + ), + keyboardType: TextInputType.emailAddress, + validator: (value) { + if (value == null || value.isEmpty) { + return 'El correo no debe estar vacío'; + } else if (!EmailValidator.validate( + value)) { + return 'El formato del correo es inválido'; + } + return null; + }, + ), + spacer, + TextFormField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Contraseña', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'La contraseña no debe estar vacía'; + } + return null; + }, + ), + spacer, + SocialLoginButton( + buttonType: + SocialLoginButtonType.generalLogin, + text: 'Crear cuenta', + backgroundColor: + Theme.of(context).colorScheme.primary, + onPressed: () => onRegisterClicked(context), + ), + ], + ), + ), + ), + ), + ], + ), + ), + isLoading ? const LoadingModal() : const SizedBox.shrink(), + ], + ), + ) + ], + ), + ); + } + + void onRegisterClicked(BuildContext context) { + setState(() { + isLoading = false; + if (validateForm()) { + Auth() + .createUserWithEmailAndPassword( + email: _emailController.text, + password: _passwordController.text, + displayName: _nameController.text, + avatar: File(_avatar!.path), + ) + .then((success) { + if (success) { + Navigator.of(context).pushNamed('/onboard'); + } + }); + } + }); + } +} |