@@ -7,6 +7,31 @@ import 'dart:js_interop';
77import 'package:markdown_widget/markdown_widget.dart' ;
88import 'package:sando_diary/model/post_meta.dart' ;
99
10+ class Item {
11+ Item ({
12+ required this .headerValue,
13+ this .expandedValues = const [],
14+ this .isExpanded = false ,
15+ });
16+
17+ String headerValue;
18+ List <String > expandedValues;
19+ bool isExpanded;
20+
21+ bool get hasExpandedValues => expandedValues.isNotEmpty;
22+ }
23+
24+ List <Item > generateItems (List <String > categories) {
25+ categories.insert (0 , 'All' );
26+ return [
27+ Item (
28+ headerValue: 'Post Categories' ,
29+ expandedValues: categories,
30+ isExpanded: false ,
31+ )
32+ ];
33+ }
34+
1035class PostListPage extends StatefulWidget {
1136 const PostListPage ({super .key});
1237 @override
@@ -19,11 +44,22 @@ class _PostListPageState extends State<PostListPage> {
1944 bool isShowPostDetail = false ;
2045 late MdDoc currentPost;
2146
47+ List <Item > _data = [];
48+ List <String > categories = [];
49+
2250 @override
2351 void initState () {
2452 super .initState ();
2553 future = loadMarkdownDocs (context);
2654 df = DateFormat ('yyyy-MM-dd' ); // 한국어 로케일이면 intl 초기화 추가 가능
55+
56+ future.then ((docs) {
57+ final cats = docs.map ((d) => d.meta.category).toSet ().toList ();
58+ setState (() {
59+ categories = cats;
60+ _data = generateItems (categories);
61+ });
62+ });
2763 }
2864
2965 void _launchURLInNewTab (String url) {
@@ -73,33 +109,44 @@ class _PostListPageState extends State<PostListPage> {
73109
74110 @override
75111 Widget build (BuildContext context) {
76- return Scaffold (
77- appBar: ! isShowPostDetail
78- ? AppBar (
79- title: const Text ('Blog Posts' ),
80- )
81- : AppBar (
82- elevation: 0 ,
83- scrolledUnderElevation: 0 ,
84- shadowColor: Colors .transparent,
85- surfaceTintColor: Colors .transparent,
86- forceMaterialTransparency: true ,
87- title: Text (currentPost.meta.title),
88- leading: BackButton (
89- onPressed: () => setState (() {
90- isShowPostDetail = false ;
91- web.window.history.pushState (null , 'Posts' , '/' );
92- }),
93- ),
94- actions: [
95- IconButton (
96- icon: const Icon (Icons .share),
97- tooltip: 'Share this Post' ,
98- onPressed: _copyCurrentPostUrlToClipboard,
99- ),
100- ],
101- ),
102- body: ! isShowPostDetail
112+ Widget _buildPanel () {
113+ return ExpansionPanelList (
114+ expansionCallback: (int index, bool isExpanded) {
115+ setState (() {
116+ _data[index].isExpanded = isExpanded;
117+ });
118+ },
119+ children: _data.map <ExpansionPanel >((Item item) {
120+ return ExpansionPanel (
121+ canTapOnHeader: true ,
122+ headerBuilder: (BuildContext context, bool isExpanded) {
123+ return ListTile (title: Text (item.headerValue));
124+ },
125+ body: item.hasExpandedValues
126+ ? Column (
127+ children: item.expandedValues.map ((value) {
128+ return ListTile (
129+ title: Text (value),
130+ onTap: () {
131+ // TODO: Implement category filtering
132+ print ('Tapped on $value ' );
133+ },
134+ trailing: const Icon (Icons .chevron_right),
135+ );
136+ }).toList (),
137+ )
138+ : const SizedBox .shrink (),
139+ isExpanded: item.isExpanded,
140+ );
141+ }).toList (),
142+ );
143+ }
144+
145+ Widget _buildPostList (String ? category) {
146+ // all 카테고리면 전체, 아니면 해당 카테고리만 필터링
147+ // til 카테고리는 따로 구현?
148+
149+ return ! isShowPostDetail
103150 ? FutureBuilder <List <MdDoc >>(
104151 future: future,
105152 builder: (context, snap) {
@@ -137,7 +184,87 @@ class _PostListPageState extends State<PostListPage> {
137184 );
138185 },
139186 )
140- : _postDetail (currentPost),
141- );
187+ : _postDetail (currentPost);
188+ }
189+
190+ return Scaffold (
191+ appBar: ! isShowPostDetail
192+ ? AppBar (
193+ title: const Text ('Blog Posts' ),
194+ )
195+ : AppBar (
196+ elevation: 0 ,
197+ scrolledUnderElevation: 0 ,
198+ shadowColor: Colors .transparent,
199+ surfaceTintColor: Colors .transparent,
200+ forceMaterialTransparency: true ,
201+ title: Text (currentPost.meta.title),
202+ leading: BackButton (
203+ onPressed: () => setState (() {
204+ isShowPostDetail = false ;
205+ web.window.history.pushState (null , 'Posts' , '/' );
206+ }),
207+ ),
208+ actions: [
209+ IconButton (
210+ icon: const Icon (Icons .share),
211+ tooltip: 'Share this Post' ,
212+ onPressed: _copyCurrentPostUrlToClipboard,
213+ ),
214+ ],
215+ ),
216+ body: LayoutBuilder (
217+ builder: (BuildContext context, BoxConstraints constraints) {
218+ double width = constraints.maxWidth;
219+ if (width > 600 ) {
220+ return Row (
221+ children: [
222+ SizedBox (
223+ width: width * 0.3 ,
224+ child: Column (
225+ children: [
226+ _buildPanel (),
227+ ListTile (
228+ title: const Text ('TIL (Today I Learned)' ),
229+ onTap: () {
230+ // TODO: Implement TIL navigation
231+ },
232+ ),
233+ ],
234+ ),
235+ ),
236+ VerticalDivider (color: Colors .grey[400 ], width: 3 ),
237+ Expanded (child: _buildPostList ('All' )),
238+ ],
239+ );
240+ } else {
241+ return Column (
242+ children: [
243+ Visibility (
244+ visible: ! isShowPostDetail,
245+ child: Column (
246+ children: [
247+ _buildPanel (),
248+ ListTile (
249+ title: const Text ('TIL (Today I Learned)' ),
250+ onTap: () {
251+ // TODO: Implement TIL navigation
252+ },
253+ ),
254+ ],
255+ ),
256+ ),
257+ Divider (color: Colors .grey[400 ], height: 3 ),
258+ SizedBox (height: 16 ),
259+ Text ('Recent Posts' ,
260+ style: TextStyle (
261+ fontWeight: FontWeight .bold,
262+ fontSize: 20 ,
263+ color: Colors .grey[700 ])),
264+ Expanded (child: _buildPostList ('All' )),
265+ ],
266+ );
267+ }
268+ }));
142269 }
143270}
0 commit comments