diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 52f2618..fd60f01 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -37,4 +37,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 632d6ac0b577d6e268ff7a13a105bbc4f7941989 -COCOAPODS: 1.12.0 +COCOAPODS: 1.12.1 diff --git a/lib/main.dart b/lib/main.dart index 402e02a..033bccc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:survey_flutter/gen/assets.gen.dart'; import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:survey_flutter/gen/fonts.gen.dart'; +import 'package:survey_flutter/screens/login/login_screen.dart'; +import 'package:survey_flutter/theme/app_theme.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -11,25 +14,14 @@ void main() async { runApp(MyApp()); } -const routePathRootScreen = '/'; -const routePathSecondScreen = 'second'; - class MyApp extends StatelessWidget { MyApp({Key? key}) : super(key: key); final GoRouter _router = GoRouter( routes: [ GoRoute( - path: routePathRootScreen, - builder: (BuildContext context, GoRouterState state) => - const HomeScreen(), - routes: [ - GoRoute( - path: routePathSecondScreen, - builder: (BuildContext context, GoRouterState state) => - const SecondScreen(), - ), - ], + path: routePathLoginScreen, + builder: (context, state) => const LoginScreen(), ), ], ); @@ -37,11 +29,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp.router( - theme: ThemeData( - primarySwatch: Colors.blue, - brightness: Brightness.light, - fontFamily: Assets.fonts.neuzeit, - ), + theme: AppTheme.light, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, routeInformationProvider: _router.routeInformationProvider, @@ -50,62 +38,3 @@ class MyApp extends StatelessWidget { ); } } - -class HomeScreen extends StatelessWidget { - const HomeScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) { - return snapshot.hasData - ? Text(snapshot.data?.appName ?? "") - : const SizedBox.shrink(); - }), - ), - body: Center( - child: Column( - children: [ - const SizedBox(height: 24), - FractionallySizedBox( - widthFactor: 0.5, - child: Image.asset( - Assets.images.nimbleLogo.path, - fit: BoxFit.fitWidth, - ), - ), - const SizedBox(height: 24), - Text(AppLocalizations.of(context)!.hello), - Text( - FlutterConfig.get('SECRET'), - style: const TextStyle(color: Colors.black, fontSize: 24), - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () => context.go('/$routePathSecondScreen'), - child: const Text("Navigate to Second Screen"), - ), - ], - ), - ), - ); - } -} - -class SecondScreen extends StatelessWidget { - const SecondScreen({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Second Screen"), - ), - ); - } -} diff --git a/lib/screens/login/login_form.dart b/lib/screens/login/login_form.dart new file mode 100644 index 0000000..5651598 --- /dev/null +++ b/lib/screens/login/login_form.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:survey_flutter/screens/widgets/primary_button_style.dart'; +import 'package:survey_flutter/screens/widgets/primary_text_field_decoration.dart'; + +class LoginForm extends StatefulWidget { + const LoginForm({Key? key}) : super(key: key); + @override + State createState() => _LoginFormState(); +} + +class _LoginFormState extends State { + final _formKey = GlobalKey(); + + TextTheme get _textTheme => Theme.of(context).textTheme; + + TextFormField get _emailTextField => TextFormField( + keyboardType: TextInputType.emailAddress, + autocorrect: false, + decoration: PrimaryTextFieldDecoration( + hintText: 'Email', + hintTextStyle: _textTheme.bodyMedium, + ), + style: _textTheme.bodyMedium, + ); + + TextFormField get _passwordTextField => TextFormField( + autocorrect: false, + obscureText: true, + decoration: PrimaryTextFieldDecoration( + hintText: 'Password', + hintTextStyle: _textTheme.bodyMedium, + ), + style: _textTheme.bodyMedium, + ); + + ElevatedButton get _loginButton => ElevatedButton( + style: PrimaryButtonStyle(context), + onPressed: _submit, + child: const Text('Log in'), + ); + + void _submit() { + // TODO: Integrate with API + } + + @override + Widget build(BuildContext context) { + const fieldSpacing = 20.0; + return Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _emailTextField, + const SizedBox( + height: fieldSpacing, + ), + _passwordTextField, + const SizedBox( + height: fieldSpacing, + ), + SizedBox( + height: 56, + width: double.infinity, + child: _loginButton, + ), + ], + ), + ); + } +} diff --git a/lib/screens/login/login_screen.dart b/lib/screens/login/login_screen.dart new file mode 100644 index 0000000..54a82d9 --- /dev/null +++ b/lib/screens/login/login_screen.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:survey_flutter/screens/login/login_form.dart'; + +// TODO: Update to `/login` +const routePathLoginScreen = '/'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({Key? key}) : super(key: key); + @override + State createState() => _LoginSrceenState(); +} + +class _LoginSrceenState extends State { + late final _gradientOverlay = Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withOpacity(0.2), + Colors.black, + ], + ), + ), + ); + + late final _loginForm = Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24.0, + ), + child: LoginForm(), + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + alignment: AlignmentDirectional.center, + fit: StackFit.expand, + children: [ + _gradientOverlay, + _loginForm, + ], + ), + ); + } +} diff --git a/lib/screens/widgets/primary_button_style.dart b/lib/screens/widgets/primary_button_style.dart new file mode 100644 index 0000000..c26396e --- /dev/null +++ b/lib/screens/widgets/primary_button_style.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class PrimaryButtonStyle extends ButtonStyle { + PrimaryButtonStyle(BuildContext context) + : super( + textStyle: MaterialStateProperty.all( + Theme.of(context).textTheme.labelMedium, + ), + backgroundColor: MaterialStateProperty.all(Colors.white), + foregroundColor: MaterialStateProperty.all(Colors.black), + overlayColor: MaterialStateProperty.all(Colors.black12), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + ), + ); +} diff --git a/lib/screens/widgets/primary_text_field_decoration.dart b/lib/screens/widgets/primary_text_field_decoration.dart new file mode 100644 index 0000000..07a2dec --- /dev/null +++ b/lib/screens/widgets/primary_text_field_decoration.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class PrimaryTextFieldDecoration extends InputDecoration { + PrimaryTextFieldDecoration( + {required String hintText, required TextStyle? hintTextStyle}) + : super( + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(12.0), + ), + fillColor: Colors.white24, + filled: true, + hintText: hintText, + hintStyle: hintTextStyle?.copyWith(color: Colors.white24), + errorMaxLines: 2, + ); +} diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..0b4d6c8 --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:survey_flutter/gen/fonts.gen.dart'; + +class AppTheme { + static ThemeData get light => ThemeData( + fontFamily: FontFamily.neuzeit, + textTheme: const TextTheme( + bodyMedium: TextStyle( + color: Colors.white, + fontSize: 17, + fontWeight: FontWeight.normal, + ), + labelMedium: TextStyle( + color: Colors.white, + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ); +}