[Flutter] 스나이퍼팩토리 Flutter 기초과정 (12)

GONG·2023년 4월 6일
0

키오스크 앱 업그레이드

기존 키오스크 앱 👉 [2주차] 주간평가

Requirements

  1. 클릭시 메뉴가 주문 리스트에 담깁니다. 이 때 Chip 위젯으로 들어가게 됩니다.
    • 각 Chip은 Delete 아이콘 버튼이 있으며, 클릭시 주문 리스트에서 삭제합니다.
  2. 기존의 버튼은 초기화하기였으나, 동작하지 않는 결제하기 버튼으로 바뀝니다.
  3. 주문 리스트가 비었다면, 하단의 결제하기 버튼이 표시되지 않습니다.
  4. 앱바의 분식왕 이테디 주문하기를 더블클릭하면, 관리자 페이지로 이동하게 됩니다.

코드

  • AdminPage.dart

    import 'package:flutter/material.dart';
    
    class AdminPage extends StatelessWidget {
      const AdminPage({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: Text('Admin Page'),
          ),
          body: Column(
            children: [
              ListTile(title: Text('메뉴 추가'),),
              ListTile(title: Text('메뉴 삭제'),),
              ListTile(title: Text('메뉴 수정'),),
            ],
          ),
        );
      }
    }
  • MenuTile.dart 파일은 수정한 부분이 없습니다

  • Kiosk.dart
    - 주간평가 과제 진행할 때 주문 리스트에 메뉴를 중복해서 새롭게 추가하지 않고 메뉴이름과 리스트에서 카운팅한 개수를 함께 보여주는 방식으로 구현해 둬서 이번 과제에서도 Chip 위젯의 삭제 버튼을 누르면 count 값을 1씩 줄이고, count 값이 0이 되면 위젯을 삭제하는 방식으로 구현했습니다

    import 'package:first_app/homework/week2/kiosk/MenuTile.dart';
    import 'package:flutter/material.dart';
    
    import 'AdminPage.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class Menu {
      String imgUrl;
      String name;
    
      Menu(this.imgUrl, this.name);
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Kiosk(),
        );
      }
    }
    
    class Kiosk extends StatefulWidget {
      const Kiosk({Key? key}) : super(key: key);
    
      
      State<Kiosk> createState() => _KioskState();
    }
    
    class _KioskState extends State<Kiosk> {
      List<Menu> menuList = [
        Menu('assets/images/kiosk/option_bokki.png', '떡볶이'),
        Menu('assets/images/kiosk/option_beer.png', '맥주'),
        Menu('assets/images/kiosk/option_kimbap.png', '김밥'),
        Menu('assets/images/kiosk/option_omurice.png', '오므라이스'),
        Menu('assets/images/kiosk/option_pork_cutlets.png', '돈까스'),
        Menu('assets/images/kiosk/option_ramen.png', '라면'),
        Menu('assets/images/kiosk/option_udon.png', '우동'),
      ];
    
      // 주문할 메뉴들이 담긴 리스트
      List<String> orderList = [];
      // 주문할 메뉴와 각각의 개수를 키와 값 형태로 저장
      Map<String, int> countMenu = {};
    
      
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: GestureDetector(
                child: Text('분식왕 이테디 주문하기', style: TextStyle(color: Colors.black),),
                onDoubleTap: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => const AdminPage()),
                  );
                },
              ),
              centerTitle: true,
              backgroundColor: Colors.white,
              elevation: 0,
            ),![](https://velog.velcdn.com/images/gongd/post/50ac9d45-f974-46ad-a1f8-cbc0ebe99808/image.png)
    
            body: SafeArea(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('주문 리스트',
                        style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),),
                      SizedBox(
                          height: 40,
                          child: countMenu.isNotEmpty ? ListView.builder(
                            scrollDirection: Axis.horizontal,
                            itemCount: countMenu.length,
                            itemBuilder: (context, index) {
                              String menu = countMenu.keys.elementAt(index);
                              int count = countMenu[menu] ?? 0;
                              return Chip(
                                label: Row(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  crossAxisAlignment: CrossAxisAlignment.center,
                                  children: [
                                    Text(menu),
                                    Text('$count'),
                                  ],
                                ),
                                onDeleted: () {
                                  setState(() {
                                    countMenu[menu] != null && countMenu[menu]! > 1 ?
                                    countMenu[menu] = (countMenu[menu]! - 1) : countMenu.remove(menu);
                                  });
                                },
                              );
                            },
                          ) : Center(child: Text('주문한 메뉴가 없습니다'))
                      ),
                      Text('음식',
                        style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),),
                      GridView.builder(
                        shrinkWrap: true,
                        itemCount: menuList.length,
                        itemBuilder: (context, index) {
                          return GestureDetector(
                            onTap: () {
                              setState(() {
                                // 클릭한 메뉴 이름을 변수로 선언
                                String tapMenu = menuList[index].name;
                                // orderList에 클릭한 메뉴 이름 추가
                                orderList.add(tapMenu);
    
                                // countMenu 가 클릭한 메뉴를 키로 가지고 있다면
                                if(countMenu.containsKey(tapMenu)){
                                  // 해당 키의 카운트 값 +1
                                  countMenu[tapMenu] = countMenu[tapMenu]! + 1;
                                } else {
                                  // 클릭한 메뉴를 키로 가지고 있지 않다면 카운트 값 1로 설정
                                  countMenu[tapMenu] = 1;
                                }
                              });
                            },
                            child: MenuTile(
                                imgUrl: menuList[index].imgUrl,
                                name: menuList[index].name));
                        },
                        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 3,
                          childAspectRatio: 1 / 1,
                          mainAxisSpacing: 10,
                          crossAxisSpacing: 10,
                        ),
                      ),
                    ],
                  ),
                )
            ),
            floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
            floatingActionButton: countMenu.isNotEmpty ?
            FloatingActionButton.extended(
              onPressed: () {},
              label: Text('결제하기'),
            ) : null
        );
      }
    }


연락처 앱 업그레이드

기존 연락처 앱 👉 [1주차] 주간평가

Requirements

  • 스크린 제외 두 페이지가 존재합니다.
    • 연락처 상세보기 페이지, ContactDetailPage
    • 메인페이지, MainPage
  • MainPage는 3개의 스크린을 가집니다.
    • 연락처, ContactScreen
    • 통화기록, HistoryScreen
    • 설정, SettingScreen
  • BottomNavigationBar의 요소를 클릭시 해당 스크린으로 바뀌어 보여집니다.
    아이콘은 순서대로 quick_contacts_dialer, history, settings를 사용했습니다.
  • ContactScreen의 커스텀위젯인 ContactTile을 누르면 연락처 상세보기 페이지로 이동됩니다.
  • ContactDetailPage 안에도 ContactTile 위젯이 포함되어 있습니다.

파일

  1. ContactData.dart
    • 연락처와 통화기록 데이터가 있는 파일입니다.
  2. ContactTile.dart
    • 연락처 리스트를 구현하는 데 필요한 커스텀위젯입니다.
  3. Main.dart
    • 메인 화면입니다. app bar, bottomNavigation 등이 구현되어있습니다.
  4. ContactScreen.dart
    • 연락처 리스트를 보여주는 화면입니다.
  5. ContactDetailPage.dart
    • 연락처 리스트에서 각각의 연락처를 눌렀을 때 해당 연락처의 상세정보를 보여줍니다.
  6. HistoryScreen.dart
    • 통화기록 리스트를 보여주는 화면입니다.
  7. SettingScreen.dart
    • 설정 화면입니다.

코드

  • ContactData.dart
    class Friend {
      String img;
      String name;
      String phone;
    
      Friend(this.img, this.name, this.phone);
    }
    
    class CallRecord {
      String name;
      CallType callType;
    
      CallRecord(this.name, this.callType);
    }
    
    List<Friend> friendList = [
      Friend('http://picsum.photos/50/50', '이테디', '010-1000-2000'),
      Friend('http://picsum.photos/48/48', '김스틴', '010-9000-8000'),
      Friend('http://picsum.photos/55/55', '이주노', '010-3000-3000'),
      Friend('http://picsum.photos/60/60', '임헬렌', '010-2000-8000'),
      Friend('http://picsum.photos/40/40', '염해리', '010-1000-1000'),
    ];
    
    List<CallRecord> callRecordList = [
      CallRecord('이테디', CallType.incoming),
      CallRecord('김스틴', CallType.missed),
      CallRecord('이주노', CallType.incoming),
      CallRecord('임헬렌', CallType.missed),
      CallRecord('임헬렌', CallType.outgoing),
      CallRecord('염해리', CallType.incoming),
      CallRecord('염해리', CallType.outgoing),
    ];
    
    // 통화의 종류를 상수값의 집합으로 만들어서 사용
    enum CallType {
      incoming,
      outgoing,
      missed,
    }
  • ContactTile.dart
    import 'package:flutter/material.dart';
    
    class ContactTile extends StatelessWidget {
      const ContactTile({super.key, required this.img, required this.name, required this.phone});
    
      final String img;
      final String name;
      final String phone;
    
      
      Widget build(BuildContext context) {
        return ListTile(
          leading: SizedBox(
            width: 50, height: 50,
            child: CircleAvatar(
              backgroundImage: NetworkImage(img),
            ),
          ),
          title: Text(name),
          subtitle: Text(phone),
          trailing: Icon(Icons.phone),
        );
      }
    }
  • Main.dart
    import 'package:flutter/material.dart';
    
    import 'ContactScreen.dart';
    import 'HistoryScreen.dart';
    import 'SettingScreen.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MainPage()
        );
      }
    }
    
    class MainPage extends StatefulWidget {
      const MainPage({Key? key}) : super(key: key);
    
      
      State<MainPage> createState() => _MainPageState();
    }
    
    class _MainPageState extends State<MainPage> {
      int tapIdx = 0;
      List<Widget> pages = [
        ContactScreen(),
        HistoryScreen(),
        SettingScreen()
      ];
    
      onItemTap(index) {
        setState(() {
          tapIdx = index;
        });
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: false,
            title: Text('내 연락처'),
            actions: [
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Icon(Icons.search),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Icon(Icons.more_vert),
              )
            ],
          ),
          body: pages[tapIdx],
          floatingActionButton: FloatingActionButton(
            onPressed: () {},
            child: Icon(Icons.add),
          ),
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: tapIdx,
            onTap: onItemTap,
            items: [
              BottomNavigationBarItem(icon: Icon(Icons.quick_contacts_dialer), label: '연락처'),
              BottomNavigationBarItem(icon: Icon(Icons.history), label: '통화기록'),
              BottomNavigationBarItem(icon: Icon(Icons.settings), label: '설정'),
            ],
          ),
        );
      }
    }
  • ContactScreen.dart
    import 'package:flutter/material.dart';
    import 'ContactDetailPage.dart';
    import 'ContactData.dart';
    import 'ContactTile.dart';
    
    class ContactScreen extends StatefulWidget {
      const ContactScreen({Key? key}) : super(key: key);
    
      
      State<ContactScreen> createState() => _ContactScreenState();
    }
    
    class _ContactScreenState extends State<ContactScreen> {
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
            itemCount: friendList.length,
            itemBuilder: (context, index) {
              return GestureDetector(
                child: ContactTile(
                  img: friendList[index].img,
                  name: friendList[index].name,
                  phone: friendList[index].phone,
                ),
                onTap: () {
                  print(friendList[index].name);
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => ContactDetailPage(friend: friendList[index])),
    
                  );
                },
              );
            }
          ),
        );
      }
    }
  • ContactDetailPage.dart
    import 'package:flutter/material.dart';
    import 'ContactData.dart';
    import 'ContactTile.dart';
    
    class ContactDetailPage extends StatelessWidget {
      const ContactDetailPage({Key? key, required this.friend}) : super(key: key);
      final Friend friend;
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          extendBodyBehindAppBar: true,
          appBar: AppBar(
            centerTitle: false,
            backgroundColor: Colors.transparent,
            elevation: 0,
            title: Text('연락처 상세'),
            actions: [
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Icon(Icons.more_vert),
              )
            ],
          ),
          body: Column(
            children: [
              SizedBox(
                width: double.infinity,
                height: 200,
                child: Image.network(friend.img, fit: BoxFit.fill,)
              ),
              ContactTile(img: friend.img, name: friend.name, phone: friend.phone)
            ],
          ),
        );
      }
    }
  • HistoryScreen.dart
    import 'package:flutter/material.dart';
    import 'ContactData.dart';
    
    class HistoryScreen extends StatelessWidget {
      const HistoryScreen({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
            itemCount: callRecordList.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(callRecordList[index].name),
                trailing: Icon(
                  (() {
                    switch (callRecordList[index].callType) {
                      case CallType.incoming:
                        return Icons.call_received;
                      case CallType.outgoing:
                        return Icons.call_made;
                      case CallType.missed:
                        return Icons.call_missed;
                      default:
                        return Icons.call;
                    }
                  }()), // 함수를 선언과 동시에 실행시키고 결과값 반환
                ),
              );
            }
          ),
        );
      }
    }
  • SettingScreen.dart
    import 'package:flutter/material.dart';
    
    class SettingScreen extends StatelessWidget {
      const SettingScreen({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            children: [
              ListTile(title: Text('차단 목록')),
              ListTile(title: Text('벨소리 설정')),
              ListTile(title: Text('전화 통계')),
            ],
          ),
        );
      }
    }


12일차 끝

profile
우와재밋다

0개의 댓글