Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lib/enums/member_report_reason.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// 회원 신고 사유 enum (프론트 ↔︎ 백엔드 매핑)
///
/// id : 백엔드 코드 (MemberReportReason.code)
/// label : 클라이언트 표시용 한글 이름
/// serverName : 백엔드 enum 이름
///
/// 백엔드 enum 참고 (가정):
/// BAD_MANNERS(1), FRAUD(2), MISREPRESENTATION(3), NO_SHOW(4), ETC(5)
///
/// 사용 예시
/// MemberReportReason.values.forEach((reason) => print(reason.label));
/// final codes = selectedReasons.map((e) => e.id).toSet(); // API 전송용
enum MemberReportReason {
badManners(id: 1, label: '비매너/욕설/혐오/성적 발언', serverName: 'BAD_MANNERS'),
fraud(id: 2, label: '사기 의심/거래 금지 물품', serverName: 'FRAUD'),
misrepresentation(id: 3, label: '물건 상태 불일치(허위 매물)', serverName: 'MISREPRESENTATION'),
noShow(id: 4, label: '노쇼(약속 불이행)', serverName: 'NO_SHOW'),
etc(id: 5, label: '기타(직접 입력)', serverName: 'ETC');

final int id;
final String label;
final String serverName;

const MemberReportReason({required this.id, required this.label, required this.serverName});
}
187 changes: 187 additions & 0 deletions lib/screens/member_report_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

import 'package:romrom_fe/models/app_colors.dart';
import 'package:romrom_fe/models/app_theme.dart';
import 'package:romrom_fe/widgets/common/completion_button.dart';
import 'package:romrom_fe/enums/member_report_reason.dart';
import 'package:romrom_fe/services/apis/report_api.dart';
import 'package:romrom_fe/utils/error_utils.dart';
import 'package:romrom_fe/widgets/common/common_modal.dart';
import 'package:romrom_fe/widgets/common_app_bar.dart';

/// 회원 신고하기 페이지
class MemberReportScreen extends StatefulWidget {
final String memberId; // 신고 대상 회원 ID

const MemberReportScreen({super.key, required this.memberId});

@override
State<MemberReportScreen> createState() => _MemberReportScreenState();
}

class _MemberReportScreenState extends State<MemberReportScreen> {
// 선택된 신고 사유 집합
final Set<MemberReportReason> _selectedReasons = {};
late final TextEditingController _extraCommentController;

@override
void initState() {
super.initState();
_extraCommentController = TextEditingController();
_extraCommentController.addListener(() {
setState(() {}); // 글자 수 반영용
});
}

@override
void dispose() {
_extraCommentController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.primaryBlack,
appBar: CommonAppBar(
title: '신고하기',
onBackPressed: () {
Navigator.of(context).pop();
},
showBottomBorder: true,
),
body: Stack(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40.h),
Text('신고 사유', style: CustomTextStyles.h2.copyWith(fontWeight: FontWeight.w600)),
SizedBox(height: 24.h),
// 신고 사유 리스트
...MemberReportReason.values.map((reason) => _buildReasonRow(reason)),
if (_selectedReasons.contains(MemberReportReason.etc)) ...[
Container(
width: 345.w,
height: 140.h,
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: AppColors.secondaryBlack1,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: AppColors.textColorWhite.withValues(alpha: 0.3), width: 1.5.w),
),
child: TextField(
controller: _extraCommentController,
maxLines: null,
maxLength: 300,
style: CustomTextStyles.p2,
cursorColor: AppColors.textColorWhite,
decoration: InputDecoration(
isCollapsed: true,
border: InputBorder.none,
counterText: '', // 기본 counter 숨김
hintText: '신고 사유를 상세하게 적어주세요',
hintStyle: CustomTextStyles.p2.copyWith(color: AppColors.textColorWhite.withValues(alpha: 0.4)),
),
),
),
SizedBox(height: 8.h),
Align(
alignment: Alignment.centerRight,
child: Text(
'${_extraCommentController.text.length}/300',
style: CustomTextStyles.p3.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.textColorWhite.withValues(alpha: 0.5),
),
),
),
],
],
),
),

// 하단 고정 신고하기 버튼 (bottom 기준 97.h)
Positioned(
left: 24.w,
right: 24.w,
bottom: 97.h,
child: CompletionButton(
isEnabled: _selectedReasons.isNotEmpty,
buttonText: '신고 하기',
enabledOnPressed: () async {
try {
final api = ReportApi();
await api.reportMember(
memberId: widget.memberId,
memberReportReasons: _selectedReasons.map((e) => e.id).toSet(),
extraComment: _extraCommentController.text.trim(),
);
} catch (e) {
debugPrint('회원 신고 요청 중 오류: $e');
// 에러 코드 파싱
final messageForUser = ErrorUtils.getErrorMessage(e);

await CommonModal.error(
context: context,
message: messageForUser,
onConfirm: () => Navigator.of(context).pop(),
);
return;
}

if (!mounted) return;

Navigator.of(context).pop(true); // 성공 결과 반환
},
),
),
],
),
);
}

/// 신고 사유 선택/해제 토글
void _toggleReason(MemberReportReason reason) {
setState(() {
if (_selectedReasons.contains(reason)) {
_selectedReasons.remove(reason);
} else {
_selectedReasons.add(reason);
}
});
}

/// 신고 사유 행
Widget _buildReasonRow(MemberReportReason reason) {
return Padding(
padding: EdgeInsets.only(bottom: 24.h),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _toggleReason(reason),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 20.w,
height: 20.w,
child: Checkbox(
value: _selectedReasons.contains(reason),
onChanged: (_) => _toggleReason(reason),
activeColor: AppColors.primaryYellow,
checkColor: AppColors.primaryBlack,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.r)),
side: BorderSide(color: AppColors.primaryYellow, width: 1.w),
),
),
SizedBox(width: 12.w),
Expanded(child: Text(reason.label, style: CustomTextStyles.h3)),
],
),
),
);
}
}
16 changes: 13 additions & 3 deletions lib/screens/profile/profile_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import 'package:romrom_fe/icons/app_icons.dart';
import 'package:romrom_fe/models/app_colors.dart';
import 'package:romrom_fe/models/app_theme.dart';
import 'package:romrom_fe/models/user_info.dart';
import 'package:romrom_fe/screens/member_report_screen.dart';
import 'package:romrom_fe/screens/my_page/my_profile_edit_screen.dart';
import 'package:romrom_fe/services/apis/member_api.dart';
import 'package:romrom_fe/utils/common_utils.dart';
import 'package:romrom_fe/utils/navigation_extensions.dart';
import 'package:romrom_fe/widgets/common/common_modal.dart';
import 'package:romrom_fe/widgets/common/common_snack_bar.dart';
import 'package:romrom_fe/widgets/common/romrom_context_menu.dart';
import 'package:romrom_fe/widgets/common_app_bar.dart';
Expand Down Expand Up @@ -156,9 +159,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
}

/// 신고하기 처리
void _handleReport() {
// TODO: 신고 기능 구현 (API 확인 필요)
CommonSnackBar.show(context: context, message: '신고 기능은 준비 중입니다', type: SnackBarType.info);
Future<void> _handleReport() async {
final bool? reported = await context.navigateTo(screen: MemberReportScreen(memberId: widget.memberId));

if (reported == true && mounted) {
await CommonModal.success(
context: context,
message: '신고가 접수되었습니다.',
onConfirm: () => Navigator.of(context).pop(),
);
}
}

@override
Expand Down
25 changes: 25 additions & 0 deletions lib/services/apis/report_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,29 @@ class ReportApi {
},
);
}

/// 회원 신고 API
/// POST /api/report/member/post
Future<void> reportMember({
required String memberId,
required Set<int> memberReportReasons,
String? extraComment,
}) async {
const String url = '${AppUrls.baseUrl}/api/report/member/post';

final Map<String, dynamic> fields = {
'memberId': memberId,
'memberReportReasons': memberReportReasons.join(','),
if (extraComment != null && extraComment.isNotEmpty) 'extraComment': extraComment,
};

await ApiClient.sendMultipartRequest(
url: url,
fields: fields,
isAuthRequired: true,
onSuccess: (_) {
debugPrint('회원 신고 성공');
},
);
}
}
Loading