Skip to content

Instantly share code, notes, and snippets.

@jinyongp
Last active June 29, 2023 11:24
Show Gist options
  • Save jinyongp/beb656ed3cd6059afac59277a1d6ade8 to your computer and use it in GitHub Desktop.
Save jinyongp/beb656ed3cd6059afac59277a1d6ade8 to your computer and use it in GitHub Desktop.
Flutter MyMemo App (CRUD)
// ignore_for_file: prefer_const_constructors, must_be_immutable
import 'dart:convert';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
late SharedPreferences pref;
if (!kIsWeb) {
pref = await SharedPreferences.getInstance();
}
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => MemoService(
save: (String payload) async => await pref.setString("memo", payload),
load: () async => pref.getString("memo"),
),
),
],
child: const MyApp(),
));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
// 홈 페이지
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Consumer<MemoService>(builder: (context, memoService, child) {
return Scaffold(
appBar: AppBar(
title: Text("My Memo"),
),
body: memoService.isEmpty
? Center(child: Text("메모를 작성해주세요"))
: ListView.builder(
itemCount: memoService.count,
itemBuilder: (context, index) {
return memoListItem(index, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetailPage(index: index)),
);
});
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetailPage()),
);
},
),
);
});
}
Widget memoListItem(int index, void Function()? onTap) {
MemoService memoService = context.read<MemoService>();
Memo memo = memoService.memo(index);
DateTime updatedAt = DateTime.fromMillisecondsSinceEpoch(memo.updatedAt);
String formattedUpdatedAt =
DateFormat("yy년 MM월 dd일\nHH시 mm분 ss초").format(updatedAt);
return ListTile(
leading: IconButton(
icon: Icon(memo.pinned ? CupertinoIcons.pin_fill : CupertinoIcons.pin),
onPressed: () {
if (memo.pinned) {
memoService.unpin(index);
} else {
memoService.pin(index);
}
},
),
title: Text(
memo.content,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
onTap: onTap,
trailing: Text(
formattedUpdatedAt,
style: TextStyle(fontSize: 12),
textAlign: TextAlign.right,
),
);
}
}
// 메모 생성 및 수정 페이지
class DetailPage extends StatelessWidget {
DetailPage({super.key, this.index});
final int? index;
TextEditingController contentController = TextEditingController();
@override
Widget build(BuildContext context) {
final bool isEditMode = index != null;
MemoService memoService = context.read<MemoService>();
contentController.text = isEditMode ? memoService.memo(index!).content : "";
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
if (contentController.text.isEmpty) {
if (isEditMode) {
memoService.delete(index!);
}
} else {
if (isEditMode) {
memoService.update(index!, contentController.text);
} else {
memoService.create(contentController.text);
}
}
Navigator.pop(context);
},
icon: Icon(CupertinoIcons.back),
),
actions: [
IconButton(
onPressed: () {
showDeleteDialog(context, index ?? -1);
},
icon: Icon(Icons.delete),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
controller: contentController,
decoration: InputDecoration(
hintText: "메모를 입력하세요",
border: InputBorder.none,
),
autofocus: true,
maxLines: null,
expands: true,
keyboardType: TextInputType.multiline,
),
),
);
}
void showDeleteDialog(BuildContext context, int index) {
MemoService memoService = context.read<MemoService>();
showDialog(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text("메모 삭제"),
content: Text("정말로 삭제하시겠습니까?"),
actions: [
CupertinoDialogAction(
child: Text("취소"),
onPressed: () {
Navigator.pop(context);
},
),
CupertinoDialogAction(
child: Text("삭제"),
onPressed: () {
try {
memoService.delete(index);
} finally {
Navigator.pop(context);
Navigator.pop(context);
}
},
),
],
);
},
);
}
}
class Memo {
String _content;
int? _pinnedAt;
int _createdAt = 0;
int _updatedAt = 0;
Memo({
required String content,
int? pinnedAt,
int? createdAt,
int? updatedAt,
}) : _content = content,
_pinnedAt = pinnedAt {
int ts = DateTime.now().millisecondsSinceEpoch;
_createdAt = createdAt ?? ts;
_updatedAt = updatedAt ?? ts;
}
factory Memo.create(String content) {
return Memo(content: content);
}
String get content {
return _content;
}
get pinned {
return _pinnedAt != null;
}
int get updatedAt {
return _updatedAt;
}
void update(String content) {
_content = content;
_updatedAt = DateTime.now().millisecondsSinceEpoch;
}
void pin() {
_pinnedAt = DateTime.now().millisecondsSinceEpoch;
}
void unpin() {
_pinnedAt = null;
}
int compareTo(Memo other) {
if (pinned && other.pinned) {
return _pinnedAt! - other._pinnedAt!;
} else if (pinned) {
return -1;
} else if (other.pinned) {
return 1;
}
return other.updatedAt - updatedAt;
}
Map<String, dynamic> toJson() {
return {
'content': _content,
'pinnedAt': _pinnedAt,
'createdAt': _createdAt,
'updatedAt': _updatedAt,
};
}
factory Memo.fromJson(Map<String, dynamic> json) {
return Memo(
content: json['content'],
pinnedAt: json['pinnedAt'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
);
}
}
class MemoService extends ChangeNotifier {
final List<Memo> _memos = [];
final Future<void> Function(String payload)? save;
final Future<String?> Function()? load;
MemoService({
this.save,
this.load,
}) {
try {
_load();
} catch (error) {
// ignore
}
}
Memo get last => _memos.last;
bool get isEmpty => _memos.isEmpty;
int get count => _memos.length;
Memo memo(int index) => _memos.elementAt(index);
void create(String content) {
_memos.add(Memo.create(content));
postprocess();
}
void update(int index, String content) {
memo(index).update(content);
postprocess();
}
void delete(int index) {
_memos.remove(memo(index));
postprocess();
}
void pin(int index) {
memo(index).pin();
postprocess();
}
void unpin(int index) {
memo(index).unpin();
postprocess();
}
void postprocess() {
_memos.sort((a, b) => a.compareTo(b));
notifyListeners();
_save();
}
Future<void> _save() {
if (save == null) return Future.value();
String payload = jsonEncode(_memos.map((m) => m.toJson()).toList());
return save!(payload);
}
Future<void> _load() async {
if (load == null) return;
String? payload = await load!();
if (payload == null) return;
_memos.clear();
_memos.addAll(jsonDecode(payload).map((e) => Memo.fromJson(e)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment