diff --git a/lib/view/components/common/internet_error.dart b/lib/components/common/internet_error.dart similarity index 100% rename from lib/view/components/common/internet_error.dart rename to lib/components/common/internet_error.dart diff --git a/lib/view/components/common/loading.dart b/lib/components/common/loading.dart similarity index 100% rename from lib/view/components/common/loading.dart rename to lib/components/common/loading.dart diff --git a/lib/view/components/opaque_box.dart b/lib/components/common/opaque_box.dart similarity index 100% rename from lib/view/components/opaque_box.dart rename to lib/components/common/opaque_box.dart diff --git a/lib/view/components/common/splash.dart b/lib/components/common/splash.dart similarity index 90% rename from lib/view/components/common/splash.dart rename to lib/components/common/splash.dart index e453f34..7146df5 100644 --- a/lib/view/components/common/splash.dart +++ b/lib/components/common/splash.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../models/themes/theme.dart'; +import '../../models/themes/theme.dart'; class SplashScreen extends StatelessWidget { const SplashScreen({Key? key}) : super(key: key); diff --git a/lib/view/components/common/unknown_error.dart b/lib/components/common/unknown_error.dart similarity index 100% rename from lib/view/components/common/unknown_error.dart rename to lib/components/common/unknown_error.dart diff --git a/lib/view/components/drawing_board/drawing_board.dart b/lib/components/drawing_board/drawing_board.dart similarity index 98% rename from lib/view/components/drawing_board/drawing_board.dart rename to lib/components/drawing_board/drawing_board.dart index d136aa1..8244ec5 100644 --- a/lib/view/components/drawing_board/drawing_board.dart +++ b/lib/components/drawing_board/drawing_board.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:signature/signature.dart'; -import '../../../bloc/theme/theme_bloc.dart'; -import '../../../services/handwriting.dart'; +import '../../bloc/theme/theme_bloc.dart'; +import '../../services/handwriting.dart'; class DrawingBoard extends StatefulWidget { final Function(String)? onSuggestionChosen; diff --git a/lib/view/components/history/date_divider.dart b/lib/components/history/date_divider.dart similarity index 54% rename from lib/view/components/history/date_divider.dart rename to lib/components/history/date_divider.dart index 6d98fc1..91d4674 100644 --- a/lib/view/components/history/date_divider.dart +++ b/lib/components/history/date_divider.dart @@ -1,49 +1,44 @@ import 'package:flutter/material.dart'; -import '../../../bloc/theme/theme_bloc.dart'; -import '../../../models/themes/theme.dart'; +import '../../bloc/theme/theme_bloc.dart'; class DateDivider extends StatelessWidget { final String? text; final DateTime? date; - final EdgeInsets? margin; const DateDivider({ this.text, this.date, - this.margin = const EdgeInsets.symmetric(vertical: 10), Key? key, - }) : super(key: key); + }) : assert((text == null) ^ (date == null)), + super(key: key); String getHumanReadableDate(DateTime date) { - const Map monthTable = { - 1: 'Jan', - 2: 'Feb', - 3: 'Mar', - 4: 'Apr', - 5: 'May', - 6: 'Jun', - 7: 'Jul', - 8: 'Aug', - 9: 'Sep', - 10: 'Oct', - 11: 'Nov', - 12: 'Dec', - }; + const List monthTable = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; final int day = date.day; - final String month = monthTable[date.month]!; + final String month = monthTable[date.month - 1]; final int year = date.year; return '$day. $month $year'; } @override Widget build(BuildContext context) { - final Widget header = (text != null) - ? Text(text!) - : (date != null) - ? Text(getHumanReadableDate(date!)) - : const SizedBox.shrink(); + final Widget header = + (text != null) ? Text(text!) : Text(getHumanReadableDate(date!)); final ColorSet _menuColors = BlocProvider.of(context).state.theme.menuGreyNormal; @@ -54,7 +49,6 @@ class DateDivider extends StatelessWidget { vertical: 5, horizontal: 10, ), - margin: margin, child: DefaultTextStyle.merge( child: header, style: TextStyle(color: _menuColors.foreground), diff --git a/lib/components/history/kanji_box.dart b/lib/components/history/kanji_box.dart new file mode 100644 index 0000000..037465f --- /dev/null +++ b/lib/components/history/kanji_box.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +import '../../bloc/theme/theme_bloc.dart'; + +class KanjiBox extends StatelessWidget { + final String kanji; + + const KanjiBox({ + Key? key, + required this.kanji, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final ColorSet menuColors = + BlocProvider.of(context).state.theme.menuGreyLight; + + return IntrinsicHeight( + child: AspectRatio( + aspectRatio: 1, + child: Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: menuColors.background, + borderRadius: BorderRadius.circular(10.0), + ), + child: Center( + child: FittedBox( + child: Text( + kanji, + style: TextStyle( + color: menuColors.foreground, + fontSize: 25, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/components/history/search_item.dart b/lib/components/history/search_item.dart new file mode 100644 index 0000000..e03f8b8 --- /dev/null +++ b/lib/components/history/search_item.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +import '../../models/history/search.dart'; + +class SearchItem extends StatelessWidget { + final DateTime time; + final Widget search; + final int objectKey; + final void Function()? onDelete; + final void Function()? onFavourite; + final void Function()? onTap; + + const SearchItem({ + required this.time, + required this.search, + required this.objectKey, + this.onDelete, + this.onFavourite, + this.onTap, + Key? key, + }) : super(key: key); + + String getTime() { + final hours = time.hour.toString().padLeft(2, '0'); + final mins = time.minute.toString().padLeft(2, '0'); + return '$hours:$mins'; + } + + @override + Widget build(BuildContext context) { + return Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + SlidableAction( + label: 'Favourite', + backgroundColor: Colors.yellow, + icon: Icons.star, + onPressed: (_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('TODO: implement favourites')), + ); + onFavourite?.call(); + }, + ), + SlidableAction( + label: 'Delete', + backgroundColor: Colors.red, + icon: Icons.delete, + onPressed: (_) { + final Database db = GetIt.instance.get(); + Search.store.record(objectKey).delete(db); + onDelete?.call(); + }, + ), + ], + ), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: ListTile( + onTap: onTap, + contentPadding: EdgeInsets.zero, + title: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(getTime()), + ), + search, + ], + ), + ), + ), + ); + } +} diff --git a/lib/view/components/history/word_search_item.dart b/lib/components/history/word_search_item.dart similarity index 100% rename from lib/view/components/history/word_search_item.dart rename to lib/components/history/word_search_item.dart diff --git a/lib/view/components/kanji/kanji_result_body.dart b/lib/components/kanji/kanji_result_body.dart similarity index 51% rename from lib/view/components/kanji/kanji_result_body.dart rename to lib/components/kanji/kanji_result_body.dart index f94c128..ad29039 100644 --- a/lib/view/components/kanji/kanji_result_body.dart +++ b/lib/components/kanji/kanji_result_body.dart @@ -24,34 +24,61 @@ class KanjiResultBody extends StatelessWidget { resultData = result.data!; } + Widget get headerRow => Container( + margin: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Flexible( + fit: FlexFit.tight, + child: Center(child: SizedBox()), + ), + Flexible( + fit: FlexFit.tight, + child: Center(child: Header(kanji: query)), + ), + Flexible( + fit: FlexFit.tight, + child: Center( + child: (resultData.radical != null) + ? Radical(radical: resultData.radical!) + : const SizedBox(), + ), + ), + ], + ), + ); + + Widget get rankingColumn => Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('JLPT: ', style: TextStyle(fontSize: 20.0)), + JlptLevel(jlptLevel: resultData.jlptLevel ?? '⨉'), + ], + ), + Row( + children: [ + const Text('Grade: ', style: TextStyle(fontSize: 20.0)), + Grade(grade: resultData.taughtIn), + ], + ), + Row( + children: [ + const Text('Rank: ', style: TextStyle(fontSize: 20.0)), + Rank(rank: resultData.newspaperFrequencyRank), + ], + ), + ], + ); + @override Widget build(BuildContext context) { return ListView( children: [ - Container( - margin: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Flexible( - fit: FlexFit.tight, - child: Center(child: SizedBox()), - ), - Flexible( - fit: FlexFit.tight, - child: Center(child: Header(kanji: query)), - ), - Flexible( - fit: FlexFit.tight, - child: Center( - child: (resultData.radical != null) - ? Radical(radical: resultData.radical!) - : const SizedBox(), - ), - ), - ], - ), - ), + headerRow, YomiChips(yomi: resultData.meaning.split(', '), type: YomiType.meaning), (resultData.onyomi.isNotEmpty) ? YomiChips(yomi: resultData.onyomi, type: YomiType.onyomi) @@ -64,30 +91,7 @@ class KanjiResultBody extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ StrokeOrderGif(uri: resultData.strokeOrderGifUri), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Text('JLPT: ', style: TextStyle(fontSize: 20.0)), - JlptLevel(jlptLevel: resultData.jlptLevel ?? '⨉'), - ], - ), - Row( - children: [ - const Text('Grade: ', style: TextStyle(fontSize: 20.0)), - Grade(grade: resultData.taughtIn ?? '⨉'), - ], - ), - Row( - children: [ - const Text('Rank: ', style: TextStyle(fontSize: 20.0)), - Rank(rank: resultData.newspaperFrequencyRank ?? -1), - ], - ), - ], - ), + rankingColumn, ], ), ), diff --git a/lib/components/kanji/kanji_result_body/examples.dart b/lib/components/kanji/kanji_result_body/examples.dart new file mode 100644 index 0000000..3371f8b --- /dev/null +++ b/lib/components/kanji/kanji_result_body/examples.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:unofficial_jisho_api/api.dart'; + +import '../../../bloc/theme/theme_bloc.dart'; + +class Examples extends StatelessWidget { + final List onyomi; + final List kunyomi; + + const Examples({ + required this.onyomi, + required this.kunyomi, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + const textStyle = TextStyle(fontSize: 20); + + final yomiWidgets = + onyomi.map((onEx) => _Example(onEx, _KanaType.onyomi)).toList() + + kunyomi.map((kunEx) => _Example(kunEx, _KanaType.kunyomi)).toList(); + + const noExamplesWidget = [ + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Text('No Examples', style: textStyle), + ) + ]; + + return Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + alignment: Alignment.centerLeft, + child: const Text('Examples:', style: textStyle), + ) + ] + + (onyomi.isEmpty && kunyomi.isEmpty ? noExamplesWidget : yomiWidgets), + ); + } +} + +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) { + final theme = BlocProvider.of(context).state.theme; + final menuColors = theme.menuGreyNormal; + final kanaColors = + kanaType == _KanaType.kunyomi ? theme.kunyomiColor : theme.onyomiColor; + + return Container( + margin: const EdgeInsets.symmetric( + vertical: 5.0, + horizontal: 10.0, + ), + decoration: BoxDecoration( + color: menuColors.background, + borderRadius: BorderRadius.circular(10.0), + ), + child: IntrinsicHeight( + child: Row( + children: [ + _Kana(colors: kanaColors, example: yomiExample), + _ExampleText(colors: menuColors, example: yomiExample) + ], + ), + ), + ); + } +} + +class _Kana extends StatelessWidget { + final ColorSet colors; + final YomiExample example; + + const _Kana({ + Key? key, + required this.colors, + required this.example, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: colors.background, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + example.reading, + style: TextStyle( + color: colors.foreground, + fontSize: 15.0, + ), + ), + const SizedBox(height: 5.0), + Text( + example.example, + style: TextStyle( + color: colors.foreground, + fontSize: 20.0, + ), + ), + ], + ), + ); + } +} + +class _ExampleText extends StatelessWidget { + final ColorSet colors; + final YomiExample example; + + const _ExampleText({ + Key? key, + required this.colors, + required this.example, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Padding( + padding: const EdgeInsets.all(10), + child: Wrap( + children: [ + Text( + example.meaning, + style: TextStyle( + color: colors.foreground, + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/components/kanji/kanji_result_body/grade.dart b/lib/components/kanji/kanji_result_body/grade.dart new file mode 100644 index 0000000..0850d35 --- /dev/null +++ b/lib/components/kanji/kanji_result_body/grade.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import '../../../bloc/theme/theme_bloc.dart'; + +class Grade extends StatelessWidget { + final String? grade; + final String ifNullChar; + + const Grade({ + required this.grade, + this.ifNullChar = '⨉', + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final colors = + BlocProvider.of(context).state.theme.kanjiResultColor; + + return Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: colors.background, + shape: BoxShape.circle, + ), + child: Text( + grade ?? ifNullChar, + style: TextStyle( + color: colors.foreground, + fontSize: 20.0, + ), + ), + ); + } +} diff --git a/lib/view/components/kanji/kanji_result_body/header.dart b/lib/components/kanji/kanji_result_body/header.dart similarity index 75% rename from lib/view/components/kanji/kanji_result_body/header.dart rename to lib/components/kanji/kanji_result_body/header.dart index c86fb5d..222cdc0 100644 --- a/lib/view/components/kanji/kanji_result_body/header.dart +++ b/lib/components/kanji/kanji_result_body/header.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; class Header extends StatelessWidget { final String kanji; @@ -12,7 +12,7 @@ class Header extends StatelessWidget { @override Widget build(BuildContext context) { - final _kanjiColors = + final colors = BlocProvider.of(context).state.theme.kanjiResultColor; return AspectRatio( @@ -20,12 +20,12 @@ class Header extends StatelessWidget { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), - color: _kanjiColors.background, + color: colors.background, ), child: Center( child: Text( kanji, - style: TextStyle(fontSize: 70.0, color: _kanjiColors.foreground), + style: TextStyle(fontSize: 70.0, color: colors.foreground), ), ), ), diff --git a/lib/view/components/kanji/kanji_result_body/jlpt_level.dart b/lib/components/kanji/kanji_result_body/jlpt_level.dart similarity index 68% rename from lib/view/components/kanji/kanji_result_body/jlpt_level.dart rename to lib/components/kanji/kanji_result_body/jlpt_level.dart index f63cbc3..19bc47a 100644 --- a/lib/view/components/kanji/kanji_result_body/jlpt_level.dart +++ b/lib/components/kanji/kanji_result_body/jlpt_level.dart @@ -1,30 +1,32 @@ import 'package:flutter/material.dart'; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; class JlptLevel extends StatelessWidget { - final String jlptLevel; + final String? jlptLevel; + final String ifNullChar; const JlptLevel({ required this.jlptLevel, + this.ifNullChar = '⨉', Key? key, }) : super(key: key); @override Widget build(BuildContext context) { - final _kanjiColors = + final colors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( shape: BoxShape.circle, - color: _kanjiColors.background, + color: colors.background, ), child: Text( - jlptLevel, + jlptLevel ?? ifNullChar, style: TextStyle( - color: _kanjiColors.foreground, + color: colors.foreground, fontSize: 20.0, ), ), diff --git a/lib/view/components/kanji/kanji_result_body/radical.dart b/lib/components/kanji/kanji_result_body/radical.dart similarity index 66% rename from lib/view/components/kanji/kanji_result_body/radical.dart rename to lib/components/kanji/kanji_result_body/radical.dart index aa7ef1e..b3d9e03 100644 --- a/lib/view/components/kanji/kanji_result_body/radical.dart +++ b/lib/components/kanji/kanji_result_body/radical.dart @@ -1,28 +1,27 @@ import 'package:flutter/material.dart'; import 'package:unofficial_jisho_api/api.dart' as jisho; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; class Radical extends StatelessWidget { final jisho.Radical radical; const Radical({required this.radical, Key? key,}) : super(key: key); - @override Widget build(BuildContext context) { - final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; + final colors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(15.0), decoration: BoxDecoration( shape: BoxShape.circle, - color: _kanjiColors.background, + color: colors.background, ), child: Text( radical.symbol, style: TextStyle( - color: _kanjiColors.foreground, + color: colors.foreground, fontSize: 40.0, ), ), diff --git a/lib/components/kanji/kanji_result_body/rank.dart b/lib/components/kanji/kanji_result_body/rank.dart new file mode 100644 index 0000000..5295f0b --- /dev/null +++ b/lib/components/kanji/kanji_result_body/rank.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +import '../../../bloc/theme/theme_bloc.dart'; + +class Rank extends StatelessWidget { + final int? rank; + final String ifNullChar; + + const Rank({ + required this.rank, + this.ifNullChar = '⨉', + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final colors = + BlocProvider.of(context).state.theme.kanjiResultColor; + + return Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + shape: (rank == null) ? BoxShape.circle : BoxShape.rectangle, + borderRadius: (rank == null) ? null : BorderRadius.circular(10.0), + color: colors.background, + ), + child: Text( + rank != null ? '${rank.toString()} / 2500' : ifNullChar, + style: TextStyle( + color: colors.foreground, + fontSize: 20.0, + ), + ), + ); + } +} diff --git a/lib/view/components/kanji/kanji_result_body/stroke_order_gif.dart b/lib/components/kanji/kanji_result_body/stroke_order_gif.dart similarity index 76% rename from lib/view/components/kanji/kanji_result_body/stroke_order_gif.dart rename to lib/components/kanji/kanji_result_body/stroke_order_gif.dart index 75fb00d..decf273 100644 --- a/lib/view/components/kanji/kanji_result_body/stroke_order_gif.dart +++ b/lib/components/kanji/kanji_result_body/stroke_order_gif.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; class StrokeOrderGif extends StatelessWidget { final String uri; @@ -10,13 +10,13 @@ class StrokeOrderGif extends StatelessWidget { @override Widget build(BuildContext context) { - final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; + final colors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( margin: const EdgeInsets.symmetric(vertical: 20.0), padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( - color: _kanjiColors.background, + color: colors.background, borderRadius: BorderRadius.circular(15.0), ), child: ClipRRect( diff --git a/lib/view/components/kanji/kanji_result_body/yomi_chips.dart b/lib/components/kanji/kanji_result_body/yomi_chips.dart similarity index 66% rename from lib/view/components/kanji/kanji_result_body/yomi_chips.dart rename to lib/components/kanji/kanji_result_body/yomi_chips.dart index ce5eb20..dd57c7b 100644 --- a/lib/view/components/kanji/kanji_result_body/yomi_chips.dart +++ b/lib/components/kanji/kanji_result_body/yomi_chips.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; enum YomiType { onyomi, @@ -47,18 +47,17 @@ class YomiChips extends StatelessWidget { bool get isExpandable => yomi.length > 6; Widget yomiCard({ - required BuildContext context, required String yomi, required ColorSet colors, }) => Container( - margin: const EdgeInsets.symmetric(horizontal: 10.0), + margin: const EdgeInsets.symmetric(horizontal: 5), padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 10.0, ), decoration: BoxDecoration( - color: type.getColors(context).background, + color: colors.background, borderRadius: BorderRadius.circular(10.0), ), child: Text( @@ -72,41 +71,38 @@ class YomiChips extends StatelessWidget { Widget yomiWrapper(BuildContext context) { final yomiCards = yomi - .map( - (y) => yomiCard( - context: context, - yomi: y, - colors: type.getColors(context), - ), - ) + .map((y) => yomiCard(yomi: y, colors: type.getColors(context))) .toList(); + final yomiCardsWithTitle = [ + if (type != YomiType.meaning) + yomiCard( + yomi: type == YomiType.kunyomi ? 'Kun:' : 'On:', + colors: ColorSet( + foreground: type.getColors(context).background, + background: const Color(0x000000ff), + ), + ), + ] + + yomiCards; + + final wrap = Wrap( + runSpacing: 10.0, + crossAxisAlignment: WrapCrossAlignment.center, + children: yomiCardsWithTitle, + ); + if (!isExpandable) - return Wrap( - runSpacing: 10.0, - children: yomiCards, - ); + return wrap; else return ExpansionTile( - // initiallyExpanded: false, title: Center( - child: yomiCard( - context: context, - yomi: type.title, - colors: type.getColors(context), - ), + child: yomiCard(yomi: type.title, colors: type.getColors(context)), ), children: [ - const SizedBox( - height: 20.0, - ), - Wrap( - runSpacing: 10.0, - children: yomiCards, - ), - const SizedBox( - height: 25.0, - ), + const SizedBox(height: 20.0), + wrap, + const SizedBox(height: 25.0), ], ); } diff --git a/lib/view/components/kanji/kanji_search_body.dart b/lib/components/kanji/kanji_search_body.dart similarity index 96% rename from lib/view/components/kanji/kanji_search_body.dart rename to lib/components/kanji/kanji_search_body.dart index 3ba2e76..7d36752 100644 --- a/lib/view/components/kanji/kanji_search_body.dart +++ b/lib/components/kanji/kanji_search_body.dart @@ -1,7 +1,7 @@ import 'package:animated_size_and_fade/animated_size_and_fade.dart'; import 'package:flutter/material.dart'; -import '../../../services/kanji_suggestions.dart'; +import '../../services/kanji_suggestions.dart'; import 'kanji_search_body/kanji_grid.dart'; import 'kanji_search_body/kanji_search_bar.dart'; import 'kanji_search_body/kanji_search_options_bar.dart'; @@ -59,7 +59,7 @@ class _KanjiSearchBodyState extends State } return true; }, - child: GestureDetector( + child: InkWell( onTap: () => FocusScope.of(context).unfocus(), child: Container( decoration: const BoxDecoration(), @@ -89,6 +89,7 @@ class _KanjiSearchBodyState extends State }), ), ), + const SizedBox(height: 20), AnimatedSizeAndFade( fadeDuration: const Duration(milliseconds: 200), sizeDuration: const Duration(milliseconds: 300), diff --git a/lib/view/components/kanji/kanji_search_body/kanji_grid.dart b/lib/components/kanji/kanji_search_body/kanji_grid.dart similarity index 90% rename from lib/view/components/kanji/kanji_search_body/kanji_grid.dart rename to lib/components/kanji/kanji_search_body/kanji_grid.dart index 8373be9..49951d4 100644 --- a/lib/view/components/kanji/kanji_search_body/kanji_grid.dart +++ b/lib/components/kanji/kanji_search_body/kanji_grid.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; +import '../../../routing/routes.dart'; class KanjiGrid extends StatelessWidget { final List suggestions; @@ -33,7 +34,7 @@ class _GridItem extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: () { - Navigator.pushNamed(context, '/kanjiSearch', arguments: kanji); + Navigator.pushNamed(context, Routes.kanjiSearch, arguments: kanji); }, child: BlocBuilder( builder: (context, state) { diff --git a/lib/view/components/kanji/kanji_search_body/kanji_search_bar.dart b/lib/components/kanji/kanji_search_body/kanji_search_bar.dart similarity index 84% rename from lib/view/components/kanji/kanji_search_body/kanji_search_bar.dart rename to lib/components/kanji/kanji_search_body/kanji_search_bar.dart index ce1b806..c919115 100644 --- a/lib/view/components/kanji/kanji_search_body/kanji_search_bar.dart +++ b/lib/components/kanji/kanji_search_body/kanji_search_bar.dart @@ -21,20 +21,18 @@ class KanjiSearchBarState extends State { super.initState(); } - void runOnChanged() { - if (widget.onChanged != null) widget.onChanged!(textController.text); - } + void onChanged() => widget.onChanged?.call(textController.text); void clearText() { textController.text = ''; - runOnChanged(); + onChanged(); } Future pasteText() async { final ClipboardData? clipboardData = await Clipboard.getData('text/plain'); if (clipboardData != null && clipboardData.text != null) { textController.text = clipboardData.text!; - runOnChanged(); + onChanged(); } } @@ -52,15 +50,11 @@ class KanjiSearchBarState extends State { return TextField( controller: textController, - onChanged: (text) { - if (widget.onChanged != null) widget.onChanged!(text); - }, + onChanged: (text) => onChanged(), onSubmitted: (_) => {}, decoration: InputDecoration( prefixIcon: const Icon(Icons.search), hintText: 'Search', - // fillColor: Colors.white, - // filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10.0), ), diff --git a/lib/view/components/kanji/kanji_search_body/kanji_search_options_bar.dart b/lib/components/kanji/kanji_search_body/kanji_search_options_bar.dart similarity index 72% rename from lib/view/components/kanji/kanji_search_body/kanji_search_options_bar.dart rename to lib/components/kanji/kanji_search_body/kanji_search_options_bar.dart index 97e64ac..f6f31b9 100644 --- a/lib/view/components/kanji/kanji_search_body/kanji_search_options_bar.dart +++ b/lib/components/kanji/kanji_search_body/kanji_search_options_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import '../../../../bloc/theme/theme_bloc.dart'; +import '../../../bloc/theme/theme_bloc.dart'; +import '../../../routing/routes.dart'; class KanjiSearchOptionsBar extends StatelessWidget { const KanjiSearchOptionsBar({Key? key}) : super(key: key); @@ -14,17 +15,17 @@ class KanjiSearchOptionsBar extends StatelessWidget { _IconButton( icon: const Icon(Icons.pie_chart), onPressed: () => - Navigator.pushNamed(context, '/kanjiSearch/radicals'), + Navigator.pushNamed(context, Routes.kanjiSearchRadicals), ), - const SizedBox(width: 10,), + const SizedBox(width: 10), _IconButton( icon: const Icon(Icons.school), - onPressed: () => Navigator.pushNamed(context, '/kanjiSearch/grade'), + onPressed: () => Navigator.pushNamed(context, Routes.kanjiSearchGrade), ), - const SizedBox(width: 10,), + const SizedBox(width: 10), _IconButton( icon: const Icon(Icons.mode), - onPressed: () => Navigator.pushNamed(context, '/kanjiSearch/draw'), + onPressed: () => Navigator.pushNamed(context, Routes.kanjiSearchDraw), ), ], ), diff --git a/lib/view/components/search/language_selector.dart b/lib/components/search/language_selector.dart similarity index 97% rename from lib/view/components/search/language_selector.dart rename to lib/components/search/language_selector.dart index 66255e6..9b44f30 100644 --- a/lib/view/components/search/language_selector.dart +++ b/lib/components/search/language_selector.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../../../models/themes/theme.dart'; +import '../../models/themes/theme.dart'; class LanguageSelector extends StatefulWidget { const LanguageSelector({Key? key}) : super(key: key); diff --git a/lib/view/components/search/search_bar.dart b/lib/components/search/search_bar.dart similarity index 87% rename from lib/view/components/search/search_bar.dart rename to lib/components/search/search_bar.dart index 1a56acf..dd9ceda 100644 --- a/lib/view/components/search/search_bar.dart +++ b/lib/components/search/search_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../routing/routes.dart'; import 'language_selector.dart'; class SearchBar extends StatelessWidget { @@ -13,7 +14,7 @@ class SearchBar extends StatelessWidget { children: [ TextField( onSubmitted: (text) => - Navigator.pushNamed(context, '/search', arguments: text), + Navigator.pushNamed(context, Routes.search, arguments: text), controller: TextEditingController(), decoration: InputDecoration( labelText: 'Search', diff --git a/lib/view/components/search/search_result_body.dart b/lib/components/search/search_result_body.dart similarity index 100% rename from lib/view/components/search/search_result_body.dart rename to lib/components/search/search_result_body.dart diff --git a/lib/view/components/search/search_results_body/parts/badge.dart b/lib/components/search/search_results_body/parts/badge.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/badge.dart rename to lib/components/search/search_results_body/parts/badge.dart diff --git a/lib/view/components/search/search_results_body/parts/common_badge.dart b/lib/components/search/search_results_body/parts/common_badge.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/common_badge.dart rename to lib/components/search/search_results_body/parts/common_badge.dart diff --git a/lib/view/components/search/search_results_body/parts/header.dart b/lib/components/search/search_results_body/parts/header.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/header.dart rename to lib/components/search/search_results_body/parts/header.dart diff --git a/lib/view/components/search/search_results_body/parts/jlpt_badge.dart b/lib/components/search/search_results_body/parts/jlpt_badge.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/jlpt_badge.dart rename to lib/components/search/search_results_body/parts/jlpt_badge.dart diff --git a/lib/view/components/search/search_results_body/parts/other_forms.dart b/lib/components/search/search_results_body/parts/other_forms.dart similarity index 97% rename from lib/view/components/search/search_results_body/parts/other_forms.dart rename to lib/components/search/search_results_body/parts/other_forms.dart index d690f8c..4c3870f 100644 --- a/lib/view/components/search/search_results_body/parts/other_forms.dart +++ b/lib/components/search/search_results_body/parts/other_forms.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:unofficial_jisho_api/api.dart'; -import '../../../../../bloc/theme/theme_bloc.dart'; +import '../../../../bloc/theme/theme_bloc.dart'; class OtherForms extends StatelessWidget { final List forms; diff --git a/lib/view/components/search/search_results_body/parts/senses.dart b/lib/components/search/search_results_body/parts/senses.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/senses.dart rename to lib/components/search/search_results_body/parts/senses.dart diff --git a/lib/view/components/search/search_results_body/parts/wanikani_badge.dart b/lib/components/search/search_results_body/parts/wanikani_badge.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/wanikani_badge.dart rename to lib/components/search/search_results_body/parts/wanikani_badge.dart diff --git a/lib/view/components/search/search_results_body/parts/wikipedia_attribute.dart b/lib/components/search/search_results_body/parts/wikipedia_attribute.dart similarity index 100% rename from lib/view/components/search/search_results_body/parts/wikipedia_attribute.dart rename to lib/components/search/search_results_body/parts/wikipedia_attribute.dart diff --git a/lib/view/components/search/search_results_body/search_card.dart b/lib/components/search/search_results_body/search_card.dart similarity index 100% rename from lib/view/components/search/search_results_body/search_card.dart rename to lib/components/search/search_results_body/search_card.dart diff --git a/lib/view/components/search/word_details_body.dart b/lib/components/search/word_details_body.dart similarity index 100% rename from lib/view/components/search/word_details_body.dart rename to lib/components/search/word_details_body.dart diff --git a/lib/main.dart b/lib/main.dart index 31529b0..e26f372 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:sembast/sembast_io.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'bloc/theme/theme_bloc.dart'; -import 'router.dart'; +import 'routing/router.dart'; Future setupDatabase() async { final Directory appDocDir = await getApplicationDocumentsDirectory(); diff --git a/lib/models/history/search.dart b/lib/models/history/search.dart index 2de2096..bb2d76d 100644 --- a/lib/models/history/search.dart +++ b/lib/models/history/search.dart @@ -3,6 +3,9 @@ import 'package:sembast/sembast.dart'; import './kanji_query.dart'; import './word_query.dart'; +export 'package:get_it/get_it.dart'; +export 'package:sembast/sembast.dart'; + class Search { final DateTime timestamp; final WordQuery? wordQuery; diff --git a/lib/models/history/word_query.dart b/lib/models/history/word_query.dart index e0904e8..8192da2 100644 --- a/lib/models/history/word_query.dart +++ b/lib/models/history/word_query.dart @@ -1,4 +1,3 @@ -import './word_result.dart'; class WordQuery { final String query; diff --git a/lib/router.dart b/lib/router.dart deleted file mode 100644 index 0f923be..0000000 --- a/lib/router.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'view/home.dart'; -import 'view/screens/search/kanji_result_page.dart'; -import 'view/screens/search/search_mechanisms/drawing.dart'; -import 'view/screens/search/search_mechanisms/grade_list.dart'; -import 'view/screens/search/search_mechanisms/radical_list.dart'; -import 'view/screens/search/search_results_page.dart'; - -Route generateRoute(RouteSettings settings) { - final args = settings.arguments; - - switch (settings.name) { - case '/': - return MaterialPageRoute(builder: (_) => const Home()); - - case '/search': - final searchTerm = args! as String; - return MaterialPageRoute( - builder: (_) => SearchResultsPage(searchTerm: searchTerm), - ); - - case '/kanjiSearch': - final searchTerm = args! as String; - return MaterialPageRoute( - builder: (_) => KanjiResultPage(kanjiSearchTerm: searchTerm), - ); - - case '/kanjiSearch/draw': - return MaterialPageRoute(builder: (_) => const KanjiDrawingSearch()); - - case '/kanjiSearch/grade': - return MaterialPageRoute(builder: (_) => const KanjiGradeSearch()); - - case '/kanjiSearch/radicals': - return MaterialPageRoute(builder: (_) => const KanjiRadicalSearch()); - - default: - return MaterialPageRoute( - builder: (_) => const Text('ERROR: this route does not exist'), - ); - } -} diff --git a/lib/routing/router.dart b/lib/routing/router.dart new file mode 100644 index 0000000..4a1e50b --- /dev/null +++ b/lib/routing/router.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import '../screens/home.dart'; +import '../screens/search/kanji_result_page.dart'; +import '../screens/search/search_mechanisms/drawing.dart'; +import '../screens/search/search_mechanisms/grade_list.dart'; +import '../screens/search/search_mechanisms/radical_list.dart'; +import '../screens/search/search_results_page.dart'; +import 'routes.dart'; + +Route generateRoute(RouteSettings settings) { + final args = settings.arguments; + + switch (settings.name) { + case Routes.root: + return MaterialPageRoute(builder: (_) => const Home()); + + case Routes.search: + final searchTerm = args! as String; + return MaterialPageRoute( + builder: (_) => SearchResultsPage(searchTerm: searchTerm), + ); + + case Routes.kanjiSearch: + final searchTerm = args! as String; + return MaterialPageRoute( + builder: (_) => KanjiResultPage(kanjiSearchTerm: searchTerm), + ); + + case Routes.kanjiSearchDraw: + return MaterialPageRoute(builder: (_) => const KanjiDrawingSearch()); + + case Routes.kanjiSearchGrade: + return MaterialPageRoute(builder: (_) => const KanjiGradeSearch()); + + case Routes.kanjiSearchRadicals: + return MaterialPageRoute(builder: (_) => const KanjiRadicalSearch()); + + // TODO: Add more specific error screens. + case Routes.errorNotFound: + case Routes.errorNetwork: + case Routes.errorOther: + default: + return MaterialPageRoute( + builder: (_) => Scaffold( + appBar: + AppBar(title: const Text('Error'), backgroundColor: Colors.red), + body: Center(child: ErrorWidget('Some kind of error occured')), + ), + ); + } +} diff --git a/lib/routing/routes.dart b/lib/routing/routes.dart new file mode 100644 index 0000000..d64d3a5 --- /dev/null +++ b/lib/routing/routes.dart @@ -0,0 +1,11 @@ +class Routes { + static const String root = '/'; + static const String search = '/search'; + static const String kanjiSearch = '/kanjiSearch'; + static const String kanjiSearchDraw = '/kanjiSearch/draw'; + static const String kanjiSearchGrade = '/kanjiSearch/grade'; + static const String kanjiSearchRadicals = '/kanjiSearch/radicals'; + static const String errorNotFound = '/error/404'; + static const String errorNetwork = '/error/network'; + static const String errorOther = '/error/other'; +} diff --git a/lib/screens/debug.dart b/lib/screens/debug.dart new file mode 100644 index 0000000..2639bee --- /dev/null +++ b/lib/screens/debug.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../components/drawing_board/drawing_board.dart'; + +class DebugView extends StatelessWidget { + const DebugView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DrawingBoard( + allowHiragana: true, + allowKatakana: true, + allowOther: true, + onSuggestionChosen: (s) => ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Chose: $s'), + duration: const Duration(milliseconds: 600), + ), + ), + ), + ], + ); + } +} diff --git a/lib/screens/history.dart b/lib/screens/history.dart new file mode 100644 index 0000000..834c963 --- /dev/null +++ b/lib/screens/history.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; + +import '../components/common/loading.dart'; +import '../components/common/opaque_box.dart'; +import '../components/history/date_divider.dart'; +import '../components/history/kanji_box.dart'; +import '../components/history/search_item.dart'; +import '../models/history/search.dart'; +import '../routing/routes.dart'; + +class HistoryView extends StatelessWidget { + const HistoryView({Key? key}) : super(key: key); + + Database get _db => GetIt.instance.get(); + + Stream> get searchStream => Search.store + .query(finder: Finder(sortOrders: [SortOrder('timestamp', false)])) + .onSnapshots(_db) + .map( + (snapshot) => Map.fromEntries( + snapshot.where((snap) => snap.value != null).map( + (snap) => MapEntry( + snap.key, + Search.fromJson(snap.value! as Map), + ), + ), + ), + ); + + @override + Widget build(BuildContext context) { + return StreamBuilder>( + stream: searchStream, + builder: (context, snapshot) { + if (!snapshot.hasData) return const LoadingScreen(); + + final Map data = snapshot.data!; + if (data.isEmpty) + return const Center( + child: Text('The history is empty.\nTry searching for something!'), + ); + + return OpaqueBox( + child: ListView.separated( + itemCount: data.length + 1, + itemBuilder: historyEntryWithData(data), + separatorBuilder: + historyEntrySeparatorWithData(data.values.toList()), + ), + ); + }, + ); + } + + Widget Function(BuildContext, int) historyEntryWithData( + Map data, + ) => + (context, index) { + if (index == 0) return const SizedBox.shrink(); + + final Search search = data.values.toList()[index - 1]; + final int objectKey = data.keys.toList()[index - 1]; + + late final Widget child; + late final void Function() onTap; + + if (search.isKanji) { + child = KanjiBox(kanji: search.kanjiQuery!.kanji); + onTap = () => Navigator.pushNamed( + context, + Routes.kanjiSearch, + arguments: search.kanjiQuery!.kanji, + ); + } else { + child = Text(search.wordQuery!.query); + onTap = () => Navigator.pushNamed( + context, + Routes.search, + arguments: search.wordQuery!.query, + ); + } + + return SearchItem( + time: search.timestamp, + search: child, + objectKey: objectKey, + onTap: onTap, + onDelete: () => build(context), + ); + }; + + DateTime roundToDay(DateTime date) => + DateTime(date.year, date.month, date.day); + + bool dateChangedFromLastSearch(Search prevSearch, DateTime searchDate) { + final DateTime prevSearchDate = roundToDay(prevSearch.timestamp); + return prevSearchDate != searchDate; + } + + DateTime get today => roundToDay(DateTime.now()); + DateTime get yesterday => + roundToDay(DateTime.now().subtract(const Duration(days: 1))); + + Widget Function(BuildContext, int) historyEntrySeparatorWithData( + List data, + ) => + (context, index) { + final Search search = data[index]; + final DateTime searchDate = roundToDay(search.timestamp); + + if (index == 0 || + dateChangedFromLastSearch(data[index - 1], searchDate)) { + if (searchDate == today) + return const DateDivider(text: 'Today'); + else if (searchDate == yesterday) + return const DateDivider(text: 'Yesterday'); + else + return DateDivider(date: searchDate); + } + + return const Divider(height: 0); + }; +} diff --git a/lib/view/home.dart b/lib/screens/home.dart similarity index 85% rename from lib/view/home.dart rename to lib/screens/home.dart index 90e42f5..3a062d7 100644 --- a/lib/view/home.dart +++ b/lib/screens/home.dart @@ -1,11 +1,13 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mdi/mdi.dart'; import '../bloc/theme/theme_bloc.dart'; -import 'screens/history.dart'; -import 'screens/search/kanji_view.dart'; -import 'screens/search/search_view.dart'; -import 'screens/settings.dart'; +import 'debug.dart'; +import 'history.dart'; +import 'search/kanji_view.dart'; +import 'search/search_view.dart'; +import 'settings.dart'; class Home extends StatefulWidget { const Home({Key? key}) : super(key: key); @@ -68,7 +70,7 @@ class _HomeState extends State { ), const _Page( content: KanjiView(), - titleBar: Text('Kanji'), + titleBar: Text('Kanji Search'), item: BottomNavigationBarItem( label: 'Kanji', icon: Icon(Mdi.ideogramCjk, size: 30), @@ -98,6 +100,16 @@ class _HomeState extends State { icon: Icon(Icons.settings), ), ), + if (kDebugMode) ...[ + const _Page( + content: DebugView(), + titleBar: Text('Debug Page'), + item: BottomNavigationBarItem( + label: 'Debug', + icon: Icon(Icons.biotech), + ), + ) + ], ]; } diff --git a/lib/view/screens/saved.dart b/lib/screens/saved.dart similarity index 100% rename from lib/view/screens/saved.dart rename to lib/screens/saved.dart diff --git a/lib/view/screens/search/kanji_result_page.dart b/lib/screens/search/kanji_result_page.dart similarity index 61% rename from lib/view/screens/search/kanji_result_page.dart rename to lib/screens/search/kanji_result_page.dart index 07a6f2b..5cee2fe 100644 --- a/lib/view/screens/search/kanji_result_page.dart +++ b/lib/screens/search/kanji_result_page.dart @@ -1,25 +1,32 @@ import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; -import 'package:sembast/sembast.dart'; -import '../../../models/history/kanji_query.dart'; -import '../../../models/history/search.dart'; -import '../../../services/jisho_api/kanji_search.dart'; import '../../components/common/loading.dart'; import '../../components/kanji/kanji_result_body.dart'; +import '../../models/history/kanji_query.dart'; +import '../../models/history/search.dart'; +import '../../services/jisho_api/kanji_search.dart'; -class KanjiResultPage extends StatelessWidget { +class KanjiResultPage extends StatefulWidget { final String kanjiSearchTerm; - bool addedToDatabase = false; - KanjiResultPage({required this.kanjiSearchTerm, Key? key}) : super(key: key); + const KanjiResultPage({ + Key? key, + required this.kanjiSearchTerm, + }) : super(key: key); + + @override + _KanjiResultPageState createState() => _KanjiResultPageState(); +} + +class _KanjiResultPageState extends State { + bool addedToDatabase = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: FutureBuilder( - future: fetchKanji(kanjiSearchTerm), + future: fetchKanji(widget.kanjiSearchTerm), builder: (context, snapshot) { if (!snapshot.hasData) return const LoadingScreen(); if (snapshot.hasError) return ErrorWidget(snapshot.error!); @@ -29,7 +36,7 @@ class KanjiResultPage extends StatelessWidget { GetIt.instance.get(), Search.fromKanjiQuery( timestamp: DateTime.now(), - kanjiQuery: KanjiQuery(kanji: kanjiSearchTerm), + kanjiQuery: KanjiQuery(kanji: widget.kanjiSearchTerm), ).toJson(), ); addedToDatabase = true; diff --git a/lib/view/screens/search/kanji_view.dart b/lib/screens/search/kanji_view.dart similarity index 100% rename from lib/view/screens/search/kanji_view.dart rename to lib/screens/search/kanji_view.dart diff --git a/lib/view/screens/search/search_mechanisms/drawing.dart b/lib/screens/search/search_mechanisms/drawing.dart similarity index 90% rename from lib/view/screens/search/search_mechanisms/drawing.dart rename to lib/screens/search/search_mechanisms/drawing.dart index f4008fc..35ce101 100644 --- a/lib/view/screens/search/search_mechanisms/drawing.dart +++ b/lib/screens/search/search_mechanisms/drawing.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; + import '../../../components/drawing_board/drawing_board.dart'; +import '../../../routing/routes.dart'; class KanjiDrawingSearch extends StatelessWidget { const KanjiDrawingSearch({Key? key}) : super(key: key); @@ -15,7 +17,7 @@ class KanjiDrawingSearch extends StatelessWidget { onlyOneCharacterSuggestions: true, onSuggestionChosen: (suggestion) => Navigator.popAndPushNamed( context, - '/kanjiSearch', + Routes.kanjiSearch, arguments: suggestion, ), ), diff --git a/lib/view/screens/search/search_mechanisms/grade_list.dart b/lib/screens/search/search_mechanisms/grade_list.dart similarity index 93% rename from lib/view/screens/search/search_mechanisms/grade_list.dart rename to lib/screens/search/search_mechanisms/grade_list.dart index 6486226..7404520 100644 --- a/lib/view/screens/search/search_mechanisms/grade_list.dart +++ b/lib/screens/search/search_mechanisms/grade_list.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import '../../../../data/grades.dart'; import '../../../../models/themes/theme.dart'; +import '../../../../routing/routes.dart'; +import '../../../components/common/loading.dart'; class KanjiGradeSearch extends StatefulWidget { const KanjiGradeSearch({Key? key}) : super(key: key); @@ -28,7 +30,7 @@ class _GridItem extends StatelessWidget { SnackBar(content: Text(text)), ) : () => - Navigator.popAndPushNamed(context, '/kanjiSearch', arguments: text); + Navigator.popAndPushNamed(context, Routes.kanjiSearch, arguments: text); return InkWell( onTap: onTap, @@ -102,7 +104,7 @@ class _KanjiGradeSearchState extends State { appBar: AppBar(title: const Text('Choose by grade')), body: FutureBuilder( future: makeGrids, - initialData: const Center(child: CircularProgressIndicator()), + initialData: const LoadingScreen(), builder: (context, snapshot) => snapshot.data!, ), ); diff --git a/lib/view/screens/search/search_mechanisms/radical_list.dart b/lib/screens/search/search_mechanisms/radical_list.dart similarity index 98% rename from lib/view/screens/search/search_mechanisms/radical_list.dart rename to lib/screens/search/search_mechanisms/radical_list.dart index aab617d..87171ae 100644 --- a/lib/view/screens/search/search_mechanisms/radical_list.dart +++ b/lib/screens/search/search_mechanisms/radical_list.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import '../../../../bloc/theme/theme_bloc.dart'; import '../../../../data/radicals.dart'; +import '../../../../routing/routes.dart'; import '../../../../services/jisho_api/radicals_search.dart'; class KanjiRadicalSearch extends StatefulWidget { @@ -124,7 +125,7 @@ class _KanjiRadicalSearchState extends State { return InkWell( onTap: () => Navigator.popAndPushNamed( context, - '/kanjiSearch', + Routes.kanjiSearch, arguments: kanji, ), child: Container( diff --git a/lib/view/screens/search/search_results_page.dart b/lib/screens/search/search_results_page.dart similarity index 61% rename from lib/view/screens/search/search_results_page.dart rename to lib/screens/search/search_results_page.dart index 96f779a..6c8e4c9 100644 --- a/lib/view/screens/search/search_results_page.dart +++ b/lib/screens/search/search_results_page.dart @@ -1,28 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; -import 'package:sembast/sembast.dart'; -import '../../../models/history/search.dart'; -import '../../../models/history/word_query.dart'; -import '../../../services/jisho_api/jisho_search.dart'; import '../../components/common/loading.dart'; import '../../components/search/search_result_body.dart'; +import '../../models/history/search.dart'; +import '../../models/history/word_query.dart'; +import '../../services/jisho_api/jisho_search.dart'; -class SearchResultsPage extends StatelessWidget { +// TODO: merge with KanjiResultPage +class SearchResultsPage extends StatefulWidget { final String searchTerm; - final Future results; - bool addedToDatabase = false; - SearchResultsPage({required this.searchTerm, Key? key}) - : results = fetchJishoResults(searchTerm), - super(key: key); + const SearchResultsPage({ + Key? key, + required this.searchTerm, + }) : super(key: key); + + @override + _SearchResultsPageState createState() => _SearchResultsPageState(); +} + +class _SearchResultsPageState extends State { + bool addedToDatabase = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: FutureBuilder( - future: results, + future: fetchJishoResults(widget.searchTerm), builder: (context, snapshot) { if (!snapshot.hasData) return const LoadingScreen(); if (snapshot.hasError || snapshot.data!.data == null) @@ -33,7 +38,7 @@ class SearchResultsPage extends StatelessWidget { GetIt.instance.get(), Search.fromWordQuery( timestamp: DateTime.now(), - wordQuery: WordQuery(query: searchTerm), + wordQuery: WordQuery(query: widget.searchTerm), ).toJson(), ); addedToDatabase = true; diff --git a/lib/view/screens/search/search_view.dart b/lib/screens/search/search_view.dart similarity index 100% rename from lib/view/screens/search/search_view.dart rename to lib/screens/search/search_view.dart diff --git a/lib/view/screens/settings.dart b/lib/screens/settings.dart similarity index 87% rename from lib/view/screens/settings.dart rename to lib/screens/settings.dart index 2c19d66..7c3aa00 100644 --- a/lib/view/screens/settings.dart +++ b/lib/screens/settings.dart @@ -1,12 +1,10 @@ import 'package:confirm_dialog/confirm_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; -import 'package:get_it/get_it.dart'; -import 'package:sembast/sembast.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../../bloc/theme/theme_bloc.dart'; -import '../../models/history/search.dart'; +import '../bloc/theme/theme_bloc.dart'; +import '../models/history/search.dart'; class SettingsView extends StatefulWidget { const SettingsView({Key? key}) : super(key: key); @@ -18,18 +16,22 @@ class SettingsView extends StatefulWidget { class _SettingsViewState extends State { final SharedPreferences prefs = GetIt.instance.get(); + bool romajiEnabled = false; + bool darkThemeEnabled = false; bool autoThemeEnabled = false; @override void initState() { super.initState(); + romajiEnabled = prefs.getBool('romajiEnabled') ?? romajiEnabled; darkThemeEnabled = prefs.getBool('darkThemeEnabled') ?? darkThemeEnabled; autoThemeEnabled = prefs.getBool('autoThemeEnabled') ?? autoThemeEnabled; } /// Update stored preferences with values from setting page state Future _updatePrefs() async { + prefs.setBool('romajiEnabled', romajiEnabled); prefs.setBool('darkThemeEnabled', darkThemeEnabled); prefs.setBool('autoThemeEnabled', autoThemeEnabled); } @@ -55,6 +57,19 @@ class _SettingsViewState extends State { backgroundColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(vertical: 10), sections: [ + SettingsSection( + title: 'Dictionary', + titleTextStyle: _titleTextStyle, + tiles: [ + SettingsTile.switchTile( + title: 'Use romaji', + onToggle: (b) {}, //TODO: implement + switchValue: romajiEnabled, + enabled: false, + switchActiveColor: AppTheme.jishoGreen.background, + ), + ], + ), SettingsSection( title: 'Theme', titleTextStyle: _titleTextStyle, diff --git a/lib/services/jisho_api/kanji_search.dart b/lib/services/jisho_api/kanji_search.dart index 76e8a31..4717d30 100644 --- a/lib/services/jisho_api/kanji_search.dart +++ b/lib/services/jisho_api/kanji_search.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:unofficial_jisho_api/api.dart' as jisho; export 'package:unofficial_jisho_api/api.dart' show KanjiResult; @@ -13,8 +12,6 @@ String? _convertGrade(String grade) { 'junior high': '中' }; - debugPrint('conversion run: $grade -> ${conversionTable[grade]}'); - return conversionTable[grade]; } diff --git a/lib/view/components/history/kanji_search_item.dart b/lib/view/components/history/kanji_search_item.dart deleted file mode 100644 index 15ce4c5..0000000 --- a/lib/view/components/history/kanji_search_item.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; - -import './search_item.dart'; -import '../../../bloc/theme/theme_bloc.dart'; -import '../../../models/history/kanji_query.dart'; -import '../../../models/themes/theme.dart'; - -class _KanjiBox extends StatelessWidget { - final String kanji; - - const _KanjiBox(this.kanji); - - @override - Widget build(BuildContext context) { - final ColorSet _menuColors = - BlocProvider.of(context).state.theme.menuGreyLight; - - return IntrinsicHeight( - child: AspectRatio( - aspectRatio: 1, - child: Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: _menuColors.background, - borderRadius: BorderRadius.circular(10.0), - ), - child: Center( - child: FittedBox( - child: Text( - kanji, - style: TextStyle( - color: _menuColors.foreground, - fontSize: 25, - ), - ), - ), - ), - ), - ), - ); - } -} - -class KanjiSearchItem extends StatelessWidget { - final KanjiQuery result; - final DateTime timestamp; - - const KanjiSearchItem({ - required this.result, - required this.timestamp, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - SlidableAction( - label: 'Favourite', - backgroundColor: Colors.yellow, - icon: Icons.star, - onPressed: (_) {}, - ), - SlidableAction( - label: 'Delete', - backgroundColor: Colors.red, - icon: Icons.delete, - onPressed: (_) {}, - ), - ], - ), - child: SearchItem( - onTap: () { - Navigator.pushNamed(context, '/kanjiSearch', arguments: result.kanji); - }, - time: timestamp, - search: _KanjiBox(result.kanji), - ), - ); - } -} diff --git a/lib/view/components/history/phrase_search_item.dart b/lib/view/components/history/phrase_search_item.dart deleted file mode 100644 index d6d4377..0000000 --- a/lib/view/components/history/phrase_search_item.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; - -import './search_item.dart'; -import '../../../models/history/word_query.dart'; - -class PhraseSearchItem extends StatelessWidget { - final WordQuery search; - final DateTime timestamp; - - const PhraseSearchItem({ - required this.search, - required this.timestamp, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - - SlidableAction( - label: 'Delete', - backgroundColor: Colors.red, - icon: Icons.delete, - onPressed: (_) {}, - ), - ], - - ), - child: SearchItem( - onTap: () => Navigator.pushNamed( - context, - '/search', - arguments: search.query, - ), - time: timestamp, - search: Text(search.query), - ), - ); - } -} diff --git a/lib/view/components/history/search_item.dart b/lib/view/components/history/search_item.dart deleted file mode 100644 index 7025b18..0000000 --- a/lib/view/components/history/search_item.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -class SearchItem extends StatelessWidget { - final DateTime time; - final Widget search; - final void Function()? onTap; - - const SearchItem({ - required this.time, - required this.search, - this.onTap, - Key? key, - }) : super(key: key); - - String getTime() { - final hours = time.hour.toString().padLeft(2, '0'); - final mins = time.minute.toString().padLeft(2, '0'); - return '$hours:$mins'; - } - - @override - Widget build(BuildContext context) { - return ListTile( - onTap: onTap, - contentPadding: EdgeInsets.zero, - title: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text(getTime()), - ), - search, - ], - ), - ); - } -} diff --git a/lib/view/components/kanji/kanji_result_body/examples.dart b/lib/view/components/kanji/kanji_result_body/examples.dart deleted file mode 100644 index 8740336..0000000 --- a/lib/view/components/kanji/kanji_result_body/examples.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:unofficial_jisho_api/api.dart'; - -import '../../../../bloc/theme/theme_bloc.dart'; - -class Examples extends StatelessWidget { - final List onyomi; - final List kunyomi; - - const Examples({ - required this.onyomi, - required this.kunyomi, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - alignment: Alignment.centerLeft, - child: const Text( - 'Examples:', - style: TextStyle(fontSize: 20), - ), - ) - ], - onyomi - .map((onyomiExample) => _Example(onyomiExample, _KanaType.onyomi)) - .toList(), - kunyomi - .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) { - final _themeData = BlocProvider.of(context).state.theme; - final _kanaColors = kanaType == _KanaType.kunyomi - ? _themeData.kunyomiColor - : _themeData.onyomiColor; - final _menuColors = _themeData.menuGreyNormal; - - return Container( - margin: const EdgeInsets.symmetric( - vertical: 5.0, - horizontal: 10.0, - ), - decoration: BoxDecoration( - color: _menuColors.background, - borderRadius: BorderRadius.circular(10.0), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 10.0, - ), - decoration: BoxDecoration( - color: _kanaColors.background, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10.0), - bottomLeft: Radius.circular(10.0), - ), - ), - child: Column( - children: [ - Text( - yomiExample.reading, - style: TextStyle( - color: _kanaColors.foreground, - fontSize: 15.0, - ), - ), - const SizedBox( - height: 5.0, - ), - Text( - yomiExample.example, - style: TextStyle( - color: _kanaColors.foreground, - fontSize: 20.0, - ), - ), - ], - ), - ), - const SizedBox( - width: 15.0, - ), - Expanded( - child: Wrap( - children: [ - Text( - yomiExample.meaning, - style: TextStyle( - color: _menuColors.foreground, - ), - ) - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/view/components/kanji/kanji_result_body/grade.dart b/lib/view/components/kanji/kanji_result_body/grade.dart deleted file mode 100644 index abe2443..0000000 --- a/lib/view/components/kanji/kanji_result_body/grade.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../../bloc/theme/theme_bloc.dart'; - -class Grade extends StatelessWidget { - final String grade; - - const Grade({required this.grade, Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; - - return Container( - padding: const EdgeInsets.all(10.0), - decoration: BoxDecoration( - color: _kanjiColors.background, - shape: BoxShape.circle, - ), - child: Text( - grade, - style: TextStyle( - color: _kanjiColors.foreground, - fontSize: 20.0, - ), - ), - ); - } -} diff --git a/lib/view/components/kanji/kanji_result_body/rank.dart b/lib/view/components/kanji/kanji_result_body/rank.dart deleted file mode 100644 index 89279ac..0000000 --- a/lib/view/components/kanji/kanji_result_body/rank.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../../bloc/theme/theme_bloc.dart'; - -class Rank extends StatelessWidget { - final int rank; - - const Rank({required this.rank, Key? key,}) : super(key: key); - - - @override - Widget build(BuildContext context) { - final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; - - return Container( - padding: const EdgeInsets.all(10.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - color: _kanjiColors.background, - ), - child: Text( - '${rank.toString()} / 2500', - style: TextStyle( - color: _kanjiColors.foreground, - fontSize: 20.0, - ), - ), - ); - } -} diff --git a/lib/view/screens/history.dart b/lib/view/screens/history.dart deleted file mode 100644 index f413512..0000000 --- a/lib/view/screens/history.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; -import 'package:jisho_study_tool/view/components/common/loading.dart'; -import 'package:sembast/sembast.dart'; - -import '../../models/history/search.dart'; -import '../components/history/date_divider.dart'; -import '../components/history/kanji_search_item.dart'; -import '../components/history/phrase_search_item.dart'; -import '../components/opaque_box.dart'; - -class HistoryView extends StatelessWidget { - const HistoryView({Key? key}) : super(key: key); - - Database get _db => GetIt.instance.get(); - - Stream> get searchStream => Search.store - .query( - finder: Finder( - sortOrders: [SortOrder('timestamp', false)], - ), - ) - .onSnapshots(_db) - .map((snapshot) { - return snapshot - .map( - (snap) => (snap.value != null) - ? Search.fromJson(snap.value! as Map) - : null, - ) - .where((s) => s != null) - .map((s) => s!) - .toList(); - }); - - @override - Widget build(BuildContext context) { - return StreamBuilder>( - stream: searchStream, - builder: (context, snapshot) { - if (!snapshot.hasData) return const LoadingScreen(); - - final List data = snapshot.data!; - if (data.isEmpty) - return const Center( - child: Text('The history is empty.\nTry searching for something!'), - ); - - return OpaqueBox( - child: ListView.separated( - itemCount: data.length + 1, - itemBuilder: historyEntryWithData(data), - separatorBuilder: historyEntrySeparatorWithData(data), - ), - ); - }, - ); - } - - Widget Function(BuildContext, int) historyEntryWithData(List data) => - (context, index) { - if (index == 0) return Container(); - - final Search search = data[index - 1]; - - return (search.isKanji) - ? KanjiSearchItem( - result: search.kanjiQuery!, - timestamp: search.timestamp, - ) - : PhraseSearchItem( - search: search.wordQuery!, - timestamp: search.timestamp, - ); - }; - - DateTime roundToDay(DateTime date) => - DateTime(date.year, date.month, date.day); - - bool dateChangedFromLastSearch(Search prevSearch, DateTime searchDate) { - final DateTime prevSearchDate = roundToDay(prevSearch.timestamp); - return prevSearchDate != searchDate; - } - - DateTime get today => roundToDay(DateTime.now()); - DateTime get yesterday => - roundToDay(DateTime.now().subtract(const Duration(days: 1))); - - Widget Function(BuildContext, int) historyEntrySeparatorWithData( - List data, - ) => - (context, index) { - final Search search = data[index]; - final DateTime searchDate = roundToDay(search.timestamp); - - EdgeInsets? margin; - if (index != 0) { - margin = const EdgeInsets.only(bottom: 10); - } - - if (index == 0 || - dateChangedFromLastSearch(data[index - 1], searchDate)) { - if (searchDate == today) - return DateDivider(text: 'Today', margin: margin); - else if (searchDate == yesterday) - return DateDivider(text: 'Yesterday', margin: margin); - else - return DateDivider(date: searchDate, margin: margin); - } - - return const Divider(); - }; -} diff --git a/lib/view/screens/search/search_mechanisms/grid.dart b/lib/view/screens/search/search_mechanisms/grid.dart deleted file mode 100644 index 742e0a0..0000000 --- a/lib/view/screens/search/search_mechanisms/grid.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../components/kanji/kanji_search_body/kanji_grid.dart'; -import '../../../components/kanji/kanji_search_body/kanji_search_bar.dart'; - -class SearchGrid extends StatelessWidget { - final List suggestions; - - const SearchGrid({ - required this.suggestions, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const SizedBox(height: 10), - const KanjiSearchBar(), - const SizedBox(height: 10), - Expanded(child: KanjiGrid(suggestions: suggestions)) - ], - ); - } -}