From 25724a96050e53df45424aaec607ae969992d89f Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Thu, 17 Oct 2024 17:59:54 +0900 Subject: [PATCH 1/2] feat : add product detail image --- lib/screens/products/product_detail_page.dart | 358 ++++++++---------- pubspec.lock | 36 +- 2 files changed, 171 insertions(+), 223 deletions(-) diff --git a/lib/screens/products/product_detail_page.dart b/lib/screens/products/product_detail_page.dart index 95b1463..5fc1379 100644 --- a/lib/screens/products/product_detail_page.dart +++ b/lib/screens/products/product_detail_page.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/widgets.dart'; import 'package:saphy/screens/purchase/purchase_page.dart'; +import 'package:saphy/service/api_service.dart'; import 'package:saphy/service/authentication/secure_storage.dart'; import 'package:saphy/utils/colors.dart'; import 'package:saphy/utils/number_format.dart'; import 'package:saphy/utils/textstyles.dart'; import 'package:saphy/models/product.dart'; -import 'package:dio/dio.dart'; class ProductDetail extends StatefulWidget { final Product product; @@ -18,7 +19,7 @@ class ProductDetail extends StatefulWidget { class _ProductDetailState extends State { Product? productDetail; - bool isWished = false; // 아이템이 찜되었는지 상태를 관리 + bool isWished = false; @override void initState() { @@ -34,10 +35,17 @@ class _ProductDetailState extends State { } Future getProduct() async { - final dio = Dio(); + String token = await readJwt(); + token = token.toString().split(" ")[2]; + try { - final response = - await dio.get('https://saphy.site/api/items/${widget.product.id}'); + final response = await APIService.instance.request( + 'https://saphy.site/api/items/${widget.product.id}', + DioMethod.get, + contentType: 'application/json', + token: "Bearer $token", + ); + if (response.statusCode == 200) { final data = response.data as Map; List results = data['results']; @@ -51,7 +59,6 @@ class _ProductDetailState extends State { throw Exception('Failed to load products'); } } catch (e) { - print('Error: ${e.toString()}'); // 오류 메시지 확인 return Product( id: 0, deviceType: "deviceType", @@ -70,41 +77,35 @@ class _ProductDetailState extends State { } Future toggleWish() async { - final dio = Dio(); try { String token = await readJwt(); token = token.toString().split(" ")[2]; if (!isWished) { // 아이템을 찜하는 POST 요청 - final response = await dio.post( + + final response = await APIService.instance.request( 'https://saphy.site/item-wishes?itemId=${widget.product.id}', - options: Options( - headers: { - 'Authorization': token, - }, - ), + DioMethod.post, + contentType: 'application/json', + token: "Bearer $token", ); - if (response.statusCode == 201) { + if (response.statusCode == 200) { // 요청이 성공적으로 처리된 경우 setState(() { isWished = true; // 아이템이 찜 상태로 변경 }); } } else { - // 아이템 찜 해제하는 POST 요청 - final response = await dio.delete( - 'https://saphy.site/item-wishes/${widget.product.id}', // 삭제 요청을 보낼 URL - options: Options( - headers: { - 'Authorization': 'Bearer $token', - }, - ), + final response = await APIService.instance.request( + 'https://saphy.site/item-wishes/${widget.product.id}', + DioMethod.delete, + contentType: 'application/json', + token: "Bearer $token", ); if (response.statusCode == 200) { - // 요청이 성공적으로 처리된 경우 setState(() { isWished = false; // 아이템이 찜 해제 상태로 변경 }); @@ -145,22 +146,7 @@ class _ProductDetailState extends State { SingleChildScrollView( child: Column( children: [ - SizedBox( - height: 400, - child: Center( - child: Container( - height: 400, - width: double.infinity, - decoration: BoxDecoration( - image: DecorationImage( - image: CachedNetworkImageProvider( - widget.product.images[0].url), - fit: BoxFit.cover, - ), - ), - ), - ), - ), + productImage(widget.product.images[0].url), Padding( padding: const EdgeInsets.all(20.0), child: Column( @@ -246,180 +232,71 @@ class _ProductDetailState extends State { ], ), const SizedBox(height: 10), - Container( - // 등급 안내 상자 - width: double.infinity, - height: 100, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: const Color.fromRGBO(222, 222, 222, 1), - gradient: const LinearGradient( - begin: Alignment(-0.92, -0.39), - end: Alignment(0.92, 0.39), - colors: [ - Color(0xffa1a1a1), - Color(0xffdedede), - Color(0xffdedede), - Color(0xffdedede), - Color(0xffdedede), - white, - white, - Color(0xffffffff), - Color(0xffdedede), - Color(0xffdedede), - Color(0xffdedede), - Color(0xffa1a1a1), - ], - stops: [ - 0, - 0.16, - 0.21, - 0.24, - 0.27, - 0.36, - 0.45, - 0.6, - 0.72, - 0.8, - 0.84, - 1 - ], - ), - ), - child: Padding( - padding: const EdgeInsets.all(15.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '해당 상품은 등급 ${productDetail?.grade} 제품입니다.', - style: bodyText(), - ), - Text( - "Grade ${productDetail?.grade}", - style: titleText30(), - ) - ], - ), - Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Flexible( - flex: 1, - child: InkWell( - child: Container( - width: screenWidth * 0.32, - decoration: BoxDecoration( - color: const Color(0xff404756), - borderRadius: - BorderRadius.circular(10)), - child: const Center( - child: Text( - "실제 사진 보기", - style: TextStyle( - fontFamily: "Pretendard", - color: white), - ), - ), - ), - ), - ), - const SizedBox( - height: 5, - ), - Flexible( - flex: 1, - child: InkWell( - child: Container( - width: screenWidth * 0.32, - decoration: BoxDecoration( - color: const Color(0xff404756), - borderRadius: - BorderRadius.circular(10)), - child: Center( - child: Text( - "${productDetail?.grade}등급 리뷰 보기", - style: const TextStyle( - fontFamily: "Pretendard", - color: white), - ), - ), - ), - ), - ), - ], - ), - ], - ), - ), - ), + gradeInformation(screenWidth), const SizedBox(height: 20), - spaceDivider("다른 색상 메뉴"), - const SizedBox(height: 20), - spaceDivider("쿠폰 발급 메뉴"), - const SizedBox(height: 20), - Row( - // 버튼 - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Flexible(flex: 1, child: button("구매하기")), - const SizedBox(width: 10), - Flexible(flex: 1, child: button("판매하기")), - ], - ), - const SizedBox(height: 20), - spaceDivider("상품 정보 사진 칸"), - const SizedBox(height: 20), - spaceDivider("댓글 칸"), - const SizedBox(height: 160), + spaceDivider(""), + const SizedBox(height: 80), ], ), ), ], ), ), - Positioned( - bottom: 0, - child: Container( - width: screenWidth, - height: 100, - color: altWhite, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconButton( - onPressed: toggleWish, - icon: Icon( - isWished - ? Icons.favorite - : Icons.favorite_outline, // 찜 상태에 따라 아이콘 변경 - color: isWished - ? Colors.red - : Colors.black, // 찜 상태에 따라 색상 변경 - ), - ), - const SizedBox(width: 10), - Flexible(flex: 1, child: button("구매하기")), - const SizedBox(width: 10), - Flexible(flex: 1, child: button("판매하기")), - ], + buttonBar(screenWidth) + ], + ), + ); + } + + SizedBox productImage(String url) { + return SizedBox( + height: 400, + child: Center( + child: Container( + height: 400, + width: double.infinity, + decoration: BoxDecoration( + image: DecorationImage( + image: CachedNetworkImageProvider(widget.product.images[0].url), + fit: BoxFit.cover, + ), + ), + ), + ), + ); + } + + Positioned buttonBar(double screenWidth) { + return Positioned( + bottom: 0, + child: Container( + width: screenWidth, + height: 100, + color: altWhite, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: toggleWish, + icon: Icon( + isWished + ? Icons.favorite + : Icons.favorite_outline, // 찜 상태에 따라 아이콘 변경 + color: isWished + ? const Color(0xff9abcff) + : Colors.black, // 찜 상태에 따라 색상 변경 ), ), - ), - ) - ], + const SizedBox(width: 10), + Flexible(flex: 1, child: button("구매하기")), + const SizedBox(width: 10), + Flexible(flex: 1, child: button("판매하기")), + ], + ), + ), ), ); } @@ -456,11 +333,82 @@ class _ProductDetailState extends State { ); } - Container spaceDivider(String label) { + Container gradeInformation(double screenWidth) { return Container( + // 등급 안내 상자 width: double.infinity, height: 100, decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: const Color.fromRGBO(222, 222, 222, 1), + ), + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '해당 상품은 등급 ${productDetail?.grade} 제품입니다.', + style: bodyText(), + ), + Text( + "Grade ${productDetail?.grade}", + style: titleText30(), + ) + ], + ), + Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + gradeButton("실제 사진 보기", screenWidth), + const SizedBox( + height: 5, + ), + gradeButton("${productDetail?.grade}등급 리뷰 보기", screenWidth), + ], + ), + ], + ), + ), + ); + } + + Flexible gradeButton(String label, double screenWidth) { + return Flexible( + flex: 1, + child: InkWell( + child: Container( + width: screenWidth * 0.32, + decoration: BoxDecoration( + color: const Color(0xff404756), + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + label, + style: const TextStyle(fontFamily: "Pretendard", color: white), + ), + ), + ), + ), + ); + } + + Container spaceDivider(String label) { + return Container( + width: double.infinity, + height: 2000, + decoration: BoxDecoration( + image: DecorationImage( + image: CachedNetworkImageProvider( + productDetail?.descriptionImage.url ?? ""), + fit: BoxFit.cover), color: white, borderRadius: BorderRadius.circular(20), border: Border.all(color: gray400, width: 0.5), diff --git a/pubspec.lock b/pubspec.lock index f215cea..a0f31bb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -426,10 +426,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.22" flutter_secure_storage: dependency: "direct main" description: @@ -628,10 +628,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: d34e0d9e024e81321b2aeed7b202ec6181cc282e6a1c0c0b4e6ad07ef1065d82 + sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" url: "https://pub.dev" source: hosted - version: "0.8.12+16" + version: "0.8.12+12" image_picker_for_web: dependency: transitive description: @@ -756,18 +756,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -812,18 +812,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" mime: dependency: "direct main" description: @@ -1169,10 +1169,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" toggle_list: dependency: "direct main" description: @@ -1305,10 +1305,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.1" watcher: dependency: transitive description: @@ -1390,5 +1390,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" From fe7ec52746e0c07a4805856f5bde0d9889e2dd15 Mon Sep 17 00:00:00 2001 From: cho4u4o Date: Sun, 20 Oct 2024 00:18:35 +0900 Subject: [PATCH 2/2] feat : complete favorite product function --- lib/screens/products/liked_list_page.dart | 20 +++++------ lib/screens/products/product_detail_page.dart | 20 ++++++++--- pubspec.lock | 34 +++++++++---------- pubspec.yaml | 1 + 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/lib/screens/products/liked_list_page.dart b/lib/screens/products/liked_list_page.dart index d2b01ee..c65a5c3 100644 --- a/lib/screens/products/liked_list_page.dart +++ b/lib/screens/products/liked_list_page.dart @@ -3,12 +3,11 @@ import 'package:flutter/widgets.dart'; // import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:intl/intl.dart'; import 'package:saphy/models/product.dart'; +import 'package:saphy/service/api_service.dart'; import 'package:saphy/service/authentication/secure_storage.dart'; -import 'package:saphy/utils/textstyles.dart'; import 'package:saphy/widgets/product_card.dart'; import 'package:saphy/utils/colors.dart'; import 'package:saphy/widgets/app_bar.dart'; -import 'package:dio/dio.dart'; class LikedListPage extends StatefulWidget { const LikedListPage({super.key}); @@ -23,18 +22,17 @@ class _LikedListPageState extends State { int cnt = 0; Future> getProducts() async { - final dio = Dio(); - String? accessToken = await readAccessToken(); + String token = await readJwt(); + token = token.toString().split(" ")[2]; try { - final response = await dio.get( - 'https://saphy.site/item-wishes/', - options: Options( - headers: { - 'Authorization': 'Bearer $accessToken', // 필요한 헤더 추가 - }, - ), + final response = await APIService.instance.request( + 'https://saphy.site/item-wishes?type=ALL', + DioMethod.get, + contentType: 'application/json', + token: "Bearer $token", ); + if (response.statusCode == 200) { final data = response.data as Map; if (data['results'] != null) { diff --git a/lib/screens/products/product_detail_page.dart b/lib/screens/products/product_detail_page.dart index 5fc1379..cb9df63 100644 --- a/lib/screens/products/product_detail_page.dart +++ b/lib/screens/products/product_detail_page.dart @@ -8,6 +8,7 @@ import 'package:saphy/utils/colors.dart'; import 'package:saphy/utils/number_format.dart'; import 'package:saphy/utils/textstyles.dart'; import 'package:saphy/models/product.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class ProductDetail extends StatefulWidget { final Product product; @@ -20,11 +21,23 @@ class ProductDetail extends StatefulWidget { class _ProductDetailState extends State { Product? productDetail; bool isWished = false; + late SharedPreferences wished; + + Future initWishes() async { + wished = await SharedPreferences.getInstance(); + final wishedList = wished.getBool(widget.product.id.toString()); + if (wishedList != null) { + setState(() { + isWished = true; + }); + } else {} + } @override void initState() { super.initState(); loadProduct(); + initWishes(); } Future loadProduct() async { @@ -82,8 +95,6 @@ class _ProductDetailState extends State { token = token.toString().split(" ")[2]; if (!isWished) { - // 아이템을 찜하는 POST 요청 - final response = await APIService.instance.request( 'https://saphy.site/item-wishes?itemId=${widget.product.id}', DioMethod.post, @@ -92,9 +103,9 @@ class _ProductDetailState extends State { ); if (response.statusCode == 200) { - // 요청이 성공적으로 처리된 경우 + await wished.setBool(widget.product.id.toString(), true); setState(() { - isWished = true; // 아이템이 찜 상태로 변경 + isWished = true; }); } } else { @@ -106,6 +117,7 @@ class _ProductDetailState extends State { ); if (response.statusCode == 200) { + await wished.setBool(widget.product.id.toString(), false); setState(() { isWished = false; // 아이템이 찜 해제 상태로 변경 }); diff --git a/pubspec.lock b/pubspec.lock index a0f31bb..2cf0a2b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -716,42 +716,42 @@ packages: dependency: transitive description: name: kakao_flutter_sdk_auth - sha256: "43b81bb90d0e01516d321ab3cedc32303f661f115c564489c0fcc4e4a065c777" + sha256: "686996c63c7e006ccf9a0bdfee5307465d7ae134d2f1ae2b4a06c597604689bc" url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.9.6" kakao_flutter_sdk_common: dependency: transitive description: name: kakao_flutter_sdk_common - sha256: "0e3ffd97fa35386d6fabe854cb4c5799b303c75b25f15ce4ec10163d87a5caa5" + sha256: "698d5f63d15438ad47af3baf0815fd30dfcacfa76640e96e5a18fc8504f25bde" url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.9.6" kakao_flutter_sdk_talk: dependency: "direct main" description: name: kakao_flutter_sdk_talk - sha256: e4aea82d5ac167c57dc2f6ea2e75ae5712d1ae29dadde1343df2c007591b6b53 + sha256: d13ef12ba6ec6e1a7d510834eebfbf15c0c902807873d404d7b1f05f48f9c253 url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.9.6" kakao_flutter_sdk_template: dependency: transitive description: name: kakao_flutter_sdk_template - sha256: "7d38a7fe8984649f83e81831868aba0b85d058cd7c0568814f8e4dcb8dba4000" + sha256: "29738faf73a35ab0f8c2bd2ba7b15e46b22fafbf9cf9c7147d3e3d0747211dda" url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.9.6" kakao_flutter_sdk_user: dependency: "direct main" description: name: kakao_flutter_sdk_user - sha256: "9d3bd35d5b31cf01d45c7d114ca0c589d5098d8e6efce6f13d574ea692ba6a62" + sha256: f3692feefad530bcfcdd1b8cf685b0c2c3da4a6d28a7df7f57529e57b7040f1b url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.9.6" leak_tracker: dependency: transitive description: @@ -1009,13 +1009,13 @@ packages: source: hosted version: "3.0.1" shared_preferences: - dependency: transitive + dependency: "direct main" description: name: shared_preferences - sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.2" shared_preferences_android: dependency: transitive description: @@ -1028,10 +1028,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: @@ -1052,10 +1052,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e12c17d..e77a5e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: path: ^1.9.0 mime: ^1.0.6 http_parser: ^4.0.2 + shared_preferences: ^2.3.2 dev_dependencies: flutter_lints: ^4.0.0