굿즈의 상세한 정보를 보여주는 페이지를 만들자
저번에 만든 굿즈 메인페이지의 굿즈를 클릭하면, 그 굿즈에 대해서 정확하게 보여주는 페이지가 필요합니다.
지금부터 그 페이지를 만들겠습니다.
페이지 디자인
굿즈의 내부 정보랑, 같은 카테고리에 포함된 다른 굿즈들을 보여주는 것을 목표로 디자인을 했습니다.
이후 코딩과정에서 두 가지 변경사항이 생겼습니다.
1. 태그기능은 넣지 않고, 카테고리(분류)만 일단 넣자.
2. '같은 카테고리의 물건들' 항목은 앱의 안정성을 더 키운 이후에 업데이트하자
제작 결과물
아이폰 15프로를 기준으로 출력된 모습입니다.
페이지 소스코드
import 'package:flutter/material.dart';
import 'package:goodwishes/Models/goods_model.dart';
import 'package:goodwishes/widgets/goods/goods_detail_list.dart';
import 'package:goodwishes/widgets/goods/goods_detail_thumb.dart';
import 'package:goodwishes/widgets/goods/goods_detail_title.dart';
import 'package:provider/provider.dart';
class GoodsDetailPage extends StatelessWidget {
final String id;
const GoodsDetailPage({
super.key,
required this.id,
});
@override
Widget build(BuildContext context) {
Goods goods = context.watch<GoodsListProvider>().goodsList.firstWhere(
(element) => element.id == id,
orElse: () => Goods.createEmptyGoods());
if (goods.id.isEmpty) {
// Handle the case where the goods is not found
return const Scaffold(
body: Center(
child: Text('Goods not found'),
),
);
}
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
GoodsDetailThumb(
goods: goods,
),
GoodsDetailTitle(
goods: goods,
),
GoodsDetailList(
goods: goods,
),
],
),
),
),
);
}
}
이제 페이지를 구성하는 내부 위젯들에 대해 차례대로 알아보겠습니다.
상단 : GoodDetailThumb
화면의 맨 상단에는, 굿즈의 썸네일이 보여지는 코드를 짰습니다.
import 'package:flutter/material.dart';
import 'package:goodwishes/Models/goods_model.dart';
class GoodsDetailThumb extends StatelessWidget {
final Goods goods;
const GoodsDetailThumb({
super.key,
required this.goods,
});
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: MemoryImage(goods.thumbnail),
),
),
);
}
}
사진의 크기는 1:1로 지정을 했고, 스마트폰 크기의 가로px를 기준으로 맞췄습니다.
굿즈의 썸네일은 Uint8List로 저장됐기 때문에, MemoryImage()를 이용해서 불러와야 합니다.
그리고 BoxDecoration의 BoxFit.cover를 이용해서, 자연스럽게 썸네일 이미지가 보여지게 끔 설정했습니다.
중단 : GoodDetailTitle
썸네일 밑에서, 굿즈의 대표 정보와 북마크 여부를 표시합니다.
1. 굿즈의 이름과 획득 날짜를 표시합니다.
2. 굿즈의 북마크 여부를 알려주고, 북마크 추가/제거 기능의 버튼 역할을 합니다.
3. 굿즈의 카테고리를 표시합니다.
import 'package:flutter/material.dart';
import 'package:goodwishes/Models/goods_model.dart';
import 'package:goodwishes/constants/ui_numbers.dart';
import 'package:goodwishes/widgets/tag.dart';
import 'package:provider/provider.dart';
class GoodsDetailTitle extends StatelessWidget {
final Goods goods;
const GoodsDetailTitle({
super.key,
required this.goods,
});
@override
Widget build(BuildContext context) {
return Container(
height: 150,
width: MediaQuery.of(context).size.width,
color: const Color(0xFFDBCACA),
padding: const EdgeInsets.symmetric(
horizontal: 17,
vertical: 10,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 10,
),
Text(
goods.goodsName,
style: const TextStyle(
fontSize: 18,
),
),
Text(
goods.date,
style: const TextStyle(
fontSize: 13,
),
),
const SizedBox(
height: UIDefault.sizedBoxHeight,
),
Tag(
tagName: goods.category,
// onNavigate: () {},
),
],
),
IconButton(
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
iconSize: WidgetStatePropertyAll(UIDefault.buttonSize)),
icon: !goods.isFavorite
? const Icon(Icons.bookmark_add_outlined)
: const Icon(Icons.bookmark_added_rounded),
onPressed: () {
Provider.of<GoodsListProvider>(context, listen: false)
.updateIsFavorite(goods.id);
},
)
],
),
],
),
);
}
}
여기서 IconButton(즐겨찾기 버튼)만 좀 더 깊이 있게 보자면,
IconButton(
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
iconSize: WidgetStatePropertyAll(UIDefault.buttonSize)),
icon: !goods.isFavorite
? const Icon(Icons.bookmark_add_outlined)
: const Icon(Icons.bookmark_added_rounded),
onPressed: () {
Provider.of<GoodsListProvider>(context, listen: false)
.updateIsFavorite(goods.id);
},
)
icon : 해당 굿즈의 isFavorite 값이 true/false일 때 상황에 맞춰서 적절하게 아이콘을 출력합니다.
onPressed: 버튼을 눌렀을 때, Provider를 이용해서 굿즈의 isFavorite 여부를 업데이트합니다.
하단 : GoodDetailList
import 'package:flutter/material.dart';
import 'package:goodwishes/Models/goods_model.dart';
import 'package:goodwishes/widgets/goods/goods_delete_button.dart';
import 'package:goodwishes/widgets/goods/goods_detail_list_el.dart';
import 'package:goodwishes/widgets/goods/goods_rewrite_button.dart';
import 'package:goodwishes/widgets/section_title.dart';
class GoodsDetailList extends StatelessWidget {
final Goods goods;
const GoodsDetailList({
super.key,
required this.goods,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 30,
),
GoodsDetailListEl(
leftText: '소지 수량',
rightText: goods.amount.toString(),
),
GoodsDetailListEl(
leftText: '구매 가격',
rightText: goods.price.toString(),
),
GoodsDetailListEl(
leftText: '구매 방법',
rightText: goods.wayToBuy,
),
GoodsDetailListEl(
leftText: '보관 장소',
rightText: goods.location,
),
const SizedBox(
height: 5,
),
const SectionTitle(titleText: '메모'),
Container(
height: 240,
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(17),
decoration: const BoxDecoration(
color: Color.fromARGB(255, 228, 228, 228),
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
child: Text(goods.memo),
),
const SizedBox(
height: 30,
),
GoodsRewriteButton(
goods: goods,
categoryName: goods.category,
),
const SizedBox(
height: 15,
),
GoodsDeleteButton(
id: goods.id,
categoryName: goods.category,
),
],
),
);
}
}
GoodsDetailListEl() 위젯이 많은데,
굿즈의 상세정보들을 출력해주는 역할을 담당합니다.
import 'package:flutter/material.dart';
class GoodsDetailListEl extends StatelessWidget {
final String leftText;
final String rightText;
const GoodsDetailListEl({
super.key,
required this.leftText,
required this.rightText,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
leftText,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
const SizedBox(
width: 35,
),
Text(
rightText,
style: const TextStyle(
fontSize: 19,
),
),
],
),
const SizedBox(
height: 22,
),
],
);
}
}
최하단의 버튼들
굿즈 상세페이지를 맨 밑으로 내리면, 수정/삭제 버튼이 있습니다.
하단의 GoodsDetailList()위젯 코드의 맨 밑에,
GoodsRewriteButton(
goods: goods,
categoryName: goods.category,
),
const SizedBox(
height: 15,
),
GoodsDeleteButton(
id: goods.id,
categoryName: goods.category,
),
GoodsDeleteButton, GoodsRewriteButton이 있습니다.
GoodsRewriteButton
import 'package:flutter/material.dart';
import 'package:goodwishes/Models/goods_model.dart';
import 'package:goodwishes/pages/goods_rewrite_page.dart';
class GoodsRewriteButton extends StatelessWidget {
final Goods goods;
final String categoryName;
const GoodsRewriteButton({
super.key,
required this.goods,
required this.categoryName,
});
@override
Widget build(BuildContext context) {
return TextButton(
style:
const ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GoodsRewritePage(goods: goods),
),
);
},
child: Container(
height: 50,
decoration: const BoxDecoration(
color: Color.fromARGB(255, 224, 224, 224),
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
child: const Center(
child: Text(
'수정',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
),
);
}
}
수정 버튼을 누르면, 굿즈를 수정하는 'GoodsRewritePage'로 이동합니다.
GoodsDeleteButton
import 'package:flutter/material.dart';
import 'package:goodwishes/Functions/show_info_dialog.dart';
import 'package:goodwishes/Models/category_model.dart';
import 'package:goodwishes/Models/goods_model.dart';
import 'package:provider/provider.dart';
class GoodsDeleteButton extends StatelessWidget {
final String id;
final String categoryName;
const GoodsDeleteButton({
super.key,
required this.id,
required this.categoryName,
});
@override
Widget build(BuildContext context) {
return TextButton(
style:
const ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
onPressed: () {
Provider.of<GoodsListProvider>(context, listen: false).removeGoods(id);
Provider.of<CategoryListProvider>(context, listen: false)
.downCountCategory(categoryName);
if (context.mounted) {
Navigator.pop(context);
showInfoDialog(
context,
'알림',
'굿즈가 제거되었습니다.',
);
}
},
child: Container(
height: 50,
decoration: const BoxDecoration(
color: Color(0xFFDBCACA),
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
child: const Center(
child: Text(
'삭제',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
),
);
}
}
GoodsDeleteButton을 누르면, GoodsListProvider와 연동하여 굿즈를 지우고, CategoryListProvider와 연동하여 해당 굿즈가 들어간 카테고리의 굿즈 개수를 -1 합니다.
if (context.mounted) {
Navigator.pop(context);
showInfoDialog(
context,
'알림',
'굿즈가 제거되었습니다.',
);
}
그리고 굿즈 제거가 끝나면, '굿즈가 제거되었습니다.' 알림을 표시하면서
굿즈 상세페이지를 빠져나옵니다.
'개발일지 > GoodWishes' 카테고리의 다른 글
[굿위시 제작기] 8. 굿즈 추가 페이지 제작과정 - DatePicker 사용하기 (0) | 2024.10.17 |
---|---|
[굿위시 제작기] 7. 굿즈 추가 페이지 제작과정 - 도면, 초기세팅, 사진추가 기능 (0) | 2024.09.24 |
[굿위시 제작기] 5. 바텀 내비게이션 제작하기 (0) | 2024.09.04 |
[굿위시 제작기] 4. 굿즈 메인 페이지 제작하기 (1) | 2024.08.27 |
[굿위시 제작기] 3. 굿즈, 위시, 카테고리 Class(모델)를 선언하기 (0) | 2024.08.23 |