[Flutter] 스나이퍼팩토리 Flutter 주간평가 (4주차)

GONG·2023년 4월 16일
0
post-thumbnail

도전하기

16일차 과제 활용 문제


다음 기능들을 구현해봅시다!

16일차 과제를 활용하시기 바랍니다.

또한 디자인은 모두 예시이니 디자인이 다르더라도 동일한 기능이라면 인정됩니다.

  • 도전하기는 필수평가는 아니지만, 제출시 추가 점수가 있을 수 있습니다. *
  1. AppBar title을 누르면 GridView의 스크롤이 최상단으로 이동합니다.

(단 애니메이션이 적용되어 부드럽게 올라가야합니다.)

  1. 앱을 시작할 때 뜨는 Splash 화면 적용해봅시다

첨부된 강아지 사진을 이용해도 좋고 다른파일을 이용해도 좋습니다.

  1. 좋아요 아이콘 옆 댓글 아이콘을 누르면 새로운 페이지로 넘어가게되고, Hero 애니메이션을 적용하여 사진이 같이 이동합니다.

  2. pull to refresh하면 shimmer가 나오고 그 후 데이터가 불러오면 부드럽게 전환이 되게 합시다.

Duration은 2초로 설정해주세요.

  1. 좋아요를 누르면 회색하트가 빨간색으로 변합니다. 우측상단 하트를 누르면 bottomSheet가 등장하여 좋아요 리스트를 볼 수 있습니다. 또한 앱을 종료 후 재실행 하더라도, 좋아요 한 것은 유지가 됩니다.

(패키지 Hive 이용)

  1. 우측 상단의 X 를 누르면 좋아요가 모두 삭제가 됩니다. 단 하트가 빨간색에서 회색으로만 바뀌어야하고 화면 모두 새로고침이 되면 안됩니다.

hive 사용법

  1. 패키지 pubspec.yaml 파일에 추가

    dependencies:
      hive: ^[version]
      hive_flutter: ^[version]
    
    dev_dependencies:
      hive_generator: ^[version]
      build_runner: ^[version]
  2. 앱 시작 시 Hive를 초기화

    void main() async {
      await Hive.initFlutter();
      // ...
    }
  3. 데이터 모델 클래스를 만들기

    import 'package:hive/hive.dart';
    
    part 'person.g.dart';
    
    (typeId: 0)
    class Person {
      (0)
      String name;
    
      (1)
      int age;
    
      Person(this.name, this.age);
    }
  4. 사용하기

    // 데이터베이스 열기
    final box = await Hive.openBox('myBox');
    
    // 데이터 추가
    final person = Person('John', 30);
    await box.put('john', person);
    
    // 데이터 읽기
    final storedPerson = await box.get('john') as Person;
    print(storedPerson.name);  // John
    
    // 데이터 업데이트
    person.age = 31;
    await box.put('john', person);
    
    // 데이터 삭제
    await box.delete('john');
    
    // 데이터베이스 닫기
    await box.close();

도전하기 코드

  • hive 사용하는 부분에서 계속 에러가 나는데 새벽까지 코드를 만져봐도 만지면 만질수록 코드가 더럽혀지는 느낌이 들어서 평일 중으로 처음부터 다시 짜보고, 해결한 뒤에 추가하겠습니다...

주간평가

  • 4주차 주간과제 (비밀 앱 SNS형) 다음의 앱을 똑같이 구현하세요. 비밀듣는 고양이 API를 활용하여 다음과 같이 만들 수 있습니다.

Requirements

  1. 주어진 패키지를 활용하여 비밀공유 앱을 제작합니다.

  2. 구현이 되어야 하는 기능은 다음과 같습니다.

    • BottomSheet
    • Drawer
    • FAB
    • 밑으로 당겨서 새로고침 기능
    • 비밀 수 만큼 생성되는 커스텀 위젯(SecretCard) 생성
  3. 이 때 사용된 의존성 패키지는 다음과 같습니다.

    // ... pubspec.yaml 파일 일부입니다.
    dependencies:
      flutter:
        sdk: flutter
      animated_bottom_navigation_bar: ^1.1.0+1
      cupertino_icons: ^1.0.2
      font_awesome_flutter: ^10.4.0
      intl: ^0.18.0
      pull_to_refresh: ^2.0.0
      secret_cat_sdk: ^0.0.5+2
  4. 커스텀 위젯(SecretCard)의 조각코드가 제공됩니다. 다음의 코드를 필수로 사용하세요.

    class SecretCard extends StatelessWidget {
      const SecretCard({super.key, required this.secret});
      final Secret secret;
    
      
      Widget build(BuildContext context) {
        ...

주간평가 코드

  • main.dart
    import 'package:flutter/material.dart';
    import 'secret_cat.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(fontFamily: 'neo'),
          home: SecretCat()
        );
      }
    }
  • secret_cat_page.dart
    import 'package:animated_bottom_navigation_bar/animated_bottom_navigation_bar.dart';
    import 'package:secret_cat_sdk/api/api.dart';
    import 'author_screen.dart';
    import 'secret_screen.dart';
    import 'package:flutter/material.dart';
    import 'package:font_awesome_flutter/font_awesome_flutter.dart';
    
    class SecretCatPage extends StatefulWidget {
      const SecretCatPage({Key? key}) : super(key: key);
    
      
      State<SecretCatPage> createState() => _SecretCatPageState();
    }
    
    class _SecretCatPageState extends State<SecretCatPage> {
    
      int screenIdx = 0;
      List<Widget> screens = [SecretScreen(), AuthorScreen()];
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: false,
            title: Text('비밀듣는 고양이'),
            backgroundColor: Colors.transparent,
            foregroundColor: Colors.black,
            elevation: 0,
          ),
          drawer: Drawer(
            child: SafeArea(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  SizedBox(height:10),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text('비밀듣는 고양이 (SNS형)\n스나이퍼팩토리 교육용 앱'),
                  ),
                  SizedBox(height: 100),
                  Divider(),
                  ListTile(
                    leading: Icon(FontAwesomeIcons.dev),
                    title: Text('Teddy'),
                  )
                ],
              ),
            ),
          ),
          body: screens[screenIdx],
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              showModalBottomSheet(
                context: context,
                isScrollControlled: true,
                builder: (context) {
                  return BottomSheet();
                },
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(20.0),
                    topRight: Radius.circular(20.0),
                  ),
                ),
              );
            },
            backgroundColor: Colors.deepOrange,
            child: Icon(Icons.add),
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
          bottomNavigationBar: AnimatedBottomNavigationBar(
            icons: [
              FontAwesomeIcons.cat,
              FontAwesomeIcons.peopleGroup,
            ],
            activeIndex: screenIdx,
            gapLocation: GapLocation.center,
            onTap: (tapIdx) {
              setState(() {
                screenIdx = tapIdx;
              });
            },
          ),
        );
      }
    }
    
    // bottom sheet
    class BottomSheet extends StatelessWidget {
      const BottomSheet({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        TextEditingController textEditingController = TextEditingController();
    
        return Padding(
          // 키보드 올라오면 bottom sheet 가려지지 않게 bottom padding 설정
          padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
          child: Container(
            height: 200,
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20.0),
                topRight: Radius.circular(20.0),
              ),
            ),
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text('비밀을 공유해볼까요?'),
                  SizedBox(height: 20),
                  TextField(
                    controller: textEditingController,
                    decoration: InputDecoration(
                      hintText: '비밀을 입력하세요',
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10)
                      )
                    ),
                  ),
                  SizedBox(height: 10),
                  ElevatedButton(
                    child: Text('공유하기'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.deepOrange,
                      minimumSize: Size(double.infinity, 40)
                    ),
                    onPressed: (){
                      SecretCatApi.addSecret(textEditingController.text);
                      textEditingController.text = '';
                      Navigator.pop(context);
                    },
                  ),
                ],
              ),
            )
          ),
        );
      }
    }
  • secret_screen.dart
    import 'package:flutter/material.dart';
    import 'package:pull_to_refresh/pull_to_refresh.dart';
    import 'package:secret_cat_sdk/api/api.dart';
    
    import 'SecretCard.dart';
    
    class SecretScreen extends StatefulWidget {
      const SecretScreen({Key? key}) : super(key: key);
    
      
      State<SecretScreen> createState() => _SecretScreenState();
    }
    
    class _SecretScreenState extends State<SecretScreen> {
      RefreshController refreshController = RefreshController(initialRefresh: false);
    
      // 새로고침
      void onRefresh() async {
        setState(() {});
        refreshController.refreshCompleted();
      }
    
      
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: SecretCatApi.fetchSecrets(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return SmartRefresher(
                controller: refreshController,
                enablePullDown: true,
                onRefresh: onRefresh,
                header: WaterDropHeader(),
                child: ListView.builder(
                  itemCount: snapshot.data!.length,
                  itemBuilder: (context, index) {
                    return Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: SecretCard(secret: snapshot.data![index]),
                    );
                  },
                ),
              );
            } else {
              return Center(child: CircularProgressIndicator());
            }
          },
    
        );
      }
    }
  • SecretCard.dart
    import 'package:flutter/material.dart';
    import 'package:intl/intl.dart';
    import 'package:secret_cat_sdk/model/secret.dart';
    
    class SecretCard extends StatelessWidget {
      const SecretCard({Key? key, required this.secret}) : super(key: key);
      final Secret secret;
    
      
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(10),
              boxShadow: const [
                BoxShadow(
                  color: Colors.black12,
                  blurRadius: 10,
                  spreadRadius: 1,
                )
              ]
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              ListTile(
                leading: CircleAvatar(
                  backgroundColor: Colors.black12,
                ),
                title: Text(secret.author?.name ?? '익명의 누군가'),
                subtitle: Text(DateFormat('EEE, MM/d').format(secret.createdAt)),
              ),
              Divider(),
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(secret.secret),
              )
            ],
          ),
        );
      }
    }
  • author_screen.dart
    import 'package:flutter/material.dart';
    import 'package:pull_to_refresh/pull_to_refresh.dart';
    import 'package:secret_cat_sdk/api/api.dart';
    
    class AuthorScreen extends StatefulWidget {
      const AuthorScreen({Key? key}) : super(key: key);
    
      
      State<AuthorScreen> createState() => _AuthorScreenState();
    }
    
    class _AuthorScreenState extends State<AuthorScreen> {
      RefreshController refreshController = RefreshController(initialRefresh: false);
    
      // 새로고침
      void onRefresh() async {
        setState(() {});
        refreshController.refreshCompleted();
      }
    
      
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: SecretCatApi.fetchAuthors(),
          builder: (context, snapshot) {
            if(snapshot.connectionState == ConnectionState.done) {
              return SmartRefresher(
                controller: refreshController,
                enablePullDown: true,
                onRefresh: onRefresh,
                header: WaterDropHeader(),
                child: ListView.builder(
                  itemCount: snapshot.data!.length,
                  itemBuilder: (context, index) => ListTile(
                    leading: CircleAvatar(
                      backgroundImage: NetworkImage(snapshot.data![index].avatar!),
                      backgroundColor: Colors.deepOrange,
                    ),
                    title: Text(snapshot.data![index].name),
                  )
                ),
              );
            } else {
              return Center(child: CircularProgressIndicator());
            }
          }
        );
      }
    }

4주차 끝

재밋다....................................................

profile
우와재밋다

0개의 댓글