Merge branch 'development' of github.com:h7x4ABk3g/jisho_study_tool into development

This commit is contained in:
Oystein Kristoffer Tveit 2020-08-18 09:16:15 +02:00
commit 725586399e
11 changed files with 329 additions and 79 deletions

View File

@ -5,6 +5,7 @@ import './kanji_state.dart';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:jisho_study_tool/services/kanji_search.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_event.dart';
export './kanji_state.dart'; export './kanji_state.dart';
@ -28,6 +29,10 @@ class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
} on Exception { } on Exception {
yield KanjiSearchError('Something went wrong'); yield KanjiSearchError('Something went wrong');
} }
} else if (event is GetKanjiSuggestions) {
final suggestions = kanjiSuggestions(event.searchString);
yield KanjiSearchInput(suggestions);
} else if (event is ReturnToInitialState) { } else if (event is ReturnToInitialState) {
yield KanjiSearchInitial(); yield KanjiSearchInitial();

View File

@ -2,12 +2,16 @@ abstract class KanjiEvent {
const KanjiEvent(); const KanjiEvent();
} }
class GetKanjiSuggestions extends KanjiEvent {
final String searchString;
const GetKanjiSuggestions(this.searchString);
}
class GetKanji extends KanjiEvent { class GetKanji extends KanjiEvent {
final String kanjiSearchString; final String kanjiSearchString;
const GetKanji(this.kanjiSearchString);
GetKanji(this.kanjiSearchString);
} }
class ReturnToInitialState extends KanjiEvent { class ReturnToInitialState extends KanjiEvent {
ReturnToInitialState(); const ReturnToInitialState();
} }

View File

@ -5,18 +5,23 @@ abstract class KanjiState {
} }
class KanjiSearchInitial extends KanjiState { class KanjiSearchInitial extends KanjiState {
KanjiSearchInitial(); const KanjiSearchInitial();
}
class KanjiSearchInput extends KanjiState {
final List<String> kanjiSuggestions;
const KanjiSearchInput(this.kanjiSuggestions);
} }
class KanjiSearchLoading extends KanjiState { class KanjiSearchLoading extends KanjiState {
KanjiSearchLoading(); const KanjiSearchLoading();
} }
class KanjiSearchFinished extends KanjiState { class KanjiSearchFinished extends KanjiState {
final KanjiResult kanji; final KanjiResult kanji;
final bool starred; final bool starred;
KanjiSearchFinished({ const KanjiSearchFinished({
this.kanji, this.kanji,
this.starred = false, this.starred = false,
}); });
@ -25,7 +30,5 @@ class KanjiSearchFinished extends KanjiState {
class KanjiSearchError extends KanjiState { class KanjiSearchError extends KanjiState {
final String message; final String message;
KanjiSearchError(this.message); const KanjiSearchError(this.message);
} }
class ReKanjiSearch extends KanjiState {}

View File

@ -1,6 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
part 'search_event.dart'; part 'search_event.dart';

View File

@ -10,5 +10,3 @@ class SearchLoading extends SearchState {}
class SearchFinished extends SearchState {} class SearchFinished extends SearchState {}
class SearchError extends SearchState {} class SearchError extends SearchState {}
class ReSearch extends SearchState {}

View 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,
),
),
)
],
),
),
],
),
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:jisho_study_tool/components/kanji/kanji__search_page/examples.dart';
import 'package:unofficial_jisho_api/api.dart' as jisho; import 'package:unofficial_jisho_api/api.dart' as jisho;
@ -81,6 +82,7 @@ class KanjiResultCard extends StatelessWidget {
], ],
), ),
), ),
Examples(_result.onyomiExamples, _result.kunyomiExamples),
], ],
); );
} }

View 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),
),
),
),
),
);
}
}

View File

@ -1,28 +1,42 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jisho_study_tool/bloc/kanji/kanji_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__search_page/kanji_search_page.dart';
import 'package:jisho_study_tool/components/kanji/kanji_suggestions.dart';
import 'package:jisho_study_tool/components/loading.dart'; import 'package:jisho_study_tool/components/loading.dart';
class KanjiView extends StatelessWidget { class KanjiView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<KanjiBloc, KanjiState>( return BlocListener<KanjiBloc, KanjiState>(
builder: (context, state) { listener: (context, state) {
if (state is KanjiSearchInitial) if (state is KanjiSearchInitial) {
return Container(); FocusScope.of(context).unfocus();
else if (state is KanjiSearchLoading) } else if (state is KanjiSearchLoading) {
return LoadingScreen(); FocusScope.of(context).unfocus();
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';
}, },
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';
},
),
); );
} }
} }
@ -35,24 +49,12 @@ class KanjiViewBar extends StatelessWidget {
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.arrow_back), icon: Icon(Icons.arrow_back),
onPressed: () => BlocProvider.of<KanjiBloc>(context) onPressed: () =>
.add(ReturnToInitialState()), BlocProvider.of<KanjiBloc>(context).add(ReturnToInitialState()),
), ),
Expanded( Expanded(
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: 10.0), child: _KanjiTextField(),
child: TextField(
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)),
),
),
), ),
), ),
IconButton( IconButton(
@ -68,3 +70,87 @@ class KanjiViewBar extends StatelessWidget {
); );
} }
} }
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

@ -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();
}

View File

@ -1,16 +1,5 @@
name: jisho_study_tool name: jisho_study_tool
description: A new Flutter project. 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 version: 1.0.0+1
environment: environment:
@ -20,45 +9,24 @@ dependencies:
flutter: flutter:
sdk: 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 # cupertino_icons: ^0.1.2
unofficial_jisho_api: ^1.1.0 unofficial_jisho_api: ^1.1.0
flutter_bloc: ^5.0.1 flutter_bloc: ^5.0.1
url_launcher: ^5.5.0 url_launcher: ^5.5.0
division: ^0.8.8
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter 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: 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 uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.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: # fonts:
# - family: Schyler # - family: Schyler
# fonts: # fonts:
@ -70,6 +38,3 @@ flutter:
# - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf # - asset: fonts/TrajanPro_Bold.ttf
# weight: 700 # weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages