Merge pull request #6 from h7x4ABk3g/refactor-1

Refactor, update bits and pieces, and clean up the code
This commit is contained in:
Oystein Kristoffer Tveit 2021-03-05 22:44:35 +01:00 committed by GitHub
commit 8e11db856d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 501 additions and 442 deletions

View File

@ -4,17 +4,17 @@ A japanese dictionary with features for making studying the language easier.
## Search
Standard search using Jishos own API. This returns standard Jisho search results, including what you'd find if you searched for something through the standard jisho search bar without any #modifiers
Standard search using Jishos API. This returns standard Jisho search results, including what you'd find if you searched through the search bar without any modifiers
## Kanji Search
Standard kanji search using the #kanji modifier. This will give you detailed information about things like drawing order, radicals, different kinds of ranks and statistics and some onyomi and kunyomi example words.
Jisho kanji search just like using the #kanji modifier in the jisho search bar. This will give you detailed information about things like drawing order, radicals, different kinds of ranks and statistics and some onyomi and kunyomi example words.
## Upcoming Features
* Different kinds of kanji input like radicals and grade based lists
* Favorites, history and custom lists
* Anki export
* Memo cards
* Cloud sync
* Dark theme
- [ ] Different kinds of kanji input like radicals and grade based lists
- [ ] Favorites, history and custom lists
- [ ] Anki export
- [ ] Memo cards
- [ ] Cloud sync
- [ ] Dark theme

View File

@ -4,7 +4,7 @@ import './kanji_event.dart';
import './kanji_state.dart';
import 'package:bloc/bloc.dart';
import 'package:jisho_study_tool/services/kanji_search.dart';
import 'package:jisho_study_tool/services/jisho_api/kanji_search.dart';
import 'package:jisho_study_tool/services/kanji_suggestions.dart';
export './kanji_event.dart';
@ -15,16 +15,15 @@ class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
KanjiBloc() : super(KanjiSearchInitial());
@override
Stream<KanjiState> mapEventToState(
KanjiEvent event,
) async* {
Stream<KanjiState> mapEventToState(KanjiEvent event)
async* {
if (event is GetKanji) {
yield KanjiSearchLoading();
try {
final _kanji = await fetchKanji(event.kanjiSearchString);
if (_kanji.found) yield KanjiSearchFinished(kanji: _kanji);
final kanji = await fetchKanji(event.kanjiSearchString);
if (kanji.found) yield KanjiSearchFinished(kanji: kanji);
else yield KanjiSearchError('Something went wrong');
} on Exception {
yield KanjiSearchError('Something went wrong');

View File

@ -5,7 +5,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:jisho_study_tool/services/jisho_search.dart';
import 'package:jisho_study_tool/services/jisho_api/jisho_search.dart';
import 'package:unofficial_jisho_api/parser.dart';
part 'search_event.dart';
@ -22,8 +22,8 @@ class SearchBloc extends Bloc<SearchEvent, SearchState> {
yield SearchLoading();
try {
final _searchResults = await fetchJishoResults(event.searchString);
if (_searchResults.meta.status == 200) yield SearchFinished(_searchResults.data);
final searchResults = await fetchJishoResults(event.searchString);
if (searchResults.meta.status == 200) yield SearchFinished(searchResults.data);
} on Exception {
yield SearchError('Something went wrong');
}

View File

@ -1,29 +0,0 @@
import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/api.dart';
import 'parts/header.dart';
import 'parts/senses.dart';
import 'parts/other_forms.dart';
class SearchResultCard extends StatelessWidget {
final JishoResult _result;
JishoJapaneseWord _mainWord;
List<JishoJapaneseWord> _otherForms;
SearchResultCard(this._result) {
this._mainWord = _result.japanese[0];
this._otherForms = _result.japanese.sublist(1);
}
@override
Widget build(BuildContext context) {
return ExpansionTile(
title: JapaneseHeader(_mainWord),
children: [
Senses(_result.senses),
OtherForms(_otherForms),
],
);
}
}

View File

@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mdi/mdi.dart';
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
import 'package:jisho_study_tool/screens/kanji_search.dart';
import 'package:jisho_study_tool/screens/history.dart';
import 'package:jisho_study_tool/screens/search.dart';
import 'package:jisho_study_tool/view/screens/kanji_search.dart';
import 'package:jisho_study_tool/view/screens/history.dart';
import 'package:jisho_study_tool/view/screens/search.dart';
import 'bloc/search/search_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
@ -35,21 +36,21 @@ class Home extends StatefulWidget {
}
class _HomeState extends State<Home> {
int _selectedPage = 0;
int selectedPage = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: pages[_selectedPage].titleBar,
title: pages[selectedPage].titleBar,
centerTitle: true,
),
body: pages[_selectedPage].content,
body: pages[selectedPage].content,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedPage,
currentIndex: selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
selectedPage = index;
});
},
items: navBar,
@ -64,55 +65,55 @@ class _HomeState extends State<Home> {
final List<BottomNavigationBarItem> navBar = [
BottomNavigationBarItem(
title: Text('Search'),
label: 'Search',
icon: Icon(Icons.search),
),
BottomNavigationBarItem(
title: Text('Kanji'),
icon: Text(
'',
style: TextStyle(fontSize: 18),
),
label: 'Kanji',
icon: Icon(Mdi.ideogramCjk, size: 30,)
),
BottomNavigationBarItem(
title: Text('History'),
label: 'History',
icon: Icon(Icons.bookmark),
),
BottomNavigationBarItem(
title: Text('Memorize'),
label: 'Memorize',
icon: Icon(Icons.local_offer),
),
BottomNavigationBarItem(
title: Text('Settings'),
label: 'Settings',
icon: Icon(Icons.settings),
),
];
class Page {
class _Page {
Widget content;
Widget titleBar;
Page({
_Page({
this.content,
this.titleBar,
});
}
final List<Page> pages = [
Page(content: SearchView(), titleBar: Text('Search')),
Page(
final List<_Page> pages = [
_Page(
content: SearchView(),
titleBar: Text('Search'),
),
_Page(
content: KanjiView(),
titleBar: KanjiViewBar(),
),
Page(
_Page(
content: HistoryView(),
titleBar: Text("History"),
),
Page(
_Page(
content: Container(),
titleBar: Text("Memorization"),
),
Page(
_Page(
content: Container(),
titleBar: Text("Settings"),
),

View File

@ -1,153 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
import 'package:jisho_study_tool/components/kanji/kanji__search_page/kanji_search_page.dart';
import 'package:jisho_study_tool/components/kanji/kanji_suggestions.dart';
import 'package:jisho_study_tool/components/loading.dart';
class KanjiView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<KanjiBloc, KanjiState>(
listener: (context, state) {
if (state is KanjiSearchInitial) {
FocusScope.of(context).unfocus();
} else if (state is KanjiSearchLoading) {
FocusScope.of(context).unfocus();
}
},
child: BlocBuilder<KanjiBloc, KanjiState>(
builder: (context, state) {
if (state is KanjiSearchInitial) return Container();
else if (state is KanjiSearchInput) return KanjiSuggestions(state.kanjiSuggestions);
else if (state is KanjiSearchLoading) return LoadingScreen();
else if (state is KanjiSearchFinished)
return WillPopScope(
child: KanjiResultCard(state.kanji),
onWillPop: () async {
BlocProvider.of<KanjiBloc>(context)
.add(ReturnToInitialState());
return false;
});
throw 'No such event found';
},
),
);
}
}
class KanjiViewBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () =>
BlocProvider.of<KanjiBloc>(context).add(ReturnToInitialState()),
),
Expanded(
child: Container(
child: _KanjiTextField(),
),
),
IconButton(
icon: Icon(Icons.star_border),
onPressed: null,
),
IconButton(
icon: Icon(Icons.add),
onPressed: null,
),
],
),
);
}
}
class _KanjiTextField extends StatefulWidget {
@override
_KanjiTextFieldState createState() => new _KanjiTextFieldState();
}
enum TextFieldButton {clear, paste}
class _KanjiTextFieldState extends State<_KanjiTextField> {
FocusNode _focus = new FocusNode();
TextEditingController _textController = new TextEditingController();
TextFieldButton _button = TextFieldButton.paste;
@override
void initState() {
super.initState();
_focus.addListener(_onFocusChange);
}
void _getKanjiSuggestions(String text) =>
BlocProvider.of<KanjiBloc>(context).add(GetKanjiSuggestions(text));
void updateSuggestions() => _getKanjiSuggestions(_textController.text);
void _onFocusChange() {
debugPrint('TextField Focus Changed: ${_focus.hasFocus.toString()}');
setState(() {
_button = _focus.hasFocus ? TextFieldButton.clear : TextFieldButton.paste;
});
if (_focus.hasFocus)
updateSuggestions();
else
FocusScope.of(context).unfocus();
}
void _clearText() {
_textController.text = '';
updateSuggestions();
}
void _pasteText() async {
ClipboardData clipboardData = await Clipboard.getData('text/plain');
_textController.text = clipboardData.text;
updateSuggestions();
}
@override
Widget build(BuildContext context) {
IconButton _clearButton = IconButton(
icon: Icon(Icons.clear),
onPressed: () => _clearText(),
);
IconButton _pasteButton = IconButton(
icon: Icon(Icons.content_paste),
onPressed: () => _pasteText(),
);
return TextField(
focusNode: _focus,
controller: _textController,
onChanged: (text) => _getKanjiSuggestions(text),
onSubmitted: (text) =>
BlocProvider.of<KanjiBloc>(context).add(GetKanji(text)),
decoration: new InputDecoration(
prefixIcon: Icon(Icons.search),
hintText: 'Search for kanji',
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100.0),
),
contentPadding: EdgeInsets.symmetric(vertical: 10.0),
isDense: false,
suffixIcon: (_button == TextFieldButton.clear) ? _clearButton : _pasteButton,
),
style: TextStyle(
fontSize: 14.0,
),
);
}
}

View File

@ -1,103 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
import 'package:jisho_study_tool/components/loading.dart';
import 'package:jisho_study_tool/components/search/search_card.dart';
class SearchView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<SearchBloc, SearchState>(
listener: (context, state) {},
child: BlocBuilder<SearchBloc, SearchState>(
builder: (context, state) {
if (state is SearchInitial)
return _InitialView();
else if (state is SearchLoading)
return LoadingScreen();
else if (state is SearchFinished) {
return WillPopScope(
child: ListView(
children: state.results
.map((result) => SearchResultCard(result))
.toList(),
),
onWillPop: () async {
BlocProvider.of<SearchBloc>(context)
.add(ReturnToInitialState());
print('Popped');
return false;
},
);
}
throw 'No such event found';
},
));
}
}
class _InitialView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
SearchBar(),
]);
}
}
class _LanguageOption extends StatelessWidget {
final String _language;
final Color _color;
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Center(child: Text(_language)),
decoration: BoxDecoration(
border: Border.all(
color: Colors.black,
width: 1.0,
),
color: _color),
),
);
}
_LanguageOption(this._language, this._color);
}
class SearchBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
children: [
TextField(
onSubmitted: (text) => BlocProvider.of<SearchBloc>(context)
.add(GetSearchResults(text)),
controller: TextEditingController(),
decoration: InputDecoration(
labelText: 'Search',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
SizedBox(
height: 10.0,
),
Row(
children: [
_LanguageOption('Auto', Colors.white),
_LanguageOption('English', Colors.white),
_LanguageOption('Japanese', Colors.blue),
],
),
],
),
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:unofficial_jisho_api/api.dart' as jisho;
String _convertGrade(String grade) {
const _conversionTable = {
const conversionTable = {
"grade 1": "小1",
"grade 2": "小2",
"grade 3": "小3",
@ -11,9 +11,9 @@ String _convertGrade(String grade) {
"junior high": ""
};
print('conversion run: $grade -> ${_conversionTable[grade]}');
print('conversion run: $grade -> ${conversionTable[grade]}');
return _conversionTable[grade];
return conversionTable[grade];
}
Future<jisho.KanjiResult> fetchKanji(String kanji) async {

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
class KanjiSearchBar extends StatefulWidget {
@override
_KanjiSearchBarState createState() => new _KanjiSearchBarState();
}
enum TextFieldButton {clear, paste}
class _KanjiSearchBarState extends State<KanjiSearchBar> {
FocusNode focus = new FocusNode();
TextEditingController textController = new TextEditingController();
TextFieldButton button = TextFieldButton.paste;
@override
void initState() {
super.initState();
focus.addListener(_onFocusChange);
}
void _getKanjiSuggestions(String text) =>
BlocProvider.of<KanjiBloc>(context).add(GetKanjiSuggestions(text));
void updateSuggestions() => _getKanjiSuggestions(textController.text);
void _onFocusChange() {
debugPrint('TextField Focus Changed: ${focus.hasFocus.toString()}');
setState(() {
button = focus.hasFocus ? TextFieldButton.clear : TextFieldButton.paste;
});
if (focus.hasFocus)
updateSuggestions();
else
FocusScope.of(context).unfocus();
}
void _clearText() {
textController.text = '';
updateSuggestions();
}
void _pasteText() async {
ClipboardData clipboardData = await Clipboard.getData('text/plain');
textController.text = clipboardData.text;
updateSuggestions();
}
@override
Widget build(BuildContext context) {
IconButton clearButton = IconButton(
icon: Icon(Icons.clear),
onPressed: () => _clearText(),
);
IconButton pasteButton = IconButton(
icon: Icon(Icons.content_paste),
onPressed: () => _pasteText(),
);
return TextField(
focusNode: focus,
controller: textController,
onChanged: (text) => _getKanjiSuggestions(text),
onSubmitted: (text) =>
BlocProvider.of<KanjiBloc>(context).add(GetKanji(text)),
decoration: new InputDecoration(
prefixIcon: Icon(Icons.search),
hintText: 'Search for kanji',
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100.0),
),
contentPadding: EdgeInsets.symmetric(vertical: 10.0),
isDense: false,
suffixIcon: (button == TextFieldButton.clear) ? clearButton : pasteButton,
),
style: TextStyle(
fontSize: 14.0,
),
);
}
}

View File

@ -14,7 +14,7 @@ import 'parts/kunyomi.dart';
import 'parts/examples.dart';
class KanjiResultCard extends StatelessWidget {
final jisho.KanjiResult _result;
final jisho.KanjiResult result;
@override
Widget build(BuildContext context) {
@ -33,26 +33,26 @@ class KanjiResultCard extends StatelessWidget {
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Center(child: Header(_result.query)),
child: Center(child: Header(result.query)),
),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Center(
child: Radical(_result.radical),
child: Radical(result.radical),
),
),
],
),
),
Meaning(_result.meaning),
_result.onyomi.length != 0 ? Onyomi(_result.onyomi) : SizedBox.shrink(),
_result.kunyomi.length != 0 ? Kunyomi(_result.kunyomi) : SizedBox.shrink(),
Meaning(result.meaning),
result.onyomi.length != 0 ? Onyomi(result.onyomi) : SizedBox.shrink(),
result.kunyomi.length != 0 ? Kunyomi(result.kunyomi) : SizedBox.shrink(),
IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
StrokeOrderGif(_result.strokeOrderGifUri),
StrokeOrderGif(result.strokeOrderGifUri),
Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
@ -61,19 +61,19 @@ class KanjiResultCard extends StatelessWidget {
Row(
children: [
Text("JLPT: ", style: TextStyle(fontSize: 20.0)),
JlptLevel(_result.jlptLevel ?? ""),
JlptLevel(result.jlptLevel ?? ""),
],
),
Row(
children: [
Text("Grade: ", style: TextStyle(fontSize: 20.0)),
Grade(_result.taughtIn ?? ""),
Grade(result.taughtIn ?? ""),
],
),
Row(
children: [
Text("Rank: ", style: TextStyle(fontSize: 20.0)),
Rank(_result.newspaperFrequencyRank ?? -1),
Rank(result.newspaperFrequencyRank ?? -1),
],
),
],
@ -82,10 +82,10 @@ class KanjiResultCard extends StatelessWidget {
],
),
),
Examples(_result.onyomiExamples, _result.kunyomiExamples),
Examples(result.onyomiExamples, result.kunyomiExamples),
],
);
}
KanjiResultCard(this._result);
KanjiResultCard(this.result);
}

View File

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/api.dart';
class Examples extends StatelessWidget {
final List<YomiExample> _onyomiExamples;
final List<YomiExample> _kunyomiExamples;
final List<YomiExample> onyomiExamples;
final List<YomiExample> kunyomiExamples;
const Examples(
this._onyomiExamples,
this._kunyomiExamples,
this.onyomiExamples,
this.kunyomiExamples,
);
@override
@ -30,10 +30,10 @@ class Examples extends StatelessWidget {
),
),
children: [
_onyomiExamples
onyomiExamples
.map((onyomiExample) => _Example(onyomiExample, _KanaType.onyomi))
.toList(),
_kunyomiExamples
kunyomiExamples
.map((kunyomiExample) =>
_Example(kunyomiExample, _KanaType.kunyomi))
.toList(),
@ -44,10 +44,10 @@ class Examples extends StatelessWidget {
enum _KanaType { kunyomi, onyomi }
class _Example extends StatelessWidget {
final _KanaType _kanaType;
final YomiExample _yomiExample;
final _KanaType kanaType;
final YomiExample yomiExample;
const _Example(this._yomiExample, this._kanaType);
const _Example(this.yomiExample, this.kanaType);
@override
Widget build(BuildContext context) {
@ -67,7 +67,7 @@ class _Example extends StatelessWidget {
horizontal: 10.0,
),
decoration: BoxDecoration(
color: (_kanaType == _KanaType.kunyomi)
color: (kanaType == _KanaType.kunyomi)
? Colors.lightBlue
: Colors.orange,
borderRadius: BorderRadius.only(
@ -79,7 +79,7 @@ class _Example extends StatelessWidget {
children: [
Container(
child: Text(
_yomiExample.reading,
yomiExample.reading,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
@ -91,7 +91,7 @@ class _Example extends StatelessWidget {
),
Container(
child: Text(
_yomiExample.example,
yomiExample.example,
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
@ -109,7 +109,7 @@ class _Example extends StatelessWidget {
children: [
Container(
child: Text(
_yomiExample.meaning,
yomiExample.meaning,
style: TextStyle(
color: Colors.white,
),

View File

@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
class Grade extends StatelessWidget {
final String _grade;
final String grade;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
_grade,
grade,
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
@ -21,5 +21,5 @@ class Grade extends StatelessWidget {
);
}
Grade(this._grade);
Grade(this.grade);
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
class Header extends StatelessWidget {
final String _kanji;
final String kanji;
@override
Widget build(BuildContext context) {
@ -11,12 +11,12 @@ class Header extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
_kanji,
kanji,
style: TextStyle(fontSize: 80.0, color: Colors.white),
),
),
);
}
Header(this._kanji);
Header(this.kanji);
}

View File

@ -2,14 +2,14 @@
import 'package:flutter/material.dart';
class JlptLevel extends StatelessWidget {
final String _jlptLevel;
final String jlptLevel;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
_jlptLevel,
jlptLevel,
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
@ -22,5 +22,5 @@ class JlptLevel extends StatelessWidget {
);
}
JlptLevel(this._jlptLevel);
JlptLevel(this.jlptLevel);
}

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
class Kunyomi extends StatelessWidget {
final List<String> _kunyomi;
List<_KunyomiCard> _kunyomiCards;
bool _expandable;
final List<String> kunyomi;
List<_KunyomiCard> kunyomiCards;
bool expandable;
Kunyomi(this._kunyomi) {
_kunyomiCards = _kunyomi.map((kunyomi) => _KunyomiCard(kunyomi)).toList();
_expandable = (_kunyomi.length > 6);
Kunyomi(this.kunyomi) {
kunyomiCards = kunyomi.map((kunyomi) => _KunyomiCard(kunyomi)).toList();
expandable = (kunyomi.length > 6);
}
@override
@ -23,7 +23,7 @@ class Kunyomi extends StatelessWidget {
}
Widget _KunyomiWrapper(BuildContext context) {
if (_expandable) {
if (expandable) {
return ExpansionTile(
initiallyExpanded: false,
title: Center(child: _KunyomiCard('Kunyomi')),
@ -33,7 +33,7 @@ class Kunyomi extends StatelessWidget {
),
Wrap(
runSpacing: 10.0,
children: _kunyomiCards,
children: kunyomiCards,
),
SizedBox(
height: 25.0,
@ -43,15 +43,15 @@ class Kunyomi extends StatelessWidget {
} else {
return Wrap(
runSpacing: 10.0,
children: _kunyomiCards,
children: kunyomiCards,
);
}
}
}
class _KunyomiCard extends StatelessWidget {
final String _kunyomi;
const _KunyomiCard(this._kunyomi);
final String kunyomi;
const _KunyomiCard(this.kunyomi);
@override
Widget build(BuildContext context) {
@ -62,7 +62,7 @@ class _KunyomiCard extends StatelessWidget {
horizontal: 10.0,
),
child: Text(
_kunyomi,
kunyomi,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
class Meaning extends StatelessWidget {
List<String> _meanings;
List<_MeaningCard> _meaningCards;
bool _expandable;
List<String> meanings;
List<_MeaningCard> meaningCards;
bool expandable;
@override
Widget build(BuildContext context) {
@ -18,7 +18,7 @@ class Meaning extends StatelessWidget {
}
Widget _MeaningWrapper(BuildContext context) {
if (_expandable) {
if (expandable) {
return ExpansionTile(
initiallyExpanded: false,
title: Center(child: _MeaningCard('Meanings')),
@ -28,7 +28,7 @@ class Meaning extends StatelessWidget {
),
Wrap(
runSpacing: 10.0,
children: _meaningCards,
children: meaningCards,
),
SizedBox(
height: 25.0,
@ -38,21 +38,21 @@ class Meaning extends StatelessWidget {
} else {
return Wrap(
runSpacing: 10.0,
children: _meaningCards,
children: meaningCards,
);
}
}
Meaning(_meaning) {
this._meanings = _meaning.split(', ');
this._meaningCards =
_meanings.map((meaning) => _MeaningCard(meaning)).toList();
this._expandable = (this._meanings.length > 6);
Meaning(meaning) {
this.meanings = meaning.split(', ');
this.meaningCards =
meanings.map((m) => _MeaningCard(m)).toList();
this.expandable = (this.meanings.length > 6);
}
}
class _MeaningCard extends StatelessWidget {
final String _meaning;
final String meaning;
@override
Widget build(BuildContext context) {
@ -63,7 +63,7 @@ class _MeaningCard extends StatelessWidget {
vertical: 10.0,
),
child: Text(
_meaning,
meaning,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,
@ -76,5 +76,5 @@ class _MeaningCard extends StatelessWidget {
);
}
_MeaningCard(this._meaning);
_MeaningCard(this.meaning);
}

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
class Onyomi extends StatelessWidget {
final List<String> _onyomi;
List<_OnyomiCard> _onyomiCards;
bool _expandable;
final List<String> onyomi;
List<_OnyomiCard> onyomiCards;
bool expandable;
Onyomi(this._onyomi) {
_onyomiCards = _onyomi.map((onyomi) => _OnyomiCard(onyomi)).toList();
_expandable = (_onyomi.length > 6);
Onyomi(this.onyomi) {
onyomiCards = onyomi.map((onyomi) => _OnyomiCard(onyomi)).toList();
expandable = (onyomi.length > 6);
}
@override
@ -23,7 +23,7 @@ class Onyomi extends StatelessWidget {
}
Widget _OnyomiWrapper(BuildContext context) {
if (_expandable) {
if (expandable) {
return ExpansionTile(
initiallyExpanded: false,
title: Center(child: _OnyomiCard('Onyomi')),
@ -33,7 +33,7 @@ class Onyomi extends StatelessWidget {
),
Wrap(
runSpacing: 10.0,
children: _onyomiCards,
children: onyomiCards,
),
SizedBox(
height: 25.0,
@ -43,15 +43,15 @@ class Onyomi extends StatelessWidget {
} else {
return Wrap(
runSpacing: 10.0,
children: _onyomiCards,
children: onyomiCards,
);
}
}
}
class _OnyomiCard extends StatelessWidget {
final String _onyomi;
const _OnyomiCard(this._onyomi);
final String onyomi;
const _OnyomiCard(this.onyomi);
@override
Widget build(BuildContext context) {
@ -62,7 +62,7 @@ class _OnyomiCard extends StatelessWidget {
horizontal: 10.0,
),
child: Text(
_onyomi,
onyomi,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,

View File

@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/api.dart' as jisho;
class Radical extends StatelessWidget {
final jisho.Radical _radical;
final jisho.Radical radical;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
_radical.symbol,
radical.symbol,
style: TextStyle(
color: Colors.white,
fontSize: 40.0,
@ -22,5 +22,5 @@ class Radical extends StatelessWidget {
);
}
Radical(this._radical);
Radical(this.radical);
}

View File

@ -2,14 +2,14 @@
import 'package:flutter/material.dart';
class Rank extends StatelessWidget {
final int _rank;
final int rank;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
'${_rank.toString()} / 2500',
'${rank.toString()} / 2500',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
@ -22,5 +22,5 @@ class Rank extends StatelessWidget {
);
}
Rank(this._rank);
Rank(this.rank);
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
class StrokeOrderGif extends StatelessWidget {
final String _uri;
final String uri;
@override
Widget build(BuildContext context) {
@ -9,7 +9,7 @@ class StrokeOrderGif extends StatelessWidget {
margin: EdgeInsets.symmetric(vertical: 20.0),
padding: EdgeInsets.all(5.0),
child: ClipRRect(
child: Image.network(_uri),
child: Image.network(uri),
borderRadius: BorderRadius.circular(10.0),
),
decoration: BoxDecoration(
@ -19,5 +19,5 @@ class StrokeOrderGif extends StatelessWidget {
);
}
StrokeOrderGif(this._uri);
StrokeOrderGif(this.uri);
}

View File

@ -3,8 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
class KanjiSuggestions extends StatelessWidget {
final List<String> _suggestions;
const KanjiSuggestions(this._suggestions);
final List<String> suggestions;
const KanjiSuggestions(this.suggestions);
@override
Widget build(BuildContext context) {
@ -18,21 +18,21 @@ class KanjiSuggestions extends StatelessWidget {
crossAxisCount: 3,
mainAxisSpacing: 20.0,
crossAxisSpacing: 40.0,
children: _suggestions.map((kanji) => _Suggestion(kanji)).toList(),
children: suggestions.map((kanji) => _Suggestion(kanji)).toList(),
),
);
}
}
class _Suggestion extends StatelessWidget {
final String _kanji;
const _Suggestion(this._kanji);
final String kanji;
const _Suggestion(this.kanji);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
BlocProvider.of<KanjiBloc>(context).add(GetKanji(_kanji));
BlocProvider.of<KanjiBloc>(context).add(GetKanji(kanji));
},
child: Container(
decoration: BoxDecoration(
@ -43,7 +43,7 @@ class _Suggestion extends StatelessWidget {
margin: EdgeInsets.all(10.0),
child: FittedBox(
child: Text(
_kanji,
kanji,
style: TextStyle(color: Colors.white),
),
),

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LanguageSelector extends StatefulWidget {
@override
_LanguageSelectorState createState() => new _LanguageSelectorState();
}
class _LanguageSelectorState extends State<LanguageSelector> {
SharedPreferences prefs;
List<bool> isSelected;
@override
void initState() {
super.initState();
isSelected = [false, false, false];
SharedPreferences.getInstance()
.then((prefs) {
this.prefs = prefs;
setState(() {
isSelected = _getSelectedStatus() ?? isSelected;
});
});
}
void _updateSelectedStatus() async {
await prefs.setStringList('languageSelectorStatus',
isSelected
.map((b) => b ? '1' : '0')
.toList());
}
List<bool> _getSelectedStatus() {
return prefs
.getStringList('languageSelectorStatus')
?.map((s) => s == '1')
?.toList();
}
@override
Widget build(BuildContext context) {
return ToggleButtons(
isSelected: isSelected,
children: <Widget> [
_LanguageOption("Auto"),
_LanguageOption("Japanese"),
_LanguageOption("English")
],
selectedColor: Colors.blue,
onPressed: (int buttonIndex) {
setState(() {
for (var i in Iterable.generate(isSelected.length)) {
isSelected[i] = i == buttonIndex;
}
_updateSelectedStatus();
});
},
);
}
}
class _LanguageOption extends StatelessWidget {
final String language;
_LanguageOption(this.language);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
child: Center(child: Text(language)),
);
}
}

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
import 'package:jisho_study_tool/view/components/search/LanguageSelector.dart';
class SearchBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
children: [
TextField(
onSubmitted: (text) => BlocProvider.of<SearchBloc>(context)
.add(GetSearchResults(text)),
controller: TextEditingController(),
decoration: InputDecoration(
labelText: 'Search',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
SizedBox(
height: 10.0,
),
LanguageSelector()
],
),
);
}
}

View File

@ -2,20 +2,20 @@ import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/api.dart';
class JapaneseHeader extends StatelessWidget {
final JishoJapaneseWord _word;
const JapaneseHeader(this._word);
final JishoJapaneseWord word;
const JapaneseHeader(this.word);
@override
Widget build(BuildContext context) {
final hasFurigana = (_word.word != null);
final hasFurigana = (word.word != null && word.reading != null);
return Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 10.0),
child: Column(
children: [
(hasFurigana) ? Text(_word.reading) : Text(''),
(hasFurigana) ? Text(_word.word) : Text(_word.reading),
(hasFurigana) ? Text(word.reading) : Text(''),
(hasFurigana) ? Text(word.word) : Text(word.reading ?? word.word),
],
),
);

View File

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/api.dart';
class OtherForms extends StatelessWidget {
final List<JishoJapaneseWord> _otherForms;
OtherForms(this._otherForms);
final List<JishoJapaneseWord> otherForms;
OtherForms(this.otherForms);
@override
Widget build(BuildContext context) {
@ -15,7 +15,7 @@ class OtherForms extends StatelessWidget {
style: TextStyle(fontWeight: FontWeight.bold),
),
Row(
children: _otherForms.map((form) => _KanaBox(form)).toList(),
children: otherForms.map((form) => _KanaBox(form)).toList(),
),
],
),
@ -24,18 +24,18 @@ class OtherForms extends StatelessWidget {
}
class _KanaBox extends StatelessWidget {
final JishoJapaneseWord _word;
const _KanaBox(this._word);
final JishoJapaneseWord word;
const _KanaBox(this.word);
@override
Widget build(BuildContext context) {
final hasFurigana = (_word.word != null);
final hasFurigana = (word.word != null);
return Container(
child: Column(
children: [
(hasFurigana) ? Text(_word.reading) : Text(''),
(hasFurigana) ? Text(_word.word) : Text(_word.reading),
(hasFurigana) ? Text(word.reading) : Text(''),
(hasFurigana) ? Text(word.word) : Text(word.reading),
],
),
margin: EdgeInsets.symmetric(

View File

@ -2,24 +2,24 @@ import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/parser.dart';
class Senses extends StatelessWidget {
final List<JishoWordSense> _senses;
const Senses(this._senses);
final List<JishoWordSense> senses;
const Senses(this.senses);
@override
Widget build(BuildContext context) {
final List<Widget> _senseWidgets =
_senses.map((sense) => _Sense(sense)).toList();
final List<Widget> senseWidgets =
senses.map((sense) => _Sense(sense)).toList();
return Container(
child: Column(
children: _senseWidgets,
children: senseWidgets,
));
}
}
class _Sense extends StatelessWidget {
final JishoWordSense _sense;
const _Sense(this._sense);
final JishoWordSense sense;
const _Sense(this.sense);
@override
Widget build(BuildContext context) {
@ -27,13 +27,13 @@ class _Sense extends StatelessWidget {
child: Column(
children: [
Text(
_sense.parts_of_speech.join(', '),
sense.parts_of_speech.join(', '),
style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
Column(
children:
_sense.english_definitions.map((def) => Text(def)).toList(),
sense.english_definitions.map((def) => Text(def)).toList(),
)
],
),

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:unofficial_jisho_api/api.dart';
import './parts/header.dart';
import './parts/senses.dart';
import './parts/other_forms.dart';
class SearchResultCard extends StatelessWidget {
final JishoResult result;
JishoJapaneseWord mainWord;
List<JishoJapaneseWord> otherForms;
SearchResultCard(this.result) {
this.mainWord = result.japanese[0];
this.otherForms = result.japanese.sublist(1);
}
@override
Widget build(BuildContext context) {
return ExpansionTile(
title: JapaneseHeader(mainWord),
children: [
Senses(result.senses),
OtherForms(otherForms),
],
);
}
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
import 'package:jisho_study_tool/view/components/kanji/kanji_search_bar.dart';
import 'package:jisho_study_tool/view/components/kanji/kanji_search_result_page/kanji_search_result_page.dart';
import 'package:jisho_study_tool/view/components/kanji/kanji_search_suggestion_list/kanji_search_suggestion_list.dart';
import 'package:jisho_study_tool/view/screens/loading.dart';
class KanjiView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<KanjiBloc, KanjiState>(
listener: (context, state) {
if (state is KanjiSearchInitial) {
FocusScope.of(context).unfocus();
} else if (state is KanjiSearchLoading) {
FocusScope.of(context).unfocus();
}
},
child: BlocBuilder<KanjiBloc, KanjiState>(
builder: (context, state) {
if (state is KanjiSearchInitial) return Container();
else if (state is KanjiSearchInput) return KanjiSuggestions(state.kanjiSuggestions);
else if (state is KanjiSearchLoading) return LoadingScreen();
else if (state is KanjiSearchFinished)
return WillPopScope(
child: KanjiResultCard(state.kanji),
onWillPop: () async {
BlocProvider.of<KanjiBloc>(context)
.add(ReturnToInitialState());
return false;
});
throw 'No such event found';
},
),
);
}
}
class KanjiViewBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () =>
BlocProvider.of<KanjiBloc>(context).add(ReturnToInitialState()),
),
Expanded(
child: Container(
child: KanjiSearchBar(),
),
),
IconButton(
icon: Icon(Icons.star_border),
onPressed: null,
),
IconButton(
icon: Icon(Icons.add),
onPressed: null,
),
],
),
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
import 'package:jisho_study_tool/view/components/search/search_bar.dart';
import 'package:jisho_study_tool/view/screens/loading.dart';
import 'package:jisho_study_tool/view/components/search/search_result_page/search_card.dart';
class SearchView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<SearchBloc, SearchState>(
listener: (context, state) {},
child: BlocBuilder<SearchBloc, SearchState>(
builder: (context, state) {
if (state is SearchInitial)
return _InitialView();
else if (state is SearchLoading)
return LoadingScreen();
else if (state is SearchFinished) {
return WillPopScope(
child: ListView(
children: state.results
.map((result) => SearchResultCard(result))
.toList(),
),
onWillPop: () async {
BlocProvider.of<SearchBloc>(context)
.add(ReturnToInitialState());
print('Popped');
return false;
},
);
}
throw 'No such event found';
},
));
}
}
class _InitialView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
SearchBar(),
]);
}
}

View File

@ -9,9 +9,12 @@ dependencies:
flutter:
sdk: flutter
shared_preferences: "^2.0.3"
# cupertino_icons: ^0.1.2
mdi: ^3.0.0
unofficial_jisho_api: ^1.1.0
flutter_bloc: ^6.0.1
flutter_bloc: ^6.1.0
url_launcher: ^5.5.0
division: ^0.8.8