Skip to content

Commit

Permalink
[SCRUM-3] Implement login page ui (#1)
Browse files Browse the repository at this point in the history
* Implemented basic UI and functionality for login page. Login page appears on start up of application.

* Refactoring and modularizing aspects of the application such as theme styling, page routing, and login page. Login page rewritten for readability.
  • Loading branch information
ziadzananiri authored Jan 30, 2025
1 parent 251f3ac commit a430d8c
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 55 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ migrate_working_dir/
.pub/
/build/
/frontend/build/
devtools_options.yaml

# Symbolication related
app.*.symbols
Expand Down
21 changes: 21 additions & 0 deletions frontend/lib/common/router.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:go_router/go_router.dart';
import 'package:utm_marketplace/views/login.dart';

// TODO: These are temporary imports for testing purposes, remove when no longer needed
import 'package:utm_marketplace/views/model_view.dart';
import 'package:utm_marketplace/models/model.dart';

// Define the router as a top-level global variable
final GoRouter router = GoRouter(
initialLocation: '/login',
routes: [
GoRoute(
path: '/login',
builder: (context, state) => const Login(),
),
GoRoute(
path: '/temp_view',
builder: (context, state) => ModelView(model: Model(attribute1: 'Attribute 1', attribute2: 2)),
),
],
);
49 changes: 49 additions & 0 deletions frontend/lib/common/theme.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';

final appTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlue),
useMaterial3: true,
scaffoldBackgroundColor: Colors.white,
);

// This class is for storing text styles that are used throughout the app
// The label style is used for smaller text, such as form labels
// The header style is used for larger text, such as titles
class ThemeText {
static const TextStyle header = TextStyle(
fontSize: 40.0,
fontWeight: FontWeight.w900,
letterSpacing: 2,
);

static const TextStyle label = TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w700,
);
}

// This class is a custom text widget that uses the theme text style provided above
// This simplifies the process of creating text widgets with consistent styling
// To use, simply provide the text, style, and optional text alignment
// Will most likely be modified as we continue to develop the app
class StyleText extends StatelessWidget {
final String text;
final TextStyle style;
final TextAlign? textAlign;

const StyleText({
super.key,
required this.text,
required this.style,
this.textAlign,
});

@override
Widget build(BuildContext context) {
return Text(
text,
style: style,
textAlign: textAlign,
);
}
}
64 changes: 9 additions & 55 deletions frontend/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,70 +1,24 @@
import 'package:flutter/material.dart';
// import 'package:go_router/go_router.dart';
// import 'package:provider/provider.dart';

import 'package:utm_marketplace/common/theme.dart';
import 'package:utm_marketplace/common/router.dart';

void main() {
runApp(const MyApp());
}

// The main app widget, which initializes the app with the router and theme
// Router and theme are defined in the common/router.dart and common/theme.dart files
class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
return MaterialApp.router(
title: 'UTM Marketplace',
theme: appTheme,
routerConfig: router,
);
}
}
146 changes: 146 additions & 0 deletions frontend/lib/views/login.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:utm_marketplace/common/theme.dart';

// Note: There's a small issue regarding the flex Spacer widget, it's not too important
// but would be nice to find a way that makes it so the UI elements
// don't shift when a validator fails.

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

@override
LoginState createState() => LoginState();
}

class LoginState extends State<Login> {
final _formKey = GlobalKey<FormState>();
bool _obscureText = true;

@override
Widget build(BuildContext context) {
final logoText = StyleText(
text: 'UTMarketplace',
style: ThemeText.header.copyWith(
color: Theme.of(context).colorScheme.primary,
),
);

final emailField = TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter your UofT email',
),
// TODO: REGEX for valid UofT domain email, uncomment when ready
// validator: (value) {
// if (value == null || value.isEmpty) {
// return 'Please a valid UofT email address';
// }
// return null;
// },
);

final passwordField = TextFormField(
obscureText: _obscureText,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter your password',
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
),
// TODO: Implement email validation and password validation (backend), uncomment when ready
// validator: (value) {
// if (value == null || value.isEmpty) {
// return 'Password or email is incorrect';
// }
// return null;
// },
);

final loginButton = ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// TODO: Implement login functionality (backend)
// If this condition passed, a redirect call to the home
// page should be made
context.go('/temp_view');
}
},
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, 50),
),
child: Text('Log In'),
);

final signUpRow = Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Don\'t have an account?'),
TextButton(
onPressed: () {
// TODO: Implement redirect to sign up page
},
child: Text('Sign Up'),
),
],
);

final forgotPasswordButton = TextButton(
onPressed: () {
// TODO: Implement forgot password functionality
},
child: Text('Forgot Password?'),
);

return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(flex: 1),
logoText,
Spacer(flex: 1),
Align(
alignment: Alignment.centerLeft,
child: StyleText(
text: 'UofT Email Address',
style: ThemeText.label,
),
),
emailField,
SizedBox(height: 16.0),
Align(
alignment: Alignment.centerLeft,
child: StyleText(
text: 'Password',
style: ThemeText.label,
),
),
passwordField,
SizedBox(height: 16.0),
loginButton,
signUpRow,
forgotPasswordButton,
Spacer(flex: 1),
],
),
),
),
),
);
}
}

0 comments on commit a430d8c

Please sign in to comment.