seong
회원가입 페이지 본문
그려야할 페이지는 아래와 같다.
1. TextField를 사용해서 클라이언트의 텍스트를 입력 받는다.
2. Form을 사용해서 상태 관리
3. 관심사는 Dialog를 사용해서 선택
기본 구조는 Scaffold -> Form -> FormField 이렇게 된다.
그리고 만들때 ScrollerController를 사용해서 TextField를 클릭시 키보드가 올라가게 해준다.
1. JoinPage 큰 구조
- 서버에 요청할 객체는 싱글톤으로 만들어서 계속 값을 입력할때 마다 Set해주었다.
import 'package:finalproject_front/dto/request/auth_req_dto.dart';
import 'package:finalproject_front/pages/auth/components/join_custom_form.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:logger/logger.dart';
class JoinPage extends StatefulWidget {
JoinPage({required this.role, super.key});
State<JoinPage> createState() => _JoinPageState();
// ReqDto를 싱글톤으로 사용
JoinReqDto joinReqDto = JoinReqDto.single();
String role;
}
class _JoinPageState extends State<JoinPage> {
late ScrollController scrollController;
@override
void initState() {
super.initState();
scrollController = new ScrollController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
// Form으로 이동
body: JoinCustomForm(scrollAnimate, role: widget.role, joinReqDto: widget.joinReqDto),
);
}
AppBar _buildAppBar(BuildContext context) {
return AppBar(
elevation: 1,
centerTitle: true,
title: Text(
"회원가입",
style: TextStyle(color: Colors.black),
),
leading: IconButton(
icon: Icon(
CupertinoIcons.back,
color: Colors.black,
),
onPressed: () {
Navigator.pop(context);
}),
);
}
// 스크롤 애니메이션
void scrollAnimate() {
Future.delayed(Duration(milliseconds: 600), () {
scrollController.animateTo(MediaQuery.of(context).viewInsets.bottom,
duration: Duration(microseconds: 100), // 0.1초 이후 field가 올라간다.
curve: Curves.easeIn); //Curves - 올라갈때 애니메이션
});
}
}
2. Form
- 값을 입력할때 Req객체에 넣는 함수를 만들어서 전달했다. onChanged
import 'package:finalproject_front/controller/user_controller.dart';
import 'package:finalproject_front/dto/request/auth_req_dto.dart';
import 'package:finalproject_front/pages/auth/components/category_select_button.dart';
import 'package:finalproject_front/pages/components/custom_text_field.dart';
import 'package:finalproject_front/size.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
import '../../../constants.dart';
import '../../components/custom_main_button.dart';
class JoinCustomForm extends ConsumerWidget {
JoinCustomForm(this.scrollAnimate, {required this.role, required this.joinReqDto, super.key});
late JoinReqDto joinReqDto;
final Function scrollAnimate;
final role;
final _formKey = GlobalKey<FormState>(); // 글로벌 key
@override
Widget build(BuildContext context, WidgetRef ref) {
final uc = ref.read(userController);
return Form(
key: _formKey, // 해당 키로 Form의 상태를 관리 한다.
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView( // 키보드가 올라오면서 OverFlow발생, SingleChild사용
child: Column(
children: [
CustomTextField(
scrollAnimate,
fieldTitle: "아이디",
hint: "아이디를 입력해주세요",
lines: 1,
onChanged: (value) {
joinReqDto.username = value.trim(); // trim은 공백을 없애줌.
},
),
SizedBox(height: gap_m),
CustomTextField(
scrollAnimate,
fieldTitle: "비밀번호",
hint: "비밀번호를 입력해주세요",
lines: 1,
onChanged: (value) {
joinReqDto.password = value.trim();
},
),
SizedBox(height: gap_m),
CustomTextField(
scrollAnimate,
fieldTitle: "이메일",
hint: "이메일를 입력해주세요",
lines: 1,
onChanged: (value) {
joinReqDto.email = value.trim();
},
),
SizedBox(height: gap_m),
CustomTextField(
scrollAnimate,
fieldTitle: "휴대폰번호",
hint: "휴대폰번호를 입력해주세요",
lines: 1,
onChanged: (value) {
joinReqDto.phoneNum = value.trim();
},
),
SizedBox(height: gap_m),
Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"관심사 선택",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
SizedBox(height: gap_m),
CategorySelectButton(joinReqDto: joinReqDto),
],
),
SizedBox(height: gap_xl),
_buildJoinButton(uc, context, joinReqDto),
],
),
),
),
);
}
Widget _buildJoinButton(UserController uc, BuildContext context, JoinReqDto joinReqDto) {
return ElevatedButton(
onPressed: () {
joinReqDto.role = role;
uc.joinUser(
joinReqDto: joinReqDto,
);
},
style: ElevatedButton.styleFrom(
primary: gButtonOffColor,
minimumSize: Size(getScreenWidth(context), 60),
),
child: Align(
alignment: Alignment.center,
child: Text(
"회원가입 완료",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
);
}
}
TextField (컴포넌트)
- TextField는 여러곳에서 사용해서 미리 Conponent에 만들었었다.
- 공용 컴포넌트로 사용하기때문에 필요한 값들은 생성자로 받아서 처리해주었다.
- 여기서 일부 다른 페이지들은 TextField의 크기가 달라야했다. 처음엔 height를 주어서 바꾸려 했지만, maxline속성으로 조절이 가능했다.
import 'package:finalproject_front/constants.dart';
import 'package:finalproject_front/size.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
class CustomTextField extends StatelessWidget {
final ValueChanged<String> onChanged;
final TextEditingController? fieldController;
final Function scrollAnimate;
final String fieldTitle;
final String? subTitle;
final String hint;
final int lines;
const CustomTextField(this.scrollAnimate,
{this.subTitle, this.fieldController, required this.fieldTitle, required this.hint, required this.lines, required this.onChanged, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: "${fieldTitle}",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
if (subTitle != null)
TextSpan(
text: "${subTitle}",
style: TextStyle(color: gSubTextColor, fontSize: 10, fontWeight: FontWeight.bold),
)
],
),
),
),
SizedBox(height: gap_m),
TextFormField(
onTap: (() {
scrollAnimate;
}),
onChanged: onChanged,
controller: fieldController,
keyboardType: TextInputType.multiline,
maxLines: lines,
decoration: InputDecoration(
hintText: "${hint}",
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
color: gSubTextColor,
),
//3. 기본 textFormfield 디자인 - enabledBorder
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: gClientColor, width: 3.0),
borderRadius: BorderRadius.circular(15),
),
//마우스 올리고 난 후 스타일
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: gClientColor, width: 3.0),
borderRadius: BorderRadius.circular(15),
),
),
),
],
),
);
}
}
관심사 선택 버튼 - 라이브러리 사용
- 중복선택이 가능한 Select버튼을 찾고 있다가 라이브러리에 DialogField로 선택할 수 있는 라이브러리를 찾아서 적용시켰다.
- 보여질 List는 List<Category>타입이다, View에 뿌려줄땐 이 라이브러리가 MultiSelectItem이 필요하다고 해서
List아이템을 다시 MultiSelectItem으로 담아주는 작업이 필요하다 . - 기존의 List -> Map -> MultiSelectItem로 toList
import 'package:finalproject_front/constants.dart';
import 'package:finalproject_front/dto/request/auth_req_dto.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'package:multi_select_flutter/multi_select_flutter.dart';
import '../../../domain/category.dart';
class CategorySelectButton extends StatefulWidget {
JoinReqDto? joinReqDto;
UserUpdateReqDto? userUpdateReqDto;
CategorySelectButton({this.joinReqDto, this.userUpdateReqDto, super.key});
@override
State<CategorySelectButton> createState() => _CategorySelectButtonState();
}
class _CategorySelectButtonState extends State<CategorySelectButton> {
// ignore: prefer_final_fields
static List<Category> _category = [
// 리스트
Category(id: 1, name: "뷰티"),
Category(id: 2, name: "운동"),
Category(id: 3, name: "댄스"),
Category(id: 4, name: "뮤직"),
Category(id: 5, name: "미술"),
Category(id: 6, name: "문학"),
Category(id: 7, name: "공예"),
Category(id: 8, name: "기타"),
];
final _items = _category.map((category) => MultiSelectItem<Category>(category, category.name)).toList(); // 선택 가능한 항목을 보여줌 -> 리스트를 깊은 복사
List<Category>? _selectCategory = []; // null이 가능하다, 초기값은 null이다.
List<int>? categoryId = [];
final _multiSelectKey = GlobalKey<FormFieldState>();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MultiSelectDialogField<Category>(
//버튼에 사용할 value타입을 Category 오브젝트 타입임을 선언
items: _items,
title: Text("관심사"),
key: _multiSelectKey,
itemsTextStyle: TextStyle(color: Colors.black),
isDismissible: true,
cancelText: Text(
"취소",
style: TextStyle(fontSize: 18),
),
confirmText: Text(
"선택",
style: TextStyle(fontSize: 18),
),
selectedItemsTextStyle: TextStyle(color: gPrimaryColor),
decoration: BoxDecoration(
border: Border.all(
color: gClientColor,
width: 3,
),
borderRadius: BorderRadius.circular(10),
),
buttonIcon: Icon(
CupertinoIcons.plus_circle,
color: gClientColor,
size: 35,
),
buttonText: Text(
"관심사를 선택해 주세요.",
style: TextStyle(fontSize: 16, color: gSubTextColor),
),
onConfirm: (results) {
_selectCategory = results;
categoryId = _selectCategory?.map((e) => e.id).toList();
widget.joinReqDto?.categoryIds = categoryId;
widget.userUpdateReqDto?.categoryIds = categoryId;
},
);
}
}
만들면서 막힌곳
1. 서버에서 카테고리를 int 타입의 id만 달라고했다. 이 부분에서 막막하기만하고 생각이 나지않았다.
해결 : 기존의 list -> map -> int 타입의 List로 변환
2. 카테고리 중복 선택 버튼에서 버튼 클릭 시 값이 넘어가지 않음.
해결 : 1. Widget분리 , 2. ReqDto를 생성자로 받아서 넘겨주었다.
'Flutter > 중계 플랫폼 프로젝트' 카테고리의 다른 글
로그인 - Provider, 자동로그인 (0) | 2022.12.20 |
---|---|
회원가입 Provider적용 후 서버와 통신 (0) | 2022.12.20 |
Flutter - iamport 카카오페이 결제 테스트 및 결제 내역 확인 (0) | 2022.12.16 |
Flutter ImagePicker 사용시 Lost connect , 연결끊킴 에러 (0) | 2022.12.14 |
Flutter - 회원가입 기능 (Provider) (0) | 2022.12.12 |