Skip to content

Commit c3c6fca

Browse files
committed
refactor!: major refactor Flutter
- Use a single MaterialApp to keep the same state - Use a named route for better cache - Use Provider for dependency injection - Fix widget & integration tests - Enable linter - Split the big files into *_screen.dart
1 parent f070abc commit c3c6fca

File tree

19 files changed

+740
-392
lines changed

19 files changed

+740
-392
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ jobs:
2121
with:
2222
channel: "stable"
2323
- run: flutter pub get
24+
- run: flutter pub run build_runner build
25+
- run: flutter analyze
2426
- run: flutter test
2527
- run: flutter build apk
2628

@@ -60,4 +62,4 @@ jobs:
6062
with:
6163
name: kkweon
6264

63-
- uses: okteto/pipeline@master
65+
- uses: okteto/pipeline@master

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.idea
22
.DS_Store
33
.vscode
4+
/client/test/**/*_test.mocks.dart

Makefile

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ifeq ($(UNAME),Darwin)
1616
else ifeq ($(UNAME), Linux)
1717
PROTOC_FULL_URL := $(PROTOC_URL)/$(PROTOC_ZIP_LINUX)
1818
PROTOC_FILE := $(PROTOC_ZIP_LINUX)
19-
else ifeq($(UNAME), Windows)
19+
else ifeq ($(UNAME), Windows)
2020
PROTOC_FULL_URL := $(PROTOC_URL)/$(PROTOC_ZIP_WINDOWS)
2121
PROTOC_FILE := $(PROTOC_ZIP_WINDOWS)
2222
endif
@@ -57,7 +57,16 @@ test: test.go test.dart
5757

5858
.PHONY: test.dart
5959
test.dart:
60-
cd client && flutter test
60+
cd client && flutter pub run build_runner build && flutter analyze && flutter test
61+
62+
.PHONY: test.dart-e2e
63+
test.dart-e2e:
64+
cd client && flutter drive --target=test_driver/app.dart
65+
66+
.PHONY: format.dart
67+
format.dart:
68+
cd client && flutter format .
69+
make gen.dart
6170

6271
.PHONY: test.go
6372
test.go: lint.go
@@ -76,4 +85,4 @@ lint.go:
7685
.PHONY: clean
7786
clean:
7887
rm -rf ./client/lib/protos
79-
rm -rf ./server/pkg/pr12er/*.pb.go
88+
rm -rf ./server/pkg/pr12er/*.pb.go

client/analysis_options.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include: package:lint/analysis_options.yaml
2+
analyzer:
3+
exclude:
4+
- lib/**/*.pb*.dart

client/lib/detail.dart

Lines changed: 0 additions & 107 deletions
This file was deleted.

client/lib/main.dart

Lines changed: 21 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,32 @@
11
import 'package:flutter/material.dart';
2-
import 'detail.dart';
3-
import 'service.dart';
4-
import 'package:pr12er/protos/pkg/pr12er/messages.pb.dart';
2+
import 'package:pr12er/service.dart';
3+
import 'package:provider/provider.dart';
54

6-
void main() => runApp(MyApp());
5+
import 'screens/detail_screen.dart';
6+
import 'screens/main_screen.dart';
77

8-
class MyApp extends StatelessWidget {
9-
@override
10-
Widget build(BuildContext context) {
11-
return MaterialApp(
12-
title: 'Welcome to Flutter',
13-
home: Scaffold(
14-
appBar: AppBar(
15-
leading: Icon(Icons.search),
16-
title: Text('pr12errrrrrrrrrrrrrrrrrrr'),
17-
),
18-
body: Client()),
19-
);
20-
}
21-
}
8+
const appName = 'PR12er';
229

23-
class Client extends StatefulWidget {
24-
@override
25-
_ClientState createState() => _ClientState();
26-
}
10+
void main() => runApp(MultiProvider(providers: [
11+
Provider(
12+
create: (context) => GrpcClient(),
13+
)
14+
], child: const MainApp()));
2715

28-
class _ClientState extends State<Client> {
29-
List<Video> videos = [];
30-
final myController = TextEditingController();
31-
32-
Future<List<Video>> _fetchListItems() async {
33-
List<Video> videos = await GrpcMsgSender().getVideos();
34-
return videos;
35-
}
36-
37-
List<Widget> getCategoryWidgets(Category category) {
38-
switch (category) {
39-
case Category.CATEGORY_VISION:
40-
return [Icon(Icons.remove_red_eye), Text('CV')];
41-
case Category.CATEGORY_NLP:
42-
return [Icon(Icons.translate), Text('NLP')];
43-
case Category.CATEGORY_AUDIO:
44-
return [Icon(Icons.graphic_eq), Text('AUDIO')];
45-
case Category.CATEGORY_RS:
46-
return [Icon(Icons.assistant), Text('RS')];
47-
case Category.CATEGORY_OCR:
48-
return [Icon(Icons.text_fields), Text('OCR')];
49-
case Category.CATEGORY_UNSPECIFIED:
50-
default:
51-
return [Icon(Icons.grid_view), Text('ETC')];
52-
}
53-
}
16+
class MainApp extends StatelessWidget {
17+
const MainApp({
18+
Key? key,
19+
}) : super(key: key);
5420

5521
@override
5622
Widget build(BuildContext context) {
57-
return FutureBuilder(
58-
future: _fetchListItems(),
59-
builder: (context, AsyncSnapshot snapshot) {
60-
if (!snapshot.hasData) {
61-
return Center(child: CircularProgressIndicator());
62-
}
63-
return ListView.builder(
64-
padding: const EdgeInsets.all(8),
65-
itemCount: snapshot.data.length,
66-
itemBuilder: (BuildContext context, int index) {
67-
return getTile(index,
68-
title: snapshot.data[index].title,
69-
presenter: snapshot.data[index].presenter,
70-
keyword: "현재 키워드 지원(X)",
71-
category: snapshot.data[index].category,
72-
url: snapshot.data[index].link);
73-
});
74-
});
75-
}
76-
77-
Widget getTile(int index,
78-
{String title = "",
79-
String presenter = "",
80-
String keyword = "",
81-
Category category = Category.CATEGORY_UNSPECIFIED,
82-
String url = ""}) {
83-
return Card(
84-
child: ListTile(
85-
leading: Column(
86-
mainAxisAlignment: MainAxisAlignment.center,
87-
children: getCategoryWidgets(category)),
88-
title: Text('${title}'),
89-
subtitle: Padding(
90-
padding: EdgeInsets.only(top: 10),
91-
child: Row(children: [
92-
Column(children: [Text(presenter)]),
93-
Padding(
94-
padding: EdgeInsets.only(left: 30),
95-
child: Column(children: [
96-
Text(
97-
keyword,
98-
style: TextStyle(fontStyle: FontStyle.italic),
99-
)
100-
]))
101-
])),
102-
trailing: Column(
103-
mainAxisAlignment: MainAxisAlignment.center,
104-
children: [Icon(Icons.favorite_border_outlined)]),
105-
onTap: () {
106-
Navigator.push(
107-
context,
108-
MaterialPageRoute(
109-
builder: (context) => DetailApp(url),
110-
),
111-
);
112-
},
113-
),
23+
return MaterialApp(
24+
title: appName,
25+
initialRoute: MainScreen.routeName,
26+
routes: {
27+
MainScreen.routeName: (context) => MainScreen(),
28+
DetailScreen.routeName: (context) => DetailScreen(),
29+
},
11430
);
11531
}
11632
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:pr12er/protos/pkg/pr12er/messages.pb.dart';
3+
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
4+
import 'package:pr12er/utils/extractor.dart';
5+
6+
class DetailScreenArguments {
7+
final Video video;
8+
9+
DetailScreenArguments(this.video);
10+
}
11+
12+
class DetailScreen extends StatelessWidget {
13+
static const String routeName = "detail_app";
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
final args =
18+
// ignore: cast_nullable_to_non_nullable
19+
ModalRoute.of(context)!.settings.arguments as DetailScreenArguments;
20+
21+
return Scaffold(
22+
appBar: AppBar(
23+
leading: IconButton(
24+
icon: const Icon(Icons.arrow_back),
25+
onPressed: () {
26+
Navigator.pop(context);
27+
},
28+
),
29+
title: Text(args.video.title,
30+
key: const ValueKey("$routeName/appBar/title")),
31+
),
32+
body: Detail(youtubeId: extractYoutubeId(args.video.link)));
33+
}
34+
}
35+
36+
class Detail extends StatefulWidget {
37+
final String youtubeId;
38+
39+
const Detail({required this.youtubeId});
40+
41+
@override
42+
_DetailState createState() => _DetailState();
43+
}
44+
45+
class _DetailState extends State<Detail> {
46+
late final YoutubePlayerController _controller;
47+
48+
@override
49+
void initState() {
50+
super.initState();
51+
52+
_controller = YoutubePlayerController(
53+
initialVideoId: widget.youtubeId,
54+
);
55+
}
56+
57+
@override
58+
Widget build(BuildContext context) {
59+
return YoutubePlayerBuilder(
60+
player: YoutubePlayer(
61+
controller: _controller,
62+
),
63+
builder: (context, player) {
64+
return Column(
65+
children: [player],
66+
);
67+
},
68+
);
69+
}
70+
}

0 commit comments

Comments
 (0)