1. 필수 기능
- API 주소에서 메뉴 목록 가져오기
- 주문 목록(장바구니)을 로컬 장치에 저장
- shared_preferences 사용
2. 실행 결과 화면

3. 폴더 구조

4. 코드
저장소 폴더에 있는 파일은 장바구니에 담긴 상품 목록만 있는 클래스이기 때문에 생략합니다.
관리자 화면을 건너뜁니다.
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/placeholder.dart';
import 'package:flutter_application_week2_kioskapp/component/menu_grid_container.dart';
import 'package:flutter_application_week2_kioskapp/component/menu_in_cart_container.dart';
import 'package:flutter_application_week2_kioskapp/repository/datalists.dart';
import 'package:flutter_application_week2_kioskapp/screen/admin_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../component/cart_list_chip.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
bool isCartEmpty = true;
bool isCartShow = false;
List menuDataFromNetwork = ();
SharedPreferences? prefs;
@override
void initState() {
super.initState();
initPrefs();
}
initPrefs() async {
prefs = await SharedPreferences.getInstance();
if (prefs!.getStringList('cartList') != null) {
dataLists.cartList = prefs!.getStringList('cartList')!;
}
print(dataLists.cartList);
setState(() {});
}
@override
Widget build(BuildContext context) {
VoidCallback setHome = () {
setState(() {});
};
return SafeArea(
child: FutureBuilder(
future:
Dio().get('http://52.79.115.43:8090/api/collections/options/records'),
builder: (context, snapshot) {
if (snapshot.hasData) {
menuDataFromNetwork = snapshot.data!.data('items');
return Scaffold(
// 이하 FAB, 장바구니 버튼
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FAB(),
// 이하 앱바
appBar: AppBar(
title: GestureDetector(
child: Text('분식왕 이테디 주문하기'),
onDoubleTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AdminScreen(),
),
),
),
foregroundColor: Colors.black,
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Column(children: (
// body 상단 주문 리스트
upperCartChipList(
setHome: setHome,
prefs: prefs,
),
// 메뉴 그리드뷰
menuGridView(
menuDataFromNetwork: menuDataFromNetwork,
setHome: setHome,
prefs: prefs,
),
)),
// 장바구니, 버튼부
bottomSheet: AnimatedContainer(
duration: Duration(milliseconds: 120),
curve: Curves.linear,
color: Colors.grey(200),
height: isCartShow ? 250 : 60,
child: Column(
children: (
// 이하 장바구니
if (isCartShow)
Padding(
padding: const EdgeInsets.all(15.0),
child: DefaultTextStyle(
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: (Text('장바구니 품목'), Text('수량'), Text('가격')),
),
),
),
if (isCartShow)
Expanded(
child: ListView.separated(
itemBuilder: (context, index) => MenuInCartContainer(
menuName: dataLists.cartList.toSet().toList()(index),
menuPrice: menuDataFromNetwork(index)('price'),
),
separatorBuilder: (context, index) => Divider(),
itemCount: dataLists.cartList.toSet().length,
),
),
// 이하 버튼부
Row(
children: (
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
fixedSize: Size.fromHeight(60),
backgroundColor: Colors.black,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0))),
onPressed: () async {
dataLists.cartList.clear();
if (prefs != null) {
await prefs!
.setStringList('cartList', dataLists.cartList);
print(
'local : ${prefs!.getStringList('cartList')}');
}
setState(() {});
print(dataLists.cartList);
},
child: Text(
'장바구니 비우기',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
),
)),
Expanded(
child: dataLists.cartList.length != 0
? ElevatedButton(
style: ElevatedButton.styleFrom(
fixedSize: Size.fromHeight(60),
backgroundColor: Colors.red,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(0))),
onPressed: () {},
child: Text(
'결제하기',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
),
)
: ElevatedButton(
style: ElevatedButton.styleFrom(
fixedSize: Size.fromHeight(60),
backgroundColor: Colors.grey,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(0))),
onPressed: () {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text(
'결제할 상품이 없습니다.',
textAlign: TextAlign.center,
),
duration: Duration(milliseconds: 800),
));
},
child: Text(
'결제하기',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
)
),
)
),
),
),
);
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
));
}
FloatingActionButton FAB() {
return FloatingActionButton(
elevation: 0,
onPressed: () {
setState(() {
isCartShow = !isCartShow;
});
},
child: Badge(
textStyle: TextStyle(fontSize: 18),
largeSize: 22,
backgroundColor: Colors.red,
child: Icon(
Icons.shopping_cart,
size: 35,
),
label: Text('${dataLists.cartList.length}'),
alignment: AlignmentDirectional(30, -10),
),
backgroundColor:
dataLists.cartList.isEmpty ? Colors.grey : Colors.blue(300),
);
}
}
class menuGridView extends StatelessWidget {
const menuGridView(
{super.key,
required this.menuDataFromNetwork,
required this.setHome,
required this.prefs});
final List menuDataFromNetwork;
final VoidCallback setHome;
final prefs;
@override
Widget build(BuildContext context) {
return Expanded(
child: GridView.builder(
itemCount: menuDataFromNetwork.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return MenuGridContainer(
prefs: prefs,
menuName: menuDataFromNetwork(index)('menu'),
price: menuDataFromNetwork(index)('price'),
menuPic: menuDataFromNetwork(index)('imageUrl'),
setHome: setHome,
);
},
));
}
}
class upperCartChipList extends StatelessWidget {
const upperCartChipList(
{super.key, required this.setHome, required this.prefs});
final VoidCallback setHome;
final prefs;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(color: Colors.black),
height: 100,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: (
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'주문리스트',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(scrollDirection: Axis.horizontal, children: (
...dataLists.cartList.reversed
.map((e) => cartListChip(
prefs: prefs,
menuName: e,
index: dataLists.cartList.indexOf(e),
setHome: setHome,
))
.toList(),
)),
),
),
),
),
),
);
}
}
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:flutter_application_week2_kioskapp/repository/datalists.dart';
import 'package:flutter_application_week2_kioskapp/screen/home_screen.dart';
class MenuGridContainer extends StatelessWidget {
const MenuGridContainer({
Key? key,
required this.menuPic,
required this.menuName,
required this.price,
required this.setHome,
required this.prefs,
}) : super(key: key);
final String menuPic;
final String menuName;
final int price;
final VoidCallback setHome;
final prefs;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
dataLists.cartList.add(menuName);
print(dataLists.cartList);
await prefs.setStringList('cartList', dataLists.cartList);
print('local : ${prefs.getStringList('cartList')}');
setHome();
},
child: Card(
child: Column(
children: (
Container(
width: MediaQuery.of(context).size.width * 10 / 40,
height: MediaQuery.of(context).size.width * 10 / 50,
child: Image.network(
menuPic,
fit: BoxFit.fill,
),
),
Text(menuName),
Text('$price원')
),
),
),
);
}
}
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:flutter_application_week2_kioskapp/repository/datalists.dart';
import 'package:flutter_application_week2_kioskapp/screen/home_screen.dart';
class cartListChip extends StatelessWidget {
const cartListChip(
{Key? key,
required this.menuName,
required this.index,
required this.setHome,
required this.prefs})
: super(key: key);
final String menuName;
final int index;
final VoidCallback setHome;
final prefs;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(2.0),
child: Chip(
backgroundColor: Colors.white,
onDeleted: () async {
print('삭제한 인덱스 : $index');
dataLists.cartList.removeAt(index);
print(dataLists.cartList);
if (prefs != null) {
await prefs!.setStringList('cartList', dataLists.cartList);
print('local : ${prefs!.getStringList('cartList')}');
}
setHome();
},
deleteIcon: Icon(Icons.cancel),
label: SizedBox(
height: 20,
child: Row(
mainAxisSize: MainAxisSize.min,
children: (
Text(menuName),
SizedBox(
width: 8,
),
),
),
),
),
);
}
}
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:flutter_application_week2_kioskapp/repository/datalists.dart';
class MenuInCartContainer extends StatelessWidget {
const MenuInCartContainer({
Key? key,
required this.menuName,
required this.menuPrice
}) : super(key: key);
final String menuName;
final int menuPrice;
@override
Widget build(BuildContext context) {
int cartCount = dataLists.cartList.where((element) => element == menuName).length;
int menuTotalPrice = cartCount*menuPrice;
return Container(
height: 20,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: (
Text('$menuName'),
Text('$cartCount'),
Text('$menuTotalPrice')
)),
),
);
}
}