From 12d38a7d9229c882af43884b62d86a87f7835aed Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Wed, 18 Dec 2024 13:01:49 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/constants/paths.dart | 2 + lib/data/di/get_it_locator.dart | 3 + .../dto/response/image_save_response.dart | 14 ++++ lib/data/service/s3_service.dart | 38 +++++++++ .../mypage/image_upload_view_model.dart | 79 +++++++++++++++++++ lib/features/mypage/image_uplooad_screen.dart | 55 +++++++++++++ lib/features/mypage/mypage_screen.dart | 5 ++ lib/route/routes.dart | 9 ++- pubspec.yaml | 2 + 9 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 lib/data/dto/response/image_save_response.dart create mode 100644 lib/features/mypage/image_upload_view_model.dart create mode 100644 lib/features/mypage/image_uplooad_screen.dart diff --git a/lib/constants/paths.dart b/lib/constants/paths.dart index a53db29..00dbaf6 100644 --- a/lib/constants/paths.dart +++ b/lib/constants/paths.dart @@ -40,4 +40,6 @@ abstract class Paths { static const String report = '/report'; static const String reportDetail = '/report_detail'; + + static const String image = '/image'; } diff --git a/lib/data/di/get_it_locator.dart b/lib/data/di/get_it_locator.dart index ab13f22..1681826 100644 --- a/lib/data/di/get_it_locator.dart +++ b/lib/data/di/get_it_locator.dart @@ -1,5 +1,6 @@ import 'package:cogo/data/service/mentor_service.dart'; import 'package:cogo/data/service/refresh_service.dart'; +import 'package:cogo/data/service/s3_service.dart'; import 'package:cogo/data/service/user_service.dart'; import 'package:cogo/features/auth/login/login_view_model.dart'; import 'package:cogo/features/auth/signup/club/club_selection_view_model.dart'; @@ -27,6 +28,8 @@ void setupServiceLocator() { ///뷰모델 등록 + getIt.registerLazySingleton(() => S3Service()); + getIt.registerFactory(() => LoginViewModel()); getIt.registerFactory( diff --git a/lib/data/dto/response/image_save_response.dart b/lib/data/dto/response/image_save_response.dart new file mode 100644 index 0000000..fa4594f --- /dev/null +++ b/lib/data/dto/response/image_save_response.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'image_save_response.freezed.dart'; +part 'image_save_response.g.dart'; + +@freezed +class ImageSaveResponse with _$ImageSaveResponse { + const factory ImageSaveResponse({ + String? savedUrl, + }) = _ImageSaveResponse; + + factory ImageSaveResponse.fromJson(Map json) => + _$ImageSaveResponseFromJson(json); +} diff --git a/lib/data/service/s3_service.dart b/lib/data/service/s3_service.dart index e69de29..2fce56a 100644 --- a/lib/data/service/s3_service.dart +++ b/lib/data/service/s3_service.dart @@ -0,0 +1,38 @@ +import 'package:cogo/constants/apis.dart'; +import 'package:cogo/data/di/api_client.dart'; +import 'package:cogo/data/dto/response/base_response.dart'; +import 'package:cogo/data/dto/response/image_save_response.dart'; +import 'package:dio/dio.dart'; + +class S3Service { + final ApiClient _apiClient = ApiClient(); + static const apiVersion = "api/v2/"; + + // Cogo 신청 + Future saveImage(FormData formData) async { + try { + final response = await _apiClient.dio.post( + options: Options( + extra: {'skipAuthToken': false, 'multipart': true}, //토큰 해제하지 않음 + ), + '${Apis.s3}/v2', + data: formData); + + if (response.statusCode == 201) { + //base response로 받는건 여기서 뿐임. + final baseResponse = BaseResponse.fromJson( + response.data, + (contentJson) => ImageSaveResponse.fromJson(contentJson), + ); + return baseResponse.content; + } else { + throw Exception('Failed to send verification code'); + } + } on DioException catch (e) { + throw Exception('Error: ${e.response?.data ?? e.message}'); + } catch (e) { + // 기타 모든 예외 처리 + throw Exception('An unexpected error occurred: $e'); + } + } +} diff --git a/lib/features/mypage/image_upload_view_model.dart b/lib/features/mypage/image_upload_view_model.dart new file mode 100644 index 0000000..9fd9059 --- /dev/null +++ b/lib/features/mypage/image_upload_view_model.dart @@ -0,0 +1,79 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:cogo/data/service/s3_service.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:image_picker/image_picker.dart'; + +class ImageUploadViewModel extends ChangeNotifier { + final S3Service s3service = GetIt.instance(); + + final ImagePicker _picker = ImagePicker(); + File? _selectedImage; + bool _isUploading = false; + String? _errorMessage; + + File? get selectedImage => _selectedImage; + + bool get isUploading => _isUploading; + + String? get errorMessage => _errorMessage; + + Future pickImage() async { + try { + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + _selectedImage = File(pickedFile.path); + notifyListeners(); + } + } catch (e) { + _errorMessage = "Error picking image: $e"; + notifyListeners(); + } + } + + Future uploadToS3() async { + if (_selectedImage == null) { + _errorMessage = "Please select an image first."; + notifyListeners(); + return; + } + + try { + String fileName = _selectedImage!.path.split('/').last; + log("fileName: $fileName"); + + try { + FormData formData = FormData.fromMap({ + "file": await MultipartFile.fromFile( + _selectedImage!.path, + filename: fileName, + ), + }); + + log("FormData 생성됨"); + log("파일 경로: ${_selectedImage!.path}"); + log("파일 존재 여부: ${File(_selectedImage!.path).existsSync()}"); + + // FormData 내부 데이터 출력 + formData.fields.forEach((field) { + log("Field: ${field.key}, Value: ${field.value}"); + }); + + s3service.saveImage(formData); + _isUploading = true; + notifyListeners(); + } catch (e) { + log("FormData 생성 중 오류 발생: $e"); + } + } catch (e) { + _errorMessage = "Error uploading image: $e"; + notifyListeners(); + } finally { + _isUploading = false; + notifyListeners(); + } + } +} diff --git a/lib/features/mypage/image_uplooad_screen.dart b/lib/features/mypage/image_uplooad_screen.dart new file mode 100644 index 0000000..d5c6634 --- /dev/null +++ b/lib/features/mypage/image_uplooad_screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'image_upload_view_model.dart'; + +class ImageUploadScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (_) => ImageUploadViewModel(), + child: Scaffold( + appBar: AppBar(title: Text("Image Uploader")), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Consumer( + builder: (context, viewModel, child) { + return Column( + children: [ + if (viewModel.selectedImage != null) + Image.file( + viewModel.selectedImage!, + height: 200, + width: 200, + fit: BoxFit.cover, + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: viewModel.pickImage, + child: Text("Pick Image from Gallery"), + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: + viewModel.isUploading ? null : viewModel.uploadToS3, + child: viewModel.isUploading + ? CircularProgressIndicator(color: Colors.white) + : Text("Upload to S3"), + ), + if (viewModel.errorMessage != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + viewModel.errorMessage!, + style: TextStyle(color: Colors.red), + ), + ), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/features/mypage/mypage_screen.dart b/lib/features/mypage/mypage_screen.dart index 3a7e3af..34ce601 100644 --- a/lib/features/mypage/mypage_screen.dart +++ b/lib/features/mypage/mypage_screen.dart @@ -86,6 +86,11 @@ class MypageScreen extends StatelessWidget { SvgPicture.asset('assets/image/img_image.svg'), // 기본 로컬 이미지 ], + BasicButton( + text: "프로필 사진 변경", + isClickable: true, + onPressed: () => {context.push(Paths.image)}, + ), const SizedBox(height: 13), Center( child: TagList(tags: user!.tags), diff --git a/lib/route/routes.dart b/lib/route/routes.dart index a315a97..37a00c5 100644 --- a/lib/route/routes.dart +++ b/lib/route/routes.dart @@ -223,7 +223,7 @@ final AppRouter = GoRouter( path: Paths.myMentorIntroduce, pageBuilder: (context, state) => MaterialPage( key: state.pageKey, - child: const MentorIntroductionScreen(), + child: const MyMentorIntroductionScreen(), ), ), GoRoute( @@ -265,6 +265,13 @@ final AppRouter = GoRouter( child: const ReportDetailScreen(), ), ), + GoRoute( + path: Paths.image, + pageBuilder: (context, state) => MaterialPage( + key: state.pageKey, + child: ImageUploadScreen(), + ), + ), StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return ScaffoldWithNestedNavigation( diff --git a/pubspec.yaml b/pubspec.yaml index 92f2903..2452cc9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,8 @@ dependencies: # 애니메이션 로티 lottie: ^2.2.0 + image_picker: ^0.8.7+5 + dev_dependencies: flutter_test: From fe620a657ebe35f8172c979cf06f2fa7ce294dce Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Wed, 18 Dec 2024 14:17:13 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20http?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/di/api_client.dart | 4 + lib/data/service/s3_service.dart | 21 ++- .../mypage/image_upload_view_model.dart | 125 ++++++++++++------ lib/features/mypage/image_uplooad_screen.dart | 4 +- 4 files changed, 109 insertions(+), 45 deletions(-) diff --git a/lib/data/di/api_client.dart b/lib/data/di/api_client.dart index a84a444..c054ced 100644 --- a/lib/data/di/api_client.dart +++ b/lib/data/di/api_client.dart @@ -35,6 +35,10 @@ class ApiClient { options.headers['Authorization'] = 'Bearer $token'; } } + // if (options.extra['multipart']==true){ + // options.headers.remove('Content-Type'); + // options.headers['Content-Type'] = ' multipart/form-data'; + // } log('요청 보내는 중: ${options.method} ${options.path}'); return handler.next(options); }, diff --git a/lib/data/service/s3_service.dart b/lib/data/service/s3_service.dart index 2fce56a..186f28a 100644 --- a/lib/data/service/s3_service.dart +++ b/lib/data/service/s3_service.dart @@ -1,3 +1,6 @@ +import 'dart:developer'; +import 'dart:io'; + import 'package:cogo/constants/apis.dart'; import 'package:cogo/data/di/api_client.dart'; import 'package:cogo/data/dto/response/base_response.dart'; @@ -8,15 +11,27 @@ class S3Service { final ApiClient _apiClient = ApiClient(); static const apiVersion = "api/v2/"; - // Cogo 신청 - Future saveImage(FormData formData) async { + // s3 이미지 저장 + Future saveImage(File selectedImage) async { try { + String fileName = selectedImage.path.split('/').last; + log("fileName: $fileName"); + + FormData formData = FormData.fromMap({ + "file": await MultipartFile.fromFile( + selectedImage.path, + filename: fileName, + ), + }); + + log("formData: ${formData.files.length}"); + final response = await _apiClient.dio.post( options: Options( extra: {'skipAuthToken': false, 'multipart': true}, //토큰 해제하지 않음 ), '${Apis.s3}/v2', - data: formData); + data: {'image': formData.files}); if (response.statusCode == 201) { //base response로 받는건 여기서 뿐임. diff --git a/lib/features/mypage/image_upload_view_model.dart b/lib/features/mypage/image_upload_view_model.dart index 9fd9059..50adbaa 100644 --- a/lib/features/mypage/image_upload_view_model.dart +++ b/lib/features/mypage/image_upload_view_model.dart @@ -1,79 +1,124 @@ import 'dart:developer'; import 'dart:io'; -import 'package:cogo/data/service/s3_service.dart'; -import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; +import 'package:cogo/constants/apis.dart'; +import 'package:cogo/data/repository/local/secure_storage_repository.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_config/flutter_config.dart'; +import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; class ImageUploadViewModel extends ChangeNotifier { - final S3Service s3service = GetIt.instance(); - - final ImagePicker _picker = ImagePicker(); + final SecureStorageRepository _secureStorage = SecureStorageRepository(); File? _selectedImage; bool _isUploading = false; + String? _uploadResult; String? _errorMessage; + // Getters File? get selectedImage => _selectedImage; bool get isUploading => _isUploading; + String? get uploadResult => _uploadResult; + String? get errorMessage => _errorMessage; - Future pickImage() async { + // 갤러리에서 이미지 선택 + Future pickImageFromGallery() async { + try { + final picker = ImagePicker(); + final pickedFile = await picker.pickImage(source: ImageSource.gallery); + + if (pickedFile != null) { + _selectedImage = File(pickedFile.path); + _errorMessage = null; + notifyListeners(); + } + } catch (e) { + _errorMessage = '이미지 선택 중 오류 발생: ${e.toString()}'; + notifyListeners(); + } + } + + // 카메라로 이미지 촬영 + Future takeImageFromCamera() async { try { - final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + final picker = ImagePicker(); + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { _selectedImage = File(pickedFile.path); + _errorMessage = null; notifyListeners(); } } catch (e) { - _errorMessage = "Error picking image: $e"; + _errorMessage = '카메라 촬영 중 오류 발생: ${e.toString()}'; notifyListeners(); } } - Future uploadToS3() async { + // 이미지 업로드 + Future uploadImage() async { + // 이미지가 선택되지 않은 경우 if (_selectedImage == null) { - _errorMessage = "Please select an image first."; + _errorMessage = '먼저 이미지를 선택해주세요.'; notifyListeners(); return; } try { - String fileName = _selectedImage!.path.split('/').last; - log("fileName: $fileName"); - - try { - FormData formData = FormData.fromMap({ - "file": await MultipartFile.fromFile( - _selectedImage!.path, - filename: fileName, - ), - }); - - log("FormData 생성됨"); - log("파일 경로: ${_selectedImage!.path}"); - log("파일 존재 여부: ${File(_selectedImage!.path).existsSync()}"); - - // FormData 내부 데이터 출력 - formData.fields.forEach((field) { - log("Field: ${field.key}, Value: ${field.value}"); - }); - - s3service.saveImage(formData); - _isUploading = true; - notifyListeners(); - } catch (e) { - log("FormData 생성 중 오류 발생: $e"); + //todo dio+service로 바꾸기 + // 업로드 시작 + _isUploading = true; + _errorMessage = null; + notifyListeners(); + + // API 엔드포인트 URL + const apiVersion = "api/v2/"; + final url = + Uri.parse('${FlutterConfig.get("base_url")}$apiVersion${Apis.s3}/v2'); + + // Multipart 요청 생성 + final req = http.MultipartRequest('POST', url); + var token = await _secureStorage.readAccessToken(); + + // 헤더 설정 + req.headers['accept'] = '*/*'; + req.headers['Authorization'] = 'Bearer $token'; + req.headers['Content-Type'] = 'multipart/form-data'; + + // 이미지 파일 추가 + req.files.add( + await http.MultipartFile.fromPath('image', _selectedImage!.path)); + + // 요청 전송 + final stream = await req.send(); + final res = await http.Response.fromStream(stream); + + // 상태 코드 확인 + if (res.statusCode != 201) { + throw Exception('업로드 실패: 상태 코드 = ${res.statusCode}'); } - } catch (e) { - _errorMessage = "Error uploading image: $e"; + + // 업로드 성공 + _uploadResult = res.body; + log(_uploadResult.toString()); + _isUploading = false; notifyListeners(); - } finally { + } catch (e) { + // 에러 처리 + _errorMessage = '업로드 중 오류 발생: ${e.toString()}'; _isUploading = false; notifyListeners(); } } + + // 선택된 이미지 초기화 + void clearSelectedImage() { + _selectedImage = null; + _uploadResult = null; + _errorMessage = null; + notifyListeners(); + } } diff --git a/lib/features/mypage/image_uplooad_screen.dart b/lib/features/mypage/image_uplooad_screen.dart index d5c6634..9797859 100644 --- a/lib/features/mypage/image_uplooad_screen.dart +++ b/lib/features/mypage/image_uplooad_screen.dart @@ -25,13 +25,13 @@ class ImageUploadScreen extends StatelessWidget { ), SizedBox(height: 20), ElevatedButton( - onPressed: viewModel.pickImage, + onPressed: viewModel.pickImageFromGallery, child: Text("Pick Image from Gallery"), ), SizedBox(height: 20), ElevatedButton( onPressed: - viewModel.isUploading ? null : viewModel.uploadToS3, + viewModel.isUploading ? null : viewModel.uploadImage, child: viewModel.isUploading ? CircularProgressIndicator(color: Colors.white) : Text("Upload to S3"), From c5a86300ba8cf2ac25e296f890775b8574f48113 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Wed, 18 Dec 2024 14:33:47 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20dio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/image_upload_view_model.dart | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/features/mypage/image_upload_view_model.dart b/lib/features/mypage/image_upload_view_model.dart index 50adbaa..b57ce53 100644 --- a/lib/features/mypage/image_upload_view_model.dart +++ b/lib/features/mypage/image_upload_view_model.dart @@ -3,13 +3,15 @@ import 'dart:io'; import 'package:cogo/constants/apis.dart'; import 'package:cogo/data/repository/local/secure_storage_repository.dart'; +import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_config/flutter_config.dart'; -import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; class ImageUploadViewModel extends ChangeNotifier { final SecureStorageRepository _secureStorage = SecureStorageRepository(); + final Dio _dio = Dio(); + File? _selectedImage; bool _isUploading = false; String? _uploadResult; @@ -17,11 +19,8 @@ class ImageUploadViewModel extends ChangeNotifier { // Getters File? get selectedImage => _selectedImage; - bool get isUploading => _isUploading; - String? get uploadResult => _uploadResult; - String? get errorMessage => _errorMessage; // 갤러리에서 이미지 선택 @@ -68,7 +67,6 @@ class ImageUploadViewModel extends ChangeNotifier { } try { - //todo dio+service로 바꾸기 // 업로드 시작 _isUploading = true; _errorMessage = null; @@ -76,38 +74,50 @@ class ImageUploadViewModel extends ChangeNotifier { // API 엔드포인트 URL const apiVersion = "api/v2/"; - final url = - Uri.parse('${FlutterConfig.get("base_url")}$apiVersion${Apis.s3}/v2'); + final url = '${FlutterConfig.get("base_url")}$apiVersion${Apis.s3}/v2'; - // Multipart 요청 생성 - final req = http.MultipartRequest('POST', url); + // 토큰 가져오기 var token = await _secureStorage.readAccessToken(); - // 헤더 설정 - req.headers['accept'] = '*/*'; - req.headers['Authorization'] = 'Bearer $token'; - req.headers['Content-Type'] = 'multipart/form-data'; - - // 이미지 파일 추가 - req.files.add( - await http.MultipartFile.fromPath('image', _selectedImage!.path)); + // FormData 생성 + final formData = FormData.fromMap({ + 'image': await MultipartFile.fromFile(_selectedImage!.path, + filename: _selectedImage!.path.split('/').last) + }); + + // Dio 요청 옵션 설정 + final options = Options( + headers: { + 'accept': '*/*', + 'Authorization': 'Bearer $token', + 'Content-Type': 'multipart/form-data', + }, + ); // 요청 전송 - final stream = await req.send(); - final res = await http.Response.fromStream(stream); + final response = await _dio.post( + url, + data: formData, + options: options, + ); // 상태 코드 확인 - if (res.statusCode != 201) { - throw Exception('업로드 실패: 상태 코드 = ${res.statusCode}'); + if (response.statusCode != 201) { + throw Exception('업로드 실패: 상태 코드 = ${response.statusCode}'); } // 업로드 성공 - _uploadResult = res.body; + _uploadResult = response.data.toString(); log(_uploadResult.toString()); _isUploading = false; notifyListeners(); + } on DioException catch (e) { + // Dio 특화된 에러 처리 + _errorMessage = '업로드 중 오류 발생: ${e.response?.data ?? e.message}'; + _isUploading = false; + notifyListeners(); } catch (e) { - // 에러 처리 + // 기타 예외 처리 _errorMessage = '업로드 중 오류 발생: ${e.toString()}'; _isUploading = false; notifyListeners(); @@ -121,4 +131,4 @@ class ImageUploadViewModel extends ChangeNotifier { _errorMessage = null; notifyListeners(); } -} +} \ No newline at end of file From 8c2aa614cda47adaee6be6a0a31357581a53dff6 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Wed, 18 Dec 2024 14:52:06 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/service/s3_service.dart | 82 ++++++++++++------- .../mypage/image_upload_view_model.dart | 60 +++----------- 2 files changed, 65 insertions(+), 77 deletions(-) diff --git a/lib/data/service/s3_service.dart b/lib/data/service/s3_service.dart index 186f28a..446b728 100644 --- a/lib/data/service/s3_service.dart +++ b/lib/data/service/s3_service.dart @@ -1,37 +1,62 @@ import 'dart:developer'; -import 'dart:io'; -import 'package:cogo/constants/apis.dart'; -import 'package:cogo/data/di/api_client.dart'; import 'package:cogo/data/dto/response/base_response.dart'; import 'package:cogo/data/dto/response/image_save_response.dart'; +import 'package:cogo/data/repository/local/secure_storage_repository.dart'; import 'package:dio/dio.dart'; +import 'package:flutter_config/flutter_config.dart'; + +import '../../../constants/apis.dart'; class S3Service { - final ApiClient _apiClient = ApiClient(); - static const apiVersion = "api/v2/"; + final Dio _dio = Dio(); + final SecureStorageRepository _secureStorage = SecureStorageRepository(); + + S3Service(); - // s3 이미지 저장 - Future saveImage(File selectedImage) async { + Future uploadImage(String imagePath) async { try { - String fileName = selectedImage.path.split('/').last; - log("fileName: $fileName"); - - FormData formData = FormData.fromMap({ - "file": await MultipartFile.fromFile( - selectedImage.path, - filename: fileName, - ), + // API 엔드포인트 URL + const apiVersion = "api/v2/"; + final url = '${FlutterConfig.get("base_url")}$apiVersion${Apis.s3}/v2'; + + // 토큰 가져오기 + var token = await _secureStorage.readAccessToken(); + + // FormData 생성 + final formData = FormData.fromMap({ + 'image': await MultipartFile.fromFile(imagePath, + filename: imagePath.split('/').last) }); - log("formData: ${formData.files.length}"); + // Dio 요청 옵션 설정 + final options = Options( + headers: { + 'accept': '*/*', + 'Authorization': 'Bearer $token', + 'Content-Type': 'multipart/form-data', + }, + ); + + // 요청 전송 + final response = await _dio.post( + url, + data: formData, + options: options, + ); - final response = await _apiClient.dio.post( - options: Options( - extra: {'skipAuthToken': false, 'multipart': true}, //토큰 해제하지 않음 - ), - '${Apis.s3}/v2', - data: {'image': formData.files}); + // Log Interceptor 추가 + _dio.interceptors.add(LogInterceptor( + request: true, + requestHeader: true, + requestBody: true, + responseHeader: true, + responseBody: true, + error: true, + logPrint: (obj) { + log("서버통신 $obj"); + }, + )); if (response.statusCode == 201) { //base response로 받는건 여기서 뿐임. @@ -39,15 +64,16 @@ class S3Service { response.data, (contentJson) => ImageSaveResponse.fromJson(contentJson), ); - return baseResponse.content; + return baseResponse.content.savedUrl; } else { - throw Exception('Failed to send verification code'); + throw Exception('서버 통신 실패 ${response.statusCode}'); } } on DioException catch (e) { - throw Exception('Error: ${e.response?.data ?? e.message}'); + // Dio 특화된 에러 처리 + throw Exception('업로드 중 오류 발생: ${e.response?.data ?? e.message}'); } catch (e) { - // 기타 모든 예외 처리 - throw Exception('An unexpected error occurred: $e'); + // 기타 예외 처리 + throw Exception('업로드 중 오류 발생: ${e.toString()}'); } } -} +} \ No newline at end of file diff --git a/lib/features/mypage/image_upload_view_model.dart b/lib/features/mypage/image_upload_view_model.dart index b57ce53..efd00c4 100644 --- a/lib/features/mypage/image_upload_view_model.dart +++ b/lib/features/mypage/image_upload_view_model.dart @@ -1,22 +1,21 @@ -import 'dart:developer'; import 'dart:io'; -import 'package:cogo/constants/apis.dart'; -import 'package:cogo/data/repository/local/secure_storage_repository.dart'; -import 'package:dio/dio.dart'; +import 'package:cogo/data/service/s3_service.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_config/flutter_config.dart'; +import 'package:get_it/get_it.dart'; import 'package:image_picker/image_picker.dart'; class ImageUploadViewModel extends ChangeNotifier { - final SecureStorageRepository _secureStorage = SecureStorageRepository(); - final Dio _dio = Dio(); + final S3Service s3service = GetIt.instance(); File? _selectedImage; bool _isUploading = false; String? _uploadResult; String? _errorMessage; + // 생성자 + ImageUploadViewModel(); + // Getters File? get selectedImage => _selectedImage; bool get isUploading => _isUploading; @@ -72,53 +71,16 @@ class ImageUploadViewModel extends ChangeNotifier { _errorMessage = null; notifyListeners(); - // API 엔드포인트 URL - const apiVersion = "api/v2/"; - final url = '${FlutterConfig.get("base_url")}$apiVersion${Apis.s3}/v2'; - - // 토큰 가져오기 - var token = await _secureStorage.readAccessToken(); - - // FormData 생성 - final formData = FormData.fromMap({ - 'image': await MultipartFile.fromFile(_selectedImage!.path, - filename: _selectedImage!.path.split('/').last) - }); - - // Dio 요청 옵션 설정 - final options = Options( - headers: { - 'accept': '*/*', - 'Authorization': 'Bearer $token', - 'Content-Type': 'multipart/form-data', - }, - ); - - // 요청 전송 - final response = await _dio.post( - url, - data: formData, - options: options, - ); - - // 상태 코드 확인 - if (response.statusCode != 201) { - throw Exception('업로드 실패: 상태 코드 = ${response.statusCode}'); - } + // 서비스를 통해 이미지 업로드 + _uploadResult = await s3service.uploadImage(_selectedImage!.path); + //todo 이미지 업로드 api // 업로드 성공 - _uploadResult = response.data.toString(); - log(_uploadResult.toString()); - _isUploading = false; - notifyListeners(); - } on DioException catch (e) { - // Dio 특화된 에러 처리 - _errorMessage = '업로드 중 오류 발생: ${e.response?.data ?? e.message}'; _isUploading = false; notifyListeners(); } catch (e) { - // 기타 예외 처리 - _errorMessage = '업로드 중 오류 발생: ${e.toString()}'; + // 에러 처리 + _errorMessage = e.toString(); _isUploading = false; notifyListeners(); } From a4761d380e945b38cb3a7eb271e263e5faf9cc93 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Wed, 18 Dec 2024 15:16:49 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20imageUrl=EC=9D=84=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=B4=EC=84=9C=20=EC=84=9C=EB=B2=84=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/di/api_client.dart | 4 -- lib/data/service/s3_service.dart | 70 +++++-------------- lib/data/service/user_service.dart | 30 ++++++++ .../mypage/image_upload_view_model.dart | 7 ++ 4 files changed, 56 insertions(+), 55 deletions(-) diff --git a/lib/data/di/api_client.dart b/lib/data/di/api_client.dart index c054ced..a84a444 100644 --- a/lib/data/di/api_client.dart +++ b/lib/data/di/api_client.dart @@ -35,10 +35,6 @@ class ApiClient { options.headers['Authorization'] = 'Bearer $token'; } } - // if (options.extra['multipart']==true){ - // options.headers.remove('Content-Type'); - // options.headers['Content-Type'] = ' multipart/form-data'; - // } log('요청 보내는 중: ${options.method} ${options.path}'); return handler.next(options); }, diff --git a/lib/data/service/s3_service.dart b/lib/data/service/s3_service.dart index 446b728..29555e7 100644 --- a/lib/data/service/s3_service.dart +++ b/lib/data/service/s3_service.dart @@ -1,27 +1,15 @@ -import 'dart:developer'; - -import 'package:cogo/data/dto/response/base_response.dart'; -import 'package:cogo/data/dto/response/image_save_response.dart'; -import 'package:cogo/data/repository/local/secure_storage_repository.dart'; +import 'package:cogo/constants/apis.dart'; +import 'package:cogo/data/di/api_client.dart'; import 'package:dio/dio.dart'; -import 'package:flutter_config/flutter_config.dart'; -import '../../../constants/apis.dart'; class S3Service { - final Dio _dio = Dio(); - final SecureStorageRepository _secureStorage = SecureStorageRepository(); - - S3Service(); + final ApiClient _apiClient = ApiClient(); + static const apiVersion = "api/v2/"; Future uploadImage(String imagePath) async { try { - // API 엔드포인트 URL - const apiVersion = "api/v2/"; - final url = '${FlutterConfig.get("base_url")}$apiVersion${Apis.s3}/v2'; - - // 토큰 가져오기 - var token = await _secureStorage.readAccessToken(); + const url = '$apiVersion${Apis.s3}/v2'; // FormData 생성 final formData = FormData.fromMap({ @@ -29,45 +17,25 @@ class S3Service { filename: imagePath.split('/').last) }); - // Dio 요청 옵션 설정 - final options = Options( - headers: { - 'accept': '*/*', - 'Authorization': 'Bearer $token', - 'Content-Type': 'multipart/form-data', - }, - ); - // 요청 전송 - final response = await _dio.post( + final response = await _apiClient.dio.post( url, data: formData, - options: options, + options: Options( + headers: { + 'accept': '*/*', + 'Content-Type': 'multipart/form-data', + }, + ), ); - // Log Interceptor 추가 - _dio.interceptors.add(LogInterceptor( - request: true, - requestHeader: true, - requestBody: true, - responseHeader: true, - responseBody: true, - error: true, - logPrint: (obj) { - log("서버통신 $obj"); - }, - )); - - if (response.statusCode == 201) { - //base response로 받는건 여기서 뿐임. - final baseResponse = BaseResponse.fromJson( - response.data, - (contentJson) => ImageSaveResponse.fromJson(contentJson), - ); - return baseResponse.content.savedUrl; - } else { - throw Exception('서버 통신 실패 ${response.statusCode}'); + // 상태 코드 확인 + if (response.statusCode != 201) { + throw Exception('업로드 실패: 상태 코드 = ${response.statusCode}'); } + + // 업로드 성공 + return response.data.toString(); } on DioException catch (e) { // Dio 특화된 에러 처리 throw Exception('업로드 중 오류 발생: ${e.response?.data ?? e.message}'); @@ -76,4 +44,4 @@ class S3Service { throw Exception('업로드 중 오류 발생: ${e.toString()}'); } } -} \ No newline at end of file +} diff --git a/lib/data/service/user_service.dart b/lib/data/service/user_service.dart index 68c06b2..df1912a 100644 --- a/lib/data/service/user_service.dart +++ b/lib/data/service/user_service.dart @@ -180,4 +180,34 @@ class UserService { throw Exception('An unexpected error occurred: $e'); } } + + ///PUT /api/v2/users/picture 이미지 저장하기 + Future saveImage(String imageUrl) async { + try { + final response = + await _apiClient.dio.put(apiVersion + Apis.saveImage, data: imageUrl); + + //todo 여기 response가 존재하나 필요가 없어서 안받음 + // { + // "statusCode": "201", + // "message": "CREATED", + // "content": { + // "username": "113343694546635833713", + // "name": "222", + // "email": "objet917@gmail.com", + // "role": "ROLE_MENTOR", + // "phoneNum": "123-1231-2312", + // "picture": "\"https://cogo-bucket.s3.ap-northeast-2.amazonaws.com/v2/113343694546635833713\"" + // } + // } + if (response.statusCode != 201) { + throw Exception('Failed to send verification code ${response.data}'); + } + return true; + } on DioException catch (e) { + throw Exception('Error: ${e.response?.data ?? e.message}'); + } catch (e) { + throw Exception('An unexpected error occurred: $e'); + } + } } diff --git a/lib/features/mypage/image_upload_view_model.dart b/lib/features/mypage/image_upload_view_model.dart index efd00c4..6e6ce7f 100644 --- a/lib/features/mypage/image_upload_view_model.dart +++ b/lib/features/mypage/image_upload_view_model.dart @@ -1,15 +1,18 @@ import 'dart:io'; import 'package:cogo/data/service/s3_service.dart'; +import 'package:cogo/data/service/user_service.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; import 'package:image_picker/image_picker.dart'; class ImageUploadViewModel extends ChangeNotifier { final S3Service s3service = GetIt.instance(); + final UserService userService = GetIt.instance(); File? _selectedImage; bool _isUploading = false; + bool _isUpload = false; String? _uploadResult; String? _errorMessage; @@ -19,6 +22,8 @@ class ImageUploadViewModel extends ChangeNotifier { // Getters File? get selectedImage => _selectedImage; bool get isUploading => _isUploading; + + bool get isUpload => _isUpload; String? get uploadResult => _uploadResult; String? get errorMessage => _errorMessage; @@ -75,7 +80,9 @@ class ImageUploadViewModel extends ChangeNotifier { _uploadResult = await s3service.uploadImage(_selectedImage!.path); //todo 이미지 업로드 api + // 업로드 성공 + _isUpload = await userService.saveImage(_uploadResult!); _isUploading = false; notifyListeners(); } catch (e) { From 9f5f2eb0fde61d44df73ddffd15863d2f3bdcf14 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:28:28 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20baseresponse=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B2=97=EA=B2=A8=EB=82=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/service/s3_service.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/data/service/s3_service.dart b/lib/data/service/s3_service.dart index 29555e7..adc6d6a 100644 --- a/lib/data/service/s3_service.dart +++ b/lib/data/service/s3_service.dart @@ -1,8 +1,9 @@ import 'package:cogo/constants/apis.dart'; import 'package:cogo/data/di/api_client.dart'; +import 'package:cogo/data/dto/response/base_response.dart'; +import 'package:cogo/data/dto/response/image_save_response.dart'; import 'package:dio/dio.dart'; - class S3Service { final ApiClient _apiClient = ApiClient(); static const apiVersion = "api/v2/"; @@ -34,8 +35,11 @@ class S3Service { throw Exception('업로드 실패: 상태 코드 = ${response.statusCode}'); } - // 업로드 성공 - return response.data.toString(); + final baseResponse = BaseResponse.fromJson( + response.data, + (contentJson) => ImageSaveResponse.fromJson(contentJson), + ); + return baseResponse.content.savedUrl; } on DioException catch (e) { // Dio 특화된 에러 처리 throw Exception('업로드 중 오류 발생: ${e.response?.data ?? e.message}'); From ecd7db05965bd4de3b961fbf3a86f3aa4ae1c3c9 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:29:03 +0900 Subject: [PATCH 07/12] =?UTF-8?q?chore:=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B7=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/image_upload_view_model.dart | 2 - lib/features/mypage/image_uplooad_screen.dart | 75 ++++++++++++------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/lib/features/mypage/image_upload_view_model.dart b/lib/features/mypage/image_upload_view_model.dart index 6e6ce7f..204f5d6 100644 --- a/lib/features/mypage/image_upload_view_model.dart +++ b/lib/features/mypage/image_upload_view_model.dart @@ -79,8 +79,6 @@ class ImageUploadViewModel extends ChangeNotifier { // 서비스를 통해 이미지 업로드 _uploadResult = await s3service.uploadImage(_selectedImage!.path); - //todo 이미지 업로드 api - // 업로드 성공 _isUpload = await userService.saveImage(_uploadResult!); _isUploading = false; diff --git a/lib/features/mypage/image_uplooad_screen.dart b/lib/features/mypage/image_uplooad_screen.dart index 9797859..c67895b 100644 --- a/lib/features/mypage/image_uplooad_screen.dart +++ b/lib/features/mypage/image_uplooad_screen.dart @@ -1,9 +1,13 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'image_upload_view_model.dart'; class ImageUploadScreen extends StatelessWidget { + const ImageUploadScreen({super.key}); + @override Widget build(BuildContext context) { return ChangeNotifierProvider( @@ -14,37 +18,50 @@ class ImageUploadScreen extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: Consumer( builder: (context, viewModel, child) { - return Column( - children: [ - if (viewModel.selectedImage != null) - Image.file( - viewModel.selectedImage!, - height: 200, - width: 200, - fit: BoxFit.cover, - ), - SizedBox(height: 20), - ElevatedButton( - onPressed: viewModel.pickImageFromGallery, - child: Text("Pick Image from Gallery"), - ), - SizedBox(height: 20), - ElevatedButton( - onPressed: - viewModel.isUploading ? null : viewModel.uploadImage, - child: viewModel.isUploading - ? CircularProgressIndicator(color: Colors.white) - : Text("Upload to S3"), - ), - if (viewModel.errorMessage != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - viewModel.errorMessage!, - style: TextStyle(color: Colors.red), + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (viewModel.selectedImage != null) + Image.file( + viewModel.selectedImage!, + height: 200, + width: 200, + fit: BoxFit.cover, ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: viewModel.pickImageFromGallery, + child: const Text("갤러리")), + ElevatedButton( + onPressed: viewModel.takeImageFromCamera, + child: const Text("카메라")), + ], ), - ], + const SizedBox(height: 20), + ElevatedButton( + onPressed: () async => { + log("사진을 바꿔요"), + viewModel.uploadImage(), + if (viewModel.isUpload) {Navigator.of(context).pop()} + }, + child: viewModel.isUploading + ? const CircularProgressIndicator(color: Colors.white) + : const Text("저장하기"), + ), + if (viewModel.errorMessage != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + viewModel.errorMessage!, + style: TextStyle(color: Colors.red), + ), + ), + ], + ), ); }, ), From 4689ba71c7bf00a40efd15d5b00c752f3b8256b1 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:29:13 +0900 Subject: [PATCH 08/12] =?UTF-8?q?chore:=20stack=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/mypage/mypage_screen.dart | 168 ++++++++++++++++--------- 1 file changed, 107 insertions(+), 61 deletions(-) diff --git a/lib/features/mypage/mypage_screen.dart b/lib/features/mypage/mypage_screen.dart index 34ce601..13ac120 100644 --- a/lib/features/mypage/mypage_screen.dart +++ b/lib/features/mypage/mypage_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:cogo/common/enums/role.dart'; import 'package:cogo/common/widgets/tag_list.dart'; import 'package:cogo/common/widgets/widgets.dart'; @@ -61,74 +63,118 @@ class MypageScreen extends StatelessWidget { final user = state.myPageInfo; - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 13), - Text( - '${user?.name ?? "사용자"}님', - style: CogoTextStyle.bodySB20, - textAlign: TextAlign.center, - ), - const SizedBox(height: 13), - if (user?.picture != null && user!.picture!.isNotEmpty) ...[ - Image.network( - user.picture!, - errorBuilder: (context, error, stackTrace) { - return SvgPicture.asset( - 'assets/image/img_image.svg'); // 기본 로컬 이미지 - }, - fit: BoxFit.cover, + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 13), + Text( + '${user?.name ?? "사용자"}님', + style: CogoTextStyle.bodySB20, + textAlign: TextAlign.center, ), - ] else ...[ - SvgPicture.asset('assets/image/img_image.svg'), - // 기본 로컬 이미지 - ], - BasicButton( - text: "프로필 사진 변경", - isClickable: true, - onPressed: () => {context.push(Paths.image)}, - ), - const SizedBox(height: 13), - Center( - child: TagList(tags: user!.tags), - ), - const SizedBox(height: 20), - ListTile( - title: const Text('내 정보 관리', style: CogoTextStyle.body16), - trailing: const Icon(Icons.chevron_right), - onTap: () => context.push(Paths.myInfo), - ), - if (user.role == Role.MENTOR.name) ...[ + const SizedBox(height: 13), + if (user?.picture != null && + user!.picture!.isNotEmpty) ...[ + GestureDetector( + onTap: () { + context.push(Paths.image); + }, + child: Stack( + children: [ + ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(20)), + child: Image.network( + user.picture?.isNotEmpty == true + ? user.picture! + : '', + width: double.infinity, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + log("===이미지 에러==="); + return Image.asset( + 'assets/default_img.png', // 로컬 기본 이미지 + width: double.infinity, + height: 150, + fit: BoxFit.cover, + ); + }, + ), + ), + const Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: Align( + alignment: Alignment.center, // 중앙에 배치 + child: Icon( + Icons.camera_alt, + size: 48, + color: Colors.grey, + ), + ), + ), + ], + ), + ), + ] else ...[ + GestureDetector( + onTap: () { + context.push(Paths.image); + }, + child: SvgPicture.asset( + 'assets/image/img_image.svg', // 기본 로컬 이미지 + ), + ), + ], + const SizedBox(height: 13), + Center( + child: TagList(tags: user!.tags), + ), + const SizedBox(height: 20), ListTile( title: - const Text('자기소개 관리', style: CogoTextStyle.body16), + const Text('내 정보 관리', style: CogoTextStyle.body16), trailing: const Icon(Icons.chevron_right), - onTap: () => context.push(Paths.myMentorIntroduce), + onTap: () => context.push(Paths.myInfo), ), + if (user.role == Role.MENTOR.name) ...[ + ListTile( + title: const Text('자기소개 관리', + style: CogoTextStyle.body16), + trailing: const Icon(Icons.chevron_right), + onTap: () => context.push(Paths.myMentorIntroduce), + ), + ListTile( + title: + const Text('시간 설정', style: CogoTextStyle.body16), + trailing: const Icon(Icons.chevron_right), + onTap: () => context.push(Paths.timeSetting), + ), + ], ListTile( - title: const Text('시간 설정', style: CogoTextStyle.body16), - trailing: const Icon(Icons.chevron_right), - onTap: () => context.push(Paths.timeSetting), - ), + title: + const Text('로그아웃', style: CogoTextStyle.body16), + trailing: const Icon(Icons.chevron_right), + onTap: () => { + viewModel.logOut(), + context.go(Paths.login), //라우팅 히스토리를 다 지움 + }), + ListTile( + title: + const Text('탈퇴하기', style: CogoTextStyle.body16), + trailing: const Icon(Icons.chevron_right), + onTap: () => { + viewModel.signOut(), + context.go(Paths.login), //라우팅 히스토리를 다 지움 + }), ], - ListTile( - title: const Text('로그아웃', style: CogoTextStyle.body16), - trailing: const Icon(Icons.chevron_right), - onTap: () => { - viewModel.logOut(), - context.go(Paths.login), //라우팅 히스토리를 다 지움 - }), - ListTile( - title: const Text('탈퇴하기', style: CogoTextStyle.body16), - trailing: const Icon(Icons.chevron_right), - onTap: () => { - viewModel.signOut(), - context.go(Paths.login), //라우팅 히스토리를 다 지움 - }), - ], + ), ), ); }, From 101b788cf379a1b2d504fb5b282ac6cf2d30e7a5 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:29:38 +0900 Subject: [PATCH 09/12] =?UTF-8?q?chore:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20er?= =?UTF-8?q?ror=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/profile_card.dart | 23 +++++++++----- .../home/profile/profile_detail_screen.dart | 30 ++++++++++++------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/common/widgets/profile_card.dart b/lib/common/widgets/profile_card.dart index a729b75..dc8c1d4 100644 --- a/lib/common/widgets/profile_card.dart +++ b/lib/common/widgets/profile_card.dart @@ -1,7 +1,7 @@ import 'package:cogo/common/widgets/atoms/texts/texts.dart'; import 'package:cogo/common/widgets/tag_list.dart'; -import 'package:flutter/material.dart'; import 'package:cogo/constants/constants.dart'; +import 'package:flutter/material.dart'; class ProfileCard extends StatelessWidget { final String picture; @@ -58,13 +58,20 @@ class ProfileCard extends StatelessWidget { topLeft: Radius.circular(20), topRight: Radius.circular(20), ), - child: Image.asset( - picture.isNotEmpty ? picture : 'assets/default_img.png', - width: double.infinity, - height: 150, - fit: BoxFit.cover, - ), - ), + child: Image.network( + picture.isNotEmpty ? picture : '', // 빈 문자열로 설정해 에러를 유도 + width: double.infinity, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + 'assets/default_img.png', // 로컬 기본 이미지 + width: double.infinity, + height: 150, + fit: BoxFit.cover, + ); + }, + )), Positioned( top: 15, left: 15, diff --git a/lib/features/home/profile/profile_detail_screen.dart b/lib/features/home/profile/profile_detail_screen.dart index 1300cf3..6718565 100644 --- a/lib/features/home/profile/profile_detail_screen.dart +++ b/lib/features/home/profile/profile_detail_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:cogo/common/widgets/atoms/texts/styles.dart'; import 'package:cogo/common/widgets/components/basic_button.dart'; import 'package:cogo/constants/colors.dart'; @@ -85,20 +87,28 @@ class ProfileDetailScreen extends StatelessWidget { } return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(20)), - child: Image.asset( - profile.imageUrl.isNotEmpty - ? profile.imageUrl - : 'assets/default_img.png', - width: double.infinity, - height: 150, - fit: BoxFit.cover, - ), - ), + child: Image.network( + profile.imageUrl.isNotEmpty + ? profile.imageUrl + : '', // 빈 문자열로 설정해 에러를 유도 + width: double.infinity, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + log("===이미지 에러==="); + return Image.asset( + 'assets/default_img.png', // 로컬 기본 이미지 + width: double.infinity, + height: 150, + fit: BoxFit.cover, + ); + }, + )), const SizedBox(height: 30), Center( child: Row( From 7366d40e0ee69bdebb3ac8c7c0472b2dc6e9a06f Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:29:46 +0900 Subject: [PATCH 10/12] =?UTF-8?q?setting:=20=EC=BD=94=ED=8B=80=EB=A6=B0=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/settings.gradle b/android/settings.gradle index 536165d..f75a38f 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "1.9.0" apply false } include ":app" From cdedb742641145e37ca7a46b9323cab3e8940da4 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:30:02 +0900 Subject: [PATCH 11/12] =?UTF-8?q?chore:=20=EB=9D=BC=EC=9A=B0=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/route/routes.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/route/routes.dart b/lib/route/routes.dart index 37a00c5..f6dc144 100644 --- a/lib/route/routes.dart +++ b/lib/route/routes.dart @@ -25,6 +25,7 @@ import 'package:cogo/features/home/profile/profile_detail_screen.dart'; import 'package:cogo/features/home/report/report_detail_screen.dart'; import 'package:cogo/features/home/report/report_screen.dart'; import 'package:cogo/features/home/search/search_screen.dart'; +import 'package:cogo/features/mypage/image_uplooad_screen.dart'; import 'package:cogo/features/mypage/mentor_introduce/my_mentor_introduce_screen.dart'; import 'package:cogo/features/mypage/mentor_time_checking/mentor_time_checking_screen.dart'; import 'package:cogo/features/mypage/mentor_time_setting/mentor_time_setting_screen.dart'; From ffec3917a83e25d5b4b2cab5522a81bd24400435 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 14 Jan 2025 18:49:23 +0900 Subject: [PATCH 12/12] =?UTF-8?q?chore:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=AC=B4=EC=A1=B0=EA=B1=B4=20=EC=B5=9C=EC=83=81=EB=8B=A8?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/mypage/mypage_screen.dart | 30 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/features/mypage/mypage_screen.dart b/lib/features/mypage/mypage_screen.dart index 13ac120..33b41ec 100644 --- a/lib/features/mypage/mypage_screen.dart +++ b/lib/features/mypage/mypage_screen.dart @@ -85,16 +85,26 @@ class MypageScreen extends StatelessWidget { child: Stack( children: [ ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(20)), - child: Image.network( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + child: Image.network( user.picture?.isNotEmpty == true ? user.picture! : '', width: double.infinity, height: 150, fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { + loadingBuilder: + (context, child, loadingProgress) { + // 이미지를 로딩 중일 때, 아이콘을 항상 위에 놓음 + if (loadingProgress == null) { + return child; + } else { + return const Center( + child: CircularProgressIndicator()); + } + }, + errorBuilder: (context, error, stackTrace) { log("===이미지 에러==="); return Image.asset( 'assets/default_img.png', // 로컬 기본 이미지 @@ -105,8 +115,8 @@ class MypageScreen extends StatelessWidget { }, ), ), - const Positioned( - top: 0, + Positioned( + top: 0, left: 0, right: 0, bottom: 0, @@ -115,13 +125,13 @@ class MypageScreen extends StatelessWidget { child: Icon( Icons.camera_alt, size: 48, - color: Colors.grey, - ), + color: Colors.grey + .withOpacity(0.6), // 투명도 조절 + ), ), ), ], - ), - ), + )), ] else ...[ GestureDetector( onTap: () {