프로필 꾸미기 미리보기 테두리/체험 버튼 추가, 확인 버튼 엔터 지원, 비밀번호 홀드 보기 추가

- 프로필 꾸미기: 미리보기 중인 큰 타일의 테두리도 테마 색으로 바뀌게 수정,
  미구매 템플릿에 "체험하기" 버튼을 따로 둬서 사기 전에도 눌러볼 수 있게 함
- 앱 전반의 확인/삭제/저장 같은 다이얼로그 기본 액션 버튼에 autofocus를 줘서
  엔터 키로도 눌리게 함 (텍스트 입력이 있는 다이얼로그는 입력창 쪽에
  autofocus + onSubmitted를 달아 입력 중 바로 엔터로 제출되게 처리)
- HoldToRevealPasswordField 위젯 추가 - 눈 아이콘을 누르고 있는 동안만
  비밀번호가 평문으로 보임. 로그인/간편인증/초기 비밀번호 변경 화면에 적용
This commit is contained in:
2026-10-01 05:52:57 +00:00
parent 6b53214913
commit edf0444b71
15 changed files with 203 additions and 75 deletions
+45 -50
View File
@@ -6,6 +6,7 @@ import '../function/login_controller.dart';
import '../function/session_store.dart';
import '../theme/app_palette.dart';
import 'app_notice.dart';
import 'hold_to_reveal_password_field.dart';
import 'main_dashboard.dart';
import 'teacher_register_screen.dart';
@@ -112,6 +113,36 @@ class _LoginScreenState extends State<LoginScreen> {
return StatefulBuilder(
builder: (context, setDialogState) {
Future<void> submit() async {
String newPassword = newPwController.text.trim();
if (newPassword.isEmpty) {
AppNotice.show(context, '새 비밀번호를 입력해 주세요.');
return;
}
setDialogState(() => isUpdating = true);
final (success, message) = await _controller.changePassword(
studentId: studentId,
newPassword: newPassword,
);
setDialogState(() => isUpdating = false);
if (success) {
Navigator.pop(dialogContext);
AppNotice.show(context, message);
_navigateBasedOnRole(
role,
studentId,
name,
isDeviceMatched,
grade,
);
} else {
AppNotice.show(context, message);
}
}
return AlertDialog(
title: const Text(
'초기 비밀번호 변경',
@@ -125,14 +156,13 @@ class _LoginScreenState extends State<LoginScreen> {
style: TextStyle(color: AppPalette.danger),
),
const SizedBox(height: 16),
TextField(
HoldToRevealPasswordField(
controller: newPwController,
obscureText: true,
decoration: const InputDecoration(
labelText: '새 비밀번호',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock_reset),
),
autofocus: true,
labelText: '새 비밀번호',
prefixIcon: Icons.lock_reset,
textInputAction: TextInputAction.done,
onSubmitted: (_) => submit(),
),
],
),
@@ -147,36 +177,7 @@ class _LoginScreenState extends State<LoginScreen> {
backgroundColor: AppPalette.ink,
foregroundColor: Colors.white,
),
onPressed: () async {
String newPassword = newPwController.text.trim();
if (newPassword.isEmpty) {
AppNotice.show(context, '새 비밀번호를 입력해 주세요.');
return;
}
setDialogState(() => isUpdating = true);
final (success, message) = await _controller
.changePassword(
studentId: studentId,
newPassword: newPassword,
);
setDialogState(() => isUpdating = false);
if (success) {
Navigator.pop(dialogContext);
AppNotice.show(context, message);
_navigateBasedOnRole(
role,
studentId,
name,
isDeviceMatched,
grade,
);
} else {
AppNotice.show(context, message);
}
},
onPressed: submit,
child: const Text('변경하고 시작하기'),
),
],
@@ -234,6 +235,7 @@ class _LoginScreenState extends State<LoginScreen> {
content: Text(message),
actions: [
TextButton(
autofocus: true,
onPressed: () => Navigator.pop(ctx),
child: const Text('확인'),
),
@@ -254,9 +256,11 @@ class _LoginScreenState extends State<LoginScreen> {
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text('$title 권한 인증 (기존 방식)'),
content: TextField(
content: HoldToRevealPasswordField(
controller: passwordController,
obscureText: true,
autofocus: true,
labelText: '비밀번호',
hintText: '비밀번호 4자리를 입력하세요',
keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
@@ -270,10 +274,6 @@ class _LoginScreenState extends State<LoginScreen> {
AppNotice.show(context, '비밀번호가 올바르지 않습니다.');
}
},
decoration: const InputDecoration(
hintText: '비밀번호 4자리를 입력하세요',
border: OutlineInputBorder(),
),
),
actions: [
TextButton(
@@ -341,16 +341,11 @@ class _LoginScreenState extends State<LoginScreen> {
),
),
const SizedBox(height: 16),
TextField(
HoldToRevealPasswordField(
controller: _pwController,
obscureText: true,
labelText: '비밀번호',
textInputAction: TextInputAction.done,
onSubmitted: (_) => _login(),
decoration: const InputDecoration(
labelText: '비밀번호',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock),
),
),
const SizedBox(height: 8),
InkWell(