Skip to content

Commit 1224838

Browse files
committed
feature: word lookup
1 parent a1fa53d commit 1224838

4 files changed

Lines changed: 169 additions & 29 deletions

File tree

lib/model/word.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:nhk_easy/model/word_definition.dart';
2+
3+
class Word {
4+
String idInNews;
5+
6+
String name;
7+
8+
List<WordDefinition> definitions;
9+
10+
Word();
11+
12+
factory Word.fromJson(Map<String, dynamic> json) {
13+
final word = Word();
14+
word.idInNews = json['idInNews'];
15+
word.name = json['name'];
16+
word.definitions = new List<WordDefinition>.from(
17+
json['definitions'].map((x) => WordDefinition.fromJson(x)));
18+
19+
return word;
20+
}
21+
}

lib/model/word_definition.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class WordDefinition {
2+
String definition;
3+
4+
String definitionWithRuby;
5+
6+
WordDefinition();
7+
8+
factory WordDefinition.fromJson(Map<String, dynamic> json) {
9+
final wordDefinition = WordDefinition();
10+
wordDefinition.definition = json['definition'];
11+
wordDefinition.definitionWithRuby = json['definitionWithRuby'];
12+
13+
return wordDefinition;
14+
}
15+
}

lib/service/word_service.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/http.dart' as http;
4+
import 'package:nhk_easy/model/word.dart';
5+
6+
class WordService {
7+
Future<List<Word>> fetchWordList(String newsId) async {
8+
final response =
9+
await http.get('https://nhk.dekiru.app/words?newsId=$newsId');
10+
11+
if (response.statusCode == 200) {
12+
final decoder = Utf8Decoder();
13+
final wordList = List.of(json.decode(decoder.convert(response.bodyBytes)))
14+
.map((news) => Word.fromJson(news))
15+
.toList();
16+
17+
return wordList;
18+
} else {
19+
throw Exception('Failed to fetch news');
20+
}
21+
}
22+
}

lib/widget/news_detail.dart

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import 'dart:convert';
22

3+
import 'package:flutter/cupertino.dart';
34
import 'package:flutter/material.dart';
45
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
56
import 'package:just_audio/just_audio.dart';
67
import 'package:nhk_easy/error_reporter.dart';
78
import 'package:nhk_easy/model/news.dart';
9+
import 'package:nhk_easy/model/word.dart';
10+
import 'package:nhk_easy/service/word_service.dart';
811

912
class NewsDetail extends StatefulWidget {
1013
final News news;
@@ -19,7 +22,10 @@ class NewsDetailState extends State<NewsDetail> {
1922
News _news;
2023
bool _isPlaying = false;
2124
AudioPlayer _audioPlayer;
22-
InAppWebViewController _inAppWebViewController;
25+
bool _showDictionary = false;
26+
Word _currentWord = null;
27+
List<Word> _words = [];
28+
WordService _wordService = WordService();
2329

2430
@override
2531
void initState() {
@@ -33,6 +39,13 @@ class NewsDetailState extends State<NewsDetail> {
3339
ErrorReporter.reportError(error, stackTrace);
3440
});
3541
}
42+
43+
_wordService
44+
.fetchWordList(this._news.newsId)
45+
.then((words) => this._words = words)
46+
.catchError((error, stackTrace) {
47+
ErrorReporter.reportError(error, stackTrace);
48+
});
3649
}
3750

3851
@override
@@ -55,34 +68,76 @@ class NewsDetailState extends State<NewsDetail> {
5568
),
5669
body: Padding(
5770
padding: EdgeInsets.all(16.0),
58-
child: InAppWebView(
59-
initialUrl: Uri.dataFromString(_buildHtml(_news),
60-
mimeType: 'text/html', encoding: utf8)
61-
.toString(),
62-
onWebViewCreated: (InAppWebViewController inAppWebViewController) {
63-
setState(() {
64-
this._inAppWebViewController = inAppWebViewController;
65-
});
66-
67-
inAppWebViewController.addJavaScriptHandler(
68-
handlerName: 'lookup',
69-
callback: (args) {
70-
print(args);
71-
});
72-
},
73-
onLoadStop:
74-
(InAppWebViewController inAppWebViewController, String url) {
75-
inAppWebViewController.injectJavascriptFileFromAsset(
76-
assetFilePath: 'assets/js/news-detail.js');
77-
},
78-
onConsoleMessage: (InAppWebViewController inAppWebViewController,
79-
ConsoleMessage consoleMessage) {
80-
print(consoleMessage.message);
81-
82-
if (consoleMessage.messageLevel == ConsoleMessageLevel.ERROR) {
83-
ErrorReporter.reportError(consoleMessage.message, null);
84-
}
85-
},
71+
child: Stack(
72+
children: <Widget>[
73+
InAppWebView(
74+
initialUrl: Uri.dataFromString(_buildHtml(_news),
75+
mimeType: 'text/html', encoding: utf8)
76+
.toString(),
77+
onWebViewCreated:
78+
(InAppWebViewController inAppWebViewController) {
79+
inAppWebViewController.addJavaScriptHandler(
80+
handlerName: 'lookup',
81+
callback: (args) {
82+
String wordId = args.length > 0 ? args[0] : null;
83+
Word word = _words.firstWhere(
84+
(word) => 'id-${word.idInNews}' == wordId);
85+
86+
if (word != null) {
87+
setState(() {
88+
_currentWord = word;
89+
_showDictionary = true;
90+
});
91+
}
92+
});
93+
},
94+
onLoadStop: (InAppWebViewController inAppWebViewController,
95+
String url) {
96+
inAppWebViewController.injectJavascriptFileFromAsset(
97+
assetFilePath: 'assets/js/news-detail.js');
98+
},
99+
onConsoleMessage:
100+
(InAppWebViewController inAppWebViewController,
101+
ConsoleMessage consoleMessage) {
102+
print(consoleMessage.message);
103+
104+
if (consoleMessage.messageLevel ==
105+
ConsoleMessageLevel.ERROR) {
106+
ErrorReporter.reportError(consoleMessage.message, null);
107+
}
108+
},
109+
),
110+
Container(
111+
child: _showDictionary
112+
? Center(
113+
child: Card(
114+
child: Container(
115+
padding: EdgeInsets.all(16),
116+
child: Column(
117+
crossAxisAlignment: CrossAxisAlignment.start,
118+
mainAxisSize: MainAxisSize.min,
119+
children: <Widget>[
120+
_buildWordDefinitions(_currentWord),
121+
ButtonBar(
122+
children: <Widget>[
123+
FlatButton(
124+
child: const Text('Close'),
125+
onPressed: () {
126+
setState(() {
127+
_currentWord = null;
128+
_showDictionary = false;
129+
});
130+
},
131+
),
132+
],
133+
),
134+
],
135+
),
136+
)),
137+
)
138+
: Container(),
139+
)
140+
],
86141
),
87142
),
88143
floatingActionButton: _hasAudio()
@@ -147,4 +202,31 @@ class NewsDetailState extends State<NewsDetail> {
147202
bool _hasAudio() {
148203
return _news.m3u8Url != null && _news.m3u8Url != '';
149204
}
205+
206+
Widget _buildWordDefinitions(Word word) {
207+
if (word == null) {
208+
return Container();
209+
}
210+
211+
final definitions = word.definitions
212+
.asMap()
213+
.entries
214+
.map((entry) => Text(
215+
'${entry.key + 1}. ${entry.value.definition}',
216+
style: TextStyle(fontSize: 16),
217+
))
218+
.toList();
219+
220+
List<Widget> columns = [];
221+
columns.add(Text(
222+
word.name,
223+
style: TextStyle(fontSize: 18),
224+
));
225+
columns.addAll(definitions);
226+
227+
return Column(
228+
crossAxisAlignment: CrossAxisAlignment.start,
229+
children: columns,
230+
);
231+
}
150232
}

0 commit comments

Comments
 (0)