20일차 – 키오스크 앱 기능 추가

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')
        )),
      ),
    );
  }
}