diff --git a/lib/core/widget/EmailTextWidget.dart b/lib/core/widget/EmailTextWidget.dart new file mode 100644 index 0000000..688e02d --- /dev/null +++ b/lib/core/widget/EmailTextWidget.dart @@ -0,0 +1,20 @@ +import 'package:blueberry_flutter_template/feature/user/provider/GetUserEmailProviderProvider.dart'; +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; + +class EmailTextWidget extends ConsumerWidget { + const EmailTextWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userEmail = ref.watch(getUserEmailProvider); + return userEmail.when( + data: (email) => Text( + email, + ), + loading: () => const Text(''), + error: (e, s) => const Text(AppStrings.emailTextWidgetError), + ); + } +} diff --git a/lib/core/widget/UserSignUpDataTextWidget.dart b/lib/core/widget/UserSignUpDataTextWidget.dart new file mode 100644 index 0000000..e9db100 --- /dev/null +++ b/lib/core/widget/UserSignUpDataTextWidget.dart @@ -0,0 +1,20 @@ +import 'package:blueberry_flutter_template/feature/user/provider/GetUserSignUpDataProvider.dart'; +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; + +class UserSignUpTextWidget extends ConsumerWidget { + const UserSignUpTextWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userSignUpDate = ref.watch(getUserSignUpProvider); + return userSignUpDate.when( + data: (date) => Text( + date, + ), + loading: () => const Text(''), + error: (e, s) => const Text(AppStrings.dateTextWidgetError), + ); + } +} \ No newline at end of file diff --git a/lib/feature/login/LoginScreen.dart b/lib/feature/login/LoginScreen.dart index 0b4b115..910b588 100644 --- a/lib/feature/login/LoginScreen.dart +++ b/lib/feature/login/LoginScreen.dart @@ -95,10 +95,11 @@ Widget _buildLogin(BuildContext context, WidgetRef ref) { if (value == null || value.isEmpty) { return AppStrings.errorMessage_emptyPassword; } - if (!RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$') - .hasMatch(value)) { - return AppStrings.errorMessage_invalidPassword; - } + // if (!RegExp(r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[@!#$%^&*()_+\-=\[\]{};:"\\|,.<>/?])[\w@!#$%^&*()_+\-=\[\]{};:"\\|,.<>/?]*$') + // .hasMatch(value)) { + // return AppStrings.errorMessage_invalidPassword; + // } + // 회원가입 시 비밀번호 정규식이 걸려 있기 때문에 여기서 걸 필요 없는거 같아서 우선 주석처리 해둘께요. 추후 삭제 하거나 사용 할지 결정 해야함. return null; }, ), @@ -129,7 +130,7 @@ Widget _buildLogin(BuildContext context, WidgetRef ref) { try { await ref .read(firebaseAuthServiceProvider) - .signInWithEmailPassword(email, password); + .signInWithEmailPassword(context, email, password); } catch (e) { showDialog( context: context, diff --git a/lib/feature/mypage/MyPageScreen.dart b/lib/feature/mypage/MyPageScreen.dart index 22967dd..3c64a1c 100644 --- a/lib/feature/mypage/MyPageScreen.dart +++ b/lib/feature/mypage/MyPageScreen.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:blueberry_flutter_template/feature/mypage/provider/ProfileImageProvider.dart'; +import 'package:blueberry_flutter_template/services/FirebaseService.dart'; import 'package:cloud_functions/cloud_functions.dart'; import 'package:easy_engine/easy_engine.dart'; import 'package:firebase_auth/firebase_auth.dart'; @@ -112,7 +113,7 @@ class MyPageScreen extends ConsumerWidget { ), const CustomDividerWidget(), GestureDetector( - onTap: () {}, + onTap: () {}, child: const ListTile( leading: Icon(Icons.chat_bubble_outline), title: Text( @@ -156,35 +157,7 @@ class MyPageScreen extends ConsumerWidget { //Logout button GestureDetector( onTap: () async { - try { - final re = await engine.deleteAccount(); - debugPrint(re.toString()); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('회원탈퇴가 완료되었습니다.'), - ), - ); - } - ref.read(firebaseAuthServiceProvider).signOut(); - } on FirebaseFunctionsException catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error: ${e.code}/${e.message}'), - ), - ); - } - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Error: $e'), // e.code: internal, e.message: INTERNAL - ), - ); - } - } + FirebaseService().requestAccountDeletion(context, ref); }, child: const ListTile( leading: Icon(Icons.person_off), diff --git a/lib/feature/user/provider/GetUserDeletionDateProvider.dart b/lib/feature/user/provider/GetUserDeletionDateProvider.dart new file mode 100644 index 0000000..9923188 --- /dev/null +++ b/lib/feature/user/provider/GetUserDeletionDateProvider.dart @@ -0,0 +1,19 @@ +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:blueberry_flutter_template/utils/Formatter.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final getUserDeletionDateProvider = StreamProvider((ref) { + final firestore = FirebaseFirestore.instance; + final userId = FirebaseAuth.instance.currentUser!.uid; + + return firestore.collection('users').doc(userId).snapshots().map((snapshot) { + final deletionRequestedAt = snapshot.get('scheduledDeletionTime'); + if (deletionRequestedAt is Timestamp) { + return formatTimestamp(deletionRequestedAt); + } else { + return AppStrings.dateTextWidgetError; + } + }); +}); \ No newline at end of file diff --git a/lib/feature/user/provider/GetUserDeletionRequestDateProvider.dart b/lib/feature/user/provider/GetUserDeletionRequestDateProvider.dart new file mode 100644 index 0000000..ee1c1be --- /dev/null +++ b/lib/feature/user/provider/GetUserDeletionRequestDateProvider.dart @@ -0,0 +1,20 @@ +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:blueberry_flutter_template/utils/Formatter.dart'; +import 'package:blueberry_flutter_template/utils/Talker.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final getUserDeletionRequestDateProvider = StreamProvider((ref) { + final firestore = FirebaseFirestore.instance; + final userId = FirebaseAuth.instance.currentUser!.uid; + + return firestore.collection('users').doc(userId).snapshots().map((snapshot) { + final deletionRequestedAt = snapshot.get('deletionRequestedAt'); + if (deletionRequestedAt is Timestamp) { + return formatTimestamp(deletionRequestedAt); + } else { + return AppStrings.dateTextWidgetError; + } + }); +}); \ No newline at end of file diff --git a/lib/feature/user/provider/GetUserEmailProviderProvider.dart b/lib/feature/user/provider/GetUserEmailProviderProvider.dart new file mode 100644 index 0000000..0825dcd --- /dev/null +++ b/lib/feature/user/provider/GetUserEmailProviderProvider.dart @@ -0,0 +1,12 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final getUserEmailProvider = StreamProvider((ref) { + final firestore = FirebaseFirestore.instance; + final userId = FirebaseAuth.instance.currentUser!.uid; + + return firestore.collection('users').doc(userId).snapshots().map((snapshot) { + return snapshot['email'] as String; + }); +}); \ No newline at end of file diff --git a/lib/feature/user/provider/GetUserSignUpDataProvider.dart b/lib/feature/user/provider/GetUserSignUpDataProvider.dart new file mode 100644 index 0000000..69a0a24 --- /dev/null +++ b/lib/feature/user/provider/GetUserSignUpDataProvider.dart @@ -0,0 +1,19 @@ +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:blueberry_flutter_template/utils/Formatter.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final getUserSignUpProvider = StreamProvider((ref) { + final firestore = FirebaseFirestore.instance; + final userId = FirebaseAuth.instance.currentUser!.uid; + + return firestore.collection('users').doc(userId).snapshots().map((snapshot) { + final createdAt = snapshot.get('createdAt'); + if (createdAt is Timestamp) { + return formatTimestamp(createdAt); + } else { + return AppStrings.dateTextWidgetError; + } + }); +}); diff --git a/lib/feature/user/restore/RestoreDeletedUserScreen.dart b/lib/feature/user/restore/RestoreDeletedUserScreen.dart new file mode 100644 index 0000000..db440a9 --- /dev/null +++ b/lib/feature/user/restore/RestoreDeletedUserScreen.dart @@ -0,0 +1,12 @@ +import 'package:blueberry_flutter_template/feature/user/restore/widget/DeletedUserDataWidget.dart'; +import 'package:flutter/material.dart'; + +class RestoreDeletedUserScreen extends StatelessWidget { + static const String name = '/RestoreDeletedUserScreen'; + const RestoreDeletedUserScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const DeletedUserDataWidget(); + } +} diff --git a/lib/feature/user/restore/widget/DeletedUserDataWidget.dart b/lib/feature/user/restore/widget/DeletedUserDataWidget.dart new file mode 100644 index 0000000..782a349 --- /dev/null +++ b/lib/feature/user/restore/widget/DeletedUserDataWidget.dart @@ -0,0 +1,28 @@ +import 'package:blueberry_flutter_template/feature/user/restore/widget/DeletionRequestUserInfoTextWidget.dart'; +import 'package:blueberry_flutter_template/feature/user/restore/widget/RestoreDeletionUserBottomButtonWidget.dart'; +import 'package:flutter/material.dart'; + + +class DeletedUserDataWidget extends StatelessWidget { + const DeletedUserDataWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + // ignore the back button + ), + body: const Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: DeletionRequestUserInfoListWidget() + ), + RestoreDeletionUserBottomButtonWidget(), + ], + ), + ); + } +} diff --git a/lib/feature/user/restore/widget/DeletionRequestUserInfoTextWidget.dart b/lib/feature/user/restore/widget/DeletionRequestUserInfoTextWidget.dart new file mode 100644 index 0000000..69d8026 --- /dev/null +++ b/lib/feature/user/restore/widget/DeletionRequestUserInfoTextWidget.dart @@ -0,0 +1,40 @@ +import 'package:blueberry_flutter_template/core/widget/EmailTextWidget.dart'; +import 'package:blueberry_flutter_template/core/widget/UserSignUpDataTextWidget.dart'; +import 'package:blueberry_flutter_template/feature/user/restore/widget/UserDeletionDateTextWidget.dart'; +import 'package:blueberry_flutter_template/feature/user/restore/widget/UserDeletionRequestDateTextWidget.dart'; +import 'package:flutter/material.dart'; + +class DeletionRequestUserInfoListWidget extends StatelessWidget { + const DeletionRequestUserInfoListWidget({super.key}); + + @override + Widget build(BuildContext context) { + final List> userInfoItems = [ + {'아이디: ': const EmailTextWidget()}, + {'최초 계정 생성 일: ': const UserSignUpTextWidget()}, + {'탈퇴 요청 일: ': const UserDeletionRequestDateTextWidget()}, + {'탈퇴 예정 일: ': const UserDeletionDateTextWidget()}, + ]; + + return Padding( + padding: const EdgeInsets.all(16.0), + child: ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: userInfoItems.length, + itemBuilder: (context, index) { + final item = userInfoItems[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + children: [ + Text(item.keys.first), + item.values.first, + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/feature/user/restore/widget/RestoreDeletionUserBottomButtonWidget.dart b/lib/feature/user/restore/widget/RestoreDeletionUserBottomButtonWidget.dart new file mode 100644 index 0000000..c05e014 --- /dev/null +++ b/lib/feature/user/restore/widget/RestoreDeletionUserBottomButtonWidget.dart @@ -0,0 +1,44 @@ +import 'package:blueberry_flutter_template/services/FirebaseAuthServiceProvider.dart'; +import 'package:blueberry_flutter_template/services/FirebaseService.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class RestoreDeletionUserBottomButtonWidget extends ConsumerWidget { + const RestoreDeletionUserBottomButtonWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: RestoreBtn(), + ), + const SizedBox(width: 16), + Expanded( + child: cancelBtn(ref), + ), + ], + ), + ); + } + + ElevatedButton cancelBtn(WidgetRef ref) { + return ElevatedButton( + onPressed: () async { + ref.read(firebaseAuthServiceProvider).signOut(); + }, + child: const Text('나가기'), + ); + } + + ElevatedButton RestoreBtn() { + return ElevatedButton( + onPressed: () async { + await FirebaseService().cancelAccountDeletion(); + }, + child: const Text('복원 하기'), + ); + } +} diff --git a/lib/feature/user/restore/widget/UserDeletionDateTextWidget.dart b/lib/feature/user/restore/widget/UserDeletionDateTextWidget.dart new file mode 100644 index 0000000..0f95f31 --- /dev/null +++ b/lib/feature/user/restore/widget/UserDeletionDateTextWidget.dart @@ -0,0 +1,20 @@ +import 'package:blueberry_flutter_template/feature/user/provider/GetUserDeletionDateProvider.dart'; +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; + +class UserDeletionDateTextWidget extends ConsumerWidget { + const UserDeletionDateTextWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userDeletionDate = ref.watch(getUserDeletionDateProvider); + return userDeletionDate.when( + data: (date) => Text( + date, + ), + loading: () => const Text(''), + error: (e, s) => const Text(AppStrings.dateTextWidgetError), + ); + } +} \ No newline at end of file diff --git a/lib/feature/user/restore/widget/UserDeletionRequestDateTextWidget.dart b/lib/feature/user/restore/widget/UserDeletionRequestDateTextWidget.dart new file mode 100644 index 0000000..4607a08 --- /dev/null +++ b/lib/feature/user/restore/widget/UserDeletionRequestDateTextWidget.dart @@ -0,0 +1,20 @@ +import 'package:blueberry_flutter_template/feature/user/provider/GetUserDeletionRequestDateProvider.dart'; +import 'package:blueberry_flutter_template/utils/AppStrings.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; + +class UserDeletionRequestDateTextWidget extends ConsumerWidget { + const UserDeletionRequestDateTextWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userDeletionRequestDate = ref.watch(getUserDeletionRequestDateProvider); + return userDeletionRequestDate.when( + data: (date) => Text( + date, + ), + loading: () => const Text(''), + error: (e, s) => const Text(AppStrings.dateTextWidgetError), + ); + } +} \ No newline at end of file diff --git a/lib/router/RouterProvider.dart b/lib/router/RouterProvider.dart index a620226..42279f6 100644 --- a/lib/router/RouterProvider.dart +++ b/lib/router/RouterProvider.dart @@ -5,6 +5,7 @@ import 'package:blueberry_flutter_template/feature/admin/AdminUserListPage.dart' import 'package:blueberry_flutter_template/feature/chat/ChatScreen.dart'; import 'package:blueberry_flutter_template/feature/payment/widget/WebPaymentWidget.dart'; import 'package:blueberry_flutter_template/feature/setting/SettingScreen.dart'; +import 'package:blueberry_flutter_template/feature/user/restore/RestoreDeletedUserScreen.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -138,6 +139,12 @@ final routerProvider = Provider((ref) { name: ProfileDetailScreen.name, builder: (context, state) => ResponsiveLayoutBuilder(context, const ProfileDetailScreen()), + ), + GoRoute( + path: 'restoredeleteduser', + name: RestoreDeletedUserScreen.name, + builder: (context, state) => + ResponsiveLayoutBuilder(context, const RestoreDeletedUserScreen()), ) ], ), diff --git a/lib/services/FirebaseAuthServiceProvider.dart b/lib/services/FirebaseAuthServiceProvider.dart index 5a4a6c5..b1f9285 100644 --- a/lib/services/FirebaseAuthServiceProvider.dart +++ b/lib/services/FirebaseAuthServiceProvider.dart @@ -1,5 +1,10 @@ +import 'package:blueberry_flutter_template/services/FirebaseService.dart'; +import 'package:blueberry_flutter_template/services/FirebaseStoreServiceProvider.dart'; +import 'package:blueberry_flutter_template/utils/Talker.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; /// FirebaseAuthServiceProvider.dart /// @@ -38,12 +43,26 @@ class FirebaseAuthService { } } - // 이메일/비밀번호 로그인 - Future signInWithEmailPassword(String email, String password) async { + + + Future signInWithEmailPassword(BuildContext context, String email, String password) async { try { UserCredential result = await _auth.signInWithEmailAndPassword( email: email, password: password); - return result.user; + User? user = result.user; + + if (user != null) { + bool hasDeletionRequest = await FirebaseService().checkDeletionRequest(user.uid); + if (hasDeletionRequest) { + talker.log("계정 삭제 진행중 입니다."); + if (context.mounted) { + context.goNamed('/RestoreDeletedUserScreen'); + } + throw Exception('계정 삭제가 진행 중입니다. 고객 센터에 문의하세요.'); + } + } + + return user; } catch (e) { throw Exception('로그인 실패: $e'); } diff --git a/lib/services/FirebaseService.dart b/lib/services/FirebaseService.dart index 287d64e..fb7a4a3 100644 --- a/lib/services/FirebaseService.dart +++ b/lib/services/FirebaseService.dart @@ -1,7 +1,12 @@ import 'package:blueberry_flutter_template/model/UserModel.dart'; +import 'package:blueberry_flutter_template/services/FirebaseAuthServiceProvider.dart'; +import 'package:blueberry_flutter_template/utils/Talker.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cloud_functions/cloud_functions.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../model/ChatMessageModel.dart'; class FirebaseService { @@ -46,7 +51,7 @@ class FirebaseService { age: 1, isMemberShip: false, profileImageUrl: '', - createdAt: DateTime.now(), + createdAt: DateTime.timestamp(), mbti: 'NULL', userClass: 'user', likeGivens: [""], @@ -54,7 +59,7 @@ class FirebaseService { // 멤버쉽 모델은 추후에 인앱 결제시 유저가 구독하고 있거나 유저 상태에 대한 변경을 주기 위해 추가했음 await _firestore.collection('users').doc(user.uid).set(newUser.toJson()); } catch (e) { - print('Error updating user: $e'); + talker.error('Error updating user: $e'); throw Exception('Failed to update user'); } } @@ -72,11 +77,61 @@ class FirebaseService { 'isMemberShip': true, }); } catch (e) { - print('Error updating user membership: $e'); + talker.error('Error updating user membership: $e'); throw Exception('Failed to update user membership'); } } + Future requestAccountDeletion(BuildContext context, WidgetRef ref) async { + try { + var user = FirebaseAuth.instance.currentUser!.uid; + if (user != null) { + final callable = FirebaseFunctions.instance.httpsCallable('requestAccountDeletion'); + talker.log('Calling Firebase Function: requestAccountDeletion'); + final result = await callable.call(); + + talker.log('Firebase Function response: ${result.data}'); + + if (result.data['success'] == true) { + talker.log('탈퇴 요청을 성공적으로 보냈습니다'); + await ref.read(firebaseAuthServiceProvider).signOut(); + + } else { + talker.error('탈퇴 요청 실패: ${result.data['message']}'); + throw Exception('탈퇴 요청 실패: ${result.data['message']}'); + } + } else { + talker.error('현재 로그인된 사용자가 없습니다.'); + throw Exception('현재 로그인된 사용자가 없습니다.'); + } + } on FirebaseFunctionsException catch (e) { + talker.error('Firebase Function 오류: ${e.code} - ${e.message}'); + throw Exception('Firebase Function 오류: ${e.message}'); + } catch (e) { + talker.error('계정 삭제 요청 중 오류 발생: $e'); + throw Exception('계정 삭제 요청 중 오류 발생: $e'); + } + } + + Future cancelAccountDeletion() async{ + try { + var user = FirebaseAuth.instance.currentUser; + + if (user == null) { + throw Exception('No current user found'); + } + + await _firestore.collection('users').doc(user.uid).update({ + 'deletionRequestedAt': FieldValue.delete(), + 'status': FieldValue.delete(), + 'scheduledDeletionTime': FieldValue.delete(), + }); + } catch (e) { + talker.error('Error canceling account deletion: $e'); + throw Exception('Failed to cancel account deletion'); + } + } + // 채팅방 생성 함수 Future createChatRoom(String roomName) async { try { @@ -85,8 +140,24 @@ class FirebaseService { 'timestamp': DateTime.now(), }); } catch (e) { - print('Error creating chat room: $e'); + talker.error('Error creating chat room: $e'); throw Exception('Failed to create chat room'); } } + + Future checkDeletionRequest(String uid) async { + try { + DocumentSnapshot userDoc = await _firestore.collection('users').doc(uid).get(); + + if (userDoc.exists) { + Map userData = userDoc.data() as Map; + return userData['deletionRequestedAt'] != null; + } + + return false; // 문서가 존재하지 않으면 삭제 요청이 없는 것으로 간주 + } catch (e) { + talker.error('삭제 요청 확인 중 오류 발생: $e'); + throw Exception('사용자 상태 확인 실패: $e'); + } + } } diff --git a/lib/utils/AppStrings.dart b/lib/utils/AppStrings.dart index 686c1f5..04f6f83 100644 --- a/lib/utils/AppStrings.dart +++ b/lib/utils/AppStrings.dart @@ -17,6 +17,8 @@ class AppStrings { //NickNameTextWidget.dart static const String nickNameTextWidgetdefaultNickName = '닉네임'; static const String nickNameTextWidgetError = '오류'; + static const String emailTextWidgetError = '이메일 정보를 가져오지 못했습니다.'; + static const String dateTextWidgetError = '날짜 정보를 가져오지 못했습니다.'; //MyPageScreen.dart static const String myPageTitle = '내 페이지'; diff --git a/lib/utils/Formatter.dart b/lib/utils/Formatter.dart index ea3a6f8..1658047 100644 --- a/lib/utils/Formatter.dart +++ b/lib/utils/Formatter.dart @@ -1,3 +1,4 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:intl/intl.dart'; String formatWonNumber(int number) { @@ -20,3 +21,8 @@ String timeAgo(DateTime dateTime) { return 'Just now'; } } + +String formatTimestamp(Timestamp timestamp) { + final DateTime dateTime = timestamp.toDate(); + return DateFormat('yyyy년 MM시 dd일').format(dateTime); +}