회원가입, 로그인, id pw 찾기 페이지 완료

This commit is contained in:
eld_master 2024-12-17 00:55:48 +09:00
parent 5048ad8873
commit 0f450c884f
8 changed files with 858 additions and 213 deletions

BIN
flutter_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,11 +1,190 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:convert' show utf8;
import 'login_page.dart';
import 'pw_finding_page.dart';
import 'signup_page.dart';
class IdFindingPage extends StatelessWidget {
class IdFindingPage extends StatefulWidget {
const IdFindingPage({Key? key}) : super(key: key);
@override
_IdFindingPageState createState() => _IdFindingPageState();
}
class _IdFindingPageState extends State<IdFindingPage> {
final TextEditingController nicknameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
String nicknameErrorMessage = '';
String emailErrorMessage = '';
String foundIdMessage = '';
String authId = '';
Future<void> _findId(String nickname, String email) async {
//
showDialog(
context: context,
barrierDismissible: false, //
builder: (BuildContext context) {
return const Center(child: CircularProgressIndicator());
},
);
try {
final response = await http.post(
Uri.parse('https://eldsoft.com:8097/user/find/id'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'nickname': nickname,
'user_email': email,
}),
).timeout(const Duration(seconds: 10)); // 10
String responseBody = utf8.decode(response.bodyBytes);
Navigator.of(context).pop(); //
if (response.statusCode == 200) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
print('ID 찾기 성공: $jsonResponse');
//
setState(() {
nicknameErrorMessage = '';
emailErrorMessage = '';
foundIdMessage = ''; // ID
});
if (jsonResponse['response_info']['msg_title'] == '닉네임 확인') {
setState(() {
nicknameErrorMessage = '닉네임을 다시 확인해주세요'; //
});
} else if (jsonResponse['response_info']['msg_title'] == '이메일 확인') {
setState(() {
emailErrorMessage = '이메일을 다시 확인해주세요'; //
});
} else if (jsonResponse['result'] == 'OK') {
// ID
setState(() {
foundIdMessage = '당신의 ID는 ${jsonResponse['data']['user_id']} 입니다'; // ID
authId = jsonResponse['data']['auth']; // auth_id
});
} else {
_showErrorDialog(jsonResponse['response_info']['msg_title'], jsonResponse['response_info']['msg_content'], 'STAY');
}
} else {
//
_showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY');
}
} catch (e) {
Navigator.of(context).pop(); //
_showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY');
}
}
Future<void> _findAllId() async {
// ID
print('ID 전체 찾기 요청 $authId'); //
//
showDialog(
context: context,
barrierDismissible: false, //
builder: (BuildContext context) {
return const Center(child: CircularProgressIndicator());
},
);
try {
final response = await http.post(
Uri.parse('https://eldsoft.com:8097/user/find/id/full'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'auth': authId, // authId
}),
).timeout(const Duration(seconds: 10)); // 10
String responseBody = utf8.decode(response.bodyBytes); // UTF-8
Navigator.of(context).pop(); //
if (response.statusCode == 200) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
print('ID 전체 찾기 성공: $jsonResponse');
if (jsonResponse['result'] == 'OK') {
//
_showSuccessDialog('이메일로 전체 ID를 발송했습니다.');
} else {
//
_showErrorDialog(jsonResponse['response_info']['msg_title'], jsonResponse['response_info']['msg_content'], 'STAY');
}
} else {
//
_showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY');
}
} catch (e) {
Navigator.of(context).pop(); //
_showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY');
}
}
void _showErrorDialog(String title, String content, String action) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white, //
title: Text(title, style: const TextStyle(color: Colors.black)),
content: Text(content, style: const TextStyle(color: Colors.black)),
actions: <Widget>[
Center(
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white, //
),
child: const Text('확인'),
onPressed: () {
Navigator.of(context).pop(); //
if (action == 'LOGIN') {
Navigator.of(context).pop(); //
}
},
),
),
],
);
},
);
}
void _showSuccessDialog(String message) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('성공'),
content: Text(message),
actions: <Widget>[
TextButton(
child: const Text('확인'),
onPressed: () {
Navigator.of(context).pop(); //
Navigator.of(context).pop(); //
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -16,76 +195,116 @@ class IdFindingPage extends StatelessWidget {
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'ID 찾기',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 32),
TextField(
decoration: InputDecoration(
labelText: '닉네임',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (foundIdMessage.isEmpty) ...[
const Text(
'ID 찾기',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '이메일',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
const SizedBox(height: 32),
TextField(
controller: nicknameController,
decoration: InputDecoration(
labelText: '닉네임',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
),
if (nicknameErrorMessage.isNotEmpty) //
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
nicknameErrorMessage,
style: const TextStyle(color: Colors.red),
),
),
const SizedBox(height: 16),
TextField(
controller: emailController,
decoration: InputDecoration(
labelText: '이메일',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
),
if (emailErrorMessage.isNotEmpty) //
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
emailErrorMessage,
style: const TextStyle(color: Colors.red),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
_findId(nicknameController.text, emailController.text);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
child: const Text('ID 찾기'),
),
] else ...[
// ID
Text(
foundIdMessage,
style: const TextStyle(fontSize: 20, color: Colors.black),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
_findAllId(); // ID
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
child: const Text('ID 전체 찾기'),
),
],
const SizedBox(height: 16),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('로그인', style: TextStyle(color: Colors.black)),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// ID
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const PwFindingPage()),
);
},
child: const Text('PW 찾기', style: TextStyle(color: Colors.black)),
),
child: const Text('ID 찾기'),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const PwFindingPage()),
);
},
child: const Text('PW 찾기', style: TextStyle(color: Colors.black)),
),
TextButton(
onPressed: () {
Navigator.pop(context); //
},
child: const Text('로그인', style: TextStyle(color: Colors.black)),
),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SignUpPage()),
);
},
child: const Text('회원가입', style: TextStyle(color: Colors.black)),
),
],
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SignUpPage()),
);
},
child: const Text('회원가입', style: TextStyle(color: Colors.black)),
),
],
),
),
),
);

View File

@ -1,4 +1,9 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:convert' show utf8;
import 'package:crypto/crypto.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'id_finding_page.dart';
import 'pw_finding_page.dart';
import 'signup_page.dart';
@ -11,7 +16,80 @@ class LoginPage extends StatefulWidget {
}
class _LoginPageState extends State<LoginPage> {
bool _isChecked = false;
final TextEditingController idController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
bool autoLogin = false;
String loginErrorMessage = '';
Future<void> _login() async {
String id = idController.text;
String password = passwordController.text;
String autoLoginStatus = autoLogin ? 'Y' : 'N';
var bytes = utf8.encode(password);
var digest = sha256.convert(bytes);
try {
final response = await http.post(
Uri.parse('https://eldsoft.com:8097/user/login'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'user_id': id,
'user_pw': digest.toString(),
}),
).timeout(const Duration(seconds: 10));
String responseBody = utf8.decode(response.bodyBytes);
if (response.statusCode == 200) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
if (jsonResponse['result'] == 'OK') {
print('로그인 성공');
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', jsonResponse['auth']['token']);
await prefs.setBool('auto_login', autoLogin);
} else if (jsonResponse['response_info']['msg_title'] == '로그인 실패') {
setState(() {
loginErrorMessage = '회원정보를 다시 확인해주세요.';
});
}
} else {
_showDialog('오류', '로그인에 실패했습니다. 관리자에게 문의해주세요.');
}
} catch (e) {
_showDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.');
}
}
void _showDialog(String title, String content) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
title: Text(title, style: const TextStyle(color: Colors.black)),
content: Text(content, style: const TextStyle(color: Colors.black)),
actions: <Widget>[
Center(
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
child: const Text('확인'),
onPressed: () {
Navigator.of(context).pop();
},
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
@ -36,6 +114,7 @@ class _LoginPageState extends State<LoginPage> {
),
const SizedBox(height: 32),
TextField(
controller: idController,
decoration: InputDecoration(
labelText: 'ID',
labelStyle: const TextStyle(color: Colors.black),
@ -47,6 +126,8 @@ class _LoginPageState extends State<LoginPage> {
),
const SizedBox(height: 16),
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'PW',
labelStyle: const TextStyle(color: Colors.black),
@ -55,15 +136,23 @@ class _LoginPageState extends State<LoginPage> {
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
obscureText: true,
),
if (loginErrorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
loginErrorMessage,
style: const TextStyle(color: Colors.red),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Checkbox(
value: _isChecked,
onChanged: (value) {
value: autoLogin,
onChanged: (bool? value) {
setState(() {
_isChecked = value ?? false;
autoLogin = value ?? false;
});
},
),
@ -72,9 +161,7 @@ class _LoginPageState extends State<LoginPage> {
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
//
},
onPressed: _login,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,

View File

@ -1,11 +1,118 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:convert' show utf8;
import 'login_page.dart'; //
import 'signup_page.dart'; //
import 'id_finding_page.dart'; // ID
class PwFindingPage extends StatelessWidget {
class PwFindingPage extends StatefulWidget {
const PwFindingPage({Key? key}) : super(key: key);
@override
_PwFindingPageState createState() => _PwFindingPageState();
}
class _PwFindingPageState extends State<PwFindingPage> {
final TextEditingController idController = TextEditingController(); // ID
final TextEditingController emailController = TextEditingController(); //
String emailErrorMessage = ''; //
String idErrorMessage = ''; // ID
Future<void> _findPassword(String id, String email) async {
// PW
print('PW 찾기 요청: ID: $id, 이메일: $email'); //
//
showDialog(
context: context,
barrierDismissible: false, //
builder: (BuildContext context) {
return const Center(child: CircularProgressIndicator());
},
);
try {
final response = await http.post(
Uri.parse('https://eldsoft.com:8097/user/find/password'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'user_id': id,
'user_email': email,
}),
).timeout(const Duration(seconds: 10)); // 10
String responseBody = utf8.decode(response.bodyBytes); // UTF-8
Navigator.of(context).pop(); //
if (response.statusCode == 200) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
print('PW 찾기 성공: $jsonResponse');
//
setState(() {
emailErrorMessage = '';
idErrorMessage = '';
});
if (jsonResponse['response_info']['msg_title'] == '아이디 확인') {
setState(() {
idErrorMessage = '아이디를 다시 확인해주세요'; // ID
});
} else if (jsonResponse['response_info']['msg_title'] == '이메일 확인') {
setState(() {
emailErrorMessage = '이메일을 다시 확인해주세요'; //
});
} else if (jsonResponse['result'] == 'OK') {
//
_showDialog('비밀번호 찾기 안내', '임시 비밀번호가 입력하신 이메일로 발송되었습니다.', 'LOGIN');
} else {
//
_showDialog(jsonResponse['response_info']['msg_title'], jsonResponse['response_info']['msg_content'], 'STAY');
}
} else {
//
_showDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY');
}
} catch (e) {
Navigator.of(context).pop(); //
_showDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY');
}
}
void _showDialog(String title, String content, String action) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white, //
title: Text(title, style: const TextStyle(color: Colors.black)),
content: Text(content, style: const TextStyle(color: Colors.black)),
actions: <Widget>[
Center(
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white, //
),
child: const Text('확인'),
onPressed: () {
Navigator.of(context).pop(); //
if (action == 'LOGIN') {
Navigator.of(context).pop(); //
}
},
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -29,6 +136,7 @@ class PwFindingPage extends StatelessWidget {
),
const SizedBox(height: 32),
TextField(
controller: idController, // ID
decoration: InputDecoration(
labelText: 'ID',
labelStyle: const TextStyle(color: Colors.black),
@ -38,8 +146,17 @@ class PwFindingPage extends StatelessWidget {
),
),
),
if (idErrorMessage.isNotEmpty) // ID
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
idErrorMessage,
style: const TextStyle(color: Colors.red),
),
),
const SizedBox(height: 16),
TextField(
controller: emailController, //
decoration: InputDecoration(
labelText: '이메일',
labelStyle: const TextStyle(color: Colors.black),
@ -49,10 +166,18 @@ class PwFindingPage extends StatelessWidget {
),
),
),
if (emailErrorMessage.isNotEmpty) //
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
emailErrorMessage,
style: const TextStyle(color: Colors.red),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// PW
_findPassword(idController.text, emailController.text); // PW
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,

View File

@ -1,5 +1,9 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // http
import 'dart:convert'; // JSON
import 'login_page.dart'; //
import 'dart:convert' show utf8; // UTF-8
import 'package:crypto/crypto.dart'; // crypto
class SignUpPage extends StatefulWidget {
const SignUpPage({Key? key}) : super(key: key);
@ -9,27 +13,170 @@ class SignUpPage extends StatefulWidget {
}
class _SignUpPageState extends State<SignUpPage> {
//
bool _isAgreed = false; //
String _username = ''; //
String? _usernameError; //
String _username = '', _password = '', _confirmPassword = '', _nickname = '', _email = '';
String _department = '', _introduceMyself = ''; //
String? _usernameError, _passwordError, _confirmPasswordError, _nicknameError, _emailError;
//
bool _isUsernameValid(String username) {
final RegExp regex = RegExp(r'^(?![0-9])[A-Za-z0-9]{6,20}$');
return regex.hasMatch(username);
}
//
bool _isUsernameValid(String username) => RegExp(r'^(?![0-9])[A-Za-z0-9]{6,20}$').hasMatch(username);
bool _isPasswordValidPattern(String password) => RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$').hasMatch(password);
bool _isEmailValid(String email) => RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
bool _isNicknameValid(String nickname) => RegExp(r'^[A-Za-z가-힣0-9]{2,20}$').hasMatch(nickname);
void _validateUsername(String value) {
//
void _validateInput(String label) {
setState(() {
_username = value;
if (_isUsernameValid(value)) {
_usernameError = null; //
} else {
_usernameError = '* 아이디는 6~20자 영문 대소문자와 숫자 조합이어야 하며, 숫자로 시작할 수 없습니다.';
if (label == '아이디') {
_usernameError = _isUsernameValid(_username) ? null : '아이디는 6~20자 영문 대소문자와 숫자 조합이어야 하며, 숫자로 시작할 수 없습니다.';
} else if (label == '비밀번호') {
_passwordError = _isPasswordValidPattern(_password) ? null : '비밀번호는 8~20자 영문과 숫자가 반드시 포함된 조합이어야 합니다.';
} else if (label == '비밀번호 확인') {
_confirmPasswordError = _password == _confirmPassword ? null : '비밀번호가 일치하지 않습니다.';
} else if (label == '닉네임') {
_nicknameError = _isNicknameValid(_nickname) ? null : '* 닉네임은 2~20자 영문, 한글, 숫자만 사용할 수 있습니다.';
} else if (label == '이메일') {
_emailError = _isEmailValid(_email) ? null : (_email.isNotEmpty ? '올바른 이메일 형식을 입력해주세요.' : null);
}
});
}
//
Future<void> _signUp() async {
final url = 'https://eldsoft.com:8097/user/signup';
// mandatory_terms_yn
final mandatoryTermsYn = _isAgreed ? 'Y' : 'N';
// SHA-256
final hashedPassword = _hashPassword(_password);
final body = {
"user_id": _username,
"user_pw": hashedPassword, //
"nickname": _nickname,
"user_email": _email,
"department": _department,
"introduce_myself": _introduceMyself,
"mandatory_terms_yn": mandatoryTermsYn //
};
try {
final response = await http.post(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
// body를 UTF-8
final resBody = json.decode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200) {
// result가 OK이어야만
print(resBody);
if (resBody['result'] == 'OK') {
if (resBody['response_info']['msg_type'] == 'OK') {
_showDialog('회원가입 성공', '회원가입이 완료되었습니다.');
} else {
_showDialog('회원가입 실패', '${resBody['response_info']['msg_content']}');
}
} else {
_showDialog('회원가입 실패', '${resBody['response_info']['msg_content']}');
}
} else {
final errorData = json.decode(response.body);
_showDialog('회원가입 실패', errorData['message'] ?? '회원가입 실패');
}
} catch (error) {
_showDialog('네트워크 오류', '네트워크 오류: $error');
}
}
//
String _hashPassword(String password) {
final bytes = utf8.encode(password); //
final digest = sha256.convert(bytes); // SHA-256
return digest.toString(); //
}
//
void _showDialog(String title, String message) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white, //
title: Text(
title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black, //
),
),
content: Column(
mainAxisSize: MainAxisSize.min, //
children: [
Text(
message,
style: const TextStyle(
fontSize: 16,
color: Colors.black, //
),
),
const SizedBox(height: 20), //
TextButton(
onPressed: () {
Navigator.of(context).pop(); //
if (title == '회원가입 성공') {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginPage())); //
}
// '회원가입 성공'
},
style: TextButton.styleFrom(
foregroundColor: Colors.white, //
backgroundColor: Colors.black, //
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), //
),
child: const Text('확인'),
),
],
),
);
},
);
}
//
Widget _buildTextField(String label, Function(String) onChanged, {bool obscureText = false, String? errorText}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (value) {
onChanged(value);
_validateInput(label); // label에
},
obscureText: obscureText,
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(borderSide: const BorderSide(color: Colors.black, width: 2.0)),
errorStyle: const TextStyle(color: Colors.red, fontSize: 12),
),
),
if (errorText != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(errorText, style: const TextStyle(color: Colors.red, fontSize: 12)),
),
const SizedBox(height: 16),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -39,136 +186,36 @@ class _SignUpPageState extends State<SignUpPage> {
backgroundColor: Colors.black,
leading: IconButton(
icon: const Icon(Icons.chevron_left, color: Colors.white),
onPressed: () {
Navigator.pop(context); //
},
onPressed: () => Navigator.pop(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView( //
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
'회원가입',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const Text('회원가입', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black)),
const SizedBox(height: 32),
TextField(
onChanged: _validateUsername, //
decoration: InputDecoration(
labelText: '아이디',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
),
if (_usernameError != null) //
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
_usernameError!,
style: const TextStyle(color: Colors.red, fontSize: 12), //
),
),
_buildTextField('아이디', (value) => _username = value, errorText: _usernameError),
_buildTextField('비밀번호', (value) => _password = value, obscureText: true, errorText: _passwordError),
_buildTextField('비밀번호 확인', (value) => _confirmPassword = value, obscureText: true, errorText: _confirmPasswordError),
_buildTextField('닉네임', (value) => _nickname = value, errorText: _nicknameError),
_buildTextField('이메일', (value) => _email = value, errorText: _emailError),
_buildTextField('소속(선택사항)', (value) => _department = value),
_buildTextField('자기소개(선택사항)', (value) => _introduceMyself = value),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '비밀번호',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
obscureText: true,
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '비밀번호 확인',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
obscureText: true,
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '닉네임',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '이메일',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '소속',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: '자기소개',
labelStyle: const TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black, width: 2.0),
),
),
maxLines: 3, //
),
const SizedBox(height: 16),
const Text(
'개인정보 수집 및 이용 동의서',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const Text('개인정보 수집 및 이용 동의서', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)),
const SizedBox(height: 8),
Container(
height: 200, //
decoration: BoxDecoration(
border: Border.all(color: Colors.black.withOpacity(0.5)), //
borderRadius: BorderRadius.circular(8), //
),
child: Scrollbar( //
thickness: 5, //
radius: Radius.circular(5), //
height: 200,
decoration: BoxDecoration(border: Border.all(color: Colors.black.withOpacity(0.5)), borderRadius: BorderRadius.circular(8)),
child: Scrollbar(
thickness: 5,
radius: const Radius.circular(5),
child: SingleChildScrollView(
child: const Padding(
padding: EdgeInsets.all(15.0), //
padding: EdgeInsets.all(15.0),
child: Text(
'올스코어(이하 "회사"라 합니다)는 이용자의 개인정보를 중요시하며, '
'「개인정보 보호법」 등 관련 법령을 준수하고 있습니다. '
@ -183,7 +230,7 @@ class _SignUpPageState extends State<SignUpPage> {
'부정 이용 방지 및 비인가 사용 방지\n'
'서비스 이용에 따른 문의 사항 처리\n'
'서비스 제공\n'
'게임 생성 및 참여 등 기본 서비스 제공\n'
'게임 생성 및 참여 등 기본 서비스 제공\n'
'통계 및 순위 제공 등 부가 서비스 제공\n'
'고객 지원 및 공지사항 전달\n'
'서비스 관련 중요한 공지사항 전달\n'
@ -237,10 +284,7 @@ class _SignUpPageState extends State<SignUpPage> {
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
//
Navigator.pop(context); //
},
onPressed: _signUp, //
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,

View File

@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@ -41,6 +41,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.0"
crypto:
dependency: "direct dev"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
cupertino_icons:
dependency: "direct main"
description:
@ -57,6 +65,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
dependency: "direct main"
description: flutter
@ -75,6 +99,27 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: "direct dev"
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
leak_tracker:
dependency: transitive
description:
@ -139,6 +184,102 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
shared_preferences:
dependency: "direct dev"
description:
name: shared_preferences
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
@ -192,6 +333,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.3"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
@ -208,6 +357,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.3.0"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.24.0"

View File

@ -38,6 +38,9 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
http: ^1.2.2
crypto: ^3.0.1
shared_preferences: ^2.0.6
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is