Skip to content

Commit 0fb48ee

Browse files
authored
feat: add bookmark feature (#143)
* add bookmarking feature with bottom nav bar * remove unused import
1 parent 63da367 commit 0fb48ee

8 files changed

+421
-115
lines changed

client/ios/Podfile.lock

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ PODS:
1212
- Flutter
1313
- share_plus (0.0.1):
1414
- Flutter
15+
- shared_preferences (0.0.1):
16+
- Flutter
1517
- url_launcher (0.0.1):
1618
- Flutter
1719

@@ -20,6 +22,7 @@ DEPENDENCIES:
2022
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
2123
- path_provider (from `.symlinks/plugins/path_provider/ios`)
2224
- share_plus (from `.symlinks/plugins/share_plus/ios`)
25+
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
2326
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
2427

2528
SPEC REPOS:
@@ -35,6 +38,8 @@ EXTERNAL SOURCES:
3538
:path: ".symlinks/plugins/path_provider/ios"
3639
share_plus:
3740
:path: ".symlinks/plugins/share_plus/ios"
41+
shared_preferences:
42+
:path: ".symlinks/plugins/shared_preferences/ios"
3843
url_launcher:
3944
:path: ".symlinks/plugins/url_launcher/ios"
4045

@@ -44,8 +49,9 @@ SPEC CHECKSUMS:
4449
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
4550
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
4651
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
52+
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
4753
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
4854

4955
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
5056

51-
COCOAPODS: 1.10.1
57+
COCOAPODS: 1.10.0

client/lib/screens/main_screen.dart

+36-72
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/widgets.dart';
3-
import 'package:pr12er/custom_theme.dart';
4-
import 'package:pr12er/widgets/main/pr12video.dart';
5-
import 'package:pr12er/widgets/main/report.dart';
6-
import 'package:pr12er/widgets/main/video_search_delegate.dart';
3+
import 'package:pr12er/screens/main_screen_all.dart';
4+
import 'package:pr12er/screens/main_screen_bookmark.dart';
75
import 'package:provider/provider.dart';
8-
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
96

107
import '../protos/pkg/pr12er/messages.pb.dart';
118
import '../service.dart';
@@ -14,72 +11,17 @@ const appName = 'PR12er';
1411

1512
enum VertMenu { themeMode, issueReport }
1613

17-
class MainScreen extends StatelessWidget {
14+
class MainScreen extends StatefulWidget {
1815
static const String routeName = "main_screen";
19-
final VideoSearchDelegate videoSearchDelegate = VideoSearchDelegate();
2016

2117
@override
22-
Widget build(BuildContext context) {
23-
return Scaffold(
24-
appBar: AppBar(
25-
actions: <Widget>[
26-
IconButton(
27-
icon: const Icon(Icons.search),
28-
onPressed: () {
29-
// do something
30-
showSearch(context: context, delegate: videoSearchDelegate);
31-
},
32-
),
33-
const SizedBox(
34-
width: 5,
35-
),
36-
PopupMenuButton(
37-
itemBuilder: (BuildContext context) => <PopupMenuEntry<VertMenu>>[
38-
PopupMenuItem<VertMenu>(
39-
value: VertMenu.themeMode,
40-
child: ListTile(
41-
leading: context.read<CustomTheme>().icon,
42-
title: context.read<CustomTheme>().text)),
43-
const PopupMenuDivider(height: 5),
44-
const PopupMenuItem<VertMenu>(
45-
value: VertMenu.issueReport,
46-
child: ListTile(
47-
leading: Icon(
48-
Icons.report,
49-
),
50-
title: Text("이슈 리포트"))),
51-
],
52-
onSelected: (value) {
53-
switch (value) {
54-
case VertMenu.themeMode:
55-
context.read<CustomTheme>().toggleMode();
56-
break;
57-
case VertMenu.issueReport:
58-
showMaterialModalBottomSheet(
59-
context: context,
60-
builder: (context) => const ReportWidget());
61-
break;
62-
}
63-
},
64-
child: const Icon(Icons.more_vert),
65-
),
66-
const SizedBox(
67-
width: 15,
68-
)
69-
// IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert))
70-
],
71-
title: const Text(appName),
72-
),
73-
body: PRVideos(videoSearchDelegate: videoSearchDelegate),
74-
);
75-
}
18+
_MainScreenState createState() => _MainScreenState();
7619
}
7720

78-
class PRVideos extends StatelessWidget {
79-
final VideoSearchDelegate videoSearchDelegate;
21+
class _MainScreenState extends State<MainScreen> {
22+
late List<Video> cleanList;
8023

81-
const PRVideos({Key? key, required this.videoSearchDelegate})
82-
: super(key: key);
24+
int _selectedBtmNavIndex = 0;
8325

8426
@override
8527
Widget build(BuildContext context) {
@@ -94,13 +36,35 @@ class PRVideos extends StatelessWidget {
9436
.where((video) => video.hasTitle() && video.hasLink())
9537
.toList();
9638

97-
videoSearchDelegate.dataRef = cleanList;
98-
99-
return ListView.builder(
100-
padding: const EdgeInsets.all(8),
101-
itemCount: cleanList.length,
102-
itemBuilder: (BuildContext context, int index) =>
103-
PR12Video(index: index, video: cleanList[index]));
39+
return Scaffold(
40+
body: IndexedStack(
41+
index: _selectedBtmNavIndex,
42+
children: [
43+
MainScreenAll(cleanList: cleanList),
44+
MainScreenBookmark(cleanList: cleanList),
45+
],
46+
),
47+
bottomNavigationBar: BottomNavigationBar(
48+
items: const <BottomNavigationBarItem>[
49+
BottomNavigationBarItem(
50+
icon: Icon(Icons.list),
51+
label: '전체',
52+
),
53+
BottomNavigationBarItem(
54+
icon: Icon(
55+
Icons.bookmark_added,
56+
),
57+
label: '북마크',
58+
),
59+
],
60+
currentIndex: _selectedBtmNavIndex,
61+
selectedItemColor: Colors.amber[800],
62+
onTap: (int index) {
63+
setState(() {
64+
_selectedBtmNavIndex = index;
65+
});
66+
}),
67+
);
10468
});
10569
}
10670
}
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:pr12er/custom_theme.dart';
4+
import 'package:pr12er/widgets/main/pr12video.dart';
5+
import 'package:pr12er/widgets/main/report.dart';
6+
import 'package:pr12er/widgets/main/video_search_delegate.dart';
7+
import 'package:provider/provider.dart';
8+
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
9+
10+
import '../protos/pkg/pr12er/messages.pb.dart';
11+
12+
const appName = 'PR12er';
13+
14+
enum VertMenu { themeMode, issueReport }
15+
16+
class MainScreenAll extends StatefulWidget {
17+
static const String routeName = "main_screen";
18+
final List<Video> cleanList;
19+
20+
const MainScreenAll({Key? key, required this.cleanList}) : super(key: key);
21+
22+
@override
23+
_MainScreenAllState createState() => _MainScreenAllState();
24+
}
25+
26+
class _MainScreenAllState extends State<MainScreenAll> {
27+
final VideoSearchDelegate videoSearchDelegate = VideoSearchDelegate()
28+
..ignoreBookmarkIcon = false;
29+
30+
@override
31+
Widget build(BuildContext context) {
32+
return Scaffold(
33+
appBar: AppBar(
34+
actions: <Widget>[
35+
IconButton(
36+
icon: const Icon(Icons.search),
37+
onPressed: () {
38+
// do something
39+
showSearch(context: context, delegate: videoSearchDelegate);
40+
},
41+
),
42+
const SizedBox(
43+
width: 5,
44+
),
45+
PopupMenuButton(
46+
itemBuilder: (BuildContext context) => <PopupMenuEntry<VertMenu>>[
47+
PopupMenuItem<VertMenu>(
48+
value: VertMenu.themeMode,
49+
child: ListTile(
50+
leading: context.read<CustomTheme>().icon,
51+
title: context.read<CustomTheme>().text)),
52+
const PopupMenuDivider(height: 5),
53+
const PopupMenuItem<VertMenu>(
54+
value: VertMenu.issueReport,
55+
child: ListTile(
56+
leading: Icon(
57+
Icons.report,
58+
),
59+
title: Text("이슈 리포트"))),
60+
],
61+
onSelected: (value) {
62+
switch (value) {
63+
case VertMenu.themeMode:
64+
context.read<CustomTheme>().toggleMode();
65+
break;
66+
case VertMenu.issueReport:
67+
showMaterialModalBottomSheet(
68+
context: context,
69+
builder: (context) => const ReportWidget());
70+
break;
71+
}
72+
},
73+
child: const Icon(Icons.more_vert),
74+
),
75+
const SizedBox(
76+
width: 15,
77+
)
78+
// IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert))
79+
],
80+
title: const Text(appName),
81+
),
82+
body: PRVideos(
83+
cleanList: widget.cleanList,
84+
videoSearchDelegate: videoSearchDelegate,
85+
ignoreBookmarkIcon: false,
86+
),
87+
);
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:pr12er/custom_theme.dart';
4+
import 'package:pr12er/widgets/main/pr12video.dart';
5+
import 'package:pr12er/widgets/main/report.dart';
6+
import 'package:pr12er/widgets/main/video_search_delegate.dart';
7+
import 'package:provider/provider.dart';
8+
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
9+
import 'package:shared_preferences/shared_preferences.dart';
10+
11+
import '../protos/pkg/pr12er/messages.pb.dart';
12+
13+
const appName = 'PR12er';
14+
15+
enum VertMenu { themeMode, issueReport }
16+
17+
class MainScreenBookmark extends StatefulWidget {
18+
static const String routeName = "main_screen";
19+
final List<Video> cleanList;
20+
21+
const MainScreenBookmark({Key? key, required this.cleanList})
22+
: super(key: key);
23+
24+
@override
25+
_MainScreenBookmarkState createState() => _MainScreenBookmarkState();
26+
}
27+
28+
class _MainScreenBookmarkState extends State<MainScreenBookmark> {
29+
final VideoSearchDelegate videoSearchDelegate = VideoSearchDelegate()
30+
..ignoreBookmarkIcon = true;
31+
List<Video> bookmarkVideos = [];
32+
33+
Future<void> getBookmarks() async {
34+
final SharedPreferences prefs = await SharedPreferences.getInstance();
35+
final List<String>? stringIndicies = prefs.getStringList("bookmark");
36+
37+
if (stringIndicies != null) {
38+
final List<Video> tmpBookmarkVideos = [];
39+
40+
for (final String index in stringIndicies) {
41+
tmpBookmarkVideos.add(widget.cleanList[int.parse(index)]);
42+
}
43+
44+
setState(() {
45+
bookmarkVideos = tmpBookmarkVideos;
46+
});
47+
}
48+
}
49+
50+
@override
51+
void initState() {
52+
super.initState();
53+
getBookmarks();
54+
}
55+
56+
@override
57+
Widget build(BuildContext context) {
58+
return Scaffold(
59+
appBar: AppBar(
60+
actions: <Widget>[
61+
IconButton(
62+
icon: const Icon(Icons.search),
63+
onPressed: () {
64+
// do something
65+
showSearch(context: context, delegate: videoSearchDelegate);
66+
},
67+
),
68+
const SizedBox(
69+
width: 5,
70+
),
71+
PopupMenuButton(
72+
itemBuilder: (BuildContext context) => <PopupMenuEntry<VertMenu>>[
73+
PopupMenuItem<VertMenu>(
74+
value: VertMenu.themeMode,
75+
child: ListTile(
76+
leading: context.read<CustomTheme>().icon,
77+
title: context.read<CustomTheme>().text)),
78+
const PopupMenuDivider(height: 5),
79+
const PopupMenuItem<VertMenu>(
80+
value: VertMenu.issueReport,
81+
child: ListTile(
82+
leading: Icon(
83+
Icons.report,
84+
),
85+
title: Text("이슈 리포트"))),
86+
],
87+
onSelected: (value) {
88+
switch (value) {
89+
case VertMenu.themeMode:
90+
context.read<CustomTheme>().toggleMode();
91+
break;
92+
case VertMenu.issueReport:
93+
showMaterialModalBottomSheet(
94+
context: context,
95+
builder: (context) => const ReportWidget());
96+
break;
97+
}
98+
},
99+
child: const Icon(Icons.more_vert),
100+
),
101+
const SizedBox(
102+
width: 15,
103+
)
104+
],
105+
title: const Text(appName),
106+
),
107+
body: RefreshIndicator(
108+
onRefresh: () => getBookmarks(),
109+
child: PRVideos(
110+
cleanList: bookmarkVideos,
111+
videoSearchDelegate: videoSearchDelegate,
112+
ignoreBookmarkIcon: true,
113+
)),
114+
);
115+
}
116+
}

0 commit comments

Comments
 (0)