16일차 과제 활용 문제
다음 기능들을 구현해봅시다!
16일차 과제를 활용하시기 바랍니다.
또한 디자인은 모두 예시이니 디자인이 다르더라도 동일한 기능이라면 인정됩니다.
- 도전하기는 필수평가는 아니지만, 제출시 추가 점수가 있을 수 있습니다. *
- AppBar title을 누르면 GridView의 스크롤이 최상단으로 이동합니다.
(단 애니메이션이 적용되어 부드럽게 올라가야합니다.)
- 앱을 시작할 때 뜨는 Splash 화면 적용해봅시다
첨부된 강아지 사진을 이용해도 좋고 다른파일을 이용해도 좋습니다.
좋아요 아이콘 옆 댓글 아이콘을 누르면 새로운 페이지로 넘어가게되고, Hero 애니메이션을 적용하여 사진이 같이 이동합니다.
pull to refresh하면 shimmer가 나오고 그 후 데이터가 불러오면 부드럽게 전환이 되게 합시다.
Duration은 2초로 설정해주세요.
- 좋아요를 누르면 회색하트가 빨간색으로 변합니다. 우측상단 하트를 누르면 bottomSheet가 등장하여 좋아요 리스트를 볼 수 있습니다. 또한 앱을 종료 후 재실행 하더라도, 좋아요 한 것은 유지가 됩니다.
(패키지 Hive 이용)
- 우측 상단의 X 를 누르면 좋아요가 모두 삭제가 됩니다. 단 하트가 빨간색에서 회색으로만 바뀌어야하고 화면 모두 새로고침이 되면 안됩니다.
패키지 pubspec.yaml
파일에 추가
dependencies:
hive: ^[version]
hive_flutter: ^[version]
dev_dependencies:
hive_generator: ^[version]
build_runner: ^[version]
앱 시작 시 Hive를 초기화
void main() async {
await Hive.initFlutter();
// ...
}
데이터 모델 클래스를 만들기
import 'package:hive/hive.dart';
part 'person.g.dart';
(typeId: 0)
class Person {
(0)
String name;
(1)
int age;
Person(this.name, this.age);
}
사용하기
// 데이터베이스 열기
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();
주어진 패키지를 활용하여 비밀공유 앱을 제작합니다.
구현이 되어야 하는 기능은 다음과 같습니다.
이 때 사용된 의존성 패키지는 다음과 같습니다.
// ... 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
커스텀 위젯(SecretCard)의 조각코드가 제공됩니다. 다음의 코드를 필수로 사용하세요.
class SecretCard extends StatelessWidget {
const SecretCard({super.key, required this.secret});
final Secret secret;
Widget build(BuildContext context) {
...
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()
);
}
}
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);
},
),
],
),
)
),
);
}
}
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());
}
},
);
}
}
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),
)
],
),
);
}
}
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());
}
}
);
}
}
재밋다....................................................