mirror of
https://github.com/h7x4/Jisho-Study-Tool.git
synced 2024-12-30 17:07:29 +01:00
commit
5eaf220683
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,3 +35,5 @@ lib/generated_plugin_registrant.dart
|
||||
|
||||
# Exceptions to above rules.
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
||||
pubspec.lock
|
24
README.md
24
README.md
@ -1,16 +1,20 @@
|
||||
# jisho_study_tool
|
||||
# Jisho Study tool
|
||||
|
||||
A new Flutter project.
|
||||
A japanese dictionary with features for making studying the language easier.
|
||||
|
||||
## Getting Started
|
||||
## Search
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
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
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
## Kanji Search
|
||||
|
||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||
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.
|
||||
|
||||
For help getting started with Flutter, view our
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
## 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
|
||||
|
18
lib/bloc/history/bloc/history_bloc.dart
Normal file
18
lib/bloc/history/bloc/history_bloc.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
part 'history_event.dart';
|
||||
part 'history_state.dart';
|
||||
|
||||
class HistoryBloc extends Bloc<HistoryEvent, HistoryState> {
|
||||
HistoryBloc() : super(HistoryInitial());
|
||||
|
||||
@override
|
||||
Stream<HistoryState> mapEventToState(
|
||||
HistoryEvent event,
|
||||
) async* {
|
||||
// TODO: implement mapEventToState
|
||||
}
|
||||
}
|
4
lib/bloc/history/bloc/history_event.dart
Normal file
4
lib/bloc/history/bloc/history_event.dart
Normal file
@ -0,0 +1,4 @@
|
||||
part of 'history_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class HistoryEvent {}
|
6
lib/bloc/history/bloc/history_state.dart
Normal file
6
lib/bloc/history/bloc/history_state.dart
Normal file
@ -0,0 +1,6 @@
|
||||
part of 'history_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class HistoryState {}
|
||||
|
||||
class HistoryInitial extends HistoryState {}
|
41
lib/bloc/kanji/kanji_bloc.dart
Normal file
41
lib/bloc/kanji/kanji_bloc.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/kanji_suggestions.dart';
|
||||
|
||||
export './kanji_event.dart';
|
||||
export './kanji_state.dart';
|
||||
|
||||
class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
|
||||
|
||||
KanjiBloc() : super(KanjiSearchInitial());
|
||||
|
||||
@override
|
||||
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);
|
||||
else yield KanjiSearchError('Something went wrong');
|
||||
} on Exception {
|
||||
yield KanjiSearchError('Something went wrong');
|
||||
}
|
||||
} else if (event is GetKanjiSuggestions) {
|
||||
|
||||
final suggestions = kanjiSuggestions(event.searchString);
|
||||
yield KanjiSearchInput(suggestions);
|
||||
|
||||
} else if (event is ReturnToInitialState) {
|
||||
yield KanjiSearchInitial();
|
||||
}
|
||||
}
|
||||
}
|
17
lib/bloc/kanji/kanji_event.dart
Normal file
17
lib/bloc/kanji/kanji_event.dart
Normal file
@ -0,0 +1,17 @@
|
||||
abstract class KanjiEvent {
|
||||
const KanjiEvent();
|
||||
}
|
||||
|
||||
class GetKanjiSuggestions extends KanjiEvent {
|
||||
final String searchString;
|
||||
const GetKanjiSuggestions(this.searchString);
|
||||
}
|
||||
|
||||
class GetKanji extends KanjiEvent {
|
||||
final String kanjiSearchString;
|
||||
const GetKanji(this.kanjiSearchString);
|
||||
}
|
||||
|
||||
class ReturnToInitialState extends KanjiEvent {
|
||||
const ReturnToInitialState();
|
||||
}
|
34
lib/bloc/kanji/kanji_state.dart
Normal file
34
lib/bloc/kanji/kanji_state.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'package:unofficial_jisho_api/api.dart';
|
||||
|
||||
abstract class KanjiState {
|
||||
const KanjiState();
|
||||
}
|
||||
|
||||
class KanjiSearchInitial extends KanjiState {
|
||||
const KanjiSearchInitial();
|
||||
}
|
||||
|
||||
class KanjiSearchInput extends KanjiState {
|
||||
final List<String> kanjiSuggestions;
|
||||
const KanjiSearchInput(this.kanjiSuggestions);
|
||||
}
|
||||
|
||||
class KanjiSearchLoading extends KanjiState {
|
||||
const KanjiSearchLoading();
|
||||
}
|
||||
|
||||
class KanjiSearchFinished extends KanjiState {
|
||||
final KanjiResult kanji;
|
||||
final bool starred;
|
||||
|
||||
const KanjiSearchFinished({
|
||||
this.kanji,
|
||||
this.starred = false,
|
||||
});
|
||||
}
|
||||
|
||||
class KanjiSearchError extends KanjiState {
|
||||
final String message;
|
||||
|
||||
const KanjiSearchError(this.message);
|
||||
}
|
34
lib/bloc/search/search_bloc.dart
Normal file
34
lib/bloc/search/search_bloc.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
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:unofficial_jisho_api/parser.dart';
|
||||
|
||||
part 'search_event.dart';
|
||||
part 'search_state.dart';
|
||||
|
||||
class SearchBloc extends Bloc<SearchEvent, SearchState> {
|
||||
SearchBloc() : super(SearchInitial());
|
||||
|
||||
@override
|
||||
Stream<SearchState> mapEventToState(
|
||||
SearchEvent event,
|
||||
) async* {
|
||||
if (event is GetSearchResults) {
|
||||
yield SearchLoading();
|
||||
|
||||
try {
|
||||
final _searchResults = await fetchJishoResults(event.searchString);
|
||||
if (_searchResults.meta.status == 200) yield SearchFinished(_searchResults.data);
|
||||
} on Exception {
|
||||
yield SearchError('Something went wrong');
|
||||
}
|
||||
} else if (event is ReturnToInitialState) {
|
||||
yield SearchInitial();
|
||||
}
|
||||
}
|
||||
}
|
15
lib/bloc/search/search_event.dart
Normal file
15
lib/bloc/search/search_event.dart
Normal file
@ -0,0 +1,15 @@
|
||||
part of 'search_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class SearchEvent {
|
||||
const SearchEvent();
|
||||
}
|
||||
|
||||
class GetSearchResults extends SearchEvent {
|
||||
final String searchString;
|
||||
const GetSearchResults(this.searchString);
|
||||
}
|
||||
|
||||
class ReturnToInitialState extends SearchEvent {
|
||||
const ReturnToInitialState();
|
||||
}
|
26
lib/bloc/search/search_state.dart
Normal file
26
lib/bloc/search/search_state.dart
Normal file
@ -0,0 +1,26 @@
|
||||
part of 'search_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class SearchState {
|
||||
const SearchState();
|
||||
}
|
||||
|
||||
class SearchInitial extends SearchState {
|
||||
const SearchInitial();
|
||||
}
|
||||
|
||||
class SearchLoading extends SearchState {
|
||||
const SearchLoading();
|
||||
}
|
||||
|
||||
class SearchFinished extends SearchState {
|
||||
final List<JishoResult> results;
|
||||
|
||||
const SearchFinished(this.results);
|
||||
}
|
||||
|
||||
class SearchError extends SearchState {
|
||||
final String message;
|
||||
|
||||
const SearchError(this.message);
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:unofficial_jisho_api/api.dart' as jisho;
|
||||
|
||||
import 'parts/grade.dart';
|
||||
import 'parts/header.dart';
|
||||
import 'parts/jlpt_level.dart';
|
||||
import 'parts/meaning.dart';
|
||||
import 'parts/radical.dart';
|
||||
import 'parts/rank.dart';
|
||||
import 'parts/stroke_order_gif.dart';
|
||||
import 'parts/onyomi.dart';
|
||||
import 'parts/kunyomi.dart';
|
||||
import 'parts/examples.dart';
|
||||
|
||||
class KanjiResultCard extends StatelessWidget {
|
||||
final jisho.KanjiResult _result;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
fit: FlexFit.tight,
|
||||
child: Center(child: SizedBox()),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
fit: FlexFit.tight,
|
||||
child: Center(child: Header(_result.query)),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
fit: FlexFit.tight,
|
||||
child: Center(
|
||||
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(),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
StrokeOrderGif(_result.strokeOrderGifUri),
|
||||
Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text("JLPT: ", style: TextStyle(fontSize: 20.0)),
|
||||
JlptLevel(_result.jlptLevel ?? "⨉"),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text("Grade: ", style: TextStyle(fontSize: 20.0)),
|
||||
Grade(_result.taughtIn ?? "⨉"),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text("Rank: ", style: TextStyle(fontSize: 20.0)),
|
||||
Rank(_result.newspaperFrequencyRank ?? -1),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Examples(_result.onyomiExamples, _result.kunyomiExamples),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
KanjiResultCard(this._result);
|
||||
}
|
126
lib/components/kanji/kanji__search_page/parts/examples.dart
Normal file
126
lib/components/kanji/kanji__search_page/parts/examples.dart
Normal file
@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unofficial_jisho_api/api.dart';
|
||||
|
||||
class Examples extends StatelessWidget {
|
||||
final List<YomiExample> _onyomiExamples;
|
||||
final List<YomiExample> _kunyomiExamples;
|
||||
|
||||
const Examples(
|
||||
this._onyomiExamples,
|
||||
this._kunyomiExamples,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpansionTile(
|
||||
title: Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
child: Text(
|
||||
'Examples',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
_onyomiExamples
|
||||
.map((onyomiExample) => _Example(onyomiExample, _KanaType.onyomi))
|
||||
.toList(),
|
||||
_kunyomiExamples
|
||||
.map((kunyomiExample) =>
|
||||
_Example(kunyomiExample, _KanaType.kunyomi))
|
||||
.toList(),
|
||||
].expand((list) => list).toList());
|
||||
}
|
||||
}
|
||||
|
||||
enum _KanaType { kunyomi, onyomi }
|
||||
|
||||
class _Example extends StatelessWidget {
|
||||
final _KanaType _kanaType;
|
||||
final YomiExample _yomiExample;
|
||||
|
||||
const _Example(this._yomiExample, this._kanaType);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
horizontal: 10.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey, borderRadius: BorderRadius.circular(10.0)),
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
horizontal: 10.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: (_kanaType == _KanaType.kunyomi)
|
||||
? Colors.lightBlue
|
||||
: Colors.orange,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10.0),
|
||||
bottomLeft: Radius.circular(10.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
_yomiExample.reading,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 15.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 5.0,
|
||||
),
|
||||
Container(
|
||||
child: Text(
|
||||
_yomiExample.example,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 15.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
_yomiExample.meaning,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
25
lib/components/kanji/kanji__search_page/parts/grade.dart
Normal file
25
lib/components/kanji/kanji__search_page/parts/grade.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Grade extends StatelessWidget {
|
||||
final String _grade;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
_grade,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Grade(this._grade);
|
||||
}
|
22
lib/components/kanji/kanji__search_page/parts/header.dart
Normal file
22
lib/components/kanji/kanji__search_page/parts/header.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Header extends StatelessWidget {
|
||||
final String _kanji;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10.0), color: Colors.blue),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
_kanji,
|
||||
style: TextStyle(fontSize: 80.0, color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Header(this._kanji);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class JlptLevel extends StatelessWidget {
|
||||
final String _jlptLevel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
_jlptLevel,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.blue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
JlptLevel(this._jlptLevel);
|
||||
}
|
77
lib/components/kanji/kanji__search_page/parts/kunyomi.dart
Normal file
77
lib/components/kanji/kanji__search_page/parts/kunyomi.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Kunyomi extends StatelessWidget {
|
||||
final List<String> _kunyomi;
|
||||
List<_KunyomiCard> _kunyomiCards;
|
||||
bool _expandable;
|
||||
|
||||
Kunyomi(this._kunyomi) {
|
||||
_kunyomiCards = _kunyomi.map((kunyomi) => _KunyomiCard(kunyomi)).toList();
|
||||
_expandable = (_kunyomi.length > 6);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 5.0,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _KunyomiWrapper(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _KunyomiWrapper(BuildContext context) {
|
||||
if (_expandable) {
|
||||
return ExpansionTile(
|
||||
initiallyExpanded: false,
|
||||
title: Center(child: _KunyomiCard('Kunyomi')),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20.0,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 10.0,
|
||||
children: _kunyomiCards,
|
||||
),
|
||||
SizedBox(
|
||||
height: 25.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Wrap(
|
||||
runSpacing: 10.0,
|
||||
children: _kunyomiCards,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _KunyomiCard extends StatelessWidget {
|
||||
final String _kunyomi;
|
||||
const _KunyomiCard(this._kunyomi);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
horizontal: 10.0,
|
||||
),
|
||||
child: Text(
|
||||
_kunyomi,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightBlue,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
80
lib/components/kanji/kanji__search_page/parts/meaning.dart
Normal file
80
lib/components/kanji/kanji__search_page/parts/meaning.dart
Normal file
@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Meaning extends StatelessWidget {
|
||||
List<String> _meanings;
|
||||
List<_MeaningCard> _meaningCards;
|
||||
bool _expandable;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 5.0,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _MeaningWrapper(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _MeaningWrapper(BuildContext context) {
|
||||
if (_expandable) {
|
||||
return ExpansionTile(
|
||||
initiallyExpanded: false,
|
||||
title: Center(child: _MeaningCard('Meanings')),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20.0,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 10.0,
|
||||
children: _meaningCards,
|
||||
),
|
||||
SizedBox(
|
||||
height: 25.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Wrap(
|
||||
runSpacing: 10.0,
|
||||
children: _meaningCards,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Meaning(_meaning) {
|
||||
this._meanings = _meaning.split(', ');
|
||||
this._meaningCards =
|
||||
_meanings.map((meaning) => _MeaningCard(meaning)).toList();
|
||||
this._expandable = (this._meanings.length > 6);
|
||||
}
|
||||
}
|
||||
|
||||
class _MeaningCard extends StatelessWidget {
|
||||
final String _meaning;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: Text(
|
||||
_meaning,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_MeaningCard(this._meaning);
|
||||
}
|
77
lib/components/kanji/kanji__search_page/parts/onyomi.dart
Normal file
77
lib/components/kanji/kanji__search_page/parts/onyomi.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Onyomi extends StatelessWidget {
|
||||
final List<String> _onyomi;
|
||||
List<_OnyomiCard> _onyomiCards;
|
||||
bool _expandable;
|
||||
|
||||
Onyomi(this._onyomi) {
|
||||
_onyomiCards = _onyomi.map((onyomi) => _OnyomiCard(onyomi)).toList();
|
||||
_expandable = (_onyomi.length > 6);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 5.0,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _OnyomiWrapper(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _OnyomiWrapper(BuildContext context) {
|
||||
if (_expandable) {
|
||||
return ExpansionTile(
|
||||
initiallyExpanded: false,
|
||||
title: Center(child: _OnyomiCard('Onyomi')),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20.0,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 10.0,
|
||||
children: _onyomiCards,
|
||||
),
|
||||
SizedBox(
|
||||
height: 25.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Wrap(
|
||||
runSpacing: 10.0,
|
||||
children: _onyomiCards,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _OnyomiCard extends StatelessWidget {
|
||||
final String _onyomi;
|
||||
const _OnyomiCard(this._onyomi);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
horizontal: 10.0,
|
||||
),
|
||||
child: Text(
|
||||
_onyomi,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
26
lib/components/kanji/kanji__search_page/parts/radical.dart
Normal file
26
lib/components/kanji/kanji__search_page/parts/radical.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unofficial_jisho_api/api.dart' as jisho;
|
||||
|
||||
class Radical extends StatelessWidget {
|
||||
final jisho.Radical _radical;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
_radical.symbol,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 40.0,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.blue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Radical(this._radical);
|
||||
}
|
26
lib/components/kanji/kanji__search_page/parts/rank.dart
Normal file
26
lib/components/kanji/kanji__search_page/parts/rank.dart
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Rank extends StatelessWidget {
|
||||
final int _rank;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
'${_rank.toString()} / 2500',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
color: Colors.blue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Rank(this._rank);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StrokeOrderGif extends StatelessWidget {
|
||||
final String _uri;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20.0),
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: ClipRRect(
|
||||
child: Image.network(_uri),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
StrokeOrderGif(this._uri);
|
||||
}
|
11
lib/components/kanji/kanji_list/kanji_list.dart
Normal file
11
lib/components/kanji/kanji_list/kanji_list.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class KanjiGrid extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 3,
|
||||
);
|
||||
}
|
||||
}
|
54
lib/components/kanji/kanji_suggestions.dart
Normal file
54
lib/components/kanji/kanji_suggestions.dart
Normal file
@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
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);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.grey[300],
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 20.0,
|
||||
horizontal: 40.0,
|
||||
),
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 20.0,
|
||||
crossAxisSpacing: 40.0,
|
||||
children: _suggestions.map((kanji) => _Suggestion(kanji)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Suggestion extends StatelessWidget {
|
||||
final String _kanji;
|
||||
const _Suggestion(this._kanji);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<KanjiBloc>(context).add(GetKanji(_kanji));
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(10.0),
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
_kanji,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
12
lib/components/loading.dart
Normal file
12
lib/components/loading.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoadingScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
23
lib/components/search/parts/header.dart
Normal file
23
lib/components/search/parts/header.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unofficial_jisho_api/api.dart';
|
||||
|
||||
class JapaneseHeader extends StatelessWidget {
|
||||
final JishoJapaneseWord _word;
|
||||
const JapaneseHeader(this._word);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasFurigana = (_word.word != 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
59
lib/components/search/parts/other_forms.dart
Normal file
59
lib/components/search/parts/other_forms.dart
Normal file
@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unofficial_jisho_api/api.dart';
|
||||
|
||||
class OtherForms extends StatelessWidget {
|
||||
final List<JishoJapaneseWord> _otherForms;
|
||||
OtherForms(this._otherForms);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Other Forms',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Row(
|
||||
children: _otherForms.map((form) => _KanaBox(form)).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KanaBox extends StatelessWidget {
|
||||
final JishoJapaneseWord _word;
|
||||
const _KanaBox(this._word);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasFurigana = (_word.word != null);
|
||||
|
||||
return Container(
|
||||
child: Column(
|
||||
children: [
|
||||
(hasFurigana) ? Text(_word.reading) : Text(''),
|
||||
(hasFurigana) ? Text(_word.word) : Text(_word.reading),
|
||||
],
|
||||
),
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 5.0,
|
||||
vertical: 5.0,
|
||||
),
|
||||
padding: EdgeInsets.all(5.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 0.5,
|
||||
offset: Offset(1, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
42
lib/components/search/parts/senses.dart
Normal file
42
lib/components/search/parts/senses.dart
Normal file
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unofficial_jisho_api/parser.dart';
|
||||
|
||||
class Senses extends StatelessWidget {
|
||||
final List<JishoWordSense> _senses;
|
||||
const Senses(this._senses);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> _senseWidgets =
|
||||
_senses.map((sense) => _Sense(sense)).toList();
|
||||
|
||||
return Container(
|
||||
child: Column(
|
||||
children: _senseWidgets,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class _Sense extends StatelessWidget {
|
||||
final JishoWordSense _sense;
|
||||
const _Sense(this._sense);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
_sense.parts_of_speech.join(', '),
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
Column(
|
||||
children:
|
||||
_sense.english_definitions.map((def) => Text(def)).toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
29
lib/components/search/search_card.dart
Normal file
29
lib/components/search/search_card.dart
Normal 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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
140
lib/main.dart
140
lib/main.dart
@ -1,4 +1,11 @@
|
||||
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/screens/kanji_search.dart';
|
||||
import 'package:jisho_study_tool/screens/history.dart';
|
||||
import 'package:jisho_study_tool/screens/search.dart';
|
||||
|
||||
import 'bloc/search/search_bloc.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
@ -7,11 +14,17 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
title: 'Jisho Study Tool',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: Home(),
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (context) => SearchBloc()),
|
||||
BlocProvider(create: (context) => KanjiBloc()),
|
||||
],
|
||||
child: Home(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -19,55 +32,88 @@ class MyApp extends StatelessWidget {
|
||||
class Home extends StatefulWidget {
|
||||
@override
|
||||
_HomeState createState() => _HomeState();
|
||||
|
||||
}
|
||||
|
||||
class _HomeState extends State<Home> {
|
||||
int _selectedPage = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Jisho Study Tool')
|
||||
),
|
||||
body: Container(),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: _selectedPage,
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
_selectedPage = index;
|
||||
});
|
||||
},
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Search'),
|
||||
icon: Icon(Icons.search)
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Kanji'),
|
||||
icon: Text(
|
||||
'漢',
|
||||
style: TextStyle(
|
||||
fontSize: 18
|
||||
),
|
||||
)
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Memorize'),
|
||||
icon: Icon(Icons.book)
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Settings'),
|
||||
icon: Icon(Icons.settings)
|
||||
),
|
||||
],
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
unselectedItemColor: Colors.blue,
|
||||
selectedItemColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: pages[_selectedPage].titleBar,
|
||||
centerTitle: true,
|
||||
),
|
||||
body: pages[_selectedPage].content,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: _selectedPage,
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
_selectedPage = index;
|
||||
});
|
||||
},
|
||||
items: navBar,
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
unselectedItemColor: Colors.blue,
|
||||
selectedItemColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
final List<BottomNavigationBarItem> navBar = [
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Search'),
|
||||
icon: Icon(Icons.search),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Kanji'),
|
||||
icon: Text(
|
||||
'漢',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('History'),
|
||||
icon: Icon(Icons.bookmark),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Memorize'),
|
||||
icon: Icon(Icons.local_offer),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Settings'),
|
||||
icon: Icon(Icons.settings),
|
||||
),
|
||||
];
|
||||
|
||||
class Page {
|
||||
Widget content;
|
||||
Widget titleBar;
|
||||
|
||||
Page({
|
||||
this.content,
|
||||
this.titleBar,
|
||||
});
|
||||
}
|
||||
|
||||
final List<Page> pages = [
|
||||
Page(content: SearchView(), titleBar: Text('Search')),
|
||||
Page(
|
||||
content: KanjiView(),
|
||||
titleBar: KanjiViewBar(),
|
||||
),
|
||||
Page(
|
||||
content: HistoryView(),
|
||||
titleBar: Text("History"),
|
||||
),
|
||||
Page(
|
||||
content: Container(),
|
||||
titleBar: Text("Memorization"),
|
||||
),
|
||||
Page(
|
||||
content: Container(),
|
||||
titleBar: Text("Settings"),
|
||||
),
|
||||
];
|
||||
|
10
lib/screens/history.dart
Normal file
10
lib/screens/history.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HistoryView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) => ListTile(),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import 'package:unofficial_jisho_api/api.dart' as jisho;
|
||||
|
||||
Future<jisho.JishoAPIResult> fetchJishoResults(searchTerm) async {
|
||||
return await jisho.searchForPhrase(searchTerm);
|
||||
}
|
23
lib/services/kanji_search.dart
Normal file
23
lib/services/kanji_search.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:unofficial_jisho_api/api.dart' as jisho;
|
||||
|
||||
String _convertGrade(String grade) {
|
||||
const _conversionTable = {
|
||||
"grade 1": "小1",
|
||||
"grade 2": "小2",
|
||||
"grade 3": "小3",
|
||||
"grade 4": "小4",
|
||||
"grade 5": "小5",
|
||||
"grade 6": "小6",
|
||||
"junior high": "中"
|
||||
};
|
||||
|
||||
print('conversion run: $grade -> ${_conversionTable[grade]}');
|
||||
|
||||
return _conversionTable[grade];
|
||||
}
|
||||
|
||||
Future<jisho.KanjiResult> fetchKanji(String kanji) async {
|
||||
final result = await jisho.searchForKanji(kanji);
|
||||
result.taughtIn = _convertGrade(result.taughtIn);
|
||||
return result;
|
||||
}
|
5
lib/services/kanji_suggestions.dart
Normal file
5
lib/services/kanji_suggestions.dart
Normal file
@ -0,0 +1,5 @@
|
||||
final kanjiPattern = RegExp(r'[\u3400-\u4DB5\u4E00-\u9FCB\uF900-\uFA6A]');
|
||||
|
||||
List<String> kanjiSuggestions(String string) {
|
||||
return kanjiPattern.allMatches(string).map((match) => match.group(0)).toList();
|
||||
}
|
237
pubspec.lock
237
pubspec.lock
@ -1,237 +0,0 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.14.11"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.14.0+3"
|
||||
html_unescape:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html_unescape
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1+3"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.6"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.8"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.4"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pedantic
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0+1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.5"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.3"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
unofficial_jisho_api:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: unofficial_jisho_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.5.0"
|
||||
sdks:
|
||||
dart: ">=2.7.0 <3.0.0"
|
45
pubspec.yaml
45
pubspec.yaml
@ -1,16 +1,5 @@
|
||||
name: jisho_study_tool
|
||||
description: A new Flutter project.
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
# followed by an optional build number separated by a +.
|
||||
# Both the version and the builder number may be overridden in flutter
|
||||
# build by specifying --build-name and --build-number, respectively.
|
||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
@ -20,43 +9,24 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^0.1.2
|
||||
unofficial_jisho_api: ^1.0.2
|
||||
# cupertino_icons: ^0.1.2
|
||||
unofficial_jisho_api: ^1.1.0
|
||||
flutter_bloc: ^6.0.1
|
||||
url_launcher: ^5.5.0
|
||||
division: ^0.8.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
@ -68,6 +38,3 @@ flutter:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
|
Loading…
Reference in New Issue
Block a user