今天我們來做忘記密碼頁和註冊頁,若是之前程式碼都是用複製貼上的,這次建議可以慢慢自己做看看,模式都差不多,這次的頁面也不會有新功能或新widget,就讓我們一起來試試看吧
新手小技巧分享:
還記得我們對屬性、類別的命名皆是駝峰式的,IDE (Android Studio)在我們從鍵盤輸入時,能夠依輸入的文字去搜尋建議的屬性或類別
快速建立 StatelessWidget
快速建立 StatefulWidget
快速import 資源,當程式碼出現未import的問題時,會在未import的類別等等文字下方出現紅色抖線,此時游標移到該位置並按下option
鍵與enter
鍵,就會出現推薦的解決方法,包括推薦的import
首先一樣構想一下忘記密碼頁 :由上而下 AppBar,標題,描述文字,email 輸入框,送出的按鈕,註冊連結
整體而言都跟登入頁差不多,而且我們發現註冊連結,在我們做登入頁時,並沒有拉出來當共用widget,我們這邊補上
在lib
資料夾下的components
資料夾中建立no_account_text.dart
no_account_text.dart
:
import 'package:flutter/material.dart';
import '../constants.dart';
import '../size_config.dart';
class NoAccountText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Don’t have an account? ",
style: TextStyle(fontSize: getProportionateScreenWidth(16)),
),
GestureDetector(
onTap: () {}, //導入SignUpScreen
child: Text(
"Sign Up",
style: TextStyle(
fontSize: getProportionateScreenWidth(16),
decoration: TextDecoration.underline,
color: kPrimaryColor),
),
),
],
);
}
}
記得登入頁的註冊也可以修改成 NoAccountText()
我們在lib
資料夾下的screen
資料夾建立我們的 forgot_password
資料夾,用來放我們忘記密碼頁的程式,分別建forgot_password_screen.dart
,再在 forgot_password
資料夾建一個components
資料夾放我們忘記密碼頁的自定義widget
而我們忘記密碼頁內容主要有我們剛剛想的:標題,描述文字,email 輸入框,送出的按鈕,註冊連結
其中比較複雜的應該是 email 輸入框以及送出的按鈕,它們需要幫我們在按送出時,檢查欄位是否正確,錯誤時還要顯示錯誤訊息,我們新建一個forgot_password_form.dart
在 forgot_password
的components
裡,來把它拉出來另外做處理,可以參考登入頁的範例,試試看自己做
forgot_password_form.dart
:
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:travel_note/components/default_button.dart';
import 'package:travel_note/components/form_error.dart';
import 'package:travel_note/constants.dart';
import 'package:travel_note/size_config.dart';
class ForgotPasswordForm extends StatefulWidget {
@override
_ForgotPasswordFormState createState() => _ForgotPasswordFormState();
}
class _ForgotPasswordFormState extends State<ForgotPasswordForm> {
final _formKey = GlobalKey<FormState>();
List<String> errors = [];
String email;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.emailAddress,
onSaved: (newValue) => email = newValue,
onChanged: (value) {
if (value.isNotEmpty && errors.contains(kEmailNullError)) {
setState(() {
errors.remove(kEmailNullError);
});
}
if (emailValidatorRegExp.hasMatch(value) &&
errors.contains(kInvalidEmailError)) {
setState(() {
errors.remove(kInvalidEmailError);
});
}
},
validator: (value) {
if (value.isEmpty && !errors.contains(kEmailNullError)) {
setState(() {
errors.add(kEmailNullError);
if (errors.contains(kInvalidEmailError)) {
errors.remove(kInvalidEmailError);
}
});
return "";
} else if (value.isNotEmpty &&
!emailValidatorRegExp.hasMatch(value) &&
!errors.contains(kInvalidEmailError)) {
setState(() {
errors.add(kInvalidEmailError);
});
return "";
}
return null;
},
decoration: InputDecoration(
labelText: "Email",
hintText: "Enter your email",
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: Icon(
MdiIcons.fromString("email-outline"),
),
),
),
VerticalSpacing(of: 40),
FormError(errors: errors),
VerticalSpacing(of: 25),
DefaultButton(
text: "Submit",
press: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// 傳送密碼至mail 驗證等等
}
},
)
],
),
);
}
}
之後我們來完成忘記密碼頁的內容, 在 forgot_password
的components
裡新建body.dart
依序分別為剛剛構想的設計畫面
body.dart
:
import 'package:flutter/material.dart';
import 'package:travel_note/components/no_account_text.dart';
import 'package:travel_note/constants.dart';
import 'package:travel_note/screens/forgot_password/components/forgot_password_form.dart';
import 'package:travel_note/size_config.dart';
class Body extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(25)),
child: Column(
children: [
Text(
"Forgot Password",
style: TextStyle(
fontSize: getProportionateScreenWidth(24),
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
VerticalSpacing(of: 16),
Text(
"Please enter your email and we will send you a link to return to your account",
textAlign: TextAlign.left,
style: TextStyle(
color: kTextColor,
height: 1.5,
fontSize: getProportionateScreenWidth(16),
),
),
VerticalSpacing(of: 25),
ForgotPasswordForm(),
VerticalSpacing(of: 25),
NoAccountText(),
VerticalSpacing(of: 25),
],
),
),
);
}
}
之後在forgot_password_screen.dart
裡串上就完成我們忘記密碼頁
forgot_password_screen.dart
:
import 'package:flutter/material.dart';
import 'package:travel_note/screens/forgot_password/components/body.dart';
class ForgotPasswordScreen extends StatelessWidget {
static String routeName = "/forgot_password";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Forgot Password"),
),
body: Body(),
);
}
}
最後記得在routes.dart
補上連結,還有在登入頁,補上點擊忘記密碼時的事件,就都完成了!
接下來就來完成我們的註冊頁,這樣我們app 專案的入口畫面也就完成了
首先我們要的畫面有:
由上而下 AppBar,標題,描述文字,email 輸入框,密碼輸入框,確認密碼輸入框,送出的按鈕,透過社群軟體登入的UI
我們一樣發現有重複的widget 未被提出來共用 (透過社群軟體登入的UI),故拉出來在/lib/components
,新建
social_media.dart
:
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class SocialMedia extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(MdiIcons.fromString("google")),
iconSize: 24,
onPressed: () {}),
IconButton(
icon: Icon(MdiIcons.fromString("facebook")),
iconSize: 24,
onPressed: () {}),
IconButton(
icon: Icon(MdiIcons.fromString("twitter")),
iconSize: 24,
onPressed: () {}),
],
);
}
}
登入頁的社群軟體登入的UI也可以修改成 SocialMedia()
我們建立註冊頁要用的資料夾:lib/screens/sign_up
、lib/screens/sign_up/components
並新建註冊的lib/screens/sign_up/sign_up_screen.dart
、lib/screens/sign_up/components/body.dart
、lib/screens/sign_up/components/sign_up_form.dart
sign_up_form.dart
:
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:travel_note/components/default_button.dart';
import 'package:travel_note/components/form_error.dart';
import 'package:travel_note/constants.dart';
import 'package:travel_note/size_config.dart';
class SignUpForm extends StatefulWidget {
@override
_SignUpFormState createState() => _SignUpFormState();
}
class _SignUpFormState extends State<SignUpForm> {
final _formKey = GlobalKey<FormState>();
String email;
String password;
String confirmPassword;
final List<String> errors = [];
void addError({String error}) {
if (!errors.contains(error))
setState(() {
errors.add(error);
});
}
void removeError({String error}) {
if (errors.contains(error))
setState(() {
errors.remove(error);
});
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
buildEmailFormField(),
VerticalSpacing(of: 25),
buildPasswordFormField(),
VerticalSpacing(of: 25),
buildConfirmPasswordFormField(),
VerticalSpacing(of: 40),
FormError(errors: errors),
VerticalSpacing(of: 25),
DefaultButton(
text: "Sign Up",
press: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// if all are valid then go to success screen
// Navigator.pushNamed(context, LoginSuccessScreen.routeName);
}
},
),
],
),
);
}
TextFormField buildEmailFormField() {
return TextFormField(
keyboardType: TextInputType.emailAddress,
onSaved: (newValue) => email = newValue,
onChanged: (value) {
if (value.isNotEmpty) {
removeError(error: kEmailNullError);
}
if (emailValidatorRegExp.hasMatch(value)) {
removeError(error: kInvalidEmailError);
}
},
validator: (value) {
if (value.isEmpty) {
addError(error: kEmailNullError);
removeError(error: kInvalidEmailError);
return "";
} else if (!emailValidatorRegExp.hasMatch(value)) {
addError(error: kInvalidEmailError);
return "";
}
return null;
},
decoration: InputDecoration(
labelText: "Email",
hintText: "Enter your email",
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: Icon(
MdiIcons.fromString("email-outline"),
),
),
);
}
TextFormField buildPasswordFormField() {
return TextFormField(
obscureText: true,
onSaved: (newValue) => password = newValue,
onChanged: (value) {
password = value;
if (value.isNotEmpty) {
removeError(error: kPasswordNullError);
}
if (value.length >= 8) {
removeError(error: kShortPasswordError);
}
},
validator: (value) {
if (value.isEmpty) {
addError(error: kPasswordNullError);
removeError(error: kShortPasswordError);
return "";
} else if (value.length < 8) {
addError(error: kShortPasswordError);
return "";
}
return null;
},
decoration: InputDecoration(
labelText: "Password",
hintText: "Enter your password",
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: Icon(
MdiIcons.fromString("lock-outline"),
),
),
);
}
TextFormField buildConfirmPasswordFormField() {
return TextFormField(
obscureText: true,
onSaved: (newValue) => confirmPassword = newValue,
onChanged: (value) {
if (value.isNotEmpty) {
removeError(error: kConfirmPasswordNullError);
}
print("value $value, password $password");
if (value == password) {
removeError(error: kMatchPasswordError);
}
},
validator: (value) {
if (value.isEmpty) {
addError(error: kConfirmPasswordNullError);
removeError(error: kMatchPasswordError);
return "";
} else if (value != password) {
addError(error: kMatchPasswordError);
return "";
}
return null;
},
decoration: InputDecoration(
labelText: "Confirm Password",
hintText: "Re-enter your password",
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: Icon(
MdiIcons.fromString("lock-outline"),
),
),
);
}
}
body.dart
:
import 'package:flutter/material.dart';
import 'package:travel_note/components/social_media.dart';
import 'package:travel_note/constants.dart';
import 'package:travel_note/screens/sign_up/components/sign_up_form.dart';
import 'package:travel_note/size_config.dart';
class Body extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(25)),
child: Column(
children: [
Text(
"Register Account",
style: headingStyle,
),
VerticalSpacing(of: 16),
Text(
"Sign up with your email or continue with social media",
style: contentStyle,
),
VerticalSpacing(of: 25),
SignUpForm(),
VerticalSpacing(of: 25),
SocialMedia(),
VerticalSpacing(of: 25),
],
),
),
);
}
}
sign_up_screen.dart
:
import 'package:flutter/material.dart';
import 'package:travel_note/screens/sign_up/components/body.dart';
class SignUpScreen extends StatelessWidget {
static String routeName = "/sign_up";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sign Up"),
),
body: Body(),
);
}
}
最後記得一樣在routes.dart
補上連結,還有在登入頁、忘記密碼頁,補上點擊註冊時的事件,就都完成了!
我們完成了一個app 的登入、註冊等的畫面,但功能我們都還沒串上,為了之後要使用 Firebase Authentication,來讓我們完成一個簡單的會員系統,下一篇我們先來介紹 Firebase