diff --git a/assets/images/Question-3d.png b/assets/images/Question-3d.png new file mode 100644 index 0000000..68ba5a3 Binary files /dev/null and b/assets/images/Question-3d.png differ diff --git a/lib/screens/main/main_screen.dart b/lib/screens/main/main_screen.dart index b0b7a63..dfedc1b 100644 --- a/lib/screens/main/main_screen.dart +++ b/lib/screens/main/main_screen.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; import 'package:saphy/models/product.dart'; @@ -36,13 +37,13 @@ class _MainScreenState extends State { .toList(); return products; } else { - throw Exception('No results found in the response'); + return []; } } else { - throw Exception('Failed to load products'); + // 로드 실패 + return []; } } catch (e) { - print('Error: ${e.toString()}'); return []; } } @@ -173,7 +174,7 @@ class _MainScreenState extends State { onTap: () { Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( builder: (context) => ItemListPage( name: category, url: url, diff --git a/lib/screens/products/item_list_page.dart b/lib/screens/products/item_list_page.dart index a9495d0..994ccc7 100644 --- a/lib/screens/products/item_list_page.dart +++ b/lib/screens/products/item_list_page.dart @@ -60,7 +60,10 @@ class _ItemListPageState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xfff4f4f4), - appBar: TopAppBar(label: widget.name), + appBar: TopAppBar( + label: widget.name, + searchable: true, + ), body: CustomScrollView( slivers: [ const SliverToBoxAdapter( @@ -99,36 +102,43 @@ class _ItemListPageState extends State { return SliverPadding( padding: const EdgeInsets.only(bottom: 50, right: 20, left: 20, top: 10), sliver: SliverToBoxAdapter( - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.spaceBetween, - spacing: 15, - runSpacing: 15, - children: [ - FutureBuilder( - future: _products, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error.toString()}'); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text('상품이 없습니다')); - } else { - final products = snapshot.data!; - return Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.spaceBetween, - spacing: 15, - runSpacing: 15, - children: products - .map((product) => ProductCard(product: product)) - .toList(), - ); - } - }, - ) - ], + child: FutureBuilder( + future: _products, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Text('Error: ${snapshot.error.toString()}'); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Image.asset( + 'assets/images/Question-3d.png', + width: 180.0, + ), + ), + Text( + '상품이 없습니다', + style: textStyle(20, true, null), + ), + ], + )); + } else { + final products = snapshot.data!; + return Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.spaceBetween, + spacing: 15, + runSpacing: 15, + children: products + .map((product) => ProductCard(product: product)) + .toList(), + ); + } + }, ), ), ); diff --git a/lib/screens/products/liked_list_page.dart b/lib/screens/products/liked_list_page.dart index c65a5c3..7b1013b 100644 --- a/lib/screens/products/liked_list_page.dart +++ b/lib/screens/products/liked_list_page.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; // import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:intl/intl.dart'; +import 'package:path/path.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'; @@ -21,13 +23,13 @@ class _LikedListPageState extends State { late Future> _products; int cnt = 0; - Future> getProducts() async { + Future> getProducts(String url) async { String token = await readJwt(); token = token.toString().split(" ")[2]; try { final response = await APIService.instance.request( - 'https://saphy.site/item-wishes?type=ALL', + 'https://saphy.site/item-wishes?type=$url', DioMethod.get, contentType: 'application/json', token: "Bearer $token", @@ -54,12 +56,12 @@ class _LikedListPageState extends State { @override void initState() { super.initState(); - _products = getProducts(); - countProducts(); + _products = getProducts("ALL"); + countProducts("ALL"); } - Future countProducts() async { - List products = await getProducts(); + Future countProducts(String url) async { + List products = await getProducts(url); setState(() { cnt = products.length; }); @@ -69,7 +71,10 @@ class _LikedListPageState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xfff4f4f4), - appBar: const TopAppBar(label: "찜 목록"), + appBar: const TopAppBar( + label: "찜 목록", + searchable: true, + ), body: CustomScrollView( slivers: [ SliverPadding( @@ -82,26 +87,39 @@ class _LikedListPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - buildFilterButton("전체"), - const SizedBox(width: 10), - buildFilterButton("스마트폰"), - const SizedBox(width: 10), - buildFilterButton("스마트폰"), - const SizedBox(width: 10), - buildFilterButton("스마트폰"), - const SizedBox(width: 10), - buildFilterButton("스마트폰"), - const SizedBox(width: 10), - buildFilterButton("스마트폰"), - const SizedBox(width: 10), + buildFilterButton("전체", "ALL"), + const SizedBox( + width: 10, + ), + buildFilterButton("스마트폰", "PHONE"), + const SizedBox( + width: 10, + ), + buildFilterButton("태블릿", "TABLET"), + const SizedBox( + width: 10, + ), + buildFilterButton("노트북", "LAPTOP"), + const SizedBox( + width: 10, + ), + buildFilterButton("음향기기", "headphone-3d"), + const SizedBox( + width: 10, + ), + buildFilterButton("웨어러블", "wearable-3d"), ], ), ), ), ), ), + const SliverToBoxAdapter( + child: SizedBox(height: 10), + ), + _buildSorter(), SliverPadding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), sliver: SliverToBoxAdapter( child: Wrap( direction: Axis.horizontal, @@ -117,7 +135,22 @@ class _LikedListPageState extends State { } else if (snapshot.hasError) { return Text('Error: ${snapshot.error.toString()}'); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text('상품이 없습니다')); + return Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Image.asset( + 'assets/images/Question-3d.png', + width: 180.0, + ), + ), + Text( + '상품이 없습니다', + style: textStyle(20, true, null), + ), + ], + )); } else { final products = snapshot.data!; return Wrap( @@ -141,23 +174,48 @@ class _LikedListPageState extends State { ); } - Container buildFilterButton(String label) { - return Container( - height: 45, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - border: Border.all(color: gray300, width: 1), - color: white, + SliverPadding _buildSorter() { + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 30), + sliver: SliverToBoxAdapter( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '상품 $cnt', + style: subTitleText(), + ), + IconButton( + icon: const Icon(Icons.sort_outlined), + onPressed: () {}, + ), + ], + ), ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 13.0, vertical: 8), - child: Center( - child: Text( - label, - style: const TextStyle( - fontFamily: "Pretendard", - fontSize: 15, - fontWeight: FontWeight.bold), + ); + } + + InkWell buildFilterButton(String label, String url) { + return InkWell( + onTap: () { + setState(() { + // URL에 따라 제품을 다시 가져오고, 상태를 업데이트합니다. + _products = getProducts(url); + countProducts(url); // 필터에 맞는 제품 개수를 카운트합니다. + }); + }, + child: Container( + height: 45, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: gray300, width: 1), + color: white, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 13.0, vertical: 8), + child: Center( + child: Text(label, style: textStyle(15, false, null)), ), ), ), diff --git a/lib/screens/products/product_detail_page.dart b/lib/screens/products/product_detail_page.dart index cb9df63..a1da334 100644 --- a/lib/screens/products/product_detail_page.dart +++ b/lib/screens/products/product_detail_page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/widgets.dart'; @@ -318,9 +319,9 @@ class _ProductDetailState extends State { onTap: () { Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( builder: (context) => PurchasePage( - product: widget.product, + product: productDetail!, )), ); }, diff --git a/lib/screens/purchase/purchase_page.dart b/lib/screens/purchase/purchase_page.dart index 4b4582b..072e06f 100644 --- a/lib/screens/purchase/purchase_page.dart +++ b/lib/screens/purchase/purchase_page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:saphy/screens/purchase/purchase_process_page.dart'; import 'package:saphy/utils/colors.dart'; @@ -103,7 +104,7 @@ class _PurchaseFooterState extends State { ), ), const Divider( - color: black, + color: gray800, thickness: 1, indent: 5, endIndent: 5, @@ -111,13 +112,13 @@ class _PurchaseFooterState extends State { const SizedBox(height: 10), NormalButton( title: "구매하기", - bgColor: white, - txtColor: black, + bgColor: black, + txtColor: white, onTap: _isAgreed ? () { Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( builder: (context) => PurchaseProcessPage( product: widget.product, // widget.product 사용 ), @@ -169,7 +170,7 @@ class PurchaseHeader extends StatelessWidget { ), const SizedBox(height: 10), const Divider( - color: black, + color: gray800, thickness: 1, indent: 40, endIndent: 40, diff --git a/lib/screens/purchase/purchase_process_page.dart b/lib/screens/purchase/purchase_process_page.dart index 20000b7..0241cf2 100644 --- a/lib/screens/purchase/purchase_process_page.dart +++ b/lib/screens/purchase/purchase_process_page.dart @@ -40,7 +40,7 @@ class _PurchaseProcessPageState extends State { onPressed: () { Navigator.of(context).pop(); }, - child: Text("취소", style: bodyText()), + child: Text("취소", style: textStyle(15, false, black)), ), ), ], @@ -99,13 +99,12 @@ class _PurchaseProcessPageState extends State { child: Padding( padding: const EdgeInsets.all(10.0), child: Container( - decoration: const BoxDecoration( - color: Colors.white, - // 이미지 추가 시: - // image: DecorationImage( - // image: NetworkImage('image_url'), - // fit: BoxFit.cover, - // ), + decoration: BoxDecoration( + color: Colors.transparent, + image: DecorationImage( + image: NetworkImage(widget.product.images[0].url), + fit: BoxFit.cover, + ), ), height: double.infinity, ), diff --git a/lib/screens/search/search_result_screen.dart b/lib/screens/search/search_result_screen.dart index 223d8d7..839535b 100644 --- a/lib/screens/search/search_result_screen.dart +++ b/lib/screens/search/search_result_screen.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:saphy/utils/colors.dart'; import 'package:saphy/utils/textstyles.dart'; @@ -18,12 +19,21 @@ class SearchResultScreen extends StatefulWidget { class _SearchResultScreenState extends State { late TextEditingController _controller; Future>? _products; + int cnt = 0; + + Future countProducts() async { + List products = await searchText(widget.query); + setState(() { + cnt = products.length; + }); + } @override void initState() { super.initState(); _controller = TextEditingController(); _products = searchText(widget.query); + countProducts(); } @override @@ -34,10 +44,10 @@ class _SearchResultScreenState extends State { @override Widget build(BuildContext context) { - var screenWidth = MediaQuery.of(context).size.width; return Scaffold( backgroundColor: const Color(0xfff4f4f4), appBar: AppBar( + toolbarHeight: 70, automaticallyImplyLeading: false, backgroundColor: altWhite, title: Row( @@ -58,8 +68,11 @@ class _SearchResultScreenState extends State { (String value) { Navigator.push( context, - MaterialPageRoute( - builder: (context) => SearchResultScreen(query: value), + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + SearchResultScreen(query: value), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, ), ); }, @@ -79,8 +92,9 @@ class _SearchResultScreenState extends State { ), ), body: CustomScrollView(slivers: [ + _buildSorter(), SliverPadding( - padding: const EdgeInsets.only(left: 20, right: 20, top: 30), + padding: const EdgeInsets.only(left: 20, right: 20, top: 10), sliver: SliverToBoxAdapter( child: FutureBuilder>( future: _products, @@ -91,7 +105,22 @@ class _SearchResultScreenState extends State { return Center( child: Text('Error: ${snapshot.error.toString()}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text('상품이 없습니다')); + return Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Image.asset( + 'assets/images/Question-3d.png', + width: 180.0, + ), + ), + Text( + '상품이 없습니다', + style: textStyle(20, true, null), + ), + ], + )); } else { final products = snapshot.data!; return Wrap( @@ -126,13 +155,35 @@ class _SearchResultScreenState extends State { .toList(); return products; } else { - throw Exception('검색 결과가 없습니다.'); + return []; } } else { - throw Exception('상품 로드에 실패했습니다.'); + return []; } } catch (e) { - throw Exception('에러가 발생했습니다.'); + return []; } } + + SliverPadding _buildSorter() { + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 30), + sliver: SliverToBoxAdapter( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '상품 $cnt', + style: subTitleText(), + ), + IconButton( + icon: const Icon(Icons.sort_outlined), + onPressed: () {}, + ), + ], + ), + ), + ); + } } diff --git a/lib/screens/search/search_screen.dart b/lib/screens/search/search_screen.dart index 9be0310..66032de 100644 --- a/lib/screens/search/search_screen.dart +++ b/lib/screens/search/search_screen.dart @@ -1,5 +1,11 @@ +import 'dart:ui'; + +import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:saphy/screens/screen_controller.dart'; import 'package:saphy/utils/colors.dart'; import 'package:saphy/utils/textstyles.dart'; import 'package:saphy/screens/search/search_result_screen.dart'; @@ -43,7 +49,11 @@ class _SearchScreenState extends State { size: 25, ), onPressed: () { - Navigator.of(context).pop(); + Navigator.of(context).pushReplacement( + CupertinoPageRoute( + builder: (context) => const ScreenController(), + ), + ); }, ), textField( @@ -52,8 +62,11 @@ class _SearchScreenState extends State { (String value) { Navigator.push( context, - MaterialPageRoute( - builder: (context) => SearchResultScreen(query: value), + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + SearchResultScreen(query: value), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, ), ); }, @@ -66,9 +79,11 @@ class _SearchScreenState extends State { onPressed: () { Navigator.push( context, - MaterialPageRoute( - builder: (context) => + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => SearchResultScreen(query: _controller.text), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, ), ); }, @@ -79,16 +94,52 @@ class _SearchScreenState extends State { body: Column( children: [ Padding( - padding: const EdgeInsets.all(30.0), + padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text("최근 검색어", style: titleText()), - const SizedBox(height: 20), Text("인기 검색어", style: titleText()), - const SizedBox(height: 20), + const SizedBox(height: 10), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + buildKeywordButton("iPhone 14", context), + const SizedBox(width: 10), + buildKeywordButton("iPhone SE", context), + const SizedBox(width: 10), + buildKeywordButton("iPhone 13", context), + const SizedBox(width: 10), + buildKeywordButton("Galaxy", context), + const SizedBox(width: 10), + buildKeywordButton("iPhone", context), + const SizedBox(width: 10), + ], + ), + ), + const SizedBox(height: 30), Text("인기 브랜드", style: titleText()), + const SizedBox(height: 10), + Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.spaceBetween, + spacing: 15, + runSpacing: 15, + children: [ + buildBrandButton( + "https://i.pinimg.com/564x/98/86/c2/9886c2f88c318293b04c223f18e57148.jpg", + "Apple"), + buildBrandButton( + "https://i.pinimg.com/564x/68/23/99/68239927494dbd614773edaa5e05606f.jpg", + "Samsung"), + buildBrandButton( + "https://i.pinimg.com/564x/21/9a/ba/219aba5a7e74091741fef401deb08f43.jpg", + "Xiaomi"), + buildBrandButton( + "https://i.pinimg.com/564x/bb/01/82/bb0182822f2cf41937feee1f4a91fb2b.jpg", + "Nokia"), + ]), ], ), ), @@ -96,4 +147,64 @@ class _SearchScreenState extends State { ), ); } + + InkWell buildKeywordButton(String label, BuildContext context) { + return InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + SearchResultScreen(query: label), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + ), + ); + }, + child: Container( + height: 45, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: gray300, width: 1), + color: white, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 13.0, vertical: 8), + child: Center( + child: Text(label, style: textStyle(15, false, null)), + ), + ), + ), + ); + } + + InkWell buildBrandButton(String link, String label) { + double screenWidth = MediaQuery.of(context).size.width; + return InkWell( + onTap: () {}, + child: Container( + width: (screenWidth - 55) / 2, + height: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: white, + image: DecorationImage( + image: CachedNetworkImageProvider(link), + fit: BoxFit.cover, + ), + ), + // child: SizedBox.expand( + // child: ClipRect( + // child: BackdropFilter( + // filter: ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0), // 블러 강도 + // child: Container( + // color: Colors.transparent, + // child: Center( + // child: Text(label, style: textStyle(25, true, white)))), + // ), + // ), + // ), + ), + ); + } } diff --git a/lib/widgets/product_card.dart b/lib/widgets/product_card.dart index 47d1aff..c5b0661 100644 --- a/lib/widgets/product_card.dart +++ b/lib/widgets/product_card.dart @@ -1,6 +1,6 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:intl/intl.dart'; import 'package:saphy/screens/products/product_detail_page.dart'; import 'package:saphy/utils/number_format.dart'; import 'package:saphy/utils/textstyles.dart'; @@ -18,7 +18,7 @@ class ProductCard extends StatelessWidget { onTap: () { Navigator.push( context, - MaterialPageRoute( + CupertinoPageRoute( builder: (context) => ProductDetail( product: product, ), @@ -40,8 +40,7 @@ class ProductCard extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), image: DecorationImage( - image: - CachedNetworkImageProvider(product.images[0].url ?? ""), + image: CachedNetworkImageProvider(product.images[0].url), fit: BoxFit.cover, ), ), diff --git a/lib/widgets/textfield.dart b/lib/widgets/textfield.dart index 7f0c6df..8dc2fbc 100644 --- a/lib/widgets/textfield.dart +++ b/lib/widgets/textfield.dart @@ -13,21 +13,18 @@ SizedBox textField( controller: controller, onSubmitted: onSubmitted, decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderSide: const BorderSide(color: gray800, width: 1), - borderRadius: BorderRadius.circular(15), - ), + filled: true, + fillColor: white.withOpacity(0.8), + hintText: '검색어를 입력하세요', + hintStyle: const TextStyle(color: Colors.grey), border: OutlineInputBorder( - borderSide: const BorderSide(color: gray800, width: 1), - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(12.0), // 둥근 모서리 설정 + borderSide: BorderSide.none, // 테두리 제거 ), focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: mainPrimary, width: 1), - borderRadius: BorderRadius.circular(15), - ), - hintText: '검색어를 입력하세요', - hintStyle: const TextStyle( - color: gray700, + borderRadius: BorderRadius.circular(12.0), // 포커스 시에도 둥근 모서리 유지 + borderSide: BorderSide( + color: mainPrimary.withOpacity(0.5), width: 1.5), // 포커스 시 테두리 스타일 ), ), ),