Skip to content

Commit 6a118cc

Browse files
committed
Implemented basic UI and functionality for login page. Login page appears on start up of application.
1 parent 320163e commit 6a118cc

File tree

4 files changed

+184
-54
lines changed

4 files changed

+184
-54
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ migrate_working_dir/
4848
.pub/
4949
/build/
5050
/frontend/build/
51+
devtools_options.yaml
5152

5253
# Symbolication related
5354
app.*.symbols

frontend/lib/common/theme.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'package:flutter/material.dart';
2+
3+
final appTheme = ThemeData(
4+
colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlue),
5+
useMaterial3: true,
6+
scaffoldBackgroundColor: Colors.white,
7+
);

frontend/lib/main.dart

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,43 @@
11
import 'package:flutter/material.dart';
2-
// import 'package:go_router/go_router.dart';
2+
import 'package:go_router/go_router.dart';
33
// import 'package:provider/provider.dart';
44

5+
import 'package:utm_marketplace/common/theme.dart';
6+
import 'package:utm_marketplace/views/login.dart';
7+
8+
// TODO: These are just for testing, remove when done/replace with actual views
9+
import 'package:utm_marketplace/views/model_view.dart';
10+
import 'package:utm_marketplace/models/model.dart';
11+
512
void main() {
613
runApp(const MyApp());
714
}
815

9-
class MyApp extends StatelessWidget {
10-
const MyApp({super.key});
11-
12-
@override
13-
Widget build(BuildContext context) {
14-
return MaterialApp(
15-
title: 'Flutter Demo',
16-
theme: ThemeData(
17-
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
18-
useMaterial3: true,
16+
GoRouter router() {
17+
return GoRouter(
18+
initialLocation: '/login',
19+
routes: [
20+
GoRoute(
21+
path: '/login',
22+
builder: (context, state) => const Login(),
1923
),
20-
home: const MyHomePage(title: 'Flutter Demo Home Page'),
21-
);
22-
}
23-
}
24-
25-
class MyHomePage extends StatefulWidget {
26-
const MyHomePage({super.key, required this.title});
27-
final String title;
28-
29-
@override
30-
State<MyHomePage> createState() => _MyHomePageState();
24+
GoRoute(
25+
path: '/temp_view',
26+
builder: (context, state) => ModelView(model: Model(attribute1: 'Attribute 1', attribute2: 2)),
27+
),
28+
],
29+
);
3130
}
3231

33-
class _MyHomePageState extends State<MyHomePage> {
34-
int _counter = 0;
35-
36-
void _incrementCounter() {
37-
setState(() {
38-
_counter++;
39-
});
40-
}
32+
class MyApp extends StatelessWidget {
33+
const MyApp({super.key});
4134

4235
@override
4336
Widget build(BuildContext context) {
44-
return Scaffold(
45-
appBar: AppBar(
46-
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
47-
title: Text(widget.title),
48-
),
49-
body: Center(
50-
child: Column(
51-
mainAxisAlignment: MainAxisAlignment.center,
52-
children: <Widget>[
53-
const Text(
54-
'You have pushed the button this many times:',
55-
),
56-
Text(
57-
'$_counter',
58-
style: Theme.of(context).textTheme.headlineMedium,
59-
),
60-
],
61-
),
62-
),
63-
floatingActionButton: FloatingActionButton(
64-
onPressed: _incrementCounter,
65-
tooltip: 'Increment',
66-
child: const Icon(Icons.add),
67-
),
37+
return MaterialApp.router(
38+
title: 'UTM Marketplace',
39+
theme: appTheme,
40+
routerConfig: router(),
6841
);
6942
}
7043
}

frontend/lib/views/login.dart

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
2+
import 'package:flutter/material.dart';
3+
import 'package:go_router/go_router.dart';
4+
5+
// Note: There's a small issue regarding the flex Spacer widget, it's not too important
6+
// but would be nice to find a way that makes it so the UI elements
7+
// don't shift when a validator fails.
8+
9+
class Login extends StatefulWidget {
10+
const Login({super.key});
11+
12+
@override
13+
LoginState createState() => LoginState();
14+
}
15+
16+
class LoginState extends State<Login> {
17+
final _formKey = GlobalKey<FormState>();
18+
bool _obscureText = true;
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return Scaffold(
23+
resizeToAvoidBottomInset: false,
24+
body: Container(
25+
padding: EdgeInsets.all(16.0),
26+
child: Form(
27+
key: _formKey,
28+
child: Column(
29+
crossAxisAlignment: CrossAxisAlignment.center,
30+
mainAxisAlignment: MainAxisAlignment.center,
31+
children: <Widget>[
32+
Spacer(flex: 1),
33+
Text(
34+
'UTMarketplace',
35+
style: TextStyle(
36+
fontSize: 40.0,
37+
color: Theme.of(context).colorScheme.primary,
38+
fontWeight: FontWeight.w900,
39+
letterSpacing: 2,
40+
),
41+
),
42+
Spacer(flex: 1),
43+
Align(
44+
alignment: Alignment.centerLeft,
45+
child: Text(
46+
'UofT Email Address',
47+
style: TextStyle(
48+
fontSize: 16.0,
49+
fontWeight: FontWeight.w700,
50+
),
51+
),
52+
),
53+
TextFormField(
54+
decoration: InputDecoration(
55+
border: OutlineInputBorder(),
56+
labelText: 'Enter your UofT email',
57+
errorMaxLines: 1,
58+
errorStyle: TextStyle(
59+
height: 0.0,
60+
),
61+
),
62+
// TODO: REGEX for valid UofT domain email, uncomment when ready
63+
// validator: (value) {
64+
// if (value == null || value.isEmpty) {
65+
// return 'Please a valid UofT email address';
66+
// }
67+
// return null;
68+
// },
69+
),
70+
SizedBox(height: 16.0),
71+
Align(
72+
alignment: Alignment.centerLeft,
73+
child: Text(
74+
'Password',
75+
style: TextStyle(
76+
fontSize: 16.0,
77+
fontWeight: FontWeight.w700,
78+
),
79+
),
80+
),
81+
TextFormField(
82+
obscureText: _obscureText,
83+
decoration: InputDecoration(
84+
border: OutlineInputBorder(),
85+
labelText: 'Enter your password',
86+
errorMaxLines: 1,
87+
errorStyle: TextStyle(
88+
height: 0.0,
89+
),
90+
suffixIcon: IconButton(
91+
icon: Icon(
92+
_obscureText ? Icons.visibility_off : Icons.visibility,
93+
),
94+
onPressed: () {
95+
setState(() {
96+
_obscureText = !_obscureText;
97+
});
98+
},
99+
),
100+
),
101+
// TODO: Implement email validation and password validation (backend), uncomment when ready
102+
// validator: (value) {
103+
// if (value == null || value.isEmpty) {
104+
// return 'Password or email is incorrect';
105+
// }
106+
// return null;
107+
// },
108+
),
109+
SizedBox(height: 16.0),
110+
ElevatedButton(
111+
onPressed: () {
112+
if (_formKey.currentState!.validate()) {
113+
// TODO: Implement login functionality (backend)
114+
// If this condition passed, a redirect call to the home
115+
// page should be made
116+
context.go('/temp_view');
117+
}
118+
},
119+
style: ElevatedButton.styleFrom(
120+
minimumSize: Size(double.infinity, 50),
121+
),
122+
child: Text('Log In'),
123+
),
124+
Row(
125+
mainAxisAlignment: MainAxisAlignment.center,
126+
children: <Widget>[
127+
Text('Don\'t have an account?'),
128+
TextButton(
129+
onPressed: () {
130+
// TODO: Implement redirect to sign up page
131+
},
132+
child: Text('Sign Up'),
133+
),
134+
],
135+
),
136+
TextButton(
137+
onPressed: () {
138+
// TODO: Implement forgot password functionality
139+
},
140+
child: Text('Forgot Password?'),
141+
),
142+
Spacer(flex: 1),
143+
],
144+
),
145+
),
146+
),
147+
);
148+
}
149+
}

0 commit comments

Comments
 (0)